Various modifiers can be applied to patterns to transform how they are matched. All pattern modifiers yield a new pattern with the modified behavior. Importantly, none of these modifiers represent base patterns by themselves, but instead modify or combine other patterns.
and clause
and clauses are used to combine one or more comma-separated patterns.
The and clause matches only if all of the patterns match.
In the example below, we combine the where clause with and to rewrite two types of React hooks in one pattern.
arrow_function($body) where $body <: and {
contains js"React.useState" => js"useState",
contains js"React.useMemo" => js"useMemo",
}const someComponent = () => {
const [count, setCount] = React.useState(0);
};
const anotherComponent = () => {
const [count, setCount] = React.useState(0);
const math = React.useMemo(() => count + 5, [count]);
};const someComponent = () => {
const [count, setCount] = React.useState(0);
};
const anotherComponent = () => {
const [count, setCount] = useState(0);
const math = useMemo(() => count + 5, [count]);
};or clause
or clauses are used to match against multiple patterns. They are written using the or keyword, and their arguments are separated by a comma.
The or clause matches if any of the patterns match.
or is short-circuited, so the first pattern that matches will be the one that is used.
arrow_function($body) where $body <: or {
contains js"React.useState" => js"useState",
contains js"React.useMemo" => js"useMemo",
}const someComponent = () => {
const [count, setCount] = React.useState(0);
};
const anotherComponent = () => {
const [count, setCount] = React.useState(0);
const math = React.useMemo(() => count + 5, [count]);
};const someComponent = () => {
const [count, setCount] = useState(0);
};
const anotherComponent = () => {
const [count, setCount] = useState(0);
const math = React.useMemo(() => count + 5, [count]);
};any clause
The any clause is a non-short-circuiting version of or. It will match if any of the provided patterns matches, but will continue to try to match all of the patterns and execute any applicable transformations:
arrow_function($body) where $body <: any {
contains js"React.useState" => js"useState",
contains js"React.useMemo" => js"useMemo",
}const someComponent = () => {
const [count, setCount] = React.useState(0);
};
const anotherComponent = () => {
const [count, setCount] = React.useState(0);
const math = React.useMemo(() => count + 5, [count]);
};const someComponent = () => {
const [count, setCount] = useState(0);
};
const anotherComponent = () => {
const [count, setCount] = useState(0);
const math = useMemo(() => count + 5, [count]);
};not clause
not clauses are used to negate a pattern. They are written using the not keyword.
A not clause matches if the pattern it negates does not match.
`$method($message)` => `console.warn($message)` where {
$method <: not `console.error`
}console.log("Hello, world!");
console.error("Hello, world!");console.warn("Hello, world!");
console.error("Hello, world!");maybe clause
maybe clauses are used to optionally match against a pattern. If the pattern inside does not match, the maybe clause will still match as a whole.
`throw new Error($err)` as $thrown => `throw new CustomError($err);` where {
$err <: maybe string(fragment=$fun) => `{ message: $err }`
}if (Math.random() < 0.5) {
throw new Error();
} else {
throw new Error("nest the message in an object");
}if (Math.random() < 0.5) {
throw new CustomError();
} else {
throw new CustomError({ message: "nest the message in an object" });
}Warning: In the pattern above, the metavariable $fun serves only an illustrative example and so isn't actually used after it's assigned. However, if we were to use it, we'd have to be careful: metavariables in maybe clauses are bound only if the maybe clause matches, and Grit will throw an error if you attempt to use one when the clause does not match.
contains operator
The contains keyword is used to modify a pattern to match any node that contains a specific pattern by traversing downwards through the syntax tree.
`function ($args) { $body }` where {
$args <: contains `x`
}In the example above, the pattern will only match functions where one of the arguments is x.
until modifier
The until modifier is appended to contains pattern is used to stop traversal within a contains clause.
The contains pattern will only traverse within the parts of the node that are not matched by the until pattern. When the until pattern is reached, traversal will not continue any deeper.
For example, the until here stops traversal inside sanitized calls:
`console.$_($content)` where {
$content <: contains `secret` until `sanitized($_)`
}console.log(secret); console.log(sanitized(secret)); console.log(random(secret)); console.log(other + sanitized(secret)); console.log(random(secret) + sanitized(secret));
as modifier
as can be used to assign a matched snippet to a metavariable. This is often useful when you want to perform some change on a section of code as a whole while also making more granular changes within that section. For example:
engine marzano(0.1)
language js
`function $name ($args) { $body }` as $func where {
$func => `const $name = ($args) => { $body }`,
$args <: contains `apple` => `mango`
}function fruits(apple, pear) {
console.log("fruits");
}const fruits = (mango, pear) => {
console.log("fruits");
};within clause
within restricts the pattern to only match if the target node appears within code matching another pattern.
For example, the pattern below only matches the second console.log, as it appears within the if block.
`console.log($arg)` where {
$arg <: within `if (DEBUG) { $_ }`
}console.log('Hello, world!');
if (DEBUG) {
console.log('Hello, world!');
}after clause
after clauses are used to restrict the pattern to only match if the target node is directly after a node matching a specific pattern. They are written using the after keyword.
`console.warn($_)` as $warn where {
$warn <: after `console.log($_)`
}after can also be used to retrieve the node that comes after the target node.
`const $x = 5` as $const where {
$next = after $const
} += `// Next: $next`const x = 5; const nine = 10;
const x = 5; // Next: const nine = 10; const nine = 10;
before clause
before clauses are used to restrict the pattern to only match if the target node is directly before a node matching a specific pattern. They are written using the before keyword.
`console.warn($_)` as $warn where {
$warn <: before `console.log($_)`
}before can also be used to retrieve the node that comes before the target node.
`const $_ = 10` as $const where {
$next = before $const,
$next <: `const $name = $_`
} += `// Comes after $name`const x = 5; const nine = 10;
const x = 5; const nine = 10; // Comes after x
some clause
The some clause can be used to match a metavariable which represents a list against a pattern which represents some element of that list. The list can be any ordered set of syntax tree nodes, for example, a list of statements.
As long as one of the nodes matches the pattern, the some clause will match.
`var $x = [$names]` => `var coolPeople = [$names]` where {
$names <: some { `"andrew"` }
}var people = ["alex", "andrew", "susan"]; var peopleTwo = ["beth", "max", "right"];
var coolPeople = ["alex", "andrew", "susan"]; var peopleTwo = ["beth", "max", "right"];
Tip: If you would like the some clause to succeed even if no match is found, prefix it with maybe. Ex. $names <: maybe some { `"andrew"` }
every clause
The every clause can be used to match a pattern against every element of a list. It matches only if all elements of the list match the pattern.
`var $x = [$names]` => `var coolPeople = [$names]` where {
$names <: every or {`"andrew"`, `"alex"`}
}var people = ["alex", "andrew", "alex"]; var peopleTwo = ["alex", "max", "right"]; var nobody = ["sam", "susan"];
var coolPeople = ["alex", "andrew", "susan"]; var peopleTwo = ["alex", "max", "right"]; var nobody = ["sam", "susan"];
Tip: every is short-circuited, so the first element that does not match will cause the every clause to fail.
List patterns
List patterns are used to match against a list of terms. They are written using the [] syntax.
The list pattern matches if all of the terms in the list match in order.
`var $x = [$numbers]` => `var firstPrimes = [$numbers]` where {
$numbers <: [ `2`, `3`, `5` ]
}var someNumbers = [2, 3, 5];
var firstPrimes = [2, 3, 5];
... clause
The ... clause is used to match against a sublist of a list, ignoring the ... elements.
`var $x = [$numbers]` => `var firstPrimes = [$numbers]` where {
$numbers <: [ `2`, `3`, ..., `11` ]
}var someNumbers = [2, 3, 5, 7, 11];
var firstPrimes = [2, 3, 5, 7, 11];
limit clause
The limit clause can be added after any root pattern to limit the number of matches that are returned.
Limits can only be defined in the root query and are enforced globally across all files queried.
`console.$method($message)` => `console.warn($message)` where {
$method <: not `error`
} limit 2 // Only find 2 files// @filename: file.tsx
console.log("Hello, world!");
console.log("Hello, world 2!");
// @filename: file2
console.log("This is another file");
// @filename: file3
console.log("This is a third file");// @filename: file.tsx
console.warn("Hello, world!");
console.warn("Hello, world 2!");
// @filename: file2
console.warn("This is another file");
// @filename: file3
console.log("This is a third file");Note: The limit is moved to apply to the file as part of pattern auto-wrapping. If you use sequential or multifile patterns, you will need to be careful to place the limit clause in the desired location.
When running a limit query through the studio, paid customers can configure Grit to automatically generate a new PR whenever the previous PR is merged.