GritQL Tutorial

Note: You do not need to learn GritQL to use Grit. New users should start with one of our many built-in migrations.

GritQL is a query language designed for searching and modifying source code, with semantics similar to SQL or other declarative query languages.

Patterns

The root of every GritQL query is a pattern to search for in a codebase. If you know JavaScript, you already know GritQL: any valid JavaScript code snippet is a valid GritQL pattern if you surround it with backticks.

For example, this query will find everywhere we log "Hello world!" in our codebase:

`console.log("Hello world!")`

Click the "Try it" button to run the query on a sample file. Notice how it is able to find the cases where the call is split across multiple lines or uses single quotes instead of double quotes, but it ignores the comment.

This is because GritQL is designed to do structural matching, not just string matching. Every code snippet is automatically converted into a syntax tree before it is matched against the codebase.

Warning: The downside of structural matching is that each code snippet must be valid JavaScript. If you want to match arbitrary strings, you can use quotes instead: "console.log("

Metavariables

In many cases, you will want to match a pattern that is similar to a specific code snippet, but not exactly the same. To do this, just replace the parts you want to match with a metavariable: any identifier that starts with a dollar sign ($).

For example, this query will find all console.log calls regardless of what message they are logging:

`console.log($my_message)`

You can name metavariables anything you want, but we recommend using descriptive names that make it easy to understand what the metavariable represents.

Rewrites

GritQL is useful for searching, but the real power comes from its ability to rewrite code. To convert a query into a rewrite, just add a => followed by a code snippet you want to replace it with.

For example: this query will find all console.log calls and replace them with the Winston logging library:

`console.log($my_message)` => `winston.info($my_message)`

Notice how you can use metavariables in the replacement code snippet to refer to the parts of the original code snippet you want to keep.

Conditions

Simple queries are often enough to get the job done, but you can add a where clause to filter the results. The where clause is similar to the where clause in SQL and introduces a set of side conditions that must all be true for the query to match.

The most common condition uses the <: match operator to check if a given metavariable matches another pattern.

For example, we could use a side query to turn our user-facing console.log call into an alert instead:

`console.log($my_message)` => `alert($my_message)` where {
  $my_message <: `"This is a user-facing message"`
}

Conditions in GritQL support all the normal boolean operators like and, or, or negation.

For example, you can use negation to exclude console.log calls that are user-facing from the Winston rewrite.

`console.log($my_message)` => `winston.info($my_message)` where {
  !$my_message <: r".+user-facing.+"
}

Alternative patterns

Notice how in the last example we used a regular expression to match the message instead of a code snippet.

You can also use regular expressions, string literals, and AST nodes as patterns in GritQL.

For example, you can exclude console.log calls that are not strings by referencing the string() AST node:

`console.log($my_message)` => `winston.info($my_message)` where {
  $my_message <: string()
}

Compound patterns

Boolean clauses can also be used to combine multiple patterns into a single pattern.

For example, or can be used to match either a console.log or console.error call:

or {
  `console.log($my_message)`,
  `console.error($my_message)`
} => `winston.info($my_message)`

Note that rewrites are also patterns, so you can use them in conditional clauses as well. This allows you to construct nested rewrites.

For example, this compound query rewrites all console.log calls to winston.debug and all console.error calls to winston.warn:

`console.$method($my_message)` => `winston.$method($my_message)` where {
  $method <: or {
    `log` => `debug`,
    `error` => `warn`
  }
}

Pattern modifiers

In clauses, you often want to match based on the context of a pattern, not just the pattern itself. You can use pattern modifier keywords to adjust the behavior of a pattern.

For example, you can use the within modifier to traverse upwards through the syntax tree and match console.log statements only if they are inside a try/catch block:

`console.log($my_message)` => `winston.error($my_message)` where {
  $my_message <: within `try { $_ } catch { $_ }`
}

You might have noticed the $_ metavariable in the within clause. This is an anonymous metavariable that matches any pattern. It is useful when you want to include a placeholder without binding the value to a metavariable.

Pattern calls

When refactoring code, you often want to reuse common patterns. You can "call" a pre-defined pattern by surrounding it with parentheses. This is similar to calling a function in a programming language, and pattern calls can also take named arguments.

Grit includes a robust standard library of built-in patterns that you can use in your queries.

For example, if you want to match all JavaScript literals with a value of "42" you can call the literal pattern from the Grit standard library:

`console.log($my_message)` => `winston.info($my_message)` where {
  $my_message <: literal(value="42")
}

Next steps

This tutorial only covers the basics of GritQL. For more information, try: