Within a pattern, all metavariables share a common scope. Once a metavariable is bound to a value, it retains this value throughout the target code. Therefore, the scope of the metavariable spans the entire target file. For instance, if your pattern matches the snippet console.log($x), then according to the standard Grit scoping rules, it would only match the first occurrence of $x in the target file.
Defined patterns
When you define a pattern, it establishes a new variable scope. However, pattern parameters bridge this scope. In the following pattern, $method will cross the scope boundary while $message remains local:
pattern console_method_to_info($method) {
`console.$method($message)` => `console.info($message)`
}bubble clause
The bubble clause introduces a new scope without the necessity of defining a separate pattern. Like with separate patterns, variables inside the bubble clause are isolated from the surrounding code.
This is particularly useful when you want to match a pattern in multiple places without requiring metavariables to hold the same value in each location.
`function() { $body }` where {
$body <: contains bubble `console.log($message)`=> `console.warn($message)`
}function() {
console.log('Hello, world!');
console.log('How are you?');
}function() {
console.warn('Hello, world!');
console.warn('How are you?');
}In the above pattern, the bubble clause ensures the metavariable $message is not bound to the same value in both console.log calls.
Absent the bubble, only the first console.log call would be rewritten. After it was rewritten, $message would be bound to the value 'Hello, world!'. When attempting to match the second console.log, $message would try to bind to 'How are you?' and fail.
Arguments for the bubble clause
Like pattern definitions, the bubble clause can accept arguments. Such arguments are used to "pierce" the bubble by allowing metavariables to preserve the values they had outside the bubble scope. For example:
`function $name() { $body }` where {
$body <: contains bubble($name) `console.log($message)` => `console.warn($message, $name)`
}function logger() {
console.log('Hello, world!');
console.log('How are you?');
}function logger() {
console.warn('Hello, world!', logger);
console.warn('How are you?', logger);
}If $name weren't specified as an argument for bubble, the metavariable $name would be undefined inside the bubble, leading to an error during rewriting.
Pattern auto-wrap
Unless a root pattern targets either file or body, it is automatically wrapped to become file(body = contains bubble $root_pattern). The main effect of auto-wrap is that patterns can match multiple times within a file without needing to manually specify bubble.
file(body=contains bubble $THE_PATTERN)
This default behavior often proves beneficial as it facilitates matching multiple sections within a file independently. To override this, encapsulate the pattern within file.
For example, if you want to transform console.$method calls but only for one method at a time, you can use a pattern like this:
file(body = contains `console.$method` => `println`)
console.info('Hello, world!');
console.info('How are you?');
console.error('Hello?');println('Hello, world!');
println('How are you?');
console.error('Hello?');In the pattern shown above, the metavariable $method consistently binds to a singular value, info, throughout the pattern's application.
Global metavariables
While these scoping rules suffice in most scenarios, there are times when you might want to share scope across a file within a defined pattern. This can be achieved using global metavariables, denoted with a $GLOBAL_ prefix and written in uppercase.
For example, the default import utilities use global metavariables to keep a running list of all imports in a file.