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.
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.
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
leftoperand (1) - and a
rightoperand (2)
- with a
- containing one
- with an
Your patterns match against this structure.
The ast-grep pattern language is designed to look just like the code you're writing.
What you write is what you get. The pattern if (true) will find exactly that code structure.
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)
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)
- Pattern:
-
Match a code block:
- Pattern:
if (true) { ... } - Matches: An
if (true)block regardless of what's inside it.
- Pattern:
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) $ARGScaptures: The sequencea, b, c
The most basic use of sg is to find code.
| 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). |
-
Find all
console.logcalls (JavaScript):sg -l ts -p 'console.log(...)' -
Find all
.unwrap()calls in Rust:sg -l rust -p '$VAR.unwrap()' -
Find old Python 2
printstatements:sg -l py -p 'print $ARG' -
Find all Go error checks:
sg -l go -p 'if err != nil { ... }'
This is the most powerful feature of ast-grep. You can find and replace code at scale.
| 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). |
-
Rename
console.logtoconsole.debug:sg -l ts -p 'console.log($$$ARGS)' -r 'console.debug($$$ARGS)' -U
-
Add a new
falseargument tomyFunc(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
-
Migrate Python 2
printto Python 3print():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
-
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_idtoid)sg -l rust -p 'MyStruct { user_id: $VAL, ... }' \ -r 'MyStruct { id: $VAL, ... }' -U
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 patternYou 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
logcalls that are notlog.debug(JS):sg -l ts -p 'log.$METHOD(...)' \ -R '$METHOD: { not: { pattern: "debug" } }'