This page covers some common techniques for writing GritQL patterns.
List and string accumulation
Within where
clauses, you can assign a value to a metavariable using the =
operator and then use +=
to accumulate values. This is particularly common when combined with some
to iterate over a list of values, such as in this pattern.
Strings and lists can both be accumulated, as shown in this example:
`namedColors = { $colors }` where { $keys = "", $values = [], $colors <: some bubble($keys, $values) `$name: $color` where { // Push the name onto the string $keys += $name, // Push the values onto the list $values += `$color` }, // Join the values together to make a string $new_colors = join(list=$values, separator=", ") } => `$keys = [ $new_colors ]`
const namedColors = { red: '#ff0000', green: '#00ff00', blue: '#0000ff', };
const redgreenblue = [ '#ff0000', '#00ff00', '#0000ff' ];
Creating new files
The $new_files
metavariable allows creating a new file as a side-effect of a pattern.
For example, for following pattern moves all functions which start with test
to a separate file (stuff.test.js
):
`function $functionName($_) {$_}` as $f where { $functionName <: r"test.*", $f => ., $new_file_name = `$functionName.test.js`, $new_files += file(name = $new_file_name, body = $f) }
function doStuff() {} function testStuff() {}
// stuff.js function doStuff() {} // testStuff.test.js function testStuff() {}
Warning: $new_files
does not consider the existing files, so it is possible to overwrite existing files.
Accessing the current file name
The $filename
metavariable always contains the name of the current file the query is processing.
For example, the following pattern we move the matching function to a new file that uses the same name as the original file, but with a .test.js
extension:
`function $functionName($_) {$_}` as $f where { $functionName <: r"test.*", $f => ., $filename <: r"(.*)\.js$"($base_name), $new_file_name = `$base_name.test.js`, $new_files += file(name = $new_file_name, body = $f) }
function doStuff() {} function testStuff() {}
// stuff.js function doStuff() {} // stuff.test.js function testStuff() {}
Comments
Comments are supported in GritQL. Any line starting with //
is considered a comment and ignored by the parser.
`console.log($message)` => . where { // This is a comment $message <: js"'Hello, world!'" }
Specific rewrites
Since GritQL supports nested patterns, it is usually preferable to match a larger pattern and then only rewrite the specific metavariables you want to change inside a where
clause.
This helps in several ways:
- It avoids duplication, as often the right-hand side of the rewrite repeats the left-hand side.
- It helps preserve syntactic and semantic details of the original code.
For example, the following rewrite is not specific and therefore loses the async
keyword and the comment about arguments:
`function foo($args) { $body }` => `function bar($args) {$body} `
async function foo(/* no args */) { console.log('Hello, world!'); }
function bar() { console.log('Hello, world!'); }
The following rewrite is specific and preserves the async
keyword and the comment about arguments:
`function $name($args) { $body }` where { $name <: `foo` => `bar` }
async function foo(/* no args */) { console.log('Hello, world!'); }
async function bar(/* no args */) { console.log('Hello, world!'); }
Making small pull requests
In general, huge pull requests are harder to review and more likely to have merge conflicts. You can use the limit clause to limit the number of files a pattern is applied to.
Targeting specific code blocks
You can use range
patterns together with the $filename
metavariable to target specific code blocks that are known ahead of time.
This is often helpful for integrating Grit with other static analysis tools.
For example, to target the console.log
on the 2nd line of a file called stuff.js
:
`console.log($message)` where { $message <: within range(start_line=2, end_line=2), $filename <: includes "stuff.js" }
// @filename: stuff.js console.log("hello"); console.log("goodbye"); console.log("wow"); console.log("multiline");