Skip to content

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, and fix fields.
  • id, rule and fix are required in rewriter.
  • rewriters can only be used in rewrite transformation.
  • Meta-variables defined in one rewriter are not accessible in other rewriter or the original rule.
  • utils and transform 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 same rewriter 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 in 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 or FixConfig
  • 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,

JavaScript
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.

yaml
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.

yaml
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.

yaml
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.

yaml
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.

yaml
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.

Made with ❤️ with Rust