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 ($_) { $body }` where { $body <: contains `console.log` }
In the example above, the pattern will only functions where the $body
metavariable contains a console.log
statement.
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($_)` }
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"` }
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];
Assignment =
The assignment operator =
is used to assign a value to a metavariable, much in the same way as you would assign to a variable in other programming languages. The value can be anything that can appear on the right-hand side of a rewrite.
`console.log($message)` as $log where { $new_log_call = `logger.log($message)`, $log => $new_log_call }