Skip to content

Instantly share code, notes, and snippets.

@asadm
Last active October 28, 2025 18:13
Show Gist options
  • Select an option

  • Save asadm/b755e452ad517cf6374fca80b2441852 to your computer and use it in GitHub Desktop.

Select an option

Save asadm/b755e452ad517cf6374fca80b2441852 to your computer and use it in GitHub Desktop.
ast-grep

ast-grep

The current environment has ast-grep available for you to use using command sg. ast-grep is a high-speed, command-line tool for searching and rewriting code based on its Abstract Syntax Tree (AST).

It allows you to find and modify code based on its structure, not just its text. This makes it far more powerful and accurate than grep or regex-based search-and-replace for refactoring.

Why Use ast-grep?

Regular grep or text-based search doesn't understand code. It gets confused by whitespace, newlines, comments, or variable name changes.

Example: You want to find all calls to my_func(a, b).

A text search for "my_func(a, b)" will miss all of these valid matches:

  • my_func(a,b) (different spacing)
  • my_func( a, b ) (more spacing)
  • my_func(\n a,\n b\n) (newlines)
  • my_func(a, /* a comment */ b) (comments)

ast-grep understands that all of these are the same code structure and will find all of them.

Core Concepts: How it Works

ast-grep works by first parsing your source code into an Abstract Syntax Tree (AST), which is a tree representation of your code's structure. It then matches your pattern against this tree.

For example, the code my_func(1 + 2) is not seen as text. It's seen as:

  • A call_expression
    • with an identifier (name: my_func)
    • and arguments
      • containing one binary_expression (operator: +)
        • with a left operand (1)
        • and a right operand (2)

Your patterns match against this structure.

The Pattern Language

The ast-grep pattern language is designed to look just like the code you're writing.

1. Literal Matching

What you write is what you get. The pattern if (true) will find exactly that code structure.

2. Metavariables ($VAR)

This is the most important concept. A dollar sign $ followed by an all-caps name (e.g., $VAR, $ARG, $BODY) acts as a wildcard that matches any single AST node.

  • Pattern: console.log($MESSAGE)
  • Matches:
    • console.log("Hello")
    • console.log(myVariable)
    • console.log(1 + 2)

3. Ellipsis (...)

The ellipsis ... matches zero or more nodes. This is useful for matching "the rest" of arguments, statements, or fields.

  • Match function calls with any arguments:

    • Pattern: my_function(...)
    • Matches: my_function(), my_function(a), my_function(a, b, c)
  • Match a code block:

    • Pattern: if (true) { ... }
    • Matches: An if (true) block regardless of what's inside it.

4. Multi-Metavariables ($$$ARGS)

A triple-dollar-sign $$$ captures a sequence of nodes. This is most useful in rewrites.

  • Pattern: my_function($$$ARGS)
  • Matches: my_function(a, b, c)
  • $ARGS captures: The sequence a, b, c

CLI Usage: Searching

The most basic use of sg is to find code.

Common Search Flags

Flag Alias Description
--lang <LANG> -l (Required) Sets the language. (e.g., rust, py, tsx, go, c)
--pattern <PAT> -p (Required) The AST pattern to search for.
--heading -h Prints the filename above matching lines (good for context).
--json Outputs results in JSON format for scripting.
--color Controls color output (e.g., auto, always, never).

Search Examples

  • Find all console.log calls (JavaScript):

    sg -l ts -p 'console.log(...)'
  • Find all .unwrap() calls in Rust:

    sg -l rust -p '$VAR.unwrap()'
  • Find old Python 2 print statements:

    sg -l py -p 'print $ARG'
  • Find all Go error checks:

    sg -l go -p 'if err != nil { ... }'

CLI Usage: Rewriting

This is the most powerful feature of ast-grep. You can find and replace code at scale.

Common Rewrite Flags

Flag Alias Description
--rewrite <RWT> -r The replacement pattern. Uses metavariables from --pattern.
--interactive -i Starts an interactive session to review/accept rewrites one by one.
--update-all -U Applies all rewrites immediately (non-interactive).

Gallery of Rewrite Examples

JavaScript / TypeScript

  • Rename console.log to console.debug:

    sg -l ts -p 'console.log($$$ARGS)' -r 'console.debug($$$ARGS)' -U
  • Add a new false argument to myFunc(a, b):

    sg -l ts -p 'myFunc($ARG1, $ARG2)' -r 'myFunc($ARG1, $ARG2, false)' -U
  • Replace an old import path:

    sg -l ts -p 'import { $MEMBER } from "utils/old/helpers"' \
             -r 'import { $MEMBER } from "utils/new/core"' -U

Python

  • Migrate Python 2 print to Python 3 print():

    sg -l py -p 'print $ARG' -r 'print($ARG)' -U
  • Remove (object) from class definitions:

    sg -l py -p 'class $NAME(object): $$$' -r 'class $NAME: $$$' -U

Rust

  • Replace try! macro with the ? operator:

    sg -l rust -p 'try!($EXPR)' -r '$EXPR?' -U
  • Rename a struct field during initialization: (e.g., rename user_id to id)

    sg -l rust -p 'MyStruct { user_id: $VAL, ... }' \
             -r 'MyStruct { id: $VAL, ... }' -U

Advanced Patterns (Relational Rules)

Sometimes, a simple pattern isn't enough. You can use relational rules (-R or --rule) to filter your matches.

A rule file is a YAML that looks like this:

# my-rule.yml
id: my-rule
language: rust
rule:
  pattern: $MY_MATCH
  # Add filters here:
  kind: function_item # Match only nodes of this type
  has:
    pattern: "unsafe" # Must contain this pattern
  not:
    pattern: "pub" # Must NOT contain this pattern

You can also pass this as a string:

  • Find all function names that start with test_ (Rust):

    sg -l rust -p 'fn $NAME() { ... }' \
             -R '$NAME: { regex: "^test_" }'
  • Find all log calls that are not log.debug (JS):

    sg -l ts -p 'log.$METHOD(...)' \
             -R '$METHOD: { not: { pattern: "debug" } }'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment