Rewriter
Rewriter is a powerful, and experimental, feature that allows you to manipulate the code in a more complex way.
ast-grep rule has a rewriters
field which is a list of rewriter objects that can be used to transform code of specific nodes matched by meta-variables.
A rewriter rule is similar to ordinary ast-grep rule, except that:
- It only has
id
,rule
,constraints
,transform
,utils
, andfix
fields. id
,rule
andfix
are required in rewriter.rewriters
can only be used inrewrite
transformation.- Meta-variables defined in one
rewriter
are not accessible in otherrewriter
or the original rule. utils
andtransform
are independent similar to meta-variables. That is, these two fields can only be used by the defining rewriter.- You can use other rewriters in a rewriter rule's
transform
section if they are defined in the samerewriter
list.
Consider ast-grep API
Rewriters are an advanced feature and should be used with caution, and it is experimental at the moment. If possible, you can use ast-grep's API as an alternative.
Please ask questions on StackOverflow, GitHub Discussions or discord for help.
id
- type:
String
- required: true
rule
- type:
Rule
- required: true
The object specify the method to find matching AST nodes. See details in rule object reference.
fix
- type:
String
orFixConfig
- required: true
A pattern or a FixConfig
object to auto fix the issue. See details in fix object reference.
constraints
- type:
HashMap<String, Rule>
- required: false
Additional meta variables pattern to filter matches. The key is matched meta variable name without $
. The value is a rule object.
transform
- type:
HashMap<String, Transformation>
- required: false
A dictionary to manipulate meta-variables. The dictionary key is the new variable name. The dictionary value is a transformation object that specifies how meta var is processed.
Note: variables defined transform
are only available in the rewriter
itself.
utils
- type:
HashMap<String, Rule>
- required: false
A dictionary of utility rules that can be used in matches
locally. The dictionary key is the utility rule id and the value is the rule object. See utility rule guide.
Note: util rules defined transform
are only available in the rewriter
itself.
Example
Suppose we want to rewrite a barrel import to individual imports in JavaScript. For example,
import { A, B, C } from './module';
// rewrite the above to
import A from './module/a';
import B from './module/b';
import C from './module/c';
It is impossible to do this in ast-grep YAML without rewriters because ast-grep can only replace one node at a time with a string. We cannot process multiple imported identifiers like A, B, C
.
However, rewriter rules can be applied to captured meta-variables' descendant nodes, which can achieve the multiple node processing.
Our first step is to write a rule to capture the import statement.
rule:
pattern: import {$$$IDENTS} from './module'
This will capture the imported identifiers A, B, C
in $$$IDENTS
.
Next, we need to transform $$$IDENTS
to individual imports.
The idea is that we can find the identifier nodes in the $$$IDENT
and rewrite them to individual imports.
rewriters:
- id: rewrite-identifer
rule:
pattern: $IDENT
kind: identifier
fix: import $IDENT from './module/$IDENT'
The rewrite-identifier
above will rewrite the identifier node to individual imports. To illustrate, the rewriter will change identifier A
to import A from './module/A'
.
Note the library path has the uppercase letter A
same as the identifier at the end, but we want it to be a lowercase letter in the import statement. The convert
operation in transform
can be helpful in the rewriter rule as well.
rewriters:
- id: rewrite-identifer
rule:
pattern: $IDENT
kind: identifier
transform:
LIB: { convert: { source: $IDENT, toCase: lowerCase } }
fix: import $IDENT from './module/$LIB'
We can now apply the rewriter to the matched variable $$$IDENTS
.
The rewrite
will find identifiers in $$$IDENTS
, as specified in rewrite-identifier
's rule, and rewrite it to single import statement.
transform:
IMPORTS:
rewrite:
rewriters: [rewrite-identifer]
source: $$$IDENTS
joinBy: "\n"
Note the joinBy
field in the transform
section. It is used to join the rewritten import statements with a newline character.
Finally, we can use the IMPORTS
in the fix
field to replace the original import statement.
The final rule will be like this.
id: barrel-to-single
language: JavaScript
rule:
pattern: import {$$$IDENTS} from './module'
rewriters:
- id: rewrite-identifer
rule:
pattern: $IDENT
kind: identifier
transform:
LIB: { convert: { source: $IDENT, toCase: lowerCase } }
fix: import $IDENT from './module/$LIB'
transform:
IMPORTS:
rewrite:
rewriters: [rewrite-identifer]
source: $$$IDENTS
joinBy: "\n"
fix: $IMPORTS
See the playground link for the complete example.