Common Idioms

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:

PATTERN
`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 ]`
INPUTOUTPUT
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):

PATTERN
`function $functionName($_) {$_}` as $f where {
  $functionName <: r"test.*",
  $f => .,
  $new_file_name = `$functionName.test.js`,
  $new_files += file(name = $new_file_name, body = $f)
}
INPUTOUTPUT
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:

PATTERN
`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)
}
INPUTOUTPUT
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.

GRIT
`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:

PATTERN
`function foo($args) { $body }` => `function bar($args) {$body} `
INPUTOUTPUT
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:

PATTERN
`function $name($args) { $body }` where {
  $name <: `foo` => `bar`
}
INPUTOUTPUT
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.