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!');
}