```
├── .cargo/
├── config.toml (100 tokens)
├── .editorconfig (omitted)
├── .github/
├── CONTRIBUTING.md (100 tokens)
├── FUNDING.yml
├── ISSUE_TEMPLATE/
├── bug_report.yml (500 tokens)
├── config.yml (100 tokens)
├── feature_request.md (100 tokens)
├── copilot-instructions.md (1200 tokens)
├── workflows/
├── coverage.yaml (300 tokens)
├── napi.yml (1400 tokens)
├── pyo3.yml (900 tokens)
├── pypi.yml (1200 tokens)
├── release.yml (800 tokens)
├── .gitignore (500 tokens)
├── .pre-commit-config.yaml
├── CHANGELOG.md (24.7k tokens)
├── Cargo.lock (omitted)
├── Cargo.toml (200 tokens)
├── LICENSE (omitted)
├── README.md (1100 tokens)
├── clippy.toml
├── crates/
├── cli/
├── Cargo.toml (400 tokens)
├── src/
├── bin/
├── alias.rs (100 tokens)
├── completions.rs (500 tokens)
├── config.rs (1600 tokens)
├── lang/
├── injection.rs (1500 tokens)
├── lang_globs.rs (900 tokens)
├── mod.rs (1200 tokens)
├── lib.rs (1900 tokens)
├── lsp.rs (300 tokens)
├── main.rs
├── new.rs (2.6k tokens)
├── print/
├── cloud_print.rs (1000 tokens)
├── colored_print.rs (2.6k tokens)
├── colored_print/
├── markdown.rs (300 tokens)
├── match_merger.rs (400 tokens)
├── styles.rs (800 tokens)
├── test.rs (2000 tokens)
├── interactive_print.rs (2.8k tokens)
├── json_print.rs (4.5k tokens)
├── mod.rs (1100 tokens)
├── run.rs (2.6k tokens)
├── scan.rs (2.9k tokens)
├── utils/
├── args.rs (2.5k tokens)
├── debug_query.rs (1600 tokens)
├── error_context.rs (3.1k tokens)
├── inspect.rs (1600 tokens)
├── mod.rs (1300 tokens)
├── print_diff.rs (800 tokens)
├── rule_overwrite.rs (600 tokens)
├── worker.rs (900 tokens)
├── verify.rs (2.1k tokens)
├── verify/
├── case_result.rs (1300 tokens)
├── find_file.rs (1100 tokens)
├── reporter.rs (2.8k tokens)
├── snapshot.rs (1300 tokens)
├── test_case.rs (1300 tokens)
├── tests/
├── common/
├── mod.rs (100 tokens)
├── help_test.rs (100 tokens)
├── run_test.rs (800 tokens)
├── scan_test.rs (1600 tokens)
├── verify_test.rs (600 tokens)
├── config/
├── Cargo.toml (100 tokens)
├── src/
├── check_var.rs (1700 tokens)
├── combined.rs (2.8k tokens)
├── fixer.rs (2.1k tokens)
├── label.rs (900 tokens)
├── lib.rs (1200 tokens)
├── maybe.rs (700 tokens)
├── rule/
├── deserialize_env.rs (1800 tokens)
├── mod.rs (4.3k tokens)
├── nth_child.rs (2.3k tokens)
├── range.rs (900 tokens)
├── referent_rule.rs (1500 tokens)
├── relational_rule.rs (4k tokens)
├── selector.rs (2.2k tokens)
├── stop_by.rs (1500 tokens)
├── rule_collection.rs (1400 tokens)
├── rule_config.rs (4.4k tokens)
├── rule_core.rs (2.8k tokens)
├── transform/
├── mod.rs (1000 tokens)
├── parse.rs (1700 tokens)
├── rewrite.rs (2.2k tokens)
├── string_case.rs (1500 tokens)
├── trans.rs (2.5k tokens)
├── core/
├── Cargo.toml (100 tokens)
├── src/
├── language.rs (500 tokens)
├── lib.rs (500 tokens)
├── match_tree/
├── match_node.rs (2k tokens)
├── mod.rs (1900 tokens)
├── strictness.rs (1300 tokens)
├── matcher.rs (800 tokens)
├── matcher/
├── kind.rs (700 tokens)
├── node_match.rs (700 tokens)
├── pattern.rs (3.6k tokens)
├── text.rs (200 tokens)
├── meta_var.rs (2.3k tokens)
├── node.rs (3k tokens)
├── ops.rs (3k tokens)
├── pinned.rs (1000 tokens)
├── replacer.rs (600 tokens)
├── replacer/
├── indent.rs (2.3k tokens)
├── structural.rs (1200 tokens)
├── template.rs (2.2k tokens)
├── source.rs (1200 tokens)
├── tree_sitter/
├── mod.rs (3.1k tokens)
├── traversal.rs (3.4k tokens)
├── dynamic/
├── Cargo.toml (100 tokens)
├── src/
├── custom_lang.rs (600 tokens)
├── lib.rs (2.1k tokens)
├── language/
├── Cargo.toml (600 tokens)
├── src/
├── bash.rs (200 tokens)
├── cpp.rs (300 tokens)
├── csharp.rs (200 tokens)
├── css.rs (100 tokens)
├── elixir.rs (500 tokens)
├── go.rs (200 tokens)
├── haskell.rs (300 tokens)
├── hcl.rs (300 tokens)
├── html.rs (1000 tokens)
├── json.rs (200 tokens)
├── kotlin.rs (200 tokens)
├── lib.rs (3.6k tokens)
├── lua.rs (200 tokens)
├── nix.rs (300 tokens)
├── parsers.rs (600 tokens)
├── php.rs (100 tokens)
├── python.rs (500 tokens)
├── ruby.rs (100 tokens)
├── rust.rs (400 tokens)
├── scala.rs (300 tokens)
├── solidity.rs (300 tokens)
├── swift.rs (300 tokens)
├── yaml.rs (200 tokens)
├── lsp/
├── Cargo.toml (200 tokens)
├── src/
├── lib.rs (4.4k tokens)
├── utils.rs (1400 tokens)
├── tests/
├── basic.rs (4.1k tokens)
├── napi/
├── .gitattributes (100 tokens)
├── .gitignore
├── .npmignore
├── .yarnrc.yml
├── Cargo.toml (200 tokens)
├── LICENSE (200 tokens)
├── README.md (300 tokens)
├── __test__/
├── custom.spec.ts (300 tokens)
├── index.spec.ts (2.7k tokens)
├── test.vue (100 tokens)
├── type.spec.ts (1200 tokens)
├── build.rs
├── dprint.json (100 tokens)
├── index.d.ts (omitted)
├── index.js (2.3k tokens)
├── lang/
├── .gitkeep
├── npm/
├── darwin-arm64/
├── README.md
├── package.json (100 tokens)
├── darwin-x64/
├── README.md
├── package.json (100 tokens)
├── linux-arm64-gnu/
├── README.md
├── package.json (100 tokens)
├── linux-arm64-musl/
├── README.md
├── package.json (100 tokens)
├── linux-x64-gnu/
├── README.md
├── package.json (100 tokens)
├── linux-x64-musl/
├── README.md
├── package.json (100 tokens)
├── win32-arm64-msvc/
├── README.md
├── package.json (100 tokens)
├── win32-ia32-msvc/
├── README.md
├── package.json (100 tokens)
├── win32-x64-msvc/
├── README.md
├── package.json (100 tokens)
├── package.json (400 tokens)
├── scripts/
├── constants.ts (200 tokens)
├── generateTypes.ts (600 tokens)
├── src/
├── doc.rs (1300 tokens)
├── find_files.rs (1400 tokens)
├── lib.rs (700 tokens)
├── napi_lang.rs (1800 tokens)
├── sg_node.rs (2.4k tokens)
├── tsconfig.json (100 tokens)
├── types/
├── api.d.ts (omitted)
├── config.d.ts (omitted)
├── deprecated.d.ts (omitted)
├── lang.d.ts (omitted)
├── registerDynamicLang.d.ts (omitted)
├── rule.d.ts (omitted)
├── sgnode.d.ts (omitted)
├── staticTypes.d.ts (omitted)
├── yarn.lock (omitted)
├── pyo3/
├── Cargo.toml (200 tokens)
├── README.md (300 tokens)
├── ast_grep_py/
├── __init__.py (400 tokens)
├── ast_grep_py.pyi (500 tokens)
├── py.typed
├── pyproject.toml (300 tokens)
├── src/
├── lib.rs (300 tokens)
├── py_lang.rs (700 tokens)
├── py_node.rs (2000 tokens)
├── range.rs (500 tokens)
├── unicode_position.rs (300 tokens)
├── tests/
├── test_fix.py (300 tokens)
├── test_range.py (300 tokens)
├── test_register_lang.py (200 tokens)
├── test_rule.py (800 tokens)
├── test_simple.py (700 tokens)
├── test_traversal.py (700 tokens)
├── test_wrong_usage.py (200 tokens)
├── fixtures/
├── json-linux.so
├── json-mac.so
├── npm/
├── README.md (100 tokens)
├── ast-grep (100 tokens)
├── package.json (200 tokens)
├── platforms/
├── darwin-arm64/
├── README.md
├── package.json (100 tokens)
├── darwin-x64/
├── README.md
├── package.json (100 tokens)
├── linux-arm64-gnu/
├── README.md
├── package.json (100 tokens)
├── linux-x64-gnu/
├── README.md
├── package.json (100 tokens)
├── win32-arm64-msvc/
├── README.md
├── package.json (100 tokens)
├── win32-ia32-msvc/
├── README.md
├── package.json (100 tokens)
├── win32-x64-msvc/
├── README.md
├── package.json (100 tokens)
├── postinstall.js (300 tokens)
├── sg
├── pyproject.toml (200 tokens)
├── renovate.json (omitted)
├── rust-toolchain.toml
├── rustfmt.toml
├── schemas/
├── bash_rule.json (5.8k tokens)
├── c_rule.json (6.9k tokens)
├── cpp_rule.json (8.1k tokens)
├── csharp_rule.json (8k tokens)
├── css_rule.json (5.8k tokens)
├── elixir_rule.json (5.5k tokens)
├── go_rule.json (6.5k tokens)
├── haskell_rule.json (7.3k tokens)
├── html_rule.json (5.2k tokens)
├── java_rule.json (7.1k tokens)
├── javascript_rule.json (6.6k tokens)
├── json_rule.json (5.1k tokens)
├── kotlin_rule.json (6.8k tokens)
├── languages.json (18k tokens)
├── lua_rule.json (5.7k tokens)
├── php_rule.json (7.2k tokens)
├── project.json (900 tokens)
├── python_rule.json (6.6k tokens)
├── ruby_rule.json (6.6k tokens)
├── rule.json (5k tokens)
├── rust_rule.json (7.2k tokens)
├── scala_rule.json (7k tokens)
├── swift_rule.json (7.6k tokens)
├── tsx_rule.json (7.5k tokens)
├── typescript_rule.json (7.4k tokens)
├── yaml_rule.json (5.4k tokens)
├── xtask/
├── Cargo.toml (100 tokens)
├── src/
├── main.rs (1000 tokens)
├── schema.rs (1300 tokens)
```
## /.cargo/config.toml
```toml path="/.cargo/config.toml"
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
[target.x86_64-unknown-linux-musl]
rustflags = [
"-C",
"target-feature=-crt-static",
]
[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"
rustflags = ["-C", "target-feature=-crt-static"]
[alias]
xtask = "run --manifest-path ./xtask/Cargo.toml --"
```
## /.github/CONTRIBUTING.md
# Contributing
:tada: _**We are thrilled that you are interested in contributing to the ast-grep project!**_ :tada:
You can see the contributing guide in [the official website](https://ast-grep.github.io/).
* [General contributing guide](https://ast-grep.github.io/contributing/how-to.html)
* [How to develop](https://ast-grep.github.io/contributing/development.html)
* [How to add a new language to ast-grep](https://ast-grep.github.io/contributing/add-lang.html)
## /.github/FUNDING.yml
```yml path="/.github/FUNDING.yml"
# These are supported funding model platforms
github: HerringtonDarkholme
```
## /.github/ISSUE_TEMPLATE/bug_report.yml
```yml path="/.github/ISSUE_TEMPLATE/bug_report.yml"
name: 'Bug report'
description: 'Create a report to help us improve ast-grep'
title: '[bug]'
labels: [bug]
body:
- type: markdown
attributes:
value: |
🔍 Please read [ast-grep FAQ](https://ast-grep.github.io/advanced/faq.html) before reporting a new bug as most bugs are very likely to be questions. Please fill in each section completely. Thank you!
- type: checkboxes
attributes:
label: Please read the FAQ for the bug you encountered.
description: https://ast-grep.github.io/advanced/faq.html
options:
- label: I have read the existing FAQ
required: true
- type: input
id: playground_link
attributes:
label: ⏯ Playground Link
description: |
A link to a ast-grep Playground "Share" link which shows this behavior.
This should have the same code as the code snippet below.
As a last resort, you can link to a repo, but these will be slower for us to investigate.
placeholder: 'Playground link with relevant code: https://ast-grep.github.io/playground.html'
validations:
required: true
- type: textarea
id: code
attributes:
label: 💻 Code
description: |
Please post the relevant code sample here as well.
This code and the Playground code should be the same, do not use separate examples.
We can quickly address your report if:
- The code sample is short. Nearly all ast-grep issues can be demonstrated in 20-30 lines of code!
- Remove unnecessary code/configuration. The simpler the code, the faster we can help.
- The incorrectness of the behavior is readily apparent from reading the sample.
Reports are slower to investigate if:
- We have to pare too much extraneous code.
- The sample is confusing or doesn't clearly demonstrate what's wrong.
value: |
\`\`\`
// Your code here
\`\`\`
validations:
required: false
- type: textarea
id: actual_behavior
attributes:
label: 🙁 Actual behavior
description: 'What happened, and why it was wrong.'
validations:
required: true
- type: textarea
id: expected_behavior
attributes:
label: 🙂 Expected behavior
description: What you expected to happen instead, and why
validations:
required: true
- type: textarea
id: additional_info
attributes:
label: Additional information about the issue
```
## /.github/ISSUE_TEMPLATE/config.yml
```yml path="/.github/ISSUE_TEMPLATE/config.yml"
---
blank_issues_enabled: true
contact_links:
-
about: "Please check the FAQ before filing new issues"
name: "ast-grep FAQ"
url: "https://ast-grep.github.io/advanced/faq.html"
-
about: "Please ask pattern/rule questions on StackOverflow."
name: StackOverflow
url: "https://stackoverflow.com/questions/tagged/ast-grep"
-
about: "Please ask and answer usage questions on GitHub Discussion."
name: Question
url: "https://github.com/ast-grep/ast-grep/discussions/categories/q-a"
-
about: "Alternatively, you can use the ast-grep Community Discord."
name: Chat
url: "https://discord.gg/4YZjf6htSQ"
```
## /.github/ISSUE_TEMPLATE/feature_request.md
---
name: Feature Request
about: Suggest an idea
title: '[feature]'
labels: 'enhancement'
assignees: ''
---
# ⭐ Suggestion
<!-- A summary of what you'd like to see added or changed -->
# 💻 Use Cases
<!--
What do you want to use this for?
What shortcomings exist with current approaches?
What workarounds are you using in the meantime?
-->
## /.github/copilot-instructions.md
# ast-grep Development Instructions
ast-grep is a CLI tool for structural search, lint, and code rewriting using abstract syntax trees. It's written in Rust with TypeScript/Node.js (NAPI) and Python bindings.
**Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.**
## Working Effectively
### Essential Setup and Build Commands
Run these commands in the repository root (the top-level directory of your local clone, e.g. `<repo-root>`) in order:
- Check Rust toolchain: `rustc --version && cargo --version`
- Quick validation: `cargo check` - takes 2m 40s, validates dependencies and compilation
- Debug build: `cargo build` - takes 42 seconds. **NEVER CANCEL.** Set timeout to 120+ seconds.
- Release build: `cargo build --release` - takes 2m 26s. **NEVER CANCEL.** Set timeout to 180+ seconds.
- Test suite: `cargo test` - takes 48 seconds, runs 94+ tests. **NEVER CANCEL.** Set timeout to 120+ seconds.
### Linting and Formatting
- Format check: `cargo fmt --all -- --check` - takes <1 second
- Lint check: `cargo clippy --all-targets --all-features --workspace --release --locked -- -D clippy::all` - takes 30 seconds. **NEVER CANCEL.** Set timeout to 60+ seconds.
### Running the CLI
- Main binary: `./target/release/ast-grep --help`
- Short alias: `./target/debug/sg --version` (requires ast-grep in PATH: `PATH="$(pwd)/target/debug:$PATH"`)
- Test pattern matching: `./target/release/ast-grep -p 'console.log($MSG)' -l js /path/to/file.js`
- Test rewriting: `./target/release/ast-grep -p 'var $VAR = $VALUE' -r 'let $VAR = $VALUE' -l js /path/to/file.js`
## Node.js/NAPI Bindings
### Setup and Build
- Navigate to: `cd crates/napi`
- Install dependencies: `yarn install` - takes 41 seconds. **NEVER CANCEL.** Set timeout to 90+ seconds.
- Debug build: `yarn build:debug` - takes 37 seconds. **NEVER CANCEL.** Set timeout to 90+ seconds.
- Test suite: `yarn test` - takes 5 seconds, runs 42 tests. **NEVER CANCEL.** Set timeout to 30+ seconds.
- Lint (requires network): `yarn lint` - may fail due to network restrictions, this is expected
**Note:** The NAPI bindings require Node.js >= 10. Tested with Node.js v20.19.4 and yarn 1.22.22.
## Python Bindings
### Setup and Build
- Install maturin: `pip install maturin`
- Build wheel: `maturin build -m crates/pyo3/Cargo.toml` - takes 21 seconds. **NEVER CANCEL.** Set timeout to 60+ seconds.
- Install wheel: `pip install target/wheels/ast_grep_py-*.whl`
- Test import: `python3 -c "import ast_grep_py; print('Python bindings work!')"`
**Note:** Python tests require module to be built in development mode, which needs virtualenv. Use wheel installation instead.
## Validation Scenarios
### After Making Changes - ALWAYS run these scenarios:
1. **Basic CLI functionality test:**
```bash
echo 'console.log("hello world")' > /tmp/test.js
./target/release/ast-grep -p 'console.log($MSG)' -l js /tmp/test.js
# Should output: /tmp/test.js with line match
```
2. **Rewrite functionality test:**
```bash
echo 'var x = 5;' > /tmp/test.js
./target/release/ast-grep -p 'var $VAR = $VALUE' -r 'let $VAR = $VALUE' -l js /tmp/test.js
# Should show diff replacing var with let
```
3. **Configuration-based scanning test:**
```bash
mkdir -p /tmp/test-project/src && cd /tmp/test-project
echo 'var y = 10;' > src/test.js
cat > sgconfig.yml << 'EOF'
rule:
id: no-var
message: Use let or const instead of var
severity: error
language: javascript
rule:
pattern: var $VAR = $VALUE
fix: let $VAR = $VALUE
EOF
./target/release/ast-grep scan src/
```
4. **LSP server startup test:**
```bash
timeout 5 ./target/release/ast-grep lsp || echo "LSP started and stopped as expected"
# Should show configuration error (expected) or start LSP server
```
### Pre-commit validation checklist:
- [ ] `cargo fmt --all -- --check` (format check)
- [ ] `cargo clippy --all-targets --all-features --workspace --release --locked -- -D clippy::all` (lint check)
- [ ] `cargo test` (test suite)
- [ ] Run validation scenarios above
- [ ] If touching NAPI code: `cd crates/napi && yarn test`
## Common Issues and Solutions
### Build Issues
- **"No such file or directory" for sg binary:** Ensure ast-grep is in PATH or use full path to sg binary
- **Network errors during yarn lint:** Expected in restricted environments, dprint needs internet access
- **Python test failures:** Expected, tests require development build which needs virtualenv
### Performance Expectations
- **CRITICAL:** Builds can take 2+ minutes - **NEVER CANCEL** build or test commands
- Set timeouts: Debug build (120s+), Release build (180s+), Tests (120s+), Clippy (60s+)
- Large dependency downloads on first build are normal
## Repository Structure
### Key Directories
- `crates/cli/` - Main CLI application
- `crates/core/` - Core AST processing logic
- `crates/config/` - Configuration handling
- `crates/language/` - Language-specific parsers
- `crates/napi/` - Node.js bindings
- `crates/pyo3/` - Python bindings
- `crates/lsp/` - Language Server Protocol implementation
- `fixtures/` - Test fixtures
- `npm/` - NPM package distribution
### Important Files
- `Cargo.toml` - Workspace configuration
- `crates/cli/Cargo.toml` - Main CLI package config
- `crates/napi/package.json` - Node.js package config
- `pyproject.toml` - Python package config
- `.github/workflows/` - CI/CD pipelines
## Supported Languages
ast-grep supports 20+ programming languages including JavaScript, TypeScript, Python, Rust, Go, Java, C/C++, and more through tree-sitter parsers.
## Additional Notes
- The project uses tree-sitter for parsing
- Multiple output formats supported: colored terminal, JSON, etc.
- Interactive mode available for applying fixes
- LSP server for editor integration
- WASM version available for web usage
## /.github/workflows/coverage.yaml
```yaml path="/.github/workflows/coverage.yaml"
name: coverage
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
coverage:
runs-on: ubuntu-latest
env:
CARGO_TERM_COLOR: always
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Install Rust
run: rustup update stable
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Generate code coverage
run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info
- name: Upload to codecov.io
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
files: lcov.info
fail_ci_if_error: true
check:
name: Lint/Format Check
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v5
with:
submodules: true
- name: Cache
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target/
key: ${{ runner.os }}-${{ github.sha }}
restore-keys: ${{ runner.os }}-
- name: Format
run: cargo fmt --all -- --check
- name: Lint
run: cargo clippy --all-targets --all-features --workspace --release --locked -- -D clippy::all
napi:
name: NAPI Linting
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v6
- name: Lint NAPI
run: |-
cd crates/napi
yarn install
yarn lint
```
## /.github/workflows/napi.yml
```yml path="/.github/workflows/napi.yml"
name: Build NAPI
env:
DEBUG: napi:*
APP_NAME: ast-grep-napi
MACOSX_DEPLOYMENT_TARGET: '10.13'
on:
workflow_dispatch: null
push:
tags:
- "[0-9]+.*"
schedule:
# run napi every day 9 am
- cron: '0 9 * * *'
defaults:
run:
working-directory: ./crates/napi
jobs:
build:
if: "!contains(github.event.head_commit.message, 'skip ci')"
strategy:
fail-fast: false
matrix:
settings:
- host: macos-latest
target: x86_64-apple-darwin
# no test
build: |
yarn build --target x86_64-apple-darwin
strip -x *.node
- host: windows-latest
build: |
yarn build
yarn test
target: x86_64-pc-windows-msvc
- host: windows-latest
target: aarch64-pc-windows-msvc
build: yarn build --target aarch64-pc-windows-msvc
- host: windows-latest
build: |
yarn build --target i686-pc-windows-msvc
yarn test
target: i686-pc-windows-msvc
- host: ubuntu-22.04
target: x86_64-unknown-linux-gnu
build: |-
set -e &&
yarn build --target x86_64-unknown-linux-gnu &&
strip *.node &&
yarn test
- host: ubuntu-22.04
target: x86_64-unknown-linux-musl
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine-zig
build: |-
set -e &&
cd crates/napi &&
yarn build --target x86_64-unknown-linux-musl &&
strip *.node &&
yarn test
- host: ubuntu-22.04
target: aarch64-unknown-linux-musl
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
build: |-
set -e &&
cd crates/napi &&
rustup target add aarch64-unknown-linux-musl &&
yarn build --target aarch64-unknown-linux-musl
- host: ubuntu-latest
target: aarch64-unknown-linux-gnu
docker: ghcr.io/ast-grep/ast-grep/napi-aarch64-linux-gnu:latest
# tree-sitter does not build using its official docker image
# we have to roll our own and source the rustup
build: |-
set -e &&
. "$HOME/.cargo/env" &&
cd crates/napi &&
yarn build --target aarch64-unknown-linux-gnu &&
aarch64-unknown-linux-gnu-strip *.node
- host: macos-latest
target: aarch64-apple-darwin
build: |
yarn build --target aarch64-apple-darwin
strip -x *.node
yarn test
name: stable - ${{ matrix.settings.target }} - node@20
runs-on: ${{ matrix.settings.host }}
steps:
- uses: actions/checkout@v5
- name: Setup node
uses: actions/setup-node@v6
if: ${{ !matrix.settings.docker }}
with:
node-version: 20
check-latest: true
cache: yarn
cache-dependency-path: ./crates/napi/yarn.lock
- name: Install
uses: actions-rs/toolchain@v1
if: ${{ !matrix.settings.docker }}
with:
profile: minimal
override: true
toolchain: stable
target: ${{ matrix.settings.target }}
- name: Cache cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
.cargo-cache
target/
key: ${{ matrix.settings.target }}-cargo-registry
- name: Cache NPM dependencies
uses: actions/cache@v4
with:
path: .yarn/cache
key: npm-cache-build-${{ matrix.settings.target }}-node@20
- name: Setup toolchain
run: ${{ matrix.settings.setup }}
if: ${{ matrix.settings.setup }}
shell: bash
- name: Setup node x86
if: matrix.settings.target == 'i686-pc-windows-msvc'
run: yarn config set supportedArchitectures.cpu "ia32"
shell: bash
- name: Install dependencies
run: yarn install
- name: Setup node x86
uses: actions/setup-node@v6
if: matrix.settings.target == 'i686-pc-windows-msvc'
with:
node-version: 20
check-latest: true
cache: yarn
architecture: x86
cache-dependency-path: ./crates/napi/yarn.lock
- name: Build in docker
uses: addnab/docker-run-action@v3
if: ${{ matrix.settings.docker }}
with:
image: ${{ matrix.settings.docker }}
options: --user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build
run: ${{ matrix.settings.build }}
- name: Build
run: ${{ matrix.settings.build }}
if: ${{ !matrix.settings.docker }}
shell: bash
- name: Upload artifact
uses: actions/upload-artifact@v5
with:
name: bindings-${{ matrix.settings.target }}
path: crates/napi/${{ env.APP_NAME }}.*.node
if-no-files-found: error
publish:
name: Publish
runs-on: ubuntu-22.04
needs:
- build
steps:
- uses: actions/checkout@v5
- name: Setup node
uses: actions/setup-node@v6
with:
node-version: 20
check-latest: true
cache: yarn
cache-dependency-path: ./crates/napi/yarn.lock
- name: Cache NPM dependencies
uses: actions/cache@v4
with:
path: .yarn/cache
key: npm-cache-ubuntu-22.04-publish
restore-keys: |
npm-cache-
- name: Install dependencies
run: yarn install
- name: Download all artifacts
uses: actions/download-artifact@v6
with:
path: crates/napi/artifacts
- name: Generate Types
run: |-
yarn typegen
- name: Move artifacts
run: yarn artifacts
- name: List packages
run: ls -R ./npm
shell: bash
- name: Publish
run: |
if git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+{{contextString}}quot;;
then
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
npm publish --access public
elif git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+";
then
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
npm publish --tag next --access public
else
echo "Not a release, skipping publish"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
```
## /.github/workflows/pyo3.yml
```yml path="/.github/workflows/pyo3.yml"
name: PyO3
env:
PACKAGE_NAME: ast_grep_py # note: maturin package name only accepts underscore
PYTHON_VERSION: '3.10 3.11 3.12 3.13 3.14'
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
defaults:
run:
working-directory: ./crates/pyo3
on:
workflow_dispatch:
inputs:
need_release:
description: "Select this for publish. If not selected, it will be a dry run (no uploads)."
type: boolean
push:
tags:
- "[0-9]+.*"
schedule:
# run pypi every day 9 am
- cron: '0 9 * * *'
permissions:
contents: read
jobs:
linux:
runs-on: ubuntu-22.04
strategy:
matrix:
target:
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-gnu
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
# python-version: ${{ env.PYTHON_VERSION }}
python-version: '3.14'
architecture: x64
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
# manylinux by default uses old linux so tree-sitter does not compile
# https://github.com/woodruffw/zizmor/pull/603
manylinux: "2_28"
args: --release --out dist -i ${{env.PYTHON_VERSION}}
working-directory: crates/pyo3
- name: Test sdist
# GH action only has x86 runner
if: matrix.target == 'x86_64-unknown-linux-gnu'
run: |
pip install pytest
pip install --no-index --find-links=dist ${{env.PACKAGE_NAME}} --force-reinstall
pytest
- name: Upload wheels
uses: actions/upload-artifact@v5
with:
name: wheels-${{ matrix.target }}
path: crates/pyo3/dist
windows:
runs-on: windows-latest
strategy:
matrix:
target: [x64, x86]
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
# python-version: ${{ env.PYTHON_VERSION }}
python-version: |
3.10
3.11
3.12
3.13
3.14
architecture: ${{ matrix.target }}
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist -i ${{env.PYTHON_VERSION}}
working-directory: crates/pyo3
- name: Test sdist
run: |
pip install pytest
pip install --no-index --find-links=dist ${{env.PACKAGE_NAME}} --force-reinstall
pytest
- name: Upload wheels
uses: actions/upload-artifact@v5
with:
name: wheels-${{ matrix.target }}
path: crates/pyo3/dist
macos:
runs-on: macos-latest
strategy:
matrix:
target: [x86_64, aarch64]
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: '3.14'
# python-version: ${{ env.PYTHON_VERSION }}
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist --find-interpreter
working-directory: crates/pyo3
- name: Test sdist
if: matrix.target == 'aarch64'
run: |
pip install pytest
pip install --no-index --find-links=dist ${{env.PACKAGE_NAME}} --force-reinstall
pytest
- name: Upload wheels
uses: actions/upload-artifact@v5
with:
name: wheels-${{ matrix.target }}
path: crates/pyo3/dist
sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Build sdist
uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist
working-directory: crates/pyo3
- name: Upload sdist
uses: actions/upload-artifact@v5
with:
name: wheels
path: crates/pyo3/dist
release:
name: Release
runs-on: ubuntu-latest
if: "startsWith(github.event.ref, 'refs/tags') || inputs.need_release"
permissions:
# For pypi trusted publishing
id-token: write
needs: [linux, windows, macos, sdist]
steps:
- uses: actions/download-artifact@v6
with:
pattern: wheels*
merge-multiple: true
- name: Publish to PyPI
uses: PyO3/maturin-action@v1
with:
command: upload
args: --skip-existing *
# note, release does not need working-directory
# because artifacts are stored under the root dir
# and it does not involve pyproject.toml
```
## /.github/workflows/pypi.yml
```yml path="/.github/workflows/pypi.yml"
name: "PyPi Release"
on:
workflow_dispatch:
inputs:
need_release:
description: "Select this for publish. If not selected, it will be a dry run (no uploads)."
type: boolean
push:
tags:
- "[0-9]+.*"
schedule:
# run pypi every day 9 am
- cron: '0 9 * * *'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
PACKAGE_NAME: ast_grep_cli # note: maturin package name only accepts underscore
PYTHON_VERSION: "3.10" # to build abi3 wheels
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
jobs:
sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: "Build sdist"
uses: PyO3/maturin-action@v1.49.4
with:
command: sdist
args: --out dist
- name: "Test sdist"
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*.tar.gz --force-reinstall
sg --help
- name: "Upload sdist"
uses: actions/upload-artifact@v5
with:
name: wheels
path: dist
macos-x86_64:
# use old macos due to https://github.com/actions/setup-python/issues/852
runs-on: macos-13
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
# https://github.com/actions/runner-images/issues/658#issuecomment-611921753
- name: "Build wheels - x86_64"
uses: PyO3/maturin-action@v1
env:
# old llvm does not support libc++ by default
CXXFLAGS: "-stdlib=libc++"
with:
target: x86_64
args: --release --out dist
- name: "Test wheel - x86_64"
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
sg --help
- name: "Upload wheels"
uses: actions/upload-artifact@v5
with:
name: wheels-macos-x86_64
path: dist
macos-universal:
# use old macos due to https://github.com/actions/setup-python/issues/852
runs-on: macos-13
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: "Build wheels - universal2"
uses: PyO3/maturin-action@v1
env:
# instruct old llvm to support libc++
CXXFLAGS: "-stdlib=libc++"
with:
args: --release --target universal2-apple-darwin --out dist
- name: "Test wheel - universal2"
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*universal2.whl --force-reinstall
sg --help
- name: "Upload wheels"
uses: actions/upload-artifact@v5
with:
name: wheels-aarch64-apple-darwin
path: dist
windows:
runs-on: windows-latest
strategy:
matrix:
platform:
- target: x86_64-pc-windows-msvc
arch: x64
- target: i686-pc-windows-msvc
arch: x86
- target: aarch64-pc-windows-msvc
arch: x64
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: ${{ matrix.platform.arch }}
- name: "Build wheels"
uses: PyO3/maturin-action@v1
env:
# https://github.com/rust-cross/cargo-xwin/issues/108
XWIN_VERSION: "16"
with:
target: ${{ matrix.platform.target }}
args: --release --out dist
- name: "Test wheel"
if: ${{ !startsWith(matrix.platform.target, 'aarch64') }}
shell: bash
run: |
python -m pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
sg --help
- name: "Upload wheels"
uses: actions/upload-artifact@v5
with:
name: wheels-${{ matrix.platform.target }}
path: dist
linux:
runs-on: ubuntu-22.04
strategy:
matrix:
target:
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-gnu
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: "Build wheels"
uses: PyO3/maturin-action@v1
env:
CFLAGS: "-std=c11"
CXXFLAGS: "-std=c++11"
with:
target: ${{ matrix.target }}
# manylinux by default uses old linux so tree-sitter does not compile
# https://github.com/woodruffw/zizmor/pull/603
manylinux: "2_28"
args: --release --out dist
- name: "Test wheel"
if: ${{ startsWith(matrix.target, 'x86_64') }}
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
sg --help
- name: "Upload wheels"
uses: actions/upload-artifact@v5
with:
name: wheels-${{ matrix.target }}
path: dist
upload-release:
name: Upload to PyPI
runs-on: ubuntu-latest
needs:
- macos-universal
- macos-x86_64
- windows
- linux
# - linux-cross I would want to support linux-cross but it is too hard for me to compile c projects for tree-sitter-languages
# If you don't set release flag, it's a dry run (no uploads).
if: "startsWith(github.event.ref, 'refs/tags') || inputs.need_release"
environment:
name: release
permissions:
# For pypi trusted publishing
id-token: write
steps:
- name: "Install uv"
uses: astral-sh/setup-uv@v7
- uses: actions/download-artifact@v6
with:
pattern: wheels*
merge-multiple: true
path: wheels
- name: Publish to PyPi
run: uv publish -v wheels/*
# - name: Publish to PyPi
# uses: pypa/gh-action-pypi-publish@release/v1
# with:
# skip-existing: true
# packages-dir: wheels
# verbose: true
```
## /.github/workflows/release.yml
```yml path="/.github/workflows/release.yml"
name: Release
permissions:
contents: write
on:
# enable workflow_dispatch when debugging
workflow_dispatch: null
push:
tags:
- "[0-9]+.*"
jobs:
push_crates_io:
# If you don't set an input tag, it's a dry run (no uploads).
if: "startsWith(github.event.ref, 'refs/tags')"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v5
- uses: katyo/publish-crates@v2
with:
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
publish-delay: 5000
create-release:
# If you don't set an input tag, it's a dry run (no uploads).
if: "startsWith(github.event.ref, 'refs/tags')"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v5
- uses: taiki-e/create-gh-release-action@v1
with:
changelog: CHANGELOG.md
token: ${{ secrets.GITHUB_TOKEN }}
upload-assets:
strategy:
matrix:
include:
- target: x86_64-pc-windows-msvc
os: windows-latest
- target: i686-pc-windows-msvc
os: windows-latest
- target: aarch64-pc-windows-msvc
os: windows-latest
- target: aarch64-apple-darwin
os: macos-latest
- target: x86_64-apple-darwin
os: macos-latest
- target: x86_64-unknown-linux-gnu
os: ubuntu-22.04
- target: aarch64-unknown-linux-gnu
os: ubuntu-22.04
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: taiki-e/upload-rust-binary-action@v1
with:
bin: sg,ast-grep
tar: none
# (optional) On which platform to distribute the `.zip` file.
# [default value: windows]
# [possible values: all, unix, windows, none]
zip: all
target: ${{ matrix.target }}
archive: app-$target
# uncomment this for debug
# ref: refs/tags/debug_release
env:
# (required)
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
release-npm:
runs-on: ubuntu-22.04
name: Release npm cli
needs:
- upload-assets
steps:
- uses: actions/checkout@v5
- name: Setup node
uses: actions/setup-node@v6
- name: Download artifacts
uses: robinraju/release-downloader@v1.12
with:
latest: true
fileName: "*.zip"
out-file-path: artifacts
- name: Unzip packages
run: |
files=(aarch64-apple-darwin x86_64-apple-darwin x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu)
target_dir=(darwin-arm64 darwin-x64 linux-x64-gnu linux-arm64-gnu)
length=${#files[@]}
for (( i=0; i<${length}; i++ ));
do
unzip "artifacts/app-${files[$i]}.zip" -d "npm/platforms/${target_dir[$i]}/"
done
# windows
files=(x86_64-pc-windows-msvc i686-pc-windows-msvc aarch64-pc-windows-msvc)
target_dir=(win32-x64-msvc win32-ia32-msvc win32-arm64-msvc)
length=${#files[@]}
for (( i=0; i<${length}; i++ ));
do
unzip "artifacts/app-${files[$i]}.zip" -d "npm/platforms/${target_dir[$i]}/"
done
- run: echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} > ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish to npm
run: |
cd npm
for pkg in platforms/*; do
echo "Publishing $pkg..."
cd $pkg;
npm publish;
cd ../..;
done
echo "Publishing @ast-grep/cli...";
npm publish
changelog:
runs-on: ubuntu-22.04
steps:
- name: "✏️ Generate release changelog"
uses: heinrichreimer/action-github-changelog-generator@v2.4
with:
token: ${{ secrets.GITHUB_TOKEN }}
```
## /.gitignore
```gitignore path="/.gitignore"
/target
node_modules
# IDEs
/.idea
node_modules
.DS_Store
dist
dist-ssr
*.local
# Created by https://www.toptal.com/developers/gitignore/api/node
# Edit at https://www.toptal.com/developers/gitignore?templates=node
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# End of https://www.toptal.com/developers/gitignore/api/node
#Added by cargo
/target
*.node
.yarn
# Byte-compiled / optimized / DLL files
__pycache__/
.pytest_cache/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
.venv/
env/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
include/
man/
venv/
*.egg-info/
.installed.cfg
*.egg
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
pip-selfcheck.json
# Python Ecosystem
*.mo
.mr.developer.cfg
.project
.pydevproject
.ropeproject
*.pot
# Sphinx documentation
docs/_build/
# VSCode
.vscode/
# Pyenv
.python-version
```
## /.pre-commit-config.yaml
```yaml path="/.pre-commit-config.yaml"
repos:
- repo: https://github.com/doublify/pre-commit-rust
rev: v1.0
hooks:
- id: fmt
- id: cargo-check
- id: clippy
args: ["--all-targets", "--all-features", "--", "-D", "clippy::all"]
```
## /CHANGELOG.md
### Changelog
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [0.39.9](https://github.com/ast-grep/ast-grep/compare/0.39.7...0.39.9)
- fix: do not output matched highlight when --update-all [`#2317`](https://github.com/ast-grep/ast-grep/issues/2317)
- chore(deps): update dependency @ast-grep/napi to v0.39.7 [`d6306b1`](https://github.com/ast-grep/ast-grep/commit/d6306b1d8ef42146a01d9d0a73a217e62ac430c1)
- chore(deps): update dependency oxlint to v1.26.0 [`85a256e`](https://github.com/ast-grep/ast-grep/commit/85a256e5c4f0b1e2e265ff5b2f7d9c87f5de9624)
- chore(deps): update dependency oxlint to v1.25.0 [`0c09885`](https://github.com/ast-grep/ast-grep/commit/0c0988503cf0b0c3972cd719fc461ba1b143ab0e)
#### [0.39.7](https://github.com/ast-grep/ast-grep/compare/0.39.6...0.39.7)
> 27 October 2025
- feat: LSP quick fix should support expandStart / expandEnd [`#2301`](https://github.com/ast-grep/ast-grep/issues/2301)
- chore(deps): update dependency @ast-grep/napi to v0.39.6 [`6859809`](https://github.com/ast-grep/ast-grep/commit/68598091f2ae47d4d52d701f706590a964bc2ba5)
- chore(deps): update dependency oxlint to v1.24.0 [`6ee2f84`](https://github.com/ast-grep/ast-grep/commit/6ee2f84b606ca485446263fee27bba4f2753d601)
- chore(deps): update dependency oxlint to v1.23.0 [`c88c8ca`](https://github.com/ast-grep/ast-grep/commit/c88c8ca063f9695355bacf5a891b40879bc7fe07)
#### [0.39.6](https://github.com/ast-grep/ast-grep/compare/0.39.5...0.39.6)
> 4 October 2025
- chore(deps): update dependency @napi-rs/cli to v3.2.0 [`4fc74cd`](https://github.com/ast-grep/ast-grep/commit/4fc74cdab5c41541e39443f700d94de72a51cd9d)
- fix(deps): update rust crate inquire to 0.8.0 [`575f750`](https://github.com/ast-grep/ast-grep/commit/575f750e9f470f4f9c2bbf5eaf59f53343345b7b)
- chore(deps): update dependency @ast-grep/napi to v0.39.5 [`62ac63c`](https://github.com/ast-grep/ast-grep/commit/62ac63c76b8a4876351650153bd58796a481b9d5)
#### [0.39.5](https://github.com/ast-grep/ast-grep/compare/0.39.4...0.39.5)
> 8 September 2025
- Allowing LSP clients without publish diagnostics data support to support code fixes [`#2209`](https://github.com/ast-grep/ast-grep/pull/2209)
- fix: store client cap and do not send workspace folder req [`#2211`](https://github.com/ast-grep/ast-grep/issues/2211)
- fix: comment after node should be ignored in strictness=relax [`#2216`](https://github.com/ast-grep/ast-grep/issues/2216)
- fix: apply_all_code_actions function disallow multi-line [`#2222`](https://github.com/ast-grep/ast-grep/issues/2222)
- chore(deps): update dependency oxlint to v1.13.0 [`4be8252`](https://github.com/ast-grep/ast-grep/commit/4be8252ed739a663f1a751e654ee150312fdfba3)
- chore(deps): update dependency @ast-grep/napi to v0.39.4 [`3187d39`](https://github.com/ast-grep/ast-grep/commit/3187d393a60e503c45cd16151429db45d3347ec4)
- chore(deps): update dependency oxlint to v1.14.0 [`7405f99`](https://github.com/ast-grep/ast-grep/commit/7405f9935e8038e077efeb02a3f6ab1b9014dc6b)
#### [0.39.4](https://github.com/ast-grep/ast-grep/compare/0.39.3...0.39.4)
> 16 August 2025
- Improve error messages for ast-grep test failures based on failure type [`#2174`](https://github.com/ast-grep/ast-grep/pull/2174)
- Add comprehensive GitHub Copilot development instructions [`#2152`](https://github.com/ast-grep/ast-grep/pull/2152)
- Address all code review comments: move make_rule_finder to lsp.rs, simplify logic, reduce indentation, update file watchers, remove unused deps [`8ef8ed6`](https://github.com/ast-grep/ast-grep/commit/8ef8ed63490b379f7cee54ad0cf6ff9e8d557016)
- Decouple rule finding logic from LSP crate as requested [`531aac3`](https://github.com/ast-grep/ast-grep/commit/531aac39f2465a0f53a32e437838895e637f0105)
- Complete LSP rule reloading implementation with tests [`683f20e`](https://github.com/ast-grep/ast-grep/commit/683f20eb8d3adcbfc525ad1c640413dcbf2d6bd9)
#### [0.39.3](https://github.com/ast-grep/ast-grep/compare/0.39.2...0.39.3)
> 5 August 2025
- add some tests for hcl [`756499e`](https://github.com/ast-grep/ast-grep/commit/756499eab0e895fbc5641a83a9dc53c341ad82b5)
- add tree-sitter-hcl to ast-grep-language package deps [`26b638a`](https://github.com/ast-grep/ast-grep/commit/26b638ac2dc997ac88f564ec4923bb36fb588a50)
- fix(deps): update rust crate clap to v4.5.42 [`4d047eb`](https://github.com/ast-grep/ast-grep/commit/4d047ebad029c2fc53176ba51d8929986efd42f1)
#### [0.39.2](https://github.com/ast-grep/ast-grep/compare/0.39.1...0.39.2)
> 27 July 2025
- fix(deps): update rust crate tokio to v1.47.0 [`#2124`](https://github.com/ast-grep/ast-grep/pull/2124)
- fix: ignore comments in relax/signature/template strictness [`#2122`](https://github.com/ast-grep/ast-grep/issues/2122)
- fix: prefer using env to determine bgcolor [`#2114`](https://github.com/ast-grep/ast-grep/issues/2114)
- fix: update rules [`c5fd340`](https://github.com/ast-grep/ast-grep/commit/c5fd34000af4cee2d74234e60649c7f81d97ca05)
- chore(deps): update dependency @napi-rs/cli to v3.0.4 [`b07e5bd`](https://github.com/ast-grep/ast-grep/commit/b07e5bd74cfa5c92dc336528b1b8fe5288d9173c)
- fix: temporarily remove tweaking [`d2fedd2`](https://github.com/ast-grep/ast-grep/commit/d2fedd2a7ab82109baf07216241af25164ce9f7e)
#### [0.39.1](https://github.com/ast-grep/ast-grep/compare/0.39.0...0.39.1)
> 20 July 2025
- fix: update package [`8c2327b`](https://github.com/ast-grep/ast-grep/commit/8c2327b247dbe3b218a7a888480a7cf0ad765fb4)
- fix: fix build [`d59c219`](https://github.com/ast-grep/ast-grep/commit/d59c219299af827da11a52f9e7f8ffbbb3ebaeb8)
- fix: remove json format [`90369a4`](https://github.com/ast-grep/ast-grep/commit/90369a4cf698e8d3cbdc9c872f852c277588521b)
#### [0.39.0](https://github.com/ast-grep/ast-grep/compare/0.38.7...0.39.0)
> 20 July 2025
- feat: support esquery in kind [`#2007`](https://github.com/ast-grep/ast-grep/issues/2007)
- fix: path in files: section should be relative to config file [`#2101`](https://github.com/ast-grep/ast-grep/issues/2101)
- feat: add new pattern strictness: template [`#2097`](https://github.com/ast-grep/ast-grep/issues/2097)
- fix: move applied change to stderr [`#1890`](https://github.com/ast-grep/ast-grep/issues/1890)
- fix: use better expando char for c/cpp [`#2086`](https://github.com/ast-grep/ast-grep/issues/2086)
- chore: bump napi-v3 [`d815af2`](https://github.com/ast-grep/ast-grep/commit/d815af2034bc3ec8acbeb104690b55719c1a421b)
- refactor: reorganize exports in index.d.ts and update linting configuration [`74e22e2`](https://github.com/ast-grep/ast-grep/commit/74e22e224580611712dbaddb52e9cb6159d96384)
- refactor: remove biome configuration and update linting tool to oxlint [`61ab759`](https://github.com/ast-grep/ast-grep/commit/61ab7592e91f4293b6161955fcbb58c7d7e8a50c)
#### [0.38.7](https://github.com/ast-grep/ast-grep/compare/0.38.6...0.38.7)
> 9 July 2025
- refactor: remove biome configuration and update linting tool to oxlint [`ed3e5b1`](https://github.com/ast-grep/ast-grep/commit/ed3e5b1197b1ebec1533021fd446366eac0d2408)
- chore(deps): update dependency @ast-grep/napi to v0.38.6 [`9e5f1e0`](https://github.com/ast-grep/ast-grep/commit/9e5f1e070aed4557f998db0c4cab97f4aead9fbe)
- Revert "fix(deps): update rust crate tower-lsp-server to 0.22.0" [`7d8e872`](https://github.com/ast-grep/ast-grep/commit/7d8e872b5b4aea3bd5d1c3f7311d375378e8181a)
#### [0.38.6](https://github.com/ast-grep/ast-grep/compare/0.38.5...0.38.6)
> 23 June 2025
- fix(deps): update rust crate tower-lsp-server to 0.22.0 [`#2056`](https://github.com/ast-grep/ast-grep/pull/2056)
- feat: allow sgconfig.yml to not have required ruleDirs field [`#2059`](https://github.com/ast-grep/ast-grep/issues/2059)
- fix: ast-grep -h should not fail if sgconfig is wrong [`#2054`](https://github.com/ast-grep/ast-grep/issues/2054)
- chore(deps): update dependency @ast-grep/napi to v0.38.5 [`c7a41d6`](https://github.com/ast-grep/ast-grep/commit/c7a41d62bde99f734a31ac4b325a3a4e25a9c8fe)
- Revert "fix(deps): update rust crate tower-lsp-server to 0.22.0 (#2056)" [`a5a011b`](https://github.com/ast-grep/ast-grep/commit/a5a011b3c6add149df9da410ae5a272f96832019)
- fix(deps): update rust crate toml_edit to v0.22.27 [`84cff96`](https://github.com/ast-grep/ast-grep/commit/84cff969999aae84b10bc5de17a739a636a4afb7)
#### [0.38.5](https://github.com/ast-grep/ast-grep/compare/0.38.4...0.38.5)
> 8 June 2025
- feat: add simple cli multi fix [`#2036`](https://github.com/ast-grep/ast-grep/issues/2036)
- fix: quit option should keep accepted changes [`#2031`](https://github.com/ast-grep/ast-grep/issues/2031)
- feat: add better styling handle [`1a2b72c`](https://github.com/ast-grep/ast-grep/commit/1a2b72c5a6be0e506a6e07f5332bfa47aeac63af)
- feat: add fix switching [`791c9a0`](https://github.com/ast-grep/ast-grep/commit/791c9a0258d08481bf6aa01c02a503b16e3be64d)
- chore(deps): update dependency ava to v6.4.0 [`0e51f2b`](https://github.com/ast-grep/ast-grep/commit/0e51f2b7dcda81180a7fcdd577c6fa952d0647a6)
#### [0.38.4](https://github.com/ast-grep/ast-grep/compare/0.38.3...0.38.4)
> 1 June 2025
- feat: support file level suppression [`#1541`](https://github.com/ast-grep/ast-grep/issues/1541)
- feat: add include-off rule in sg test [`#2023`](https://github.com/ast-grep/ast-grep/issues/2023)
- feat: add multiple fix in vscode lsp [`9e9a282`](https://github.com/ast-grep/ast-grep/commit/9e9a282ca1a67592902a26eeae5ee5721966e116)
- chore(deps): update dependency @ast-grep/napi to v0.38.3 [`58dcc5b`](https://github.com/ast-grep/ast-grep/commit/58dcc5b8e8ed2afccb5fb5cadd4548888852f090)
- test: add file level suppression test [`93d04c7`](https://github.com/ast-grep/ast-grep/commit/93d04c716a34c542c388c4bd44bb4298608f109d)
#### [0.38.3](https://github.com/ast-grep/ast-grep/compare/0.38.2...0.38.3)
> 28 May 2025
- feat: support object style libraryPath [`#2013`](https://github.com/ast-grep/ast-grep/issues/2013)
- feat: Support Markdown rendering in CLI [`#1976`](https://github.com/ast-grep/ast-grep/issues/1976)
- test: add more test for transform parse [`#902`](https://github.com/ast-grep/ast-grep/issues/902)
- feat: implement transform shortcut [`#902`](https://github.com/ast-grep/ast-grep/issues/902)
- feat: add note in lsp hover info [`b4f32df`](https://github.com/ast-grep/ast-grep/commit/b4f32dfb5cc244335886c9f2c88151a07e6c1373)
- feat: add test case for string transform [`0c75416`](https://github.com/ast-grep/ast-grep/commit/0c75416e5d7f753eaf267b81630fc09ac5248eba)
- refactor: simplify code [`0d3ba01`](https://github.com/ast-grep/ast-grep/commit/0d3ba0108a63e5e37ee755a30b97b8c4a34afabf)
#### [0.38.2](https://github.com/ast-grep/ast-grep/compare/0.38.1...0.38.2)
> 18 May 2025
- feat: add --include-metadata flag in ast-grep scan [`#1987`](https://github.com/ast-grep/ast-grep/issues/1987)
- fix: respect transform in fix [`#1991`](https://github.com/ast-grep/ast-grep/issues/1991)
- **Breaking change:** feat: remove tree-sitter-facade-sg [`1e8a38d`](https://github.com/ast-grep/ast-grep/commit/1e8a38d181766cf638ffeecfdaae550d1acdadd6)
- fix: remove benches [`c44c315`](https://github.com/ast-grep/ast-grep/commit/c44c315947a52b8f0741f7ac3450983029e3ea80)
- feat: add label handling in json print [`33192d7`](https://github.com/ast-grep/ast-grep/commit/33192d776b8d1277b9cb3551c0c86ffc7afb22f7)
#### [0.38.1](https://github.com/ast-grep/ast-grep/compare/0.38.0...0.38.1)
> 11 May 2025
- chore(deps): update dependency @ast-grep/napi to v0.38.0 [`227bab3`](https://github.com/ast-grep/ast-grep/commit/227bab35500a6309e21e0c72a485eaaa14e715bb)
- Revert "fix: simplify version" [`a5d9fd8`](https://github.com/ast-grep/ast-grep/commit/a5d9fd82e516df44b926a546ca37505577de6f86)
- fix(deps): update rust crate clap to v4.5.38 [`cc8047c`](https://github.com/ast-grep/ast-grep/commit/cc8047c4b43b2287f79bd12b957576a9bbc4e713)
#### [0.38.0](https://github.com/ast-grep/ast-grep/compare/0.37.0...0.38.0)
> 10 May 2025
- feat: use newer tower_lsp crate [`#1975`](https://github.com/ast-grep/ast-grep/issues/1975)
- fix: --json=stream should output a trailing newline [`#1969`](https://github.com/ast-grep/ast-grep/issues/1969)
- fix: matcher leaks environment changes when the second matcher fails [`#1956`](https://github.com/ast-grep/ast-grep/issues/1956)
- fix: remove anyhow in ast-grep-config [`#1967`](https://github.com/ast-grep/ast-grep/issues/1967)
- feat: remove language bound in matcher [`c9b450d`](https://github.com/ast-grep/ast-grep/commit/c9b450d8479cc8203cdc9e29fbaf6dc4906a9369)
- fix: bump deps version [`8265edb`](https://github.com/ast-grep/ast-grep/commit/8265edb83d9e6dd3637eaced4243b6ae486c2f6a)
- refactor: move out tree_sitter specific stuff [`6d5e4e4`](https://github.com/ast-grep/ast-grep/commit/6d5e4e4f4e26265d52db8950acf04e32bc26d21a)
#### [0.37.0](https://github.com/ast-grep/ast-grep/compare/0.36.3...0.37.0)
> 14 April 2025
- fix: use docker image instead [`#1930`](https://github.com/ast-grep/ast-grep/issues/1930)
- chore(deps): update dependency @ast-grep/napi to v0.36.3 [`3dca58c`](https://github.com/ast-grep/ast-grep/commit/3dca58c91a2de7aa3652a2487700c1da98fc3b91)
- fix: update crate [`a88a073`](https://github.com/ast-grep/ast-grep/commit/a88a073e0b388d9529e8a9424d36773e39313eee)
- perf: more efficient node ancestor calculation [`a1ed329`](https://github.com/ast-grep/ast-grep/commit/a1ed32950836ea19d4c2a03cc14ebde681872ee5)
#### [0.36.3](https://github.com/ast-grep/ast-grep/compare/0.36.2...0.36.3)
> 13 April 2025
- fix: transformation indent [`#1405`](https://github.com/ast-grep/ast-grep/issues/1405)
- feat: YAML schema validation for node and field types [`e0a5a47`](https://github.com/ast-grep/ast-grep/commit/e0a5a47f6fa4af871ed803729ac4966773c380cb)
- chore(deps): update dependency @ast-grep/napi to v0.36.2 [`f291ef3`](https://github.com/ast-grep/ast-grep/commit/f291ef3746c1565d01c7b5d785cb6df43d2d52b9)
- fix(deps): update rust crate crossterm to 0.29.0 [`4c62fe5`](https://github.com/ast-grep/ast-grep/commit/4c62fe50964c0a97ad4e0d06cd40dd817a606402)
#### [0.36.2](https://github.com/ast-grep/ast-grep/compare/0.36.1...0.36.2)
> 19 March 2025
- fix: report-style short should not print out diff [`#1884`](https://github.com/ast-grep/ast-grep/issues/1884)
- fix: fix do not push empty unused suppressions [`#1882`](https://github.com/ast-grep/ast-grep/issues/1882)
- chore: Remove tree-sitter wasm facade [`47b14fe`](https://github.com/ast-grep/ast-grep/commit/47b14fe1b8b908e1877ee32f12585817c7ffdb72)
- Revert "chore: Remove tree-sitter wasm facade" [`a449617`](https://github.com/ast-grep/ast-grep/commit/a44961716493ee51b1482d8c92a104009c438e4b)
- fix: remove optimize [`7365a68`](https://github.com/ast-grep/ast-grep/commit/7365a68d0b80ff5ada493b5b416d18e9f06826b7)
#### [0.36.1](https://github.com/ast-grep/ast-grep/compare/0.36.0...0.36.1)
> 11 March 2025
- fix: fix globs not working with lang [`#1861`](https://github.com/ast-grep/ast-grep/issues/1861)
- perf: use smallvec for filter_file_{pattern, rule} [`#1858`](https://github.com/ast-grep/ast-grep/issues/1858)
- fix(deps): update dependency @swc/core to v1.11.8 [`a0d9e10`](https://github.com/ast-grep/ast-grep/commit/a0d9e10737ecc9b5f8da9d118ed090ce11ce1430)
- perf: save pattern clone and doc clone in multi-lang-doc [`1113384`](https://github.com/ast-grep/ast-grep/commit/11133841a5cc7bb2a6328177559b595ca4e22d6c)
- perf: remove duplicate pattern match [`6eba6d9`](https://github.com/ast-grep/ast-grep/commit/6eba6d9008beac4ddfab01f0ed04910184462200)
#### [0.36.0](https://github.com/ast-grep/ast-grep/compare/0.35.0...0.36.0)
> 9 March 2025
- perf: remove preScan struct [`#1837`](https://github.com/ast-grep/ast-grep/issues/1837)
- refactor: change Worker trait signature [`#1849`](https://github.com/ast-grep/ast-grep/issues/1849)
- fix: fix inconsistent matching in relaxed mode [`#1848`](https://github.com/ast-grep/ast-grep/issues/1848)
- feat: allow richer metadata format [`#1854`](https://github.com/ast-grep/ast-grep/issues/1854)
- fix: do not display color for error when not in tty [`#1850`](https://github.com/ast-grep/ast-grep/issues/1850)
- fix: make scan respect --globs flag [`#1842`](https://github.com/ast-grep/ast-grep/issues/1842)
- feat: move processing to worker thread [`#143`](https://github.com/ast-grep/ast-grep/issues/143)
- feat: move matching to worker [`#143`](https://github.com/ast-grep/ast-grep/issues/143)
- refactor: remove Matches/Diffs macro in cli [`#1819`](https://github.com/ast-grep/ast-grep/issues/1819)
- **Breaking change:** fix: bump min msrv [`846be66`](https://github.com/ast-grep/ast-grep/commit/846be6670c29066911f834be96ff351e06138e4d)
- refactor: split colored printer [`083a5f9`](https://github.com/ast-grep/ast-grep/commit/083a5f99e8df442f7b74cde34d0d5a6d758e9696)
- refactor: moved interactive printer [`b9bedf4`](https://github.com/ast-grep/ast-grep/commit/b9bedf4bb14297ef46a626f88c151b6ffbff6624)
#### [0.35.0](https://github.com/ast-grep/ast-grep/compare/0.34.4...0.35.0)
> 15 February 2025
- fix: ensure SerializableStopBy serialization matches deserialization [`#1802`](https://github.com/ast-grep/ast-grep/pull/1802)
- fix: ensure SerializableStopBy serialization matches deserialization (#1802) [`#1802`](https://github.com/ast-grep/ast-grep/issues/1802)
- feat: allow ERROR node in pattern to match everything [`#1791`](https://github.com/ast-grep/ast-grep/issues/1791)
- **Breaking change:** feat: use php-only-language for php [`#900`](https://github.com/ast-grep/ast-grep/issues/900)
- **Breaking change:** refactor: better apis [`d91b5c5`](https://github.com/ast-grep/ast-grep/commit/d91b5c5f2ce6b8e379a9a260d325bda6b22f4322)
- chore(deps): update dependency @ast-grep/napi to v0.34.4 [`a7ca599`](https://github.com/ast-grep/ast-grep/commit/a7ca599a78c3d2e6699ac8b5a132e4873edc34ed)
- fix(deps): update dependency @babel/core to v7.26.8 [`bf5b3a4`](https://github.com/ast-grep/ast-grep/commit/bf5b3a4af7c50328afb217d88a17d8a92961f489)
#### [0.34.4](https://github.com/ast-grep/ast-grep/compare/0.34.3...0.34.4)
> 2 February 2025
- fix: fix bininstall [`#1744`](https://github.com/ast-grep/ast-grep/issues/1744)
- fix(deps): update dependency @swc/core to v1.10.12 [`3eea108`](https://github.com/ast-grep/ast-grep/commit/3eea108dd17e8ff5fec59f4f338000683f2e1f0c)
- chore(deps): update rust crate tempfile to v3.16.0 [`6939cca`](https://github.com/ast-grep/ast-grep/commit/6939cca761297283a0a13b3631824100b0cb373b)
- chore(deps): update dependency @types/node to v22.12.0 [`ea05951`](https://github.com/ast-grep/ast-grep/commit/ea059515fd740460c5e6e777879a710e2ff81e08)
#### [0.34.3](https://github.com/ast-grep/ast-grep/compare/0.34.2...0.34.3)
> 27 January 2025
- fix: fix binary command again [`2c9034c`](https://github.com/ast-grep/ast-grep/commit/2c9034c5eaacaee5c8d0dd4218e33a9de55eb431)
#### [0.34.2](https://github.com/ast-grep/ast-grep/compare/0.34.1...0.34.2)
> 27 January 2025
- fix: fix binary name for npm [`#1773`](https://github.com/ast-grep/ast-grep/issues/1773)
- fix: do not publich napi [`553f5e5`](https://github.com/ast-grep/ast-grep/commit/553f5e5ac577b6d2e0904c423bb5dbd27804328b)
#### [0.34.1](https://github.com/ast-grep/ast-grep/compare/0.34.0...0.34.1)
> 26 January 2025
- fix: fix windows error [`4e48c9f`](https://github.com/ast-grep/ast-grep/commit/4e48c9fab70887c308603e16333e2727b11b43e6)
#### [0.34.0](https://github.com/ast-grep/ast-grep/compare/0.33.1...0.34.0)
> 26 January 2025
- feat: support `cargo binstall` [`#1744`](https://github.com/ast-grep/ast-grep/pull/1744)
- feat: add tracing of resolved configuration file path [`#1755`](https://github.com/ast-grep/ast-grep/issues/1755)
- fix: report error in run with wrong yaml [`#1768`](https://github.com/ast-grep/ast-grep/issues/1768)
- **Breaking change:** feat: reduce napi binary size [`#1759`](https://github.com/ast-grep/ast-grep/issues/1759)
- feat: compile sg as an alias of ast-grep [`#1757`](https://github.com/ast-grep/ast-grep/issues/1757)
- doc: update readme [`#574`](https://github.com/ast-grep/ast-grep/issues/574)
- fix: remove sequel [`#1743`](https://github.com/ast-grep/ast-grep/issues/1743)
- **Breaking change:** feat: remove nonsupported language [`e5a0a55`](https://github.com/ast-grep/ast-grep/commit/e5a0a55479eaeca11aa98dda6e25caf0f93ec980)
- feat: use macro to simplify code [`d762257`](https://github.com/ast-grep/ast-grep/commit/d7622579324e5ffae58b2f0ef2ce9ddcc7a60d89)
- fix(deps): update dependency @babel/core to v7.26.7 [`5944dbf`](https://github.com/ast-grep/ast-grep/commit/5944dbfba4c23986c3d2b0812d80c764d41aaf57)
#### [0.33.1](https://github.com/ast-grep/ast-grep/compare/0.33.0...0.33.1)
> 12 January 2025
- feat: Support passing rule config to SgNode match methods (`matches`, `has`, `inside`, `follows`, `precedes`) [`#1730`](https://github.com/ast-grep/ast-grep/pull/1730)
- chore(deps): update dependency @ast-grep/napi to v0.33.0 [`61f7177`](https://github.com/ast-grep/ast-grep/commit/61f71771c049a6ec7f3004fa0abc9dd7e8fcec19)
- fix(deps): update dependency typescript to v5.7.3 [`85a7204`](https://github.com/ast-grep/ast-grep/commit/85a72047131345cbcfafe231b27f4b53ef34d465)
- fix(deps): update dependency tree-sitter to v0.22.4 [`ec70763`](https://github.com/ast-grep/ast-grep/commit/ec70763fdfdd58f68ca8ed69400fef1bc9f571b3)
#### [0.33.0](https://github.com/ast-grep/ast-grep/compare/0.32.3...0.33.0)
> 5 January 2025
- **Breaking change:** refactor: move pyo3 registration to CustomLang [`4ddb08f`](https://github.com/ast-grep/ast-grep/commit/4ddb08ff68c1cebeaf459744bfc9fb85c6b0c15c)
- feat: introduce napi_lang for dynamic loading [`1a8d782`](https://github.com/ast-grep/ast-grep/commit/1a8d782be7eb2cbcfa0291d87318d0594293b50b)
- chore(deps): update dependency @ast-grep/napi to v0.32.3 [`713244f`](https://github.com/ast-grep/ast-grep/commit/713244f68c79d930011099d5f0fa63bfbad4d831)
#### [0.32.3](https://github.com/ast-grep/ast-grep/compare/0.32.2...0.32.3)
> 30 December 2024
- feat: print fixed rules count [`#1708`](https://github.com/ast-grep/ast-grep/issues/1708)
- fix: move unused suppressions to config crate [`#1624`](https://github.com/ast-grep/ast-grep/issues/1624)
- fix: allow malformed pattern to be dumped as tree [`#804`](https://github.com/ast-grep/ast-grep/issues/804)
- fix: fix files not recognize in lsp [`#1691`](https://github.com/ast-grep/ast-grep/issues/1691)
- fix: fix crash when internal node has no child [`#1688`](https://github.com/ast-grep/ast-grep/issues/1688)
- feat: use mutable self in Printer trait [`5e3835a`](https://github.com/ast-grep/ast-grep/commit/5e3835acd2984060e5643499cc51ddeadfc1e9f1)
- feat: reduce binary size by remove Printer polymorphism [`8b735cf`](https://github.com/ast-grep/ast-grep/commit/8b735cfbd77eeab94109e897aed835f2b6d6cef3)
- feat: resolve rule id for ScanResult [`9a7f3cc`](https://github.com/ast-grep/ast-grep/commit/9a7f3ccb550131f912e1512280badff95d4fc7a5)
#### [0.32.2](https://github.com/ast-grep/ast-grep/compare/0.32.1...0.32.2)
> 17 December 2024
- fix: linting [`10f3e74`](https://github.com/ast-grep/ast-grep/commit/10f3e746b18e0c638d714a3169523c840786970d)
- feat: distinguish manual type annotation [`4558c48`](https://github.com/ast-grep/ast-grep/commit/4558c48126a6d3b12bd04fea3aab7032f82401c3)
- feat: add ChildTypes helper [`0d477e9`](https://github.com/ast-grep/ast-grep/commit/0d477e9fe8d65c44dce2f0b3784b61bf2ce7828f)
#### [0.32.1](https://github.com/ast-grep/ast-grep/compare/0.32.0...0.32.1)
> 17 December 2024
- fix(deps): update rust crate clap_complete to v4.5.39 [`6ad3c7b`](https://github.com/ast-grep/ast-grep/commit/6ad3c7b9bdfa82f6a235b68fda33d5be0b872b11)
- fix: add npmignore to suppress gitignore [`11bb8e4`](https://github.com/ast-grep/ast-grep/commit/11bb8e46148789fd1a8dc0072505838dd1190aef)
#### [0.32.0](https://github.com/ast-grep/ast-grep/compare/0.31.1...0.32.0)
> 17 December 2024
- feat: Add fieldChildren method to napi's SgNode and pyo3's PyNode [`#1655`](https://github.com/ast-grep/ast-grep/pull/1655)
- **Breaking change:** fix: rename range rule's row to line [`#1663`](https://github.com/ast-grep/ast-grep/issues/1663)
- fix: add biome formatting [`5a41f13`](https://github.com/ast-grep/ast-grep/commit/5a41f13eadc07162a3200666e639181a6aa11e70)
- feat(napi): Typed SgNode and SgRoot [`55e65f3`](https://github.com/ast-grep/ast-grep/commit/55e65f3cf37a7affa232dc3e27bad25326b89345)
- fix: Use ts-node and versioned node-types URLs instead of heads [`dcb7916`](https://github.com/ast-grep/ast-grep/commit/dcb79163e6c337914504580b5f46b52dfb10bb74)
#### [0.31.1](https://github.com/ast-grep/ast-grep/compare/0.31.0...0.31.1)
> 8 December 2024
- feat: update tree-sitter dependency to 0.24.4 [`91a2b46`](https://github.com/ast-grep/ast-grep/commit/91a2b469dc703b1a7765eb450257aa71970be342)
- fix(deps): update dependency @swc/core to v1.10.0 [`526648b`](https://github.com/ast-grep/ast-grep/commit/526648b1e461f773f1ed65dc3daaac8a305b01a5)
- fix: fix new clippy error [`ebfcbcd`](https://github.com/ast-grep/ast-grep/commit/ebfcbcde532377a738887dbf917f4ef0c5061759)
#### [0.31.0](https://github.com/ast-grep/ast-grep/compare/0.30.1...0.31.0)
> 2 December 2024
- feat: only scan rule senstive files [`#1635`](https://github.com/ast-grep/ast-grep/issues/1635)
- feat: better error handling for dynamic lib loading [`#1631`](https://github.com/ast-grep/ast-grep/issues/1631)
- **Breaking change:** feat: support character based column number [`#1594`](https://github.com/ast-grep/ast-grep/issues/1594)
- **Breaking change:** refactor: expose new struct Position for abstracting column [`b87dad7`](https://github.com/ast-grep/ast-grep/commit/b87dad753fb2ce87cae17d488bac4da3fd62a5a7)
- refactor: Use the utf-corrected row and column methods for the range matcher [`2b30f56`](https://github.com/ast-grep/ast-grep/commit/2b30f566b324732bcdb32ffd4bb5eccad83a98d3)
- feat: Add support for range matcher [`4fb2c52`](https://github.com/ast-grep/ast-grep/commit/4fb2c5221bb0d42613ab538b737f2fbeed533996)
#### [0.30.1](https://github.com/ast-grep/ast-grep/compare/0.30.0...0.30.1)
> 26 November 2024
- fix: fix --config=config.yml arg parse [`#1617`](https://github.com/ast-grep/ast-grep/issues/1617)
- fix: update python version [`#1614`](https://github.com/ast-grep/ast-grep/issues/1614)
- fix(deps): update dependency @swc/core to v1.9.3 [`63c1c8d`](https://github.com/ast-grep/ast-grep/commit/63c1c8d01234da5747e0ade6162320a7edee3055)
- chore: revert pyo3 [`2022f38`](https://github.com/ast-grep/ast-grep/commit/2022f387e4cbcc4c1763f06fad47bed024ca47c5)
- feat: add rule entity inspection [`c569ec7`](https://github.com/ast-grep/ast-grep/commit/c569ec7b112f0d06a409258750a71cec3ad45722)
#### [0.30.0](https://github.com/ast-grep/ast-grep/compare/0.29.0...0.30.0)
> 11 November 2024
- **Breaking change:** feat: semi-structured tracing output [`#1574`](https://github.com/ast-grep/ast-grep/issues/1574)
- fix: error=unused-suppression should change exit code [`#1585`](https://github.com/ast-grep/ast-grep/issues/1585)
- refactor: move stdErr into inspect mod [`#1575`](https://github.com/ast-grep/ast-grep/issues/1575)
- infra: switch to llvm-cov [`#1570`](https://github.com/ast-grep/ast-grep/issues/1570)
- **Breaking change:** refactor: rename --tracing to --inspect [`105945e`](https://github.com/ast-grep/ast-grep/commit/105945efdff06200462917f7f9cbed6820bb4371)
- **Breaking change:** refactor: remove --json format for tracing [`ed05be4`](https://github.com/ast-grep/ast-grep/commit/ed05be47aa14ca68083582e1800a7424c6ddcc77)
- **Breaking change:** fix: remove builtin dart support [`cd25a62`](https://github.com/ast-grep/ast-grep/commit/cd25a628f07bba546b9b7f7333079de481995def)
- **Breaking change:** fix: report unused suppression always [`893df63`](https://github.com/ast-grep/ast-grep/commit/893df632aaad462c023c5bee29c41ad6ce6860b3)
- fix(deps): update babel monorepo [`b8803ce`](https://github.com/ast-grep/ast-grep/commit/b8803ceb05dee765ee2f9f5593de2ba1497fd83e)
#### [0.29.0](https://github.com/ast-grep/ast-grep/compare/0.28.1...0.29.0)
> 30 October 2024
- feat: support override severity of unused-suppression [`#1556`](https://github.com/ast-grep/ast-grep/issues/1556)
- feat: unify configuration reading [`#1557`](https://github.com/ast-grep/ast-grep/issues/1557)
- feat: unify configuration [`#1557`](https://github.com/ast-grep/ast-grep/issues/1557)
- refactor: improve file config handling [`#1553`](https://github.com/ast-grep/ast-grep/issues/1553)
- refactor: revamp configuration file discovering and custom language registration [`#1553`](https://github.com/ast-grep/ast-grep/issues/1553)
- feat(scan): support context, before/after flags [`#1549`](https://github.com/ast-grep/ast-grep/issues/1549)
- feat: add RuleOverwrite to override rule severity from CLI [`#1061`](https://github.com/ast-grep/ast-grep/issues/1061)
- **Breaking change:** feat: support --config in sg new [`cfe472f`](https://github.com/ast-grep/ast-grep/commit/cfe472f63c7011ef5635ca38fea4846c871a1177)
- feat: add rule overwrite impl [`fb4ac07`](https://github.com/ast-grep/ast-grep/commit/fb4ac07b0eddd6d8c30194221396ab4dbf8e65ce)
- fix(deps): update dependency @swc/core to v1.7.36 [`c11fd89`](https://github.com/ast-grep/ast-grep/commit/c11fd891f7ed28c7c698a43c99a6f2bf2efa8022)
#### [0.28.1](https://github.com/ast-grep/ast-grep/compare/0.28.0...0.28.1)
> 20 October 2024
- feat: report unused suppression [`#1346`](https://github.com/ast-grep/ast-grep/issues/1346)
- feat: make number of threads configurable [`#1498`](https://github.com/ast-grep/ast-grep/issues/1498)
- doc: add bug report yaml [`#1462`](https://github.com/ast-grep/ast-grep/issues/1462)
- refactor: rename tracing [`f83dbe9`](https://github.com/ast-grep/ast-grep/commit/f83dbe926ac23c105afa4a3786d8c3bb6e14e34d)
- fix: fix bug report bug agains [`61921f7`](https://github.com/ast-grep/ast-grep/commit/61921f7765f38730c66cd7d4e0a3aa816ad14ebf)
- fix(deps): update dependency @babel/core to v7.25.8 [`cf06185`](https://github.com/ast-grep/ast-grep/commit/cf06185f90bca9cb391955f46017637d84efd532)
#### [0.28.0](https://github.com/ast-grep/ast-grep/compare/0.27.3...0.28.0)
> 6 October 2024
- feat: TypeScript types for Rules in NAPI [`#1038`](https://github.com/ast-grep/ast-grep/issues/1038)
- feat:support glob path match in CLI [`#1062`](https://github.com/ast-grep/ast-grep/issues/1062)
- refactor: move input/output args to standalone file [`3d937cb`](https://github.com/ast-grep/ast-grep/commit/3d937cb246d279d1763d1a73218110b0cac735c3)
- refactor: move worker outside of utils [`84ea621`](https://github.com/ast-grep/ast-grep/commit/84ea621fc935a2808473a399470d9be2ccbbab25)
- fix(deps): update babel monorepo to v7.25.7 [`b989f05`](https://github.com/ast-grep/ast-grep/commit/b989f05fe38bb0f018c6d20f31e3e75c38755b4f)
#### [0.27.3](https://github.com/ast-grep/ast-grep/compare/0.27.2...0.27.3)
> 22 September 2024
- feat: add prettified pattern debug [`70d33b5`](https://github.com/ast-grep/ast-grep/commit/70d33b5d0efc2e55efe1fa8ea569e1d1e1115f68)
- fix(deps): update rust crate pyo3 to v0.22.3 [`683e398`](https://github.com/ast-grep/ast-grep/commit/683e398430820549ff5ac947766e829b0c1f7bc4)
- test: add test for CST [`a79f13f`](https://github.com/ast-grep/ast-grep/commit/a79f13f857e8ec5dc890a1fd757893738ed8773e)
#### [0.27.2](https://github.com/ast-grep/ast-grep/compare/0.27.1...0.27.2)
> 14 September 2024
- feat: add follow symbolic links option to CLI [`#1461`](https://github.com/ast-grep/ast-grep/issues/1461)
- fix(deps): update dependency @swc/core to v1.7.26 [`e0b437d`](https://github.com/ast-grep/ast-grep/commit/e0b437d3defa032d674b37fc375e21069b7d7c50)
- fix(deps): update dependency @swc/core to v1.7.24 [`657bc4e`](https://github.com/ast-grep/ast-grep/commit/657bc4e87ba0e290d51eed1a6214b1c6e5445a32)
- chore(deps): update dependency typescript to v5.6.2 [`c55a404`](https://github.com/ast-grep/ast-grep/commit/c55a4042e78fb4b626348fd4a06b8188d3e49f85)
#### [0.27.1](https://github.com/ast-grep/ast-grep/compare/0.27.0...0.27.1)
> 8 September 2024
- fix: create .gitkeep file in new dirs [`#1273`](https://github.com/ast-grep/ast-grep/issues/1273)
- fix: fix unwrap error for unfound dir [`#1456`](https://github.com/ast-grep/ast-grep/issues/1456)
- fix(deps): update dependency @swc/core to v1.7.23 [`5fd4101`](https://github.com/ast-grep/ast-grep/commit/5fd410190717131f751de348493a8d09340a1960)
- chore(deps): update dependency @types/node to v20.16.5 [`294e402`](https://github.com/ast-grep/ast-grep/commit/294e40249d33919a2f62089d954a14b53eae0987)
- fix: improve error message [`d46df6a`](https://github.com/ast-grep/ast-grep/commit/d46df6a0e201fac6539fac10a09cd10516d0dc78)
#### [0.27.0](https://github.com/ast-grep/ast-grep/compare/0.26.3...0.27.0)
> 7 September 2024
- feat: support YAML language [`#1436`](https://github.com/ast-grep/ast-grep/issues/1436)
- fix(deps): update dependency @swc/core to v1.7.22 [`c577761`](https://github.com/ast-grep/ast-grep/commit/c577761fed6ce6decfef0bb42abe4db082049a0a)
- fix(deps): update dependency @swc/core to v1.7.21 [`4b0e594`](https://github.com/ast-grep/ast-grep/commit/4b0e594c11c9af99a850f82ae930796aeadb47c3)
- feat: add rule config potential kind test [`def5b21`](https://github.com/ast-grep/ast-grep/commit/def5b210635f4d32c614f98998401768c300d08a)
#### [0.26.3](https://github.com/ast-grep/ast-grep/compare/0.26.2...0.26.3)
> 25 August 2024
- fix: fix wrong transformation indentation [`#1405`](https://github.com/ast-grep/ast-grep/issues/1405)
- fix: compare only kind for unnamed nodes [`#1419`](https://github.com/ast-grep/ast-grep/issues/1419)
- fix(deps): update dependency @swc/core to v1.7.18 [`9ecb02c`](https://github.com/ast-grep/ast-grep/commit/9ecb02cc4d993adbe4a0d116b2f0c73183aeadf6)
- fix(deps): update dependency @swc/core to v1.7.14 [`459d20a`](https://github.com/ast-grep/ast-grep/commit/459d20adb6f1ea616bb8c019c832a22da30a8095)
- test: add test for transformation indentation [`e0bfaf2`](https://github.com/ast-grep/ast-grep/commit/e0bfaf2b2d856d3272939c7774c286aca5bba3ba)
#### [0.26.2](https://github.com/ast-grep/ast-grep/compare/0.26.1...0.26.2)
> 18 August 2024
- fix: readopt matched metavar in napi [`#1380`](https://github.com/ast-grep/ast-grep/issues/1380)
- chore: update napi definition [`07e084a`](https://github.com/ast-grep/ast-grep/commit/07e084a19f08d98357ce4972eac2efbd00f0690c)
- fix(deps): update dependency @swc/core to v1.7.11 [`835b06f`](https://github.com/ast-grep/ast-grep/commit/835b06f3e569b8837bd011425a6e0c2d1bf10081)
- fix(deps): update dependency @swc/core to v1.7.10 [`d7a3820`](https://github.com/ast-grep/ast-grep/commit/d7a38201a139788921af8464720090c757c6fa41)
#### [0.26.1](https://github.com/ast-grep/ast-grep/compare/0.26.0...0.26.1)
> 9 August 2024
- feat: support dynamic language in pyo3 [`#1143`](https://github.com/ast-grep/ast-grep/issues/1143)
- test: add test for load dynamic lang in python [`236f4ab`](https://github.com/ast-grep/ast-grep/commit/236f4ab4d3faef4438c0516e406f3ca415d5dac7)
- feat: add py_lang registration function [`43e74e7`](https://github.com/ast-grep/ast-grep/commit/43e74e73cd458ef4635d24221206bda673206b0d)
- feat: add pytype for register language [`8b660cf`](https://github.com/ast-grep/ast-grep/commit/8b660cf816de3afcfcd124951924521afcb2d445)
#### [0.26.0](https://github.com/ast-grep/ast-grep/compare/0.25.7...0.26.0)
> 7 August 2024
- feat: Additional impls for language types [`#1379`](https://github.com/ast-grep/ast-grep/pull/1379)
- feat: append note to error message if available [`#1384`](https://github.com/ast-grep/ast-grep/issues/1384)
- fix: LSP should skip files outside the workspace root [`#1382`](https://github.com/ast-grep/ast-grep/issues/1382)
- fix(deps): update dependency @babel/core to v7.25.2 [`3b0be88`](https://github.com/ast-grep/ast-grep/commit/3b0be882e0e280fcc7a5badd780e729cf74d2a45)
- refactor: split two macros [`b760eb8`](https://github.com/ast-grep/ast-grep/commit/b760eb80e8439c50a9b66b3f3895574613859e4d)
- refactor: simplify visitor [`5ae03ae`](https://github.com/ast-grep/ast-grep/commit/5ae03ae015ef4a37ac8a023a58157ada3c44ecba)
#### [0.25.7](https://github.com/ast-grep/ast-grep/compare/0.25.6...0.25.7)
> 5 August 2024
- feat: add charCount to split lines [`#1381`](https://github.com/ast-grep/ast-grep/issues/1381)
- chore(deps): update dependency @types/node to v20.14.14 [`bca7e7b`](https://github.com/ast-grep/ast-grep/commit/bca7e7be9a6580f44ab1979735c47f29940af106)
- fix(deps): update rust crate clap to v4.5.13 [`c143464`](https://github.com/ast-grep/ast-grep/commit/c1434644c010f1d69e8e6ae68c7549adaa79eb2c)
- fix(deps): update rust crate toml_edit to v0.22.20 [`170da4c`](https://github.com/ast-grep/ast-grep/commit/170da4c5df25b5f0aef7e7d67a902cac7e5a9271)
#### [0.25.6](https://github.com/ast-grep/ast-grep/compare/0.25.5...0.25.6)
> 4 August 2024
- feat: add `--selector` in sg run [`#1378`](https://github.com/ast-grep/ast-grep/issues/1378)
- fix: fix clippy new complaints [`a4cc793`](https://github.com/ast-grep/ast-grep/commit/a4cc793f5bf779947596fe7f441b71daed7fda19)
- fix(deps): update dependency @swc/core to v1.7.5 [`d3ee100`](https://github.com/ast-grep/ast-grep/commit/d3ee100de1d16dfec1bf453a5044709531705c3a)
- chore(deps): update rust crate tree-sitter to v0.22.2 [`93d684a`](https://github.com/ast-grep/ast-grep/commit/93d684a25a580ab30c86e628c0ddce604d33c439)
#### [0.25.5](https://github.com/ast-grep/ast-grep/compare/0.25.4...0.25.5)
> 1 August 2024
- feat: support using transform in rule message [`#1366`](https://github.com/ast-grep/ast-grep/issues/1366)
- test: add test for skipping multiple ellipsis [`#1365`](https://github.com/ast-grep/ast-grep/issues/1365)
- feat: Support ast-grep-ignore comment on same line as violation [`#1347`](https://github.com/ast-grep/ast-grep/issues/1347)
- Revert "feat: add wasm engine support" [`78b1633`](https://github.com/ast-grep/ast-grep/commit/78b1633b9bd0776e0df40ae192cbdbc7643d4b4a)
- feat: add wasm engine support [`ff77914`](https://github.com/ast-grep/ast-grep/commit/ff77914b09f7a67523c81b3a8b386b73a76bb7a8)
- refactor: remove old find/scan [`58a3646`](https://github.com/ast-grep/ast-grep/commit/58a3646ea314e3d81530e97bc0d30f32fb7647fe)
#### [0.25.4](https://github.com/ast-grep/ast-grep/compare/0.25.3...0.25.4)
> 25 July 2024
- feat: support sql experimentally [`#1300`](https://github.com/ast-grep/ast-grep/issues/1300)
- feat: consistent file path for search by removing ./ prefix [`#1343`](https://github.com/ast-grep/ast-grep/issues/1343)
- feat: allow multiple tests in a single YAML file [`#1344`](https://github.com/ast-grep/ast-grep/issues/1344)
- refactor: use HarnessBuilder [`c41ffa8`](https://github.com/ast-grep/ast-grep/commit/c41ffa8c4de10f4374c96b8927f2e1e85a28dff6)
- refactor: move out some files [`dfa38f2`](https://github.com/ast-grep/ast-grep/commit/dfa38f2c157e4e5859605952ae79ca7ba4e22d19)
- test: add test for deserde tests [`76796e4`](https://github.com/ast-grep/ast-grep/commit/76796e474d9f61d449099a356aa6228884da3859)
#### [0.25.3](https://github.com/ast-grep/ast-grep/compare/0.25.2...0.25.3)
> 22 July 2024
- feat: support custom language injection [`#1309`](https://github.com/ast-grep/ast-grep/issues/1309)
- feat: register embedded language [`416e28e`](https://github.com/ast-grep/ast-grep/commit/416e28ee9f2b2e7857aa0c613d8e5ec914a94782)
- fix(deps): update dependency @babel/core to v7.24.9 [`8c5dd00`](https://github.com/ast-grep/ast-grep/commit/8c5dd00de4fc7633ddad27aa4627fb85d89b238e)
- fix(deps): update dependency @swc/core to v1.7.0 [`61f6a14`](https://github.com/ast-grep/ast-grep/commit/61f6a14d96438a93e77ddc0bd5f8a3f51bfddc9d)
#### [0.25.2](https://github.com/ast-grep/ast-grep/compare/0.25.1...0.25.2)
> 18 July 2024
- fix: only present code action when there are truly fixable diagnostic [`#1327`](https://github.com/ast-grep/ast-grep/issues/1327)
- refactor: move some function out of lsp [`1e6b477`](https://github.com/ast-grep/ast-grep/commit/1e6b47720911d77435461437f9f9888f50d1575c)
- fix: use fix in data for fix all command [`d358d45`](https://github.com/ast-grep/ast-grep/commit/d358d454811b45d08be071a79edb08319cc34447)
- fix: update fix all lsp [`b760b9b`](https://github.com/ast-grep/ast-grep/commit/b760b9bd9527dcace44d0ddfc0ef8825a4fc8223)
#### [0.25.1](https://github.com/ast-grep/ast-grep/compare/0.25.0...0.25.1)
> 14 July 2024
- fix: make config file work outside of workspace root [`#1326`](https://github.com/ast-grep/ast-grep/issues/1326)
- fix: fix napi musl [`bde7f34`](https://github.com/ast-grep/ast-grep/commit/bde7f3436790bd994e24af65c96225075af26077)
- fix: debug napi [`2d87fc5`](https://github.com/ast-grep/ast-grep/commit/2d87fc5035653044b062bb04eb9bb290d18515a6)
#### [0.25.0](https://github.com/ast-grep/ast-grep/compare/0.24.1...0.25.0)
> 13 July 2024
- Fix Python 3.10 macOS ast-grep-py build [`#1315`](https://github.com/ast-grep/ast-grep/pull/1315)
- feat: support multiple doc in scan [`#1310`](https://github.com/ast-grep/ast-grep/issues/1310)
- fix: fix testing [`#1305`](https://github.com/ast-grep/ast-grep/issues/1305)
- feat: support searching injected code in specified lang [`#1307`](https://github.com/ast-grep/ast-grep/issues/1307)
- feat: improve HTML language extraction for ts/scss [`#1301`](https://github.com/ast-grep/ast-grep/issues/1301)
- fix: use relative path in lsp [`#1272`](https://github.com/ast-grep/ast-grep/issues/1272)
- fix: use match info in lsp message [`#1271`](https://github.com/ast-grep/ast-grep/issues/1271)
- feat: support arm64 musl [`#1227`](https://github.com/ast-grep/ast-grep/issues/1227)
- fix(deps): update dependency @babel/core to v7.24.8 [`624aa70`](https://github.com/ast-grep/ast-grep/commit/624aa70b04d46d34dd554b123af5df1daec1f163)
- feat: support multiple regions in run [`07ef11f`](https://github.com/ast-grep/ast-grep/commit/07ef11ff34904aa91ab364438718803426ceab78)
- refactor: move Html out [`4a0c177`](https://github.com/ast-grep/ast-grep/commit/4a0c1778464280b594171d81cb65bd3d1f79a3af)
#### [0.24.1](https://github.com/ast-grep/ast-grep/compare/0.24.0...0.24.1)
> 26 June 2024
- fix: skip missing node in pattern [`#1256`](https://github.com/ast-grep/ast-grep/issues/1256)
- doc: add cli help text for debug format [`1754382`](https://github.com/ast-grep/ast-grep/commit/17543825a937be9dc56dce648c2782fbe5a96eda)
#### [0.24.0](https://github.com/ast-grep/ast-grep/compare/0.23.1...0.24.0)
> 23 June 2024
- feat: add debug ast and cst [`#1218`](https://github.com/ast-grep/ast-grep/issues/1218)
- feat: add strictness in PyO3 [`#1246`](https://github.com/ast-grep/ast-grep/issues/1246)
- feat: add strictness flag in cli [`#1243`](https://github.com/ast-grep/ast-grep/issues/1243)
- test: add test for different strictness [`#1241`](https://github.com/ast-grep/ast-grep/issues/1241)
- feat: add strictness to YAML [`#1239`](https://github.com/ast-grep/ast-grep/issues/1239)
- **Breaking change:** fix: update API for fix [`4971ac2`](https://github.com/ast-grep/ast-grep/commit/4971ac2ca008731dab8eb5e7491b2792e85ef827)
- **Breaking change:** fix: change deletedLength to endPos in napi [`c0e9a65`](https://github.com/ast-grep/ast-grep/commit/c0e9a65dc197bb4abcae6fdebc65c8b128b0467e)
- feat: add colorized output [`22f279c`](https://github.com/ast-grep/ast-grep/commit/22f279ce6003fa82850ecea200507727c23dd11f)
#### [0.23.1](https://github.com/ast-grep/ast-grep/compare/0.23.0...0.23.1)
> 22 June 2024
- fix: all should not pollute env [`#1225`](https://github.com/ast-grep/ast-grep/issues/1225)
- **Breaking change:** feat: use new pattern struct [`f219b43`](https://github.com/ast-grep/ast-grep/commit/f219b433f98fdd33a4cb03872d30a14098f1dafd)
- refactor: move match_node impl out [`27c918a`](https://github.com/ast-grep/ast-grep/commit/27c918a4e172aaf7f1fa1d2fafa4b7b05e0e0efb)
- refactor: factor out match_ellipsis logic [`407d2a8`](https://github.com/ast-grep/ast-grep/commit/407d2a8bb04d0b1d3764badc9ae9b27fff0971f7)
#### [0.23.0](https://github.com/ast-grep/ast-grep/compare/0.22.6...0.23.0)
> 11 June 2024
- feat: add typings for pyo3 [`#676`](https://github.com/ast-grep/ast-grep/issues/676)
- refactor: remove unused old code [`3ffd1d9`](https://github.com/ast-grep/ast-grep/commit/3ffd1d95cc4084a40cc64c38ae556fa01f2817a8)
- fix(deps): update babel monorepo to v7.24.7 [`b4aebaf`](https://github.com/ast-grep/ast-grep/commit/b4aebafd7cefff05aef363031617d7f95259d512)
- refactor: add new abstraction for match_tree [`079bc7d`](https://github.com/ast-grep/ast-grep/commit/079bc7d3ae70fe0a8237e9839f18f8a65a1081dc)
#### [0.22.6](https://github.com/ast-grep/ast-grep/compare/0.22.5...0.22.6)
> 4 June 2024
- fix: add Edit to export list [`#1186`](https://github.com/ast-grep/ast-grep/issues/1186)
- feat: add modify edit range feature in pyo3 [`1e31253`](https://github.com/ast-grep/ast-grep/commit/1e312535a41886c7ba811a9bb0d1a797ec5c403b)
- test: add test for modifying edit [`7044811`](https://github.com/ast-grep/ast-grep/commit/7044811ad89e673d5f8803ae2ddda472c84ecf1e)
- fix: addClass Edit in pyo3 [`d81c6c3`](https://github.com/ast-grep/ast-grep/commit/d81c6c32e4fb9f1f7d9b2d9050b46efa4ad6e4e8)
#### [0.22.5](https://github.com/ast-grep/ast-grep/compare/0.22.4...0.22.5)
> 2 June 2024
- feat: support all languages in napi [`#1170`](https://github.com/ast-grep/ast-grep/issues/1170)
- **Breaking change:** refactor: rename Frontend Language to Lang [`c582c16`](https://github.com/ast-grep/ast-grep/commit/c582c164028bbdafa9fffab7c5fdadf37170af5e)
- refactor: move find files operations to standalone files [`8ebb888`](https://github.com/ast-grep/ast-grep/commit/8ebb8881ccf5a3615cc91e39b32f0fde7efc4c1f)
- feat: support more languages in napi [`19e3baa`](https://github.com/ast-grep/ast-grep/commit/19e3baaf7eb143210f6189533577d764373472fd)
#### [0.22.4](https://github.com/ast-grep/ast-grep/compare/0.22.3...0.22.4)
> 26 May 2024
- feat: use var defined in utils in transform/fix [`#1155`](https://github.com/ast-grep/ast-grep/issues/1155)
- fix: fix pattern polluting env [`#1164`](https://github.com/ast-grep/ast-grep/issues/1164)
- test: add python fix test [`#1161`](https://github.com/ast-grep/ast-grep/issues/1161)
- feat: add fix related feature to pyo3 [`#1159`](https://github.com/ast-grep/ast-grep/issues/1159)
- test: add test for napi test [`#1160`](https://github.com/ast-grep/ast-grep/issues/1160)
- feat: add commit_fix and fix to napi [`#1158`](https://github.com/ast-grep/ast-grep/issues/1158)
- fix(deps): update babel monorepo to v7.24.6 [`0898b55`](https://github.com/ast-grep/ast-grep/commit/0898b55db7d2ed1c0423ba23782307d12f16c6c3)
- fix(deps): update dependency @swc/core to v1.5.7 [`9beda6e`](https://github.com/ast-grep/ast-grep/commit/9beda6e97fe376445f1b812fd06bbeffb4fcfd66)
- feat: add edit/fix sketch [`6817854`](https://github.com/ast-grep/ast-grep/commit/68178547d99a0a5f09c70493d9ef582eb0001193)
#### [0.22.3](https://github.com/ast-grep/ast-grep/compare/0.22.2...0.22.3)
> 12 May 2024
- fix: fix napi test [`#1140`](https://github.com/ast-grep/ast-grep/issues/1140)
- fix: use dumb implementation for web-tree-sitter [`5cb7052`](https://github.com/ast-grep/ast-grep/commit/5cb7052e61879bf947ec6007e9004aeeb47c1384)
- chore: fix asset names [`70cef40`](https://github.com/ast-grep/ast-grep/commit/70cef40ae8f60432d1d0723bc0bc09c98e10f72e)
- fix [`af20be8`](https://github.com/ast-grep/ast-grep/commit/af20be8328af67b44583672f5e6eb123b83613a8)
#### [0.22.2](https://github.com/ast-grep/ast-grep/compare/0.22.1...0.22.2)
> 12 May 2024
- fix: fix releases [`3a6e553`](https://github.com/ast-grep/ast-grep/commit/3a6e55317a7904285b4f0df16c7f1f603a608543)
- fix: fix [`efaa1ca`](https://github.com/ast-grep/ast-grep/commit/efaa1ca85c4a61edf71247fb287dec4fcc60aba0)
- fix: debug [`e39fd12`](https://github.com/ast-grep/ast-grep/commit/e39fd12f19d051ff6ea2a8a65e632b241eeb95c0)
#### [0.22.1](https://github.com/ast-grep/ast-grep/compare/0.22.0...0.22.1)
> 12 May 2024
- feat(language): Add support for Haskell via `tree-sitter-haskell` [`#1128`](https://github.com/ast-grep/ast-grep/pull/1128)
- fix: add ast-grep to release [`#1130`](https://github.com/ast-grep/ast-grep/issues/1130)
- fix(deps): update dependency @swc/core to v1.5.5 [`31428dd`](https://github.com/ast-grep/ast-grep/commit/31428ddd2ab0d875b08a9a844b6aa2f067bad8ef)
- fix(deps): update dependency tree-sitter-typescript to v0.21.1 [`188525d`](https://github.com/ast-grep/ast-grep/commit/188525dfc548d17b3dcaba8a9bd1c346a76e201a)
- chore(deps): update dependency @types/node to v20.12.11 [`3fe7780`](https://github.com/ast-grep/ast-grep/commit/3fe77800d3fcd9c1b97cce565b717dffbfa2b1ef)
#### [0.22.0](https://github.com/ast-grep/ast-grep/compare/0.21.4...0.22.0)
> 8 May 2024
- test: add string test case back in Rust [`#1060`](https://github.com/ast-grep/ast-grep/issues/1060)
- chore: bump tree-sitter version [`5b45bd7`](https://github.com/ast-grep/ast-grep/commit/5b45bd7986d5fc6c3bd0ec8945079892c4ec3751)
- fix: update wasm crate [`ce9acba`](https://github.com/ast-grep/ast-grep/commit/ce9acbab57b2f94c5212692ba340bf5d22bae71b)
- fix: update ts deps [`3a67b6b`](https://github.com/ast-grep/ast-grep/commit/3a67b6bddd27d701f588b5a046a9b430aa7881ff)
#### [0.21.4](https://github.com/ast-grep/ast-grep/compare/0.21.3...0.21.4)
> 6 May 2024
- fix: build from source [`#1125`](https://github.com/ast-grep/ast-grep/pull/1125)
- doc: update how to install via cargo [`#1118`](https://github.com/ast-grep/ast-grep/issues/1118)
- feat: report undefined util rules [`#1106`](https://github.com/ast-grep/ast-grep/issues/1106)
- **Breaking change:** feat: separate RuleCoreError and RuleConfigError [`d96efa9`](https://github.com/ast-grep/ast-grep/commit/d96efa97382b0ec8e11031b54de137f43c1d8bf6)
- refactor: move Transformation into a standalone module [`b51cc95`](https://github.com/ast-grep/ast-grep/commit/b51cc957c87c98d3edbce3f5abdd8c1d43a77b18)
- fix(deps): update dependency @babel/core to v7.24.5 [`77ea8bd`](https://github.com/ast-grep/ast-grep/commit/77ea8bd8239aa2a305c5dac34d7308cb572b2ef9)
#### [0.21.3](https://github.com/ast-grep/ast-grep/compare/0.21.2...0.21.3)
> 2 May 2024
- feat: make pattern more permissive [`#1087`](https://github.com/ast-grep/ast-grep/issues/1087)
- refactor: further simplify test case [`8188473`](https://github.com/ast-grep/ast-grep/commit/8188473f6729fff25704a68786d45999b52eb7aa)
- refactor: make rewrite test simplier [`5a754ef`](https://github.com/ast-grep/ast-grep/commit/5a754eff27c6ba549a35f5728ec315a4727bfe71)
- test: add test for contextual pattern defined var [`2d6bae8`](https://github.com/ast-grep/ast-grep/commit/2d6bae82bde1ea12111d9cdf8d0ff4164489a7cc)
#### [0.21.2](https://github.com/ast-grep/ast-grep/compare/0.21.1...0.21.2)
> 1 May 2024
- feat: Allow to use meta variable captured outside of rewrite rule inside the rewriter [`#1072`](https://github.com/ast-grep/ast-grep/issues/1072)
- refactor: move rule_core test out of rule_core [`1e8af4c`](https://github.com/ast-grep/ast-grep/commit/1e8af4c4785c04eda0a0ea4990a39888ed9efa2e)
- feat: move check variable usage out to a standalone file [`f086d10`](https://github.com/ast-grep/ast-grep/commit/f086d1002a0fde9df8e902c824aeed6cbebd2496)
- refactor: move check var around [`cfe3529`](https://github.com/ast-grep/ast-grep/commit/cfe35298707fae54c617e13bd2b77745f09e8908)
#### [0.21.1](https://github.com/ast-grep/ast-grep/compare/0.21.0...0.21.1)
> 24 April 2024
- feat: find defined vars in utils [`cbc6534`](https://github.com/ast-grep/ast-grep/commit/cbc6534a37651e9f0ac35624fa548262e7d9c271)
- fix: add referent rule stack overflow test case [`f7f24e1`](https://github.com/ast-grep/ast-grep/commit/f7f24e129bb0f4680aac395758284c2f21cdf1ba)
- feat: add utils defined vars [`59001eb`](https://github.com/ast-grep/ast-grep/commit/59001eb4a1b85f20a5f5f96bc7906371d292ce30)
#### [0.21.0](https://github.com/ast-grep/ast-grep/compare/0.20.5...0.21.0)
> 24 April 2024
- feat: report unused rewriters [`#1064`](https://github.com/ast-grep/ast-grep/issues/1064)
- feat: report undefined error in fix [`#1070`](https://github.com/ast-grep/ast-grep/issues/1070)
- feat: define the resolution order of meta variables [`#1068`](https://github.com/ast-grep/ast-grep/issues/1068)
- feat: export used meta-var in transform [`#1069`](https://github.com/ast-grep/ast-grep/issues/1069)
- feat: export meta variables defined in transform [`#1066`](https://github.com/ast-grep/ast-grep/issues/1066)
- feat: export meta variables defined in constraints/rules [`#1067`](https://github.com/ast-grep/ast-grep/issues/1067)
- test: add test for Pattern::defined_vars method [`#1065`](https://github.com/ast-grep/ast-grep/issues/1065)
- feat: analyze meta variables defined in pattern [`#1065`](https://github.com/ast-grep/ast-grep/issues/1065)
- feat: add field id Error for relational rule [`#1059`](https://github.com/ast-grep/ast-grep/issues/1059)
- feat: support rewrtier check in sub-rule [`eb4c47c`](https://github.com/ast-grep/ast-grep/commit/eb4c47ca31a22314455b9b55ec10473a6a56dc69)
- test: add undefined rewriter test [`d56ad74`](https://github.com/ast-grep/ast-grep/commit/d56ad745676b229d820b068abdb843e21871f100)
- feat: add used_vars in TemplateFix [`641d3b4`](https://github.com/ast-grep/ast-grep/commit/641d3b45cb722c34a36250f657f4babeffca0cf8)
#### [0.20.5](https://github.com/ast-grep/ast-grep/compare/0.20.4...0.20.5)
> 10 April 2024
- fix: add parse global util error [`2482a86`](https://github.com/ast-grep/ast-grep/commit/2482a869724d04dfb1480c83b3ec8ad8f3fc430b)
- fix: ignore test [`6c2cd46`](https://github.com/ast-grep/ast-grep/commit/6c2cd46e3c1649dd4123b3c8d740b3ff89e8773e)
#### [0.20.4](https://github.com/ast-grep/ast-grep/compare/0.20.3...0.20.4)
> 8 April 2024
- fix: fix new deprecation note of Rust 2024 [`a70565e`](https://github.com/ast-grep/ast-grep/commit/a70565e3bfb84612107424b9f939c710dec7e898)
- fix: fix quickfix [`fef3dfc`](https://github.com/ast-grep/ast-grep/commit/fef3dfc8afdab99d9b336c16e60903d86ec6e213)
- fix: better fix message [`e0b5875`](https://github.com/ast-grep/ast-grep/commit/e0b5875b03e5b66cfd356d5cfc26066f69b18f91)
#### [0.20.3](https://github.com/ast-grep/ast-grep/compare/0.20.2...0.20.3)
> 7 April 2024
- feat(lsp): run "source.fixAll.ast-grep" onsave [`#1021`](https://github.com/ast-grep/ast-grep/pull/1021)
- test: move the integration-test to test folder [`f2ff77a`](https://github.com/ast-grep/ast-grep/commit/f2ff77a7572b770b6e37028005b8a9efbe7b782e)
- chore: cargo update [`83394e9`](https://github.com/ast-grep/ast-grep/commit/83394e92cf0b1d731e6d25aed22ff7b329608034)
- refactor: move logging and io out of on_apply_all_fix [`cd79cda`](https://github.com/ast-grep/ast-grep/commit/cd79cdae14d4179e3cccab7f4c22d16b8c0f7871)
#### [0.20.2](https://github.com/ast-grep/ast-grep/compare/0.20.1...0.20.2)
> 27 March 2024
- feat: make test --update-all not report error [`#771`](https://github.com/ast-grep/ast-grep/issues/771)
- fix: fix update cases [`f8132a4`](https://github.com/ast-grep/ast-grep/commit/f8132a48514665e5a3d0b130f09be5a0834de743)
- refactor: less indentation for interactive reporter [`203bd35`](https://github.com/ast-grep/ast-grep/commit/203bd35d30b63b042f961658e2ab243d1221bb63)
- feat: improve accept [`441a548`](https://github.com/ast-grep/ast-grep/commit/441a548fda48a0eade1b785419b41c4bb25249e6)
#### [0.20.1](https://github.com/ast-grep/ast-grep/compare/0.20.0...0.20.1)
> 24 March 2024
- feat: respect suppression in lsp [`#1019`](https://github.com/ast-grep/ast-grep/issues/1019)
#### [0.20.0](https://github.com/ast-grep/ast-grep/compare/0.19.4...0.20.0)
> 24 March 2024
- test: add test for error suppression in scan [`#1007`](https://github.com/ast-grep/ast-grep/issues/1007)
- refactor: merge CombinedScan::scan and CombinedScan::diff [`#1011`](https://github.com/ast-grep/ast-grep/issues/1011)
- feat: report error count instead of file count [`#1009`](https://github.com/ast-grep/ast-grep/issues/1009)
- feat: suppress specific rule [`#1005`](https://github.com/ast-grep/ast-grep/issues/1005)
- fix: skip suppressed error [`#1006`](https://github.com/ast-grep/ast-grep/issues/1006)
- feat: support error/warning suppression [`#446`](https://github.com/ast-grep/ast-grep/issues/446)
- fix(deps): update babel monorepo [`da9a938`](https://github.com/ast-grep/ast-grep/commit/da9a938bff6380d986fcf186f01077f7f4ed9ca8)
- fix(deps): update dependency @swc/core to v1.4.8 [`d81365a`](https://github.com/ast-grep/ast-grep/commit/d81365a92b19fb180a24ad87b74834535f1df20f)
- feat: add more states for rule suppression [`41a058c`](https://github.com/ast-grep/ast-grep/commit/41a058c9b58bca6748ea8dd032382ab1a2112500)
#### [0.19.4](https://github.com/ast-grep/ast-grep/compare/0.19.3...0.19.4)
> 10 March 2024
- LSP default message for diagnostics [`#970`](https://github.com/ast-grep/ast-grep/pull/970)
- feat: Add error messaging for rule file parsing failures [`#968`](https://github.com/ast-grep/ast-grep/pull/968)
- feat: add html testing and tweak html [`#977`](https://github.com/ast-grep/ast-grep/issues/977)
- fix(deps): update dependency @babel/core to v7.24.0 [`a14741d`](https://github.com/ast-grep/ast-grep/commit/a14741d7775062302da66aef354cfcf07d75fafe)
- fix(deps): update dependency @swc/core to v1.4.6 [`4a397f4`](https://github.com/ast-grep/ast-grep/commit/4a397f44d9e6f325979883d66000db13d2b82238)
- fix(deps): update rust crate pyo3 to 0.20.3 [`23653c2`](https://github.com/ast-grep/ast-grep/commit/23653c2f3f6263d428bfd6e4eb4b323990eeb699)
#### [0.19.3](https://github.com/ast-grep/ast-grep/compare/0.19.2...0.19.3)
> 24 February 2024
- fix(deps): update dependency @swc/core to v1.4.2 [`67450cb`](https://github.com/ast-grep/ast-grep/commit/67450cb162f0ed2315b8917750f0dbb8fc6ec8cf)
- refactor: move rewriters to SerializableRuleConfig [`d98c062`](https://github.com/ast-grep/ast-grep/commit/d98c0622c80288027bc652b62ef5a1f829cde6b8)
- fix(deps): update rust crate inquire to 0.7.0 [`86ec845`](https://github.com/ast-grep/ast-grep/commit/86ec8452f69deb2028461207d4e0531ae96a195a)
#### [0.19.2](https://github.com/ast-grep/ast-grep/compare/0.19.1...0.19.2)
> 22 February 2024
- fix: improve expando char replacement [`#883`](https://github.com/ast-grep/ast-grep/issues/883)
- feat(napi): add support for napi linux x64 musl [`c4d7902`](https://github.com/ast-grep/ast-grep/commit/c4d7902fbf13cb7c027ef22fa5d8f26d92c82af9)
- fix: fix wrong spacing matching [`0e8b4f0`](https://github.com/ast-grep/ast-grep/commit/0e8b4f0ae435f07dfd49107143543a355cac1a05)
- Update README.md [`9636a1a`](https://github.com/ast-grep/ast-grep/commit/9636a1adff617278b38e58d323cb3aa992991fe6)
#### [0.19.1](https://github.com/ast-grep/ast-grep/compare/0.19.0...0.19.1)
> 19 February 2024
- fix: avoid input stream when --update-all [`#943`](https://github.com/ast-grep/ast-grep/issues/943)
- fix: update cargo lock [`8a893e7`](https://github.com/ast-grep/ast-grep/commit/8a893e71f348ef9a76a32c5a6755d4d55b17a41f)
- fix(deps): update dependency @swc/core to v1.4.1 [`d18fd70`](https://github.com/ast-grep/ast-grep/commit/d18fd7025fb771b9eac6385222216dd5d79601bf)
- chore(deps): update dependency @types/node to v20.11.19 [`40d3fa0`](https://github.com/ast-grep/ast-grep/commit/40d3fa0abc7d490b6a83f61e5966790e3688212e)
#### [0.19.0](https://github.com/ast-grep/ast-grep/compare/0.18.1...0.19.0)
> 14 February 2024
- **Breaking change:** refactor: remove unused type generic [`9b13d41`](https://github.com/ast-grep/ast-grep/commit/9b13d4125202c006789826d2e20cebb3a0b88bc0)
- **Breaking change:** fix: update pyo3 test [`ff10e81`](https://github.com/ast-grep/ast-grep/commit/ff10e81dc01957b6836d91439c4923f48374f467)
- fix: fix parallel thread output [`be230ca`](https://github.com/ast-grep/ast-grep/commit/be230cae32b55b10f6d5316c350adcf800356ccc)
#### [0.18.1](https://github.com/ast-grep/ast-grep/compare/0.18.0...0.18.1)
> 31 January 2024
- **Breaking change:** refactor: use more concise name [`6a4a17c`](https://github.com/ast-grep/ast-grep/commit/6a4a17c22646c2e968363ccf0466161cc11f4e7a)
#### [0.18.0](https://github.com/ast-grep/ast-grep/compare/0.17.1...0.18.0)
> 30 January 2024
- **Breaking change:** refactor: remove IndentSensitive trait [`#868`](https://github.com/ast-grep/ast-grep/issues/868)
- feat: add support for rewriters [`#855`](https://github.com/ast-grep/ast-grep/issues/855)
- **Breaking change:** refactor: rename RuleWithConstraints to RuleCore [`#862`](https://github.com/ast-grep/ast-grep/issues/862)
- feat: move fix to SerializableRuleCore [`#859`](https://github.com/ast-grep/ast-grep/issues/859)
- **Breaking change:** refactor: remove MetaVarMatchers [`beb6f50`](https://github.com/ast-grep/ast-grep/commit/beb6f50e936809071e6bacae2c854aefa8e46d11)
- **Breaking change:** feat: move fixer to RuleCore [`8a43a26`](https://github.com/ast-grep/ast-grep/commit/8a43a26585919dc54f899011f791cf1442bf0e30)
- **Breaking change:** refactor: move language out of SerializableRuleCore [`c5e0dc8`](https://github.com/ast-grep/ast-grep/commit/c5e0dc8da45eaa01e4278defa5160df67cd662df)
- **Breaking change:** refactor: remove Content generic from Fixer [`87bbf93`](https://github.com/ast-grep/ast-grep/commit/87bbf93537ea881a8429a80dca6a7d00716ffe51)
- **Breaking change:** feat: improve get_fixer signature [`e629e0f`](https://github.com/ast-grep/ast-grep/commit/e629e0f0b4a1312670e95ad900e34ee131992f15)
- **Breaking change:** refactor: make TemplateFix non-generic [`0f462a5`](https://github.com/ast-grep/ast-grep/commit/0f462a5801f942bc76bfed48896d4581667a5033)
- feat: migrate constraints to Rule [`230ee9c`](https://github.com/ast-grep/ast-grep/commit/230ee9c95f85cace5378cfcd0e16265205bed03d)
- refactor: move SerializableRuleCore to rule_core mod [`3a303c1`](https://github.com/ast-grep/ast-grep/commit/3a303c18b4f75b00686420eb8db33ad39c10124a)
#### [0.17.1](https://github.com/ast-grep/ast-grep/compare/0.17.0...0.17.1)
> 12 January 2024
- feat: respect user's language extension option [`#848`](https://github.com/ast-grep/ast-grep/issues/848)
#### [0.17.0](https://github.com/ast-grep/ast-grep/compare/0.16.1...0.17.0)
> 11 January 2024
- **Breaking change:** feat: change get_fixer to return Fixer [`07c5363`](https://github.com/ast-grep/ast-grep/commit/07c5363d689e043e4fcf0875e68c04b3b31326da)
- feat: add modify_range for tweaking fix range selection [`eeb2f98`](https://github.com/ast-grep/ast-grep/commit/eeb2f98c4cf7db919188cdaffc45a114b5a0c083)
- feat: rename modify_range -> get_replace_range [`54837df`](https://github.com/ast-grep/ast-grep/commit/54837df4dad598fbcbdf8b9c3c9a9ddbd2324dc7)
#### [0.16.1](https://github.com/ast-grep/ast-grep/compare/0.16.0...0.16.1)
> 5 January 2024
- feat: add support for bash and php [`#639`](https://github.com/ast-grep/ast-grep/issues/639)
- **Breaking change:** fix: more strict meta var parsing [`13ad32b`](https://github.com/ast-grep/ast-grep/commit/13ad32b25caa375f2dc72b3e7053c5571f505818)
- **Breaking change:** refactor: remove thrift language [`26ecdf6`](https://github.com/ast-grep/ast-grep/commit/26ecdf6c1286a937a447ab83a0ab1d940795b992)
- fix(deps): update dependency @swc/core to v1.3.102 [`9ca9711`](https://github.com/ast-grep/ast-grep/commit/9ca97112f6c9594d807606ff582fdc4bf5bdba2d)
#### [0.16.0](https://github.com/ast-grep/ast-grep/compare/0.15.1...0.16.0)
> 29 December 2023
- feat: add language globs to findInFiles in napi [`#780`](https://github.com/ast-grep/ast-grep/pull/780)
- **Breaking change:** fix: rename MetaVar for better naming [`#805`](https://github.com/ast-grep/ast-grep/issues/805)
- refactor: reorgnanize napi file structure [`93f8577`](https://github.com/ast-grep/ast-grep/commit/93f8577db6ba32e806a3ae00b30c7725f66e2987)
- refactor: move file type related function out of lib [`64e33a3`](https://github.com/ast-grep/ast-grep/commit/64e33a3af21432a1c8c6835e26170daf4926d1f7)
- feat: handle FileOption in LangOption::infer [`7bc3efe`](https://github.com/ast-grep/ast-grep/commit/7bc3efe7f936c7ee6c3413e58c1c336a7e86ec10)
#### [0.15.1](https://github.com/ast-grep/ast-grep/compare/0.15.0...0.15.1)
> 17 December 2023
- feat: support mutliple rules in -r and --inline-rules [`#786`](https://github.com/ast-grep/ast-grep/issues/786)
- refactor: unify run_worker and run_std_in [`#785`](https://github.com/ast-grep/ast-grep/issues/785)
- **Breaking change:** feat: --stdin now always awaits user input [`#791`](https://github.com/ast-grep/ast-grep/issues/791)
- feat: support --inline-rules option for sg scan [`#396`](https://github.com/ast-grep/ast-grep/issues/396)
- refactor: separate PathWorker and StdInWorker and Worker [`ef3d8ad`](https://github.com/ast-grep/ast-grep/commit/ef3d8ad0e0cd269f6dcec567be77d0545dd6fc51)
- test: add test cases for sg scan [`ed35c71`](https://github.com/ast-grep/ast-grep/commit/ed35c71dde74ac04b02e47e7cc27d18b83b225d1)
- fix: revert github action [`a2f22d8`](https://github.com/ast-grep/ast-grep/commit/a2f22d84780aa76d04395965ebbdf63d7755ba67)
#### [0.15.0](https://github.com/ast-grep/ast-grep/compare/0.14.4...0.15.0)
> 15 December 2023
- **Breaking change:** feat: reduce metavar_env string allocation [`e1ab015`](https://github.com/ast-grep/ast-grep/commit/e1ab015f2b3d23de82622e9487ecc82abf930aa6)
- **Breaking change:** fix: remove pattern as replacer [`f6e4293`](https://github.com/ast-grep/ast-grep/commit/f6e4293e1f3d2a2bf9df4e9d5aaf7ce5df640377)
- **Breaking change:** feat: better metavar detection [`e1ab18a`](https://github.com/ast-grep/ast-grep/commit/e1ab18a59ca7458c62bf55e73f3634a8b76e152a)
#### [0.14.4](https://github.com/ast-grep/ast-grep/compare/0.14.3...0.14.4)
> 11 December 2023
- fix: use tempfile to remove vuln [`#765`](https://github.com/ast-grep/ast-grep/issues/765)
- chore(deps): update dependency typescript to v5.3.3 [`dbac17d`](https://github.com/ast-grep/ast-grep/commit/dbac17d8c0c005942a3248c8991a67681006329e)
- chore(deps): update dependency @napi-rs/cli to v2.17.0 [`f2d32be`](https://github.com/ast-grep/ast-grep/commit/f2d32be1a8240f3d006160a98f6baa03de45cb7e)
- chore(deps): update dependency prettier to v3.1.1 [`990f09b`](https://github.com/ast-grep/ast-grep/commit/990f09bcb43531d00e4a489277f46a5747f1de86)
#### [0.14.3](https://github.com/ast-grep/ast-grep/compare/0.14.2...0.14.3)
> 10 December 2023
- feat: add getTransformed in napi [`#551`](https://github.com/ast-grep/ast-grep/issues/551)
- fix: load custom languages in new command [`#751`](https://github.com/ast-grep/ast-grep/issues/751)
- chore(deps): update dependency ava to v6 [`496eb9d`](https://github.com/ast-grep/ast-grep/commit/496eb9d1cd9584361986ee0b46f3d85f4158886f)
- feat: add support for Elixir [`57edde1`](https://github.com/ast-grep/ast-grep/commit/57edde1345f72dee7d1073fe40883e9553c1a9d5)
- feat: rename fixer to TemplateFix [`5b53ce0`](https://github.com/ast-grep/ast-grep/commit/5b53ce0dae2809d46f83c03579f7a4d9ef19ce5c)
#### [0.14.2](https://github.com/ast-grep/ast-grep/compare/0.14.1...0.14.2)
> 2 December 2023
- fix: update line number color [`#739`](https://github.com/ast-grep/ast-grep/issues/739)
- test: add test case for running sg without arg [`#748`](https://github.com/ast-grep/ast-grep/issues/748)
- fix(deps): update dependency @babel/core to v7.23.5 [`21e404c`](https://github.com/ast-grep/ast-grep/commit/21e404c2b0a0a7abda999db4bfbdac3a4ec36560)
- fix(deps): update dependency @swc/core to v1.3.100 [`941bffe`](https://github.com/ast-grep/ast-grep/commit/941bffec8449e1e5c5ba83a7926ae8014c2001a8)
- test: add tes for lang_globs [`a16001c`](https://github.com/ast-grep/ast-grep/commit/a16001ca67af17a32826b9907fb407db3fa3f4a3)
#### [0.14.1](https://github.com/ast-grep/ast-grep/compare/0.14.0...0.14.1)
> 30 November 2023
- fix: fix napi building [`1a49c67`](https://github.com/ast-grep/ast-grep/commit/1a49c67adbe4c8af61ede427248532767beea4e0)
#### [0.14.0](https://github.com/ast-grep/ast-grep/compare/0.13.2...0.14.0)
> 30 November 2023
- feat: add languageGlobs [`#601`](https://github.com/ast-grep/ast-grep/issues/601)
- doc: import config reference link in error message [`#736`](https://github.com/ast-grep/ast-grep/issues/736)
- fix: report error for wrong config file [`#736`](https://github.com/ast-grep/ast-grep/issues/736)
- feat: support register alias language for extension override [`#601`](https://github.com/ast-grep/ast-grep/issues/601)
- chore(deps): update dependency typescript to v5.3.2 [`55a43bd`](https://github.com/ast-grep/ast-grep/commit/55a43bd73e6a8d898e1bb28d7ebfed6d5ea2c981)
- fix: remove alias languages [`1f26f62`](https://github.com/ast-grep/ast-grep/commit/1f26f6276ef8babcfbb6492c03bd36753936610b)
- feat: support language globs [`fafd27c`](https://github.com/ast-grep/ast-grep/commit/fafd27cad401b6c1c8509f47401fb3a6c4d4b2d1)
#### [0.13.2](https://github.com/ast-grep/ast-grep/compare/0.13.1...0.13.2)
> 20 November 2023
- chore: add cargo fmt/clippy to CI pipeline [`#698`](https://github.com/ast-grep/ast-grep/issues/698)
- refactor: better reusability code for benchmark [`775cee4`](https://github.com/ast-grep/ast-grep/commit/775cee48dd57b129e7e19e78665752a2fcdf2452)
- feat: add async call [`11cf0dd`](https://github.com/ast-grep/ast-grep/commit/11cf0ddbc9db18167c27b30b7d12800e0ad9935a)
- feat: support napi's parseAsync [`ac78bc0`](https://github.com/ast-grep/ast-grep/commit/ac78bc0f75346d9ef1ed7de2622dfd02c439a4a3)
#### [0.13.1](https://github.com/ast-grep/ast-grep/compare/0.13.0...0.13.1)
> 15 November 2023
- **Breaking change:** fix: use ansi by default on windows [`#680`](https://github.com/ast-grep/ast-grep/issues/680)
- fix: guard against wrong tree-sitter traversal [`#713`](https://github.com/ast-grep/ast-grep/issues/713)
- docs(binding/py): basic readme [`2d39b9b`](https://github.com/ast-grep/ast-grep/commit/2d39b9bd05c3a6a52e8406c5c3ae2ffe4d863e9d)
- fix(deps): update dependency @oxidation-compiler/napi to ^0.2.0 [`d64c09b`](https://github.com/ast-grep/ast-grep/commit/d64c09bf83fb72dbbd31e86cdf90bad18c0db7b6)
- feat: rename ast-grep-py [`3e891c0`](https://github.com/ast-grep/ast-grep/commit/3e891c0389f775a1b3a83f14e73300316bf9ab3f)
#### [0.13.0](https://github.com/ast-grep/ast-grep/compare/0.12.5...0.13.0)
> 5 November 2023
- feat: support pyo3 TypedDict [`#389`](https://github.com/ast-grep/ast-grep/issues/389)
- chore(deps): update yarn to v4 [`818bcc2`](https://github.com/ast-grep/ast-grep/commit/818bcc2cf0c6ba3f6938447f142edddde33fa7c9)
- refactor: move node outside [`40028b0`](https://github.com/ast-grep/ast-grep/commit/40028b0b23c26f4fc51edeb3e90633df983ed34c)
- chore: formatting [`254e0de`](https://github.com/ast-grep/ast-grep/commit/254e0de904ff288e17d3b649c0651258430e5ded)
#### [0.12.5](https://github.com/ast-grep/ast-grep/compare/0.12.4...0.12.5)
> 15 October 2023
- fix: rewrite out of bound panic [`#668`](https://github.com/ast-grep/ast-grep/issues/668)
- chore: bump windows action version [`#620`](https://github.com/ast-grep/ast-grep/issues/620)
- fix: correct stopBy json schema [`#666`](https://github.com/ast-grep/ast-grep/issues/666)
- feat: `cargo xtask schema` for rule's JSON schema [`#665`](https://github.com/ast-grep/ast-grep/issues/665)
- feat: add json schema [`73d6299`](https://github.com/ast-grep/ast-grep/commit/73d629942b6f50c2a06f2ffb37d317f6fe827830)
- feat: initialize pyo3 [`810c454`](https://github.com/ast-grep/ast-grep/commit/810c454f03ca853a0c00533944877191126403ac)
- feat: update rules field doc [`c9d611d`](https://github.com/ast-grep/ast-grep/commit/c9d611dc3422bff29c31e509611dee0c12c87e0d)
#### [0.12.4](https://github.com/ast-grep/ast-grep/compare/0.12.3...0.12.4)
> 3 October 2023
- fix: fix word split after delimiter appear [`b1bcd61`](https://github.com/ast-grep/ast-grep/commit/b1bcd61adc42d362b6c58eb34f963d194f08f6a9)
#### [0.12.3](https://github.com/ast-grep/ast-grep/compare/0.12.2...0.12.3)
> 3 October 2023
- fix: correct indent for multiple MV in same line [`#647`](https://github.com/ast-grep/ast-grep/issues/647)
- **Breaking change:** feat: change string case interface [`4b60f82`](https://github.com/ast-grep/ast-grep/commit/4b60f82210819317768cb9bde6254feacf328969)
- feat: add string split [`fe52289`](https://github.com/ast-grep/ast-grep/commit/fe52289e643b44dca920823d78d461fd80624fde)
- feat:change string case name and implement conversion [`f073ee3`](https://github.com/ast-grep/ast-grep/commit/f073ee328768586a7aa216649947a435378f2387)
#### [0.12.2](https://github.com/ast-grep/ast-grep/compare/0.12.1...0.12.2)
> 29 September 2023
- Add identifier convention conversions [`#638`](https://github.com/ast-grep/ast-grep/pull/638)
- **Breaking change:** feat: change API name toCase [`fa50666`](https://github.com/ast-grep/ast-grep/commit/fa506666def5066f8ed04fbef376101ab6e96672)
- fix: make code compiles [`8f29f4f`](https://github.com/ast-grep/ast-grep/commit/8f29f4feb34d2ea8ec2bd1be6253fe8dc28ea226)
- fix: remove unused func temporarily [`710f76e`](https://github.com/ast-grep/ast-grep/commit/710f76ea42106bda4003675a3b31b5e3b1bc0d9c)
#### [0.12.1](https://github.com/ast-grep/ast-grep/compare/0.12.0...0.12.1)
> 13 September 2023
- Implement string conversions described in #436 as a stand-alone chainable `convert` transform [`dc1e435`](https://github.com/ast-grep/ast-grep/commit/dc1e435d4204fdc9d9771e69b3c8738651f63b77)
- Move structs around in transform.rs for better readability [`95a9ab8`](https://github.com/ast-grep/ast-grep/commit/95a9ab8a1587d80fb1043539ac3d53074bb48fad)
- fix(deps): update rust crate serde_json to 1.0.106 [`b41cfc0`](https://github.com/ast-grep/ast-grep/commit/b41cfc038a23d196f49dd722678b33bc1c7f9c7d)
#### [0.12.0](https://github.com/ast-grep/ast-grep/compare/0.11.1...0.12.0)
> 9 September 2023
- chore(deps): update dependency windows to v2022 [`#623`](https://github.com/ast-grep/ast-grep/pull/623)
- **Breaking change:** feat: interactive print one item a time [`#444`](https://github.com/ast-grep/ast-grep/issues/444)
- refactor: mvoe combined scan to config crate [`#626`](https://github.com/ast-grep/ast-grep/issues/626)
- test: add test for test case [`#553`](https://github.com/ast-grep/ast-grep/issues/553)
- **Breaking change:** feat: revamp diff printing schema [`2b30111`](https://github.com/ast-grep/ast-grep/commit/2b301116996b7b010ed271672d35a3529fb36e56)
- **Breaking change:** feat: accept multiple scanning [`4860aa4`](https://github.com/ast-grep/ast-grep/commit/4860aa4585b85e6efc7a0e979cebce54bc49afc4)
- refactor: move reporter out of verify.rs [`58fb152`](https://github.com/ast-grep/ast-grep/commit/58fb1521488fbee246bfee776f778ffeb261b47e)
#### [0.11.1](https://github.com/ast-grep/ast-grep/compare/0.11.0...0.11.1)
> 17 August 2023
- feat: support context lines in JSON mode [`#585`](https://github.com/ast-grep/ast-grep/issues/585)
- fix(cli): update typos and usage instructions [`9fcf34f`](https://github.com/ast-grep/ast-grep/commit/9fcf34f6954eae82ff8277b7c83362b27e993ed7)
- fix: lock maturin version [`83608ac`](https://github.com/ast-grep/ast-grep/commit/83608ac9142797544b4b30fb937715bd889d4acb)
- fix: allow newer maturin [`3704f63`](https://github.com/ast-grep/ast-grep/commit/3704f6387c690ae4c8b7736eb3f639408ae7bc81)
#### [0.11.0](https://github.com/ast-grep/ast-grep/compare/0.10.1...0.11.0)
> 13 August 2023
- feat: Rename scan --rule-id → scan --filter [`#596`](https://github.com/ast-grep/ast-grep/pull/596)
- feat: improve completions command [`#595`](https://github.com/ast-grep/ast-grep/issues/595)
- **Breaking change:** feat: test -f now accepts regex [`6465200`](https://github.com/ast-grep/ast-grep/commit/646520057cf3b3e0e36a8d8edaeed8fa53fa1794)
- **Breaking change:** fix: remove -f -F short form argument [`d6d2c23`](https://github.com/ast-grep/ast-grep/commit/d6d2c23ed8e8a58bf077b9e51a8b9fce10a2013e)
- feat: add option `scan --rule-id <rule_id>…` [`a424121`](https://github.com/ast-grep/ast-grep/commit/a4241214d661bf1b44d51a7e34aaad5366ae95f3)
#### [0.10.1](https://github.com/ast-grep/ast-grep/compare/0.10.0...0.10.1)
> 4 August 2023
- [feat] Make CLI reports a soft warning if query is invalid [`#575`](https://github.com/ast-grep/ast-grep/issues/575)
- feat: improve swift support [`#573`](https://github.com/ast-grep/ast-grep/issues/573)
- doc: add descriptions and links in npm page [`3be3764`](https://github.com/ast-grep/ast-grep/commit/3be376400a3206fd220360ee4af78eac37a60aa7)
- feat: implement soft error reporting [`c581c34`](https://github.com/ast-grep/ast-grep/commit/c581c349cbd4d4db1ad7a40bfd57a464ba0a636a)
- fix: show at least three lines of context in diffing [`ff61688`](https://github.com/ast-grep/ast-grep/commit/ff616882ad79fa7ca9e549d15ced271c1bbf25f1)
#### [0.10.0](https://github.com/ast-grep/ast-grep/compare/0.9.3...0.10.0)
> 30 July 2023
- feat: support multiple JSON printing style [`#561`](https://github.com/ast-grep/ast-grep/issues/561)
- refactor: move output related arguments out [`#539`](https://github.com/ast-grep/ast-grep/issues/539)
- feat: improve readability of argument value [`#546`](https://github.com/ast-grep/ast-grep/issues/546)
- feat: use checked_ilog10 to reduce string allocation [`#550`](https://github.com/ast-grep/ast-grep/issues/550)
- feat: nicer line number printing color [`#547`](https://github.com/ast-grep/ast-grep/issues/547)
- refactor: move common input args out [`a9bd29a`](https://github.com/ast-grep/ast-grep/commit/a9bd29a9677db1bdb6c9f99d218dd4ecdc5a7927)
- refactor: reorganize utils files and add comments [`19dacf8`](https://github.com/ast-grep/ast-grep/commit/19dacf82d586db1820d779205f56cf08b4dc1acb)
- feat(test): add test case filter for sg test [`52f51e9`](https://github.com/ast-grep/ast-grep/commit/52f51e9e5cea9527ba86928d9e6bef64f77f2262)
#### [0.9.3](https://github.com/ast-grep/ast-grep/compare/0.9.2...0.9.3)
> 27 July 2023
- feat: add before/after context [`#464`](https://github.com/ast-grep/ast-grep/issues/464)
- test: move test out and fix heading never [`67037aa`](https://github.com/ast-grep/ast-grep/commit/67037aa4a7322ed7f3911b33bb71168f3b6aea8c)
- test: add json print test [`374db3b`](https://github.com/ast-grep/ast-grep/commit/374db3ba9027bcaeee6ef040e1b7b87e0e0227f1)
- fix: fix display context [`08a52d1`](https://github.com/ast-grep/ast-grep/commit/08a52d16d9fa47a499db100655153f8b70c68979)
#### [0.9.2](https://github.com/ast-grep/ast-grep/compare/0.9.1...0.9.2)
> 22 July 2023
- feat(language): add initial support for JSON [`#522`](https://github.com/ast-grep/ast-grep/pull/522)
- test: add lua language test [`#519`](https://github.com/ast-grep/ast-grep/issues/519)
- feat: Support meta-variable in ERROR [`#526`](https://github.com/ast-grep/ast-grep/issues/526)
- infra: add napi benckmark [`#442`](https://github.com/ast-grep/ast-grep/issues/442)
- feat: support GitHub format [`#499`](https://github.com/ast-grep/ast-grep/issues/499)
- fix: tweak style [`6ec490b`](https://github.com/ast-grep/ast-grep/commit/6ec490ba17bed6d7919911b32b7ec7e9ca714bab)
- chore(deps): update yarn to v3.6.1 [`23a2684`](https://github.com/ast-grep/ast-grep/commit/23a2684b9778c72539903805a9c3305d2cb8cdcc)
- fix(deps): update dependency @oxidation-compiler/napi to ^0.1.0 [`492d5c8`](https://github.com/ast-grep/ast-grep/commit/492d5c8f52d82555edc542fac4a11060df2abbf4)
#### [0.9.1](https://github.com/ast-grep/ast-grep/compare/0.9.0...0.9.1)
> 16 July 2023
- fix: fix wrong text [`#517`](https://github.com/ast-grep/ast-grep/issues/517)
- feat: support ruby language [`#518`](https://github.com/ast-grep/ast-grep/issues/518)
- refactor: use macro to unify language impl [`#516`](https://github.com/ast-grep/ast-grep/issues/516)
- fix: fix kotlin grammar [`e7faadb`](https://github.com/ast-grep/ast-grep/commit/e7faadb50c4e196a33e4880a08d21af931a16535)
- test: add test for diff printing [`5330149`](https://github.com/ast-grep/ast-grep/commit/533014982903cf18d42dde8de7ae1e7a84da5454)
- fix(deps): update rust crate dashmap to 5.5.0 [`05282aa`](https://github.com/ast-grep/ast-grep/commit/05282aa43547f17eb4a74cbb164172646526e66f)
#### [0.9.0](https://github.com/ast-grep/ast-grep/compare/0.8.0...0.9.0)
> 15 July 2023
- **Breaking change:** feat: unify `--accept-all` and `--update-snapshots` to `--update-all` [`#513`](https://github.com/ast-grep/ast-grep/issues/513)
- **Breaking change:** infra: add breaking change in changelog [`#514`](https://github.com/ast-grep/ast-grep/issues/514)
- **Breaking change:** feat: change line separator to colon `:` in CLI output [`#512`](https://github.com/ast-grep/ast-grep/issues/512)
- fix: tsx should only discover *.tsx, ts discover only *.ts [`#502`](https://github.com/ast-grep/ast-grep/issues/502)
- refactor: refactor path to parallelwalk [`d697aad`](https://github.com/ast-grep/ast-grep/commit/d697aada02c07f7a54bde180a48e38a8a61ef28a)
- fix(deps): update rust crate globset to 0.4.11 [`3da277f`](https://github.com/ast-grep/ast-grep/commit/3da277f416582c405d6d2f7f881919669815f935)
- test: add test for `sg test` command arg parsing [`2735057`](https://github.com/ast-grep/ast-grep/commit/273505712321340933b333abb27c9f64117b0256)
#### [0.8.0](https://github.com/ast-grep/ast-grep/compare/0.7.2...0.8.0)
> 10 July 2023
- feat: add changelog to tag release [`#498`](https://github.com/ast-grep/ast-grep/issues/498)
- feat: add changelog to release [`#498`](https://github.com/ast-grep/ast-grep/issues/498)
- napi: add napi declaration [`e897d93`](https://github.com/ast-grep/ast-grep/commit/e897d93cd3a9942f34eac4c0170462fc19e3bc14)
- feat: change `--no-stdin` to `--stdin` [`1e26573`](https://github.com/ast-grep/ast-grep/commit/1e26573b1743e635533b12ca2e207cb26e515edb)
- fix: update napi typing doc [`59dfcb7`](https://github.com/ast-grep/ast-grep/commit/59dfcb788ec343166b06551d692c6f8ce596c24a)
#### [0.7.2](https://github.com/ast-grep/ast-grep/compare/0.7.1...0.7.2)
> 9 July 2023
- feat: add getRoot for SgNode [`#497`](https://github.com/ast-grep/ast-grep/issues/497)
- feat: add option to disable a rule [`#489`](https://github.com/ast-grep/ast-grep/issues/489)
- feat: add pattern fixed string optimizer [`#349`](https://github.com/ast-grep/ast-grep/issues/349)
- feat: add support for --no-stdin [`#490`](https://github.com/ast-grep/ast-grep/issues/490)
- feat: change language implementations [`7525927`](https://github.com/ast-grep/ast-grep/commit/75259270cdcec2e343cbaba01035dddf223f7d27)
- test: add test for sg scan with off-rule [`0eeb99d`](https://github.com/ast-grep/ast-grep/commit/0eeb99db93db0926a4e681274504126b6a85fe94)
- fix: change returning type [`10e676b`](https://github.com/ast-grep/ast-grep/commit/10e676be35567226b74d23a80ee82eff3e0e4cde)
#### [0.7.1](https://github.com/ast-grep/ast-grep/compare/0.7.0...0.7.1)
> 29 June 2023
- feat: add supported languages in help string [`#482`](https://github.com/ast-grep/ast-grep/issues/482)
- feat: add Scala support [`84492b9`](https://github.com/ast-grep/ast-grep/commit/84492b9f1a1b57a67df7907d6859e9a707dae077)
- feat: improve language's crate's organization [`429aa35`](https://github.com/ast-grep/ast-grep/commit/429aa357d29d80237418585f1a047bfeb0fafbab)
- feat: Use existing language FromStr impl when Deserializing [`ad0e9a6`](https://github.com/ast-grep/ast-grep/commit/ad0e9a68a05c2b60d1078fcd56fc2b8fc5499b50)
#### [0.7.0](https://github.com/ast-grep/ast-grep/compare/0.6.7...0.7.0)
> 25 June 2023
- feat: support scan from stdin [`#397`](https://github.com/ast-grep/ast-grep/issues/397)
- feat: support search code from stdin [`#397`](https://github.com/ast-grep/ast-grep/issues/397)
- doc: add pip install option [`#465`](https://github.com/ast-grep/ast-grep/issues/465)
- feat: support stdin as input [`2e8f0ef`](https://github.com/ast-grep/ast-grep/commit/2e8f0ef9dbd133da842936ea348c1391c81d4f43)
- fix(deps): update rust crate toml_edit to 0.19.11 [`f0facff`](https://github.com/ast-grep/ast-grep/commit/f0facff676e576d5926918b92f380f5544e5d600)
- fix: remove redundant line number highlighting [`aaae3a3`](https://github.com/ast-grep/ast-grep/commit/aaae3a3a1e2c3ba1f91c54223154e28f44052b17)
#### [0.6.7](https://github.com/ast-grep/ast-grep/compare/0.6.6...0.6.7)
> 23 June 2023
- feat: add pypi release [`d4752fd`](https://github.com/ast-grep/ast-grep/commit/d4752fdea8d2ab868415287da5aa572c81884f37)
- feat: add more cli support [`170fbbf`](https://github.com/ast-grep/ast-grep/commit/170fbbf39b0623cc7789ba30a6f3e56db3398197)
- fix: remove linux cross [`692020b`](https://github.com/ast-grep/ast-grep/commit/692020b7111663914975d3b11756d52dff952896)
#### [0.6.6](https://github.com/ast-grep/ast-grep/compare/0.6.5...0.6.6)
> 19 June 2023
- chore: update changelog [`962246b`](https://github.com/ast-grep/ast-grep/commit/962246b9a273386e347ea5cfad1384b618d8b7f4)
- fix: add cpp to non-builtin parser [`0a473c9`](https://github.com/ast-grep/ast-grep/commit/0a473c9e3c5fcfa909f86bea864ae1d671a27cfa)
#### [0.6.5](https://github.com/ast-grep/ast-grep/compare/0.6.4...0.6.5)
> 19 June 2023
- feat: support check multivar pattern equality [`#460`](https://github.com/ast-grep/ast-grep/issues/460)
- feat(language): add initial support for C++ [`9315571`](https://github.com/ast-grep/ast-grep/commit/93155717ba48fdf1dea3138e87b5ff7a220d5cc8)
- doc: add docs string for all core modules [`e0214f7`](https://github.com/ast-grep/ast-grep/commit/e0214f79a6799a51f2ec3b95b32a6dc4838a5df0)
- test: add test for transform [`100f081`](https://github.com/ast-grep/ast-grep/commit/100f081cef8aa1a260ead335446d68b5fbd7346e)
#### [0.6.4](https://github.com/ast-grep/ast-grep/compare/0.6.3...0.6.4)
> 17 June 2023
- chore: bump changelog [`d90e2c3`](https://github.com/ast-grep/ast-grep/commit/d90e2c384eca20d995f51da21836cdc7532481fd)
- fix: skip overlapping diff [`996a423`](https://github.com/ast-grep/ast-grep/commit/996a423f15bec37aa1a6e0a6f9e4d29950857b92)
- doc: fix spelling [`5ef8982`](https://github.com/ast-grep/ast-grep/commit/5ef8982de3b628fca55e657026608e940b6473e0)
#### [0.6.3](https://github.com/ast-grep/ast-grep/compare/0.6.2...0.6.3)
> 11 June 2023
- chore: move test cases location [`3c0276e`](https://github.com/ast-grep/ast-grep/commit/3c0276e9ca2f3d8f16056339d2dd2a656c1efc9f)
- feat: add support for transform [`47feee1`](https://github.com/ast-grep/ast-grep/commit/47feee14afd894f760c0aedeaf95589179511870)
- refactor: move get text to meta_var [`5692c26`](https://github.com/ast-grep/ast-grep/commit/5692c26ab62c67a5193ed89dea202059673f01a5)
#### [0.6.2](https://github.com/ast-grep/ast-grep/compare/0.6.1...0.6.2)
> 6 June 2023
- bump version [`26583ec`](https://github.com/ast-grep/ast-grep/commit/26583eccb7bd8411f9674943302c1470bc816324)
- fix: add re-indent to fixer [`245301e`](https://github.com/ast-grep/ast-grep/commit/245301e8bf9658b5d14e3abaee799e948d001a10)
#### [0.6.1](https://github.com/ast-grep/ast-grep/compare/0.6.0...0.6.1)
> 6 June 2023
- fix: reindent back into src code [`a522875`](https://github.com/ast-grep/ast-grep/commit/a5228759822480238a7ed265e657716a8988d914)
- perf: remove all possible allocations [`45558a5`](https://github.com/ast-grep/ast-grep/commit/45558a5338649dc7b6d6a41bc1d680a86976e4c1)
- fix: use Cow to otpimize allocation [`b4c85b3`](https://github.com/ast-grep/ast-grep/commit/b4c85b3e06b25c75c6ba2226f57e2feb6d7e662b)
#### [0.6.0](https://github.com/ast-grep/ast-grep/compare/0.5.7...0.6.0)
> 5 June 2023
- feat: add indentation sensitive replacement [`#394`](https://github.com/ast-grep/ast-grep/issues/394)
- fix: use ubuntu20.04 instead of latest [`#416`](https://github.com/ast-grep/ast-grep/issues/416)
- refactor: refine replacer modules [`79dc46b`](https://github.com/ast-grep/ast-grep/commit/79dc46b538997489bf26777603cf1814b2372d33)
- test: add root replacer test back [`99aa031`](https://github.com/ast-grep/ast-grep/commit/99aa03102263583bdf673bd8011e7f8206b420b4)
- feat: add design notes for indentation sensitive fixer [`9c8cc05`](https://github.com/ast-grep/ast-grep/commit/9c8cc05dcc7228e9961c6aa7d39c7ff5903f4cad)
#### [0.5.7](https://github.com/ast-grep/ast-grep/compare/0.5.6...0.5.7)
> 29 May 2023
- wildcard and spread rust tests [`#415`](https://github.com/ast-grep/ast-grep/pull/415)
- fix: fix multi variate capturing hangs [`#411`](https://github.com/ast-grep/ast-grep/issues/411)
- feat: smarter matching algorithm [`#374`](https://github.com/ast-grep/ast-grep/issues/374)
- chore(deps): update dependency ava to v5.3.0 [`67e8ec1`](https://github.com/ast-grep/ast-grep/commit/67e8ec1c12840ed8db3886360d5dfff758ec7062)
- test: add test for match end [`3e61667`](https://github.com/ast-grep/ast-grep/commit/3e61667023749560d85a5e1a4ea756f2d3dc6d52)
- chore(deps): update rust crate criterion to 0.5 [`3579862`](https://github.com/ast-grep/ast-grep/commit/357986219ad10db7644d54f797bece1417fa175c)
#### [0.5.6](https://github.com/ast-grep/ast-grep/compare/0.5.5...0.5.6)
> 25 May 2023
- fix: bump tree-sitter-facade version and support next_all on web [`a48ba62`](https://github.com/ast-grep/ast-grep/commit/a48ba62a8f6decd253ee494a15591dda92ed6a45)
- fix: update tree-sitter-facade version [`8e44f6a`](https://github.com/ast-grep/ast-grep/commit/8e44f6ac8aa64d0ad7d7b788074e56baac02e74b)
- chore: update changelog [`78f751a`](https://github.com/ast-grep/ast-grep/commit/78f751ad1710912a81f376ae27049f5b39a8a575)
#### [0.5.5](https://github.com/ast-grep/ast-grep/compare/0.5.4...0.5.5)
> 24 May 2023
- fix: add new xtask [`37d3e40`](https://github.com/ast-grep/ast-grep/commit/37d3e403dbcc35791e0b58d18d11d8573598de04)
- chore: bump version [`20addde`](https://github.com/ast-grep/ast-grep/commit/20addde4df9dd8e425f9eaf7dda6d80f4c9a1d8d)
- fix: add version [`69b0bb6`](https://github.com/ast-grep/ast-grep/commit/69b0bb6197ba28d42104c9d36dbf2f8c664041b1)
#### [0.5.4](https://github.com/ast-grep/ast-grep/compare/0.5.3...0.5.4)
> 24 May 2023
- fix: implement generic Doc replacer for pattern [`#405`](https://github.com/ast-grep/ast-grep/issues/405)
- fix: use get_range instead as_slice to handle byte_offset and underlying arry size [`#400`](https://github.com/ast-grep/ast-grep/issues/400)
- chore: bump version [`998691d`](https://github.com/ast-grep/ast-grep/commit/998691d36b477766be92f1ede3c0bc153d0cca42)
- feat: support generic new Pattern/Root from str [`36d91e7`](https://github.com/ast-grep/ast-grep/commit/36d91e7d48316d3e645b606f2f5a293359d0138e)
- chore: use workspace package info [`960fa8d`](https://github.com/ast-grep/ast-grep/commit/960fa8d1021eb5ba932ee1dab6ecbbfdd82030a3)
#### [0.5.3](https://github.com/ast-grep/ast-grep/compare/0.5.2...0.5.3)
> 18 May 2023
- perf: optimize relational rule's stopBy: neighbor [`#358`](https://github.com/ast-grep/ast-grep/issues/358)
- perf: optimize node's next_all by tree-cursor [`#358`](https://github.com/ast-grep/ast-grep/issues/358)
- test: add test for node operators [`ac38cb8`](https://github.com/ast-grep/ast-grep/commit/ac38cb81d444e458a87a14a789cc043da6d70c8e)
- refactor: add comments for ansi link [`d9ebbe5`](https://github.com/ast-grep/ast-grep/commit/d9ebbe5cc2b47876c8ef212550f34891b64bfd28)
- test: add test for precedes/follows serialization [`1d7d8ff`](https://github.com/ast-grep/ast-grep/commit/1d7d8ff02444224553c1e2647a3a4c49026338ff)
#### [0.5.2](https://github.com/ast-grep/ast-grep/compare/0.5.1...0.5.2)
> 13 May 2023
- docs: fix readme typo [`#366`](https://github.com/ast-grep/ast-grep/pull/366)
- chore: add cargo build before git tag [`#367`](https://github.com/ast-grep/ast-grep/issues/367)
- 0.5.2 bump version [`54ba0a7`](https://github.com/ast-grep/ast-grep/commit/54ba0a7237bf30663c5f89141c0438ffcc9c2cb9)
- feat: add go setting [`a1d579b`](https://github.com/ast-grep/ast-grep/commit/a1d579b4da5766c4cc09cfe0cbf53fc24669b107)
- fix: fix fmt and clippy [`efab639`](https://github.com/ast-grep/ast-grep/commit/efab63959519c6b8f574c8cdfdf79ff9722c2a02)
#### [0.5.1](https://github.com/ast-grep/ast-grep/compare/0.5.0...0.5.1)
> 12 May 2023
- 0.5.1 bump version [`ebae85f`](https://github.com/ast-grep/ast-grep/commit/ebae85f0b18f5f69d30ba9e868b52f3ab850a224)
- fix: update deps [`ccd7a62`](https://github.com/ast-grep/ast-grep/commit/ccd7a62dc455d313ce1de9237b809ddc92db4aec)
- chore: update CHANGELOG [`990091e`](https://github.com/ast-grep/ast-grep/commit/990091e50b4e8bf0cd28026785386a60974a195e)
#### [0.5.0](https://github.com/ast-grep/ast-grep/compare/0.4.1...0.5.0)
> 12 May 2023
- feat: provide additional binary name `ast-grep` [`#361`](https://github.com/ast-grep/ast-grep/issues/361)
- fix: support custom lang for sg run [`#357`](https://github.com/ast-grep/ast-grep/issues/357)
- feat: support dynamic lib loading [`#347`](https://github.com/ast-grep/ast-grep/issues/347)
- feat: replace SupportLanguage with SgLang [`f6014f9`](https://github.com/ast-grep/ast-grep/commit/f6014f994d6b4fc6b2207b4abf47d17fa7ce25a0)
- Used result in test code to remove unwrap [`07bcf7e`](https://github.com/ast-grep/ast-grep/commit/07bcf7e7e2922adfc2c2b4df8d9904ef16a37b7a)
- feat: improve implementation of dynamic lang [`3757887`](https://github.com/ast-grep/ast-grep/commit/37578873565ab3f888bb19a3a2fe90c13f688b06)
#### [0.4.1](https://github.com/ast-grep/ast-grep/compare/0.4.0...0.4.1)
> 23 April 2023
- 0.4.1 bump version [`3ed1886`](https://github.com/ast-grep/ast-grep/commit/3ed18868be32eed98b4dbc8bd45194def03ada6a)
- chore: update changelog [`fdb657e`](https://github.com/ast-grep/ast-grep/commit/fdb657e7742014f8280580775e8dcbc031fb4af4)
- fix: fix sg new rule [`956dc46`](https://github.com/ast-grep/ast-grep/commit/956dc46cc0823c1611ac74f2a5664bae3911d58d)
#### [0.4.0](https://github.com/ast-grep/ast-grep/compare/0.3.3...0.4.0)
> 23 April 2023
- feat: reduce MetaVarEnv cloning in any [`#217`](https://github.com/ast-grep/ast-grep/issues/217)
- fix: support correct row/col offset for napi! [`#336`](https://github.com/ast-grep/ast-grep/issues/336)
- refactor: break down napi modules [`67062a6`](https://github.com/ast-grep/ast-grep/commit/67062a6233a1d6be5a4592f78e77984cd95c9f1c)
- feat: migrate node [`5e15d8d`](https://github.com/ast-grep/ast-grep/commit/5e15d8dbcfbede43dd80949a3e87fdff785bf1fd)
- feat: change ts_parser to source [`a3bef23`](https://github.com/ast-grep/ast-grep/commit/a3bef23c1d9bc9c8471309636d23346758b4a751)
#### [0.3.3](https://github.com/ast-grep/ast-grep/compare/0.3.2...0.3.3)
> 6 April 2023
- infra: add criterion benchmark [`#141`](https://github.com/ast-grep/ast-grep/issues/141)
- feat: Add edit option for rules without fix [`#297`](https://github.com/ast-grep/ast-grep/issues/297)
- feat: optimize readability of test output [`#293`](https://github.com/ast-grep/ast-grep/issues/293)
- test: add integration test for run [`#292`](https://github.com/ast-grep/ast-grep/issues/292)
- refactor: move out lib and main [`a95c19e`](https://github.com/ast-grep/ast-grep/commit/a95c19eea2844d81d6f6e055581859837006ab7d)
- fix(deps): update rust crate clap to 4.2.1 [`b3fb9c9`](https://github.com/ast-grep/ast-grep/commit/b3fb9c9bf472ecd3c35285fa8a69cb39b40b4961)
- feat: add base_dir for new command [`2a75a0a`](https://github.com/ast-grep/ast-grep/commit/2a75a0a3694826bd6414f38ba46b1b112746a3d0)
#### [0.3.2](https://github.com/ast-grep/ast-grep/compare/0.3.1...0.3.2)
> 11 March 2023
- fix: find_rule should also find contingent rule [`#286`](https://github.com/ast-grep/ast-grep/issues/286)
- feat: create new project/util/rule command [`#266`](https://github.com/ast-grep/ast-grep/issues/266)
- feat: add write sgconfig [`750df87`](https://github.com/ast-grep/ast-grep/commit/750df8700fa58a0f9a70de71c5118cbf8d0a10d4)
- feat: rename find_config to find_rules [`8e5a181`](https://github.com/ast-grep/ast-grep/commit/8e5a181c0b9cb6380a0ab74c922edd2029075335)
- 0.3.2 bump version [`4f41f46`](https://github.com/ast-grep/ast-grep/commit/4f41f461d691a47a5d1562465f634321d022ca9a)
#### [0.3.1](https://github.com/ast-grep/ast-grep/compare/0.3.0...0.3.1)
> 28 February 2023
- fix: fix panic when matching root node [`#275`](https://github.com/ast-grep/ast-grep/issues/275)
- 0.3.1 bump version [`703467e`](https://github.com/ast-grep/ast-grep/commit/703467e420215e70851b5a82d9689e78cb867b59)
- fix: fix napi error [`3d305a6`](https://github.com/ast-grep/ast-grep/commit/3d305a6833471cc3f90e31fdb4111a4a5800c50f)
- chore: bump version [`a25bef1`](https://github.com/ast-grep/ast-grep/commit/a25bef1b5e7926c08b79787c115a913e037a2336)
#### [0.3.0](https://github.com/ast-grep/ast-grep/compare/0.2.6...0.3.0)
> 28 February 2023
- fix: add is_named_leaf [`#276`](https://github.com/ast-grep/ast-grep/issues/276)
- fix: report indirect cyclic dependencies [`#272`](https://github.com/ast-grep/ast-grep/issues/272)
- fix: register global rules by dependent order [`#269`](https://github.com/ast-grep/ast-grep/issues/269)
- [feature] register util rules by their topological sort order [`#270`](https://github.com/ast-grep/ast-grep/issues/270)
- feat: change stopBy default value to neighbor [`#265`](https://github.com/ast-grep/ast-grep/issues/265)
- feat: add global utils [`#250`](https://github.com/ast-grep/ast-grep/issues/250)
- feat: add thrift support [`#255`](https://github.com/ast-grep/ast-grep/issues/255)
- fix: avoid strong reference in ReferentRule [`#253`](https://github.com/ast-grep/ast-grep/issues/253)
- refactor: factor out deserialize rule [`a6207ae`](https://github.com/ast-grep/ast-grep/commit/a6207aed7e238a980fae002cca337119fe5dc48e)
- refactor: move stop_by to separate file [`398c53d`](https://github.com/ast-grep/ast-grep/commit/398c53d144a3def075516af43aafcf5de7a2fcf6)
- refactor: reorganize rule and serialization [`37a303f`](https://github.com/ast-grep/ast-grep/commit/37a303faaf9b584b09495a082346d90c10d63398)
#### [0.2.6](https://github.com/ast-grep/ast-grep/compare/0.2.5...0.2.6)
> 8 February 2023
- feat: add maybe to differentiate null/missing/value [`#241`](https://github.com/ast-grep/ast-grep/issues/241)
- feat: add error exit code [`#238`](https://github.com/ast-grep/ast-grep/issues/238)
- refactor: disable color by default if output is not tty [`#239`](https://github.com/ast-grep/ast-grep/issues/239)
- feat: unify immediate and until to stopBy [`ff69866`](https://github.com/ast-grep/ast-grep/commit/ff69866c7f306fc6c727b7c45a004a8c27e5a3c8)
- 0.2.6 bump version [`136a75f`](https://github.com/ast-grep/ast-grep/commit/136a75f96d80481379aeab2e9cfc08972656b7ca)
- feat: support field in Inside [`c503f0f`](https://github.com/ast-grep/ast-grep/commit/c503f0f6352cee6c6f54e691e88b3a1d153fb221)
#### [0.2.5](https://github.com/ast-grep/ast-grep/compare/0.2.4...0.2.5)
> 4 February 2023
- feat: improve napi error message [`#227`](https://github.com/ast-grep/ast-grep/issues/227)
- feat: add reference [`303ec8c`](https://github.com/ast-grep/ast-grep/commit/303ec8c0b7d6e3489c4fde2e689fe6e4583126cd)
- feat: add find_in_files [`07735df`](https://github.com/ast-grep/ast-grep/commit/07735df59a88b2fbd872c49c99cf5d2e8f29f34a)
- feat: add napi findInFiles [`3f4e847`](https://github.com/ast-grep/ast-grep/commit/3f4e8478ef8deca957c89b8532363c650770d24a)
#### [0.2.4](https://github.com/ast-grep/ast-grep/compare/0.2.3...0.2.4)
> 29 January 2023
- perf: use potential_kinds for more scenarios [`#221`](https://github.com/ast-grep/ast-grep/issues/221)
- refactor: remove var_matchers from MetaVarEnv [`#218`](https://github.com/ast-grep/ast-grep/issues/218)
- refactor: remove get meta_var_matchers in Matcher [`#218`](https://github.com/ast-grep/ast-grep/issues/218)
- refactor: split scan and run [`bf1e7e2`](https://github.com/ast-grep/ast-grep/commit/bf1e7e2d2d5f1333e56a016d5f0378d06f88bb0b)
- Revert "perf: use potential_kinds for more scenarios" [`c264e32`](https://github.com/ast-grep/ast-grep/commit/c264e325dc2d7f6eeae744a5f267d38fcea18c57)
- refactor: remove duplicate MatchUnit/filter_file [`ab2222e`](https://github.com/ast-grep/ast-grep/commit/ab2222ef84be26e4628e22d8168f9b0633fa55f7)
#### [0.2.3](https://github.com/ast-grep/ast-grep/compare/0.2.2...0.2.3)
> 27 January 2023
- feat: make inside util inclusive [`#205`](https://github.com/ast-grep/ast-grep/issues/205)
- feat: support search multiple paths [`#207`](https://github.com/ast-grep/ast-grep/issues/207)
- fix: use return number for napi [`#205`](https://github.com/ast-grep/ast-grep/issues/205)
- 0.2.3 bump version [`753e972`](https://github.com/ast-grep/ast-grep/commit/753e9724ef882ca84b07db19407ecc80920ab459)
- refactor: use stop_by for all relational rule [`279edeb`](https://github.com/ast-grep/ast-grep/commit/279edeb5b19882ae270675dda35106667876cef2)
- refactor: cleaner StopBy matching [`974a307`](https://github.com/ast-grep/ast-grep/commit/974a307c9197ce64732b9a61c15ea26eb6b33cab)
#### [0.2.2](https://github.com/ast-grep/ast-grep/compare/0.2.1...0.2.2)
> 25 January 2023
- feat: add parseFile method [`9d22822`](https://github.com/ast-grep/ast-grep/commit/9d22822b74adc850d2b5e1eb041a5f564eb7a50b)
- 0.2.2 bump version [`767201a`](https://github.com/ast-grep/ast-grep/commit/767201afdf564f8171bb95528ec938091a047343)
- fix: add css [`809133c`](https://github.com/ast-grep/ast-grep/commit/809133c506beab29734d3c9a80061c9fc788e0ee)
#### [0.2.1](https://github.com/ast-grep/ast-grep/compare/0.2.0...0.2.1)
> 24 January 2023
- 0.2.1 bump version [`7d81fd7`](https://github.com/ast-grep/ast-grep/commit/7d81fd7e1abd30bd3b09e2319762154c8d530620)
- chore: update changelog [`f2907a4`](https://github.com/ast-grep/ast-grep/commit/f2907a4e7e917d684abb2393a99d6b7d395501e4)
- fix: add dart in wasm [`545de0e`](https://github.com/ast-grep/ast-grep/commit/545de0e7ddc6703a49c9a8f2a6c95f3c3a53776c)
#### [0.2.0](https://github.com/ast-grep/ast-grep/compare/0.1.18...0.2.0)
> 24 January 2023
- feat: support multi node pattern [`#199`](https://github.com/ast-grep/ast-grep/issues/199)
- [refactor] Extract a match node iterator function in matcher_tree [`#199`](https://github.com/ast-grep/ast-grep/issues/199)
- feat: print out serialized rule message [`#200`](https://github.com/ast-grep/ast-grep/issues/200)
- fix: fix nested leaf match crash in non-entrant traversal [`#197`](https://github.com/ast-grep/ast-grep/issues/197)
- feat: add Dart support [`#172`](https://github.com/ast-grep/ast-grep/issues/172)
- fix: fix wrong line number in diff [`#192`](https://github.com/ast-grep/ast-grep/issues/192)
- feat: support inclusive until [`#191`](https://github.com/ast-grep/ast-grep/issues/191)
- feat: allow shorthands to combine all rule kinds [`51f72db`](https://github.com/ast-grep/ast-grep/commit/51f72db0a65454e08786a3c76f513e1664a5104d)
- refactor: refactor deser for better error message [`f28f964`](https://github.com/ast-grep/ast-grep/commit/f28f96426e867997994c846d2fcc77044329368c)
- 0.2.0 bump version [`e580932`](https://github.com/ast-grep/ast-grep/commit/e580932f431d1958983bcb9399b5329e58ffcd2a)
#### [0.1.18](https://github.com/ast-grep/ast-grep/compare/0.1.17...0.1.18)
> 16 January 2023
- fix: revert write env for unmatched pattern [`#190`](https://github.com/ast-grep/ast-grep/issues/190)
- fix: fix Has relation [`#188`](https://github.com/ast-grep/ast-grep/issues/188)
- feat: add rule info to config fixer [`#122`](https://github.com/ast-grep/ast-grep/issues/122)
- refactor: deduplicate json printer code [`#185`](https://github.com/ast-grep/ast-grep/issues/185)
- feat: add regex matcher [`0a40ed2`](https://github.com/ast-grep/ast-grep/commit/0a40ed22337c2ebe0bfb33447f8e762adcca5077)
- 0.1.18 bump version [`bfdde22`](https://github.com/ast-grep/ast-grep/commit/bfdde226fa46c58988b58f12b19bca6b64bfa4f2)
- feat: add debugging assert [`5b17e8b`](https://github.com/ast-grep/ast-grep/commit/5b17e8b138524d1455d2842a4c7525d62865951f)
#### [0.1.17](https://github.com/ast-grep/ast-grep/compare/0.1.16...0.1.17)
> 15 January 2023
- feat: add error handling for parsing RuleConfig [`#182`](https://github.com/ast-grep/ast-grep/issues/182)
- feat: better error handling in constraints config [`#181`](https://github.com/ast-grep/ast-grep/issues/181)
- feat: improve error handling in config [`#181`](https://github.com/ast-grep/ast-grep/issues/181)
- 0.1.17 bump version [`0a638f1`](https://github.com/ast-grep/ast-grep/commit/0a638f18769803c60a7e44e9507a53cfbca3d403)
- feat: add try_new for pattern creation [`a9510d2`](https://github.com/ast-grep/ast-grep/commit/a9510d2c718a7d50cc1a5e0c614472d35f5dc028)
- refactor: cleanup public API [`875ef1e`](https://github.com/ast-grep/ast-grep/commit/875ef1e20a5ad00bc6658e31822467bf8fe9a943)
#### [0.1.16](https://github.com/ast-grep/ast-grep/compare/0.1.15...0.1.16)
> 14 January 2023
- fix(deps): update rust crate ignore to 0.4.19 [`#165`](https://github.com/ast-grep/ast-grep/pull/165)
- fix(deps): update rust crate globset to 0.4.10 [`#164`](https://github.com/ast-grep/ast-grep/pull/164)
- feat: support --no-ignore for scan/test [`#162`](https://github.com/ast-grep/ast-grep/issues/162)
- fix: fix two adjacent sticking but not overlapping matches [`#171`](https://github.com/ast-grep/ast-grep/issues/171)
- feat: add source [`1a23b6a`](https://github.com/ast-grep/ast-grep/commit/1a23b6ac5085b1ae9fabd3d58319a3195022a7b6)
- 0.1.16 bump version [`8cc6765`](https://github.com/ast-grep/ast-grep/commit/8cc67654113b10ab0631d277d5c7ebd638bab807)
- feat: add `adopt` method to safely create Node [`6ea01ae`](https://github.com/ast-grep/ast-grep/commit/6ea01ae4d4f02ce6c423d76bcd7d6714bf039757)
#### [0.1.15](https://github.com/ast-grep/ast-grep/compare/0.1.14...0.1.15)
> 8 January 2023
- feat: make tree-sitter-* parsers optional dependencies [`#160`](https://github.com/ast-grep/ast-grep/issues/160)
- 0.1.15 bump version [`fa56cf1`](https://github.com/ast-grep/ast-grep/commit/fa56cf1b5730363874e12e1a2b3e6e3ad056dd98)
- refactor: use macro to abstract away repetitive lang method impl [`fb58b15`](https://github.com/ast-grep/ast-grep/commit/fb58b153f857bcf0ea088e6ea400a6c668bf89dd)
- feat: edit cargo toml [`1611af5`](https://github.com/ast-grep/ast-grep/commit/1611af5d5e4500b632659642805e2da962e0e181)
#### [0.1.14](https://github.com/ast-grep/ast-grep/compare/0.1.13...0.1.14)
> 4 January 2023
- chore(deps): update dependency @napi-rs/cli to v2.14.0 [`#149`](https://github.com/ast-grep/ast-grep/pull/149)
- fix(deps): update rust crate num_cpus to 1.15.0 [`#147`](https://github.com/ast-grep/ast-grep/pull/147)
- fix(deps): update rust crate clap to 4.0.32 [`#145`](https://github.com/ast-grep/ast-grep/pull/145)
- chore(deps): update robinraju/release-downloader action to v1.7 [`#146`](https://github.com/ast-grep/ast-grep/pull/146)
- fix(deps): update rust crate serde_json to 1.0.91 [`#136`](https://github.com/ast-grep/ast-grep/pull/136)
- fix(deps): update rust crate serde_yaml to 0.9.16 [`#137`](https://github.com/ast-grep/ast-grep/pull/137)
- fix(deps): update rust crate serde_yaml to 0.9.15 [`#116`](https://github.com/ast-grep/ast-grep/pull/116)
- feat: merge adjacent matches in match printing [`#134`](https://github.com/ast-grep/ast-grep/issues/134)
- feat: refactor printers to accept output destination [`#153`](https://github.com/ast-grep/ast-grep/issues/153)
- feat: add traversal mod for sg-core [`#152`](https://github.com/ast-grep/ast-grep/issues/152)
- [refactor] simplify Matcher trait [`#151`](https://github.com/ast-grep/ast-grep/issues/151)
- feat: move interactive printer [`#135`](https://github.com/ast-grep/ast-grep/issues/135)
- feat: implement better heading show [`#150`](https://github.com/ast-grep/ast-grep/issues/150)
- test: add test for command arg parsing [`#128`](https://github.com/ast-grep/ast-grep/issues/128)
- feat: support run only one rule for scanning [`#139`](https://github.com/ast-grep/ast-grep/issues/139)
- feat: support color control on run/scan output [`#131`](https://github.com/ast-grep/ast-grep/issues/131)
- fix: fix stackoverflow in find_node_impl [`#148`](https://github.com/ast-grep/ast-grep/issues/148)
- feat: add labels and env info to json output [`#121`](https://github.com/ast-grep/ast-grep/issues/121)
- refactor: abstract out scan as worker [`#138`](https://github.com/ast-grep/ast-grep/issues/138)
- refactor: use mpsc for non-interactive output [`#125`](https://github.com/ast-grep/ast-grep/issues/125)
- feature: extract out a language crate [`#126`](https://github.com/ast-grep/ast-grep/issues/126)
- refactor: remove some inner call [`#3`](https://github.com/ast-grep/ast-grep/issues/3)
- refactor: extract out two printers [`9328cd7`](https://github.com/ast-grep/ast-grep/commit/9328cd77cf3b4d015d51392d31c851a577cc6a11)
- feat: fix interactive print rule [`17fbaf0`](https://github.com/ast-grep/ast-grep/commit/17fbaf03c9c6ff2b69c7beeb58f3190f471cc319)
- feat: migrate run with specific lang [`ea46263`](https://github.com/ast-grep/ast-grep/commit/ea46263f482de906f8260d10e6b3f1848abf0d7d)
#### [0.1.13](https://github.com/ast-grep/ast-grep/compare/0.1.12...0.1.13)
> 18 December 2022
- fix(deps): update rust crate serde_json to 1.0.90 [`#115`](https://github.com/ast-grep/ast-grep/pull/115)
- Update dependency @napi-rs/cli to v2.13.3 [`#114`](https://github.com/ast-grep/ast-grep/pull/114)
- feat: implement proper json output [`0c031c1`](https://github.com/ast-grep/ast-grep/commit/0c031c19e2654c88ed2506ce151423fb013768e7)
- chore: add changelog [`d6fc114`](https://github.com/ast-grep/ast-grep/commit/d6fc114bf93b8a951e8eb863bf96213485e1c638)
- feat: add json output support [`f33e39f`](https://github.com/ast-grep/ast-grep/commit/f33e39f14cb7c2a1a84f5263b280b88f65516f6b)
#### [0.1.12](https://github.com/ast-grep/ast-grep/compare/0.1.11...0.1.12)
> 15 December 2022
- Update dependency @napi-rs/cli to v2.13.2 [`#103`](https://github.com/ast-grep/ast-grep/pull/103)
- Update dependency typescript to v4.9.4 [`#104`](https://github.com/ast-grep/ast-grep/pull/104)
- Update Rust crate tree-sitter-java to 0.20.0 [`#105`](https://github.com/ast-grep/ast-grep/pull/105)
- Update dependency chalk to v5.2.0 [`#106`](https://github.com/ast-grep/ast-grep/pull/106)
- feat: use better color output for readability [`#111`](https://github.com/ast-grep/ast-grep/issues/111)
- feat: support version and help argument [`#110`](https://github.com/ast-grep/ast-grep/issues/110)
- fix: skip extremely large file [`#109`](https://github.com/ast-grep/ast-grep/issues/109)
- 0.1.12 bump version [`5ea05eb`](https://github.com/ast-grep/ast-grep/commit/5ea05eb9fbea70a412b97e16256a27c99f7c9bbb)
- fix: use `get_matcher` instead of get_rule in verify [`4b4ea8e`](https://github.com/ast-grep/ast-grep/commit/4b4ea8e39327260f086052eef8a972ece68d40a0)
- fix: better large file detection [`293a5c8`](https://github.com/ast-grep/ast-grep/commit/293a5c82647f27dc9e2eac853675fbd366231c60)
#### [0.1.11](https://github.com/ast-grep/ast-grep/compare/0.1.10...0.1.11)
> 11 December 2022
- 0.1.11 bump version [`1ba3c13`](https://github.com/ast-grep/ast-grep/commit/1ba3c13eacbbfbb3648c47a1890c7152f06acdda)
- feat: add get all keys in meta var env [`8203945`](https://github.com/ast-grep/ast-grep/commit/820394587219f7e8986e42e9f3e83975007588b7)
#### [0.1.10](https://github.com/ast-grep/ast-grep/compare/0.1.9...0.1.10)
> 7 December 2022
- Update Rust crate clap to 4.0.29 [`#102`](https://github.com/ast-grep/ast-grep/pull/102)
- Update Rust crate serde_json to 1.0.89 [`#101`](https://github.com/ast-grep/ast-grep/pull/101)
- Update Rust crate clap to 4.0.28 [`#100`](https://github.com/ast-grep/ast-grep/pull/100)
- Update dependency @napi-rs/cli to v2.13.0 [`#99`](https://github.com/ast-grep/ast-grep/pull/99)
- Update dependency ava to v5.1.0 [`#97`](https://github.com/ast-grep/ast-grep/pull/97)
- Update dependency typescript to v4.9.3 [`#98`](https://github.com/ast-grep/ast-grep/pull/98)
- Update Rust crate similar to 2.2.1 [`#95`](https://github.com/ast-grep/ast-grep/pull/95)
- Update dependency @napi-rs/cli to v2.12.1 [`#92`](https://github.com/ast-grep/ast-grep/pull/92)
- Update Rust crate clap to 4.0.23 [`#91`](https://github.com/ast-grep/ast-grep/pull/91)
- chore: break down matcher to smaller files and add potential kind [`74e5e7d`](https://github.com/ast-grep/ast-grep/commit/74e5e7db86dd06da765d7a31a41e88ba0b1372c1)
- feat: improve contingent rule handling [`9c500af`](https://github.com/ast-grep/ast-grep/commit/9c500af602b31dd8c5db84207aa2296a3fa77789)
- feat: add augmented rules [`c7dc0fe`](https://github.com/ast-grep/ast-grep/commit/c7dc0fef2459bf32b0c377e5cc2f4fa51dd54222)
#### [0.1.9](https://github.com/ast-grep/ast-grep/compare/0.1.8...0.1.9)
> 5 November 2022
- Update Rust crate regex to 1.7.0 [`#88`](https://github.com/ast-grep/ast-grep/pull/88)
- feat: add contingent rules with tests [`#82`](https://github.com/ast-grep/ast-grep/pull/82)
- Update Rust crate clap to 4.0.19 [`#83`](https://github.com/ast-grep/ast-grep/pull/83)
- Update Rust crate tree-sitter-lua to 0.0.14 [`#84`](https://github.com/ast-grep/ast-grep/pull/84)
- Update Rust crate num_cpus to 1.14.0 [`#86`](https://github.com/ast-grep/ast-grep/pull/86)
- Update Rust crate tree-sitter-rust to 0.20.3 [`#85`](https://github.com/ast-grep/ast-grep/pull/85)
- 0.1.9 bump version [`0ddb562`](https://github.com/ast-grep/ast-grep/commit/0ddb562f0348130d0fb7498259397afa4c96c85c)
#### [0.1.8](https://github.com/ast-grep/ast-grep/compare/0.1.7...0.1.8)
> 31 October 2022
- Update robinraju/release-downloader action to v1.6 [`#81`](https://github.com/ast-grep/ast-grep/pull/81)
- refactor: move relational rule outside [`4c5321d`](https://github.com/ast-grep/ast-grep/commit/4c5321d61a5f3bc45aad238febe6318ab66f4a7d)
- test: add test for inside/has [`428dd62`](https://github.com/ast-grep/ast-grep/commit/428dd62c3f1b5b789bc4a827f219511e07ac6451)
- fix: fix precedes/follows operator [`020bb3c`](https://github.com/ast-grep/ast-grep/commit/020bb3c5d5de5e8ab4e8453c3ccba65a1d68f39d)
#### [0.1.7](https://github.com/ast-grep/ast-grep/compare/0.1.6...0.1.7)
> 27 October 2022
- Update dependency ava to v5 [`#77`](https://github.com/ast-grep/ast-grep/pull/77)
- Update Rust crate serde_yaml to 0.9.14 [`#76`](https://github.com/ast-grep/ast-grep/pull/76)
- Update Rust crate clap to 4.0.18 [`#74`](https://github.com/ast-grep/ast-grep/pull/74)
- Update Rust crate serde_json to 1.0.87 [`#75`](https://github.com/ast-grep/ast-grep/pull/75)
- Update dependency chalk to v5.1.2 [`#73`](https://github.com/ast-grep/ast-grep/pull/73)
- Update dependency @napi-rs/cli to v2.12.0 [`#66`](https://github.com/ast-grep/ast-grep/pull/66)
- Update Rust crate serde_json to 1.0.86 [`#70`](https://github.com/ast-grep/ast-grep/pull/70)
- Update Rust crate tree-sitter-swift to 0.3.4 [`#69`](https://github.com/ast-grep/ast-grep/pull/69)
- Update dependency chalk to v5.1.0 [`#67`](https://github.com/ast-grep/ast-grep/pull/67)
- Revert "Update dependency @swc-node/register to v1.5.2 (#62)" [`#64`](https://github.com/ast-grep/ast-grep/pull/64)
- 0.1.6 fix napi [`d2501ed`](https://github.com/ast-grep/ast-grep/commit/d2501ed82f6dc8a82ff241c1f2bb1de2e15b8085)
- [feat] refine test command [`3802816`](https://github.com/ast-grep/ast-grep/commit/38028162f6c1887f5ac9a68e6620ecf2615aafd8)
- [feat] add test command [`e45039e`](https://github.com/ast-grep/ast-grep/commit/e45039eafe423fd58fefde23b3c5c8f80c8de508)
#### [0.1.6](https://github.com/ast-grep/ast-grep/compare/0.1.5...0.1.6)
> 3 October 2022
- Update dependency typescript to v4.8.4 [`#63`](https://github.com/ast-grep/ast-grep/pull/63)
- Update dependency @swc-node/register to v1.5.2 [`#62`](https://github.com/ast-grep/ast-grep/pull/62)
- fix(52): normalize file path output in command line [`#55`](https://github.com/ast-grep/ast-grep/pull/55)
- [docs] add interactive argument explanation [`#59`](https://github.com/ast-grep/ast-grep/issues/59)
- [chore] migrate github pages [`cc38e5a`](https://github.com/ast-grep/ast-grep/commit/cc38e5adfb139b18c99092b6dd113e3c97e7992a)
- [feat] better error reporting [`8e94d1e`](https://github.com/ast-grep/ast-grep/commit/8e94d1e5f3f6570836877f0c46b0a4edfdcd0538)
- [feat] add lsp fixing [`9f3754b`](https://github.com/ast-grep/ast-grep/commit/9f3754bed4c916aaf0e1f60fa42fa1fffd70503e)
#### [0.1.5](https://github.com/ast-grep/ast-grep/compare/0.1.4...0.1.5)
> 21 September 2022
- [feat] more thorough napi support [`6b2bfe0`](https://github.com/ast-grep/ast-grep/commit/6b2bfe0f7974b7bab46544d3d964799bb4274073)
- 0.1.4 remove optional dep [`84b0e59`](https://github.com/ast-grep/ast-grep/commit/84b0e596ce3363627c230f9849fe067e77e810f6)
- [feat] add support for rule matching in napi [`78dbe99`](https://github.com/ast-grep/ast-grep/commit/78dbe991b9c40320be0bef0c29c45dce36554198)
#### [0.1.4](https://github.com/ast-grep/ast-grep/compare/0.1.3...0.1.4)
> 19 September 2022
- [chore] remove unsupported packages [`dba569b`](https://github.com/ast-grep/ast-grep/commit/dba569b7335844d2c90892a63cd521248125423c)
- 0.1.4 fix napi package [`233be67`](https://github.com/ast-grep/ast-grep/commit/233be6780b1beaadbec33dd1fa3ec226921509c0)
#### [0.1.3](https://github.com/ast-grep/ast-grep/compare/0.1.2...0.1.3)
> 19 September 2022
- Update dependency vite to v3.1.2 [`#46`](https://github.com/ast-grep/ast-grep/pull/46)
- Update dependency @algolia/client-search to v4.14.2 [`#47`](https://github.com/ast-grep/ast-grep/pull/47)
- Update Rust crate tree-sitter-lua to 0.0.13 [`#44`](https://github.com/ast-grep/ast-grep/pull/44)
- Update Rust crate tree-sitter-swift to 0.3.3 [`#45`](https://github.com/ast-grep/ast-grep/pull/45)
- Update Rust crate clap to 3.2.22 [`#42`](https://github.com/ast-grep/ast-grep/pull/42)
- Pin dependencies [`#41`](https://github.com/ast-grep/ast-grep/pull/41)
- Update Rust crate serde_yaml to 0.9.13 [`#43`](https://github.com/ast-grep/ast-grep/pull/43)
- [fix] reimplement language `Python` to override `expando_char` [`#40`](https://github.com/ast-grep/ast-grep/pull/40)
- Update dependency vue to v3.2.39 [`#34`](https://github.com/ast-grep/ast-grep/pull/34)
- Update dependency vue-tsc to v0.40.13 [`#35`](https://github.com/ast-grep/ast-grep/pull/35)
- Update dependency @vitejs/plugin-vue to v3.1.0 [`#36`](https://github.com/ast-grep/ast-grep/pull/36)
- Update dependency vite to v3.1.0 [`#37`](https://github.com/ast-grep/ast-grep/pull/37)
- Update dependency detect-libc to v2 [`#39`](https://github.com/ast-grep/ast-grep/pull/39)
- Update actions/checkout action to v3 [`#38`](https://github.com/ast-grep/ast-grep/pull/38)
- Update dependency typescript to v4.8.3 [`#33`](https://github.com/ast-grep/ast-grep/pull/33)
- Pin dependency detect-libc to v1.0.3 [`#32`](https://github.com/ast-grep/ast-grep/pull/32)
- [feat] use vitepress to host docs [`ad09d42`](https://github.com/ast-grep/ast-grep/commit/ad09d428e3cd1ea1bb6174bede68120c41ef9f3d)
- [feat] add YAML config to playground [`e54b244`](https://github.com/ast-grep/ast-grep/commit/e54b244db6409aa7fd58f46f3be1229d9a8ad1fd)
- [feat] improve styling [`e8d4f0d`](https://github.com/ast-grep/ast-grep/commit/e8d4f0d6a880465663adf6b873b80c1a44d8ff59)
#### [0.1.2](https://github.com/ast-grep/ast-grep/compare/0.1.1...0.1.2)
> 6 September 2022
- Update dependency vue-tsc to v0.40.6 [`#29`](https://github.com/ast-grep/ast-grep/pull/29)
- Update dependency web-tree-sitter to v0.20.7 [`#28`](https://github.com/ast-grep/ast-grep/pull/28)
- Update Rust crate clap to 3.2.20 [`#27`](https://github.com/ast-grep/ast-grep/pull/27)
- Update Rust crate serde_yaml to 0.9.11 [`#18`](https://github.com/ast-grep/ast-grep/pull/18)
- Update dependency vue-tsc to v0.40.5 [`#26`](https://github.com/ast-grep/ast-grep/pull/26)
- Update Rust crate clap to 3.2.19 [`#24`](https://github.com/ast-grep/ast-grep/pull/24)
- Update Rust crate dashmap to 5.4.0 [`#25`](https://github.com/ast-grep/ast-grep/pull/25)
- Pin dependencies [`#20`](https://github.com/ast-grep/ast-grep/pull/20)
- Update dependency vue to v3.2.38 [`#23`](https://github.com/ast-grep/ast-grep/pull/23)
- Update Rust crate tree-sitter-swift to 0.3.2 [`#16`](https://github.com/ast-grep/ast-grep/pull/16)
- Update Rust crate similar to 2.2.0 [`#19`](https://github.com/ast-grep/ast-grep/pull/19)
- Update Rust crate tree-sitter-lua to 0.0.12 [`#14`](https://github.com/ast-grep/ast-grep/pull/14)
- Update Rust crate tree-sitter-swift to 0.3.1 [`#15`](https://github.com/ast-grep/ast-grep/pull/15)
- Update Rust crate clap to 3.2.17 [`#10`](https://github.com/ast-grep/ast-grep/pull/10)
- Update Rust crate tree-sitter-c to 0.20.2 [`#13`](https://github.com/ast-grep/ast-grep/pull/13)
- Update Rust crate console_error_panic_hook to 0.1.7 [`#12`](https://github.com/ast-grep/ast-grep/pull/12)
- Pin dependencies [`#9`](https://github.com/ast-grep/ast-grep/pull/9)
- Configure Renovate [`#1`](https://github.com/ast-grep/ast-grep/pull/1)
- [fix] Add Rust exmaple test, fix #6 [`#6`](https://github.com/ast-grep/ast-grep/issues/6)
- [feat] add napi support [`532f055`](https://github.com/ast-grep/ast-grep/commit/532f0557a6fb32f790e143f901a69cd73055ebb2)
- [feat]: add csharp [`9a14a4b`](https://github.com/ast-grep/ast-grep/commit/9a14a4b71cc348274ad6d153fc9011aedf9b490e)
- [fix] update napi [`ae6d5f1`](https://github.com/ast-grep/ast-grep/commit/ae6d5f161fd51692fe12c03ee5553cf6e077ec67)
#### [0.1.1](https://github.com/ast-grep/ast-grep/compare/v0.1.0...0.1.1)
> 23 August 2022
#### v0.1.0
> 5 September 2022
- Update dependency vue-tsc to v0.40.6 [`#29`](https://github.com/ast-grep/ast-grep/pull/29)
- Update dependency web-tree-sitter to v0.20.7 [`#28`](https://github.com/ast-grep/ast-grep/pull/28)
- Update Rust crate clap to 3.2.20 [`#27`](https://github.com/ast-grep/ast-grep/pull/27)
- Update Rust crate serde_yaml to 0.9.11 [`#18`](https://github.com/ast-grep/ast-grep/pull/18)
- Update dependency vue-tsc to v0.40.5 [`#26`](https://github.com/ast-grep/ast-grep/pull/26)
- Update Rust crate clap to 3.2.19 [`#24`](https://github.com/ast-grep/ast-grep/pull/24)
- Update Rust crate dashmap to 5.4.0 [`#25`](https://github.com/ast-grep/ast-grep/pull/25)
- Pin dependencies [`#20`](https://github.com/ast-grep/ast-grep/pull/20)
- Update dependency vue to v3.2.38 [`#23`](https://github.com/ast-grep/ast-grep/pull/23)
- Update Rust crate tree-sitter-swift to 0.3.2 [`#16`](https://github.com/ast-grep/ast-grep/pull/16)
- Update Rust crate similar to 2.2.0 [`#19`](https://github.com/ast-grep/ast-grep/pull/19)
- Update Rust crate tree-sitter-lua to 0.0.12 [`#14`](https://github.com/ast-grep/ast-grep/pull/14)
- Update Rust crate tree-sitter-swift to 0.3.1 [`#15`](https://github.com/ast-grep/ast-grep/pull/15)
- Update Rust crate clap to 3.2.17 [`#10`](https://github.com/ast-grep/ast-grep/pull/10)
- Update Rust crate tree-sitter-c to 0.20.2 [`#13`](https://github.com/ast-grep/ast-grep/pull/13)
- Update Rust crate console_error_panic_hook to 0.1.7 [`#12`](https://github.com/ast-grep/ast-grep/pull/12)
- Pin dependencies [`#9`](https://github.com/ast-grep/ast-grep/pull/9)
- Configure Renovate [`#1`](https://github.com/ast-grep/ast-grep/pull/1)
- fix(playground): color in dark mode [`#2`](https://github.com/ast-grep/ast-grep/pull/2)
- [fix] Add Rust exmaple test, fix #6 [`#6`](https://github.com/ast-grep/ast-grep/issues/6)
- [feat] add napi support [`532f055`](https://github.com/ast-grep/ast-grep/commit/532f0557a6fb32f790e143f901a69cd73055ebb2)
- initial commit [`94db982`](https://github.com/ast-grep/ast-grep/commit/94db982f174c493fec6e27f306fb23e9980d22ff)
- [feat] initial wasm research [`041cc64`](https://github.com/ast-grep/ast-grep/commit/041cc6405e06369d4ce69d1e491f90447d72f296)
## /Cargo.toml
```toml path="/Cargo.toml"
[workspace]
members = [
"crates/*",
"xtask"
]
default-members = ["crates/*"]
resolver = "2"
[profile.release]
lto = true
[workspace.package]
version = "0.39.9"
authors = ["Herrington Darkholme <2883231+HerringtonDarkholme@users.noreply.github.com>"]
edition = "2021"
license = "MIT"
documentation = "https://ast-grep.github.io/guide/introduction.html"
homepage = "https://ast-grep.github.io/"
repository = "https://github.com/ast-grep/ast-grep"
rust-version = "1.79"
readme = "README.md"
[workspace.dependencies]
ast-grep-core = { path = "crates/core", version = "0.39.9", default-features = false }
ast-grep-config = { path = "crates/config", version = "0.39.9" }
ast-grep-dynamic = { path = "crates/dynamic", version = "0.39.9" }
ast-grep-language = { path = "crates/language", version = "0.39.9" }
ast-grep-lsp = { path = "crates/lsp", version = "0.39.9" }
bit-set = { version = "0.8.0" }
ignore = { version = "0.4.22" }
regex = { version = "1.10.4" }
serde = { version = "1.0.200", features = ["derive"] }
serde_yaml = "0.9.33"
tree-sitter = { version = "0.25.4" }
thiserror = "2.0.0"
schemars = "1.0.0"
anyhow = "1.0.82"
dashmap = "6.0.0"
```
## /README.md
<p align=center>
<img src="https://ast-grep.github.io/logo.svg" alt="ast-grep"/>
</p>
<p align="center">
<img src="https://github.com/ast-grep/ast-grep/actions/workflows/coverage.yaml/badge.svg" alt="coverage badge"/>
<a href="https://app.codecov.io/gh/ast-grep/ast-grep"><img src="https://codecov.io/gh/ast-grep/ast-grep/branch/main/graph/badge.svg?token=37VX8H2EWV"/></a>
<a href="https://discord.gg/4YZjf6htSQ" target="_blank"><img alt="Discord" src="https://img.shields.io/discord/1107749847722889217?label=Discord"></a>
<a href="https://repology.org/project/ast-grep/versions" target="_blank"><img alt="Repology" src="https://repology.org/badge/tiny-repos/ast-grep.svg"></a>
<img src="https://img.shields.io/github/stars/ast-grep/ast-grep?style=social" alt="Badge"/>
<img src="https://img.shields.io/github/forks/ast-grep/ast-grep?style=social" alt="Badge"/>
<img alt="GitHub Sponsors" src="https://img.shields.io/github/sponsors/HerringtonDarkholme?style=social">
<a href="https://gurubase.io/g/ast-grep"><img alt="Gurubase" src="https://img.shields.io/badge/Gurubase-Ask%20ast--grep%20Guru-006BFF"></a>
</p>
## ast-grep(sg)
ast-grep(sg) is a CLI tool for code structural search, lint, and rewriting.
## Introduction
ast-grep is an [abstract syntax tree](https://dev.to/balapriya/abstract-syntax-tree-ast-explained-in-plain-english-1h38) based tool to search code by pattern code. Think of it as your old-friend [`grep`](https://en.wikipedia.org/wiki/Grep#:~:text=grep%20is%20a%20command%2Dline,which%20has%20the%20same%20effect.), but matching AST nodes instead of text.
You can write patterns as if you are writing ordinary code. It will match all code that has the same syntactical structure.
You can use `$` sign + upper case letters as a [wildcard](https://en.wikipedia.org/wiki/Wildcard_character), e.g. `$MATCH`, to match any single AST node. Think of it as [regular expression dot](https://regexone.com/lesson/wildcards_dot) `.`, except it is not textual.
Try the [online playground](https://ast-grep.github.io/playground.html) for a taste!
## Screenshot

See more screenshots on the [website](https://ast-grep.github.io/).
## Installation
You can install it from [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm), [pip](https://pypi.org/), [cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html), [cargo-binstall](https://github.com/cargo-bins/cargo-binstall), [homebrew](https://brew.sh/), [scoop](https://scoop.sh/) or [MacPorts](https://www.macports.org)!
```bash
npm install --global @ast-grep/cli
pip install ast-grep-cli
brew install ast-grep
```
<details>
<summary>Click for more installation methods</summary>
```bash
cargo install ast-grep --locked
cargo binstall ast-grep
# install via scoop, thank @brian6932
scoop install main/ast-grep
# install via MacPorts
sudo port install ast-grep
# try ast-grep in nix-shell
nix-shell -p ast-grep
```
</details>
Or you can build ast-grep from source. You need to install rustup, clone the repository and then
```bash
cargo install --path ./crates/cli --locked
```
[Packages](https://repology.org/project/ast-grep/versions) are available on other platforms too.
## Command line usage example
ast-grep has following form.
```
ast-grep --pattern 'var code = $PATTERN' --rewrite 'let code = new $PATTERN' --lang ts
```
### Example
* [Rewrite code in null coalescing operator](https://twitter.com/Hchan_mgn/status/1547061516993699841?s=20&t=ldDoj4U2nq-FRKQkU5GWXA)
```bash
ast-grep -p '$A && $A()' -l ts -r '$A?.()'
```
* [Rewrite](https://twitter.com/Hchan_mgn/status/1561802312846278657) [Zodios](https://github.com/ecyrbe/zodios#migrate-to-v8)
```bash
ast-grep -p 'new Zodios($URL, $CONF as const,)' -l ts -r 'new Zodios($URL, $CONF)' -i
```
* [Implement eslint rule using YAML.](https://twitter.com/Hchan_mgn/status/1560108625460355073)
## Sponsor

If you find ast-grep interesting and useful for your work, please [buy me a coffee](https://github.com/sponsors/HerringtonDarkholme)
so I can spend more time on the project!
## Feature Highlight
ast-grep's core is an algorithm to search and replace code based on abstract syntax tree produced by tree-sitter.
It can help you to do lightweight static analysis and massive scale code manipulation in an intuitive way.
Key highlights:
* An intuitive pattern to find and replace AST.
ast-grep's pattern looks like ordinary code you would write every day (you could say the pattern is isomorphic to code).
* jQuery like API for AST traversal and manipulation.
* YAML configuration to write new linting rules or code modification.
* Written in compiled language, with tree-sitter based parsing and utilizing multiple cores.
* Beautiful command line interface :)
ast-grep's vision is to democratize abstract syntax tree magic and to liberate one from cumbersome AST programming!
* If you are an open-source library author, ast-grep can help your library users adopt breaking changes more easily.
* if you are a tech lead in your team, ast-grep can help you enforce code best practice tailored to your business need.
* If you are a security researcher, ast-grep can help you write rules much faster.
## /clippy.toml
```toml path="/clippy.toml"
ignore-interior-mutability = ["fluent_uri::Uri"]
```
## /crates/cli/Cargo.toml
```toml path="/crates/cli/Cargo.toml"
[package]
name = "ast-grep"
description = "Search and Rewrite code at large scale using precise AST pattern"
keywords = ["ast", "pattern", "codemod", "search", "rewrite"]
categories = ["command-line-utilities", "development-tools", "parsing"]
default-run = "ast-grep"
# use relative path because maturin does not recognize
readme = "../../README.md"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
rust-version.workspace = true
[[bin]]
name = "ast-grep"
path = "src/main.rs"
[[bin]]
name = "sg"
path = "src/bin/alias.rs"
[dependencies]
ast-grep-core.workspace = true
ast-grep-config.workspace = true
ast-grep-dynamic.workspace = true
ast-grep-language.workspace = true
ast-grep-lsp.workspace = true
tree-sitter.workspace = true
ansi_term = "0.12.1"
anyhow.workspace = true
atty = "0.2.14"
clap = { version = "4.5.4", features = ["derive"] }
codespan-reporting = "0.13.0"
crossterm = "0.29.0"
ignore.workspace = true
regex.workspace = true
dashmap.workspace = true
termimad = "0.34.0"
terminal-light = "1.8.0"
inquire = "0.9.0"
serde.workspace = true
serde_json = "1.0.116"
serde_yaml.workspace = true
similar = { version = "2.5.0", features = ["inline"] }
smallvec = "1.13.2"
tokio = { version = "1.37.0", features = ["rt-multi-thread", "io-std"] }
clap_complete = "4.5.2"
[dev-dependencies]
assert_cmd = "2.0.14"
predicates = "3.1.0"
tempfile = "3.10.1"
# Support cargo bininstall, https://github.com/ast-grep/ast-grep/issues/1742
[package.metadata.binstall]
pkg-url = "{ repo }/releases/download/{ version }/app-{ target }{ archive-suffix }"
pkg-fmt = "zip"
bin-dir = "{ bin }{ binary-ext }"
disabled-strategies = ["quick-install"]
```
## /crates/cli/src/bin/alias.rs
```rs path="/crates/cli/src/bin/alias.rs"
// The alias command `sg` redirects everything to ast-grep
// we need this to avoid "multiple build target" warning
// See https://github.com/rust-lang/cargo/issues/5930
fn main() -> std::io::Result<()> {
// redirect to ast-grep
use std::env::args;
use std::process::{Command, Stdio};
let mut child = Command::new("ast-grep")
.args(args().skip(1))
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?;
let status = child.wait()?;
std::process::exit(status.code().unwrap_or(1))
}
```
## /crates/cli/src/completions.rs
```rs path="/crates/cli/src/completions.rs"
//! How to use generate shell completions.
//! Usage with bash:
//! \`\`\`console
//! sg completions > ast_grep.bash
//! $ ./ast_grep.bash
//! $ sg <TAB>
//! $ sg run --<TAB>
//! \`\`\`
//! Usage with zsh, the completion scripts have to be in a path that belongs to `$fpath`:
//! \`\`\`console
//! $ sg completions zsh > $HOME/.zsh/completions/_ast_grep
//! $ echo "fpath=($HOME/.zsh/completions $fpath)" >> ~/.zshrc
//! $ compinit
//! $ sg <TAB>
//! $ sg run --<TAB>
//! \`\`\`
//! Usage with fish:
//! \`\`\`console
//! $ sg completions fish > ast_grep.fish
//! $ ./ast_grep.fish
//! $ sg <TAB>
//! $ sg run --<TAB>
//! \`\`\`
use anyhow::Result;
use clap::{CommandFactory, Parser};
use clap_complete::{generate, Shell};
use crate::utils::ErrorContext as EC;
use std::env;
use std::io;
use std::path::Path;
#[derive(Parser)]
pub struct CompletionsArg {
/// Output the completion file for given shell.
/// If not provided, shell flavor will be inferred from environment.
#[arg(value_enum)]
shell: Option<Shell>,
}
pub fn run_shell_completion<C: CommandFactory>(arg: CompletionsArg) -> Result<()> {
run_shell_completion_impl::<C, _>(arg, &mut io::stdout())
}
fn run_shell_completion_impl<C: CommandFactory, W: io::Write>(
arg: CompletionsArg,
output: &mut W,
) -> Result<()> {
let Some(shell) = arg.shell.or_else(Shell::from_env) else {
return Err(anyhow::anyhow!(EC::CannotInferShell));
};
let mut cmd = C::command();
let cmd_name = match get_bin_name() {
Some(cmd) => cmd,
None => cmd.get_name().to_string(),
};
generate(shell, &mut cmd, cmd_name, output);
Ok(())
}
// https://github.com/clap-rs/clap/blob/063b1536289f72369bcd59d61449d355aa3a1d6b/clap_builder/src/builder/command.rs#L781
fn get_bin_name() -> Option<String> {
let bin_path = env::args().next()?;
let p = Path::new(&bin_path);
let name = p.file_name()?;
Some(name.to_str()?.to_string())
}
#[cfg(test)]
mod test {
use super::*;
use crate::App;
#[test]
fn test_generate_command() {
let mut output = vec![];
let arg = CompletionsArg {
shell: Some(Shell::Bash),
};
run_shell_completion_impl::<App, _>(arg, &mut output).expect("should succeed");
let output = String::from_utf8(output).expect("should be valid");
assert!(output.contains("ast_grep"));
}
}
```
## /crates/cli/src/config.rs
```rs path="/crates/cli/src/config.rs"
use crate::lang::{CustomLang, LanguageGlobs, SerializableInjection, SgLang};
use crate::utils::{ErrorContext as EC, RuleOverwrite, RuleTrace};
use anyhow::{Context, Result};
use ast_grep_config::{
from_str, from_yaml_string, DeserializeEnv, GlobalRules, RuleCollection, RuleConfig,
};
use ast_grep_language::config_file_type;
use ignore::WalkBuilder;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::read_to_string;
use std::path::{Path, PathBuf};
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct TestConfig {
pub test_dir: PathBuf,
/// Specify the directory containing snapshots. The path is relative to `test_dir`
#[serde(skip_serializing_if = "Option::is_none")]
pub snapshot_dir: Option<PathBuf>,
}
impl From<PathBuf> for TestConfig {
fn from(path: PathBuf) -> Self {
TestConfig {
test_dir: path,
snapshot_dir: None,
}
}
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct AstGrepConfig {
/// YAML rule directories
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub rule_dirs: Vec<PathBuf>,
/// test configurations
#[serde(skip_serializing_if = "Option::is_none")]
pub test_configs: Option<Vec<TestConfig>>,
/// util rules directories
#[serde(skip_serializing_if = "Option::is_none")]
pub util_dirs: Option<Vec<PathBuf>>,
/// configuration for custom languages
#[serde(skip_serializing_if = "Option::is_none")]
pub custom_languages: Option<HashMap<String, CustomLang>>,
/// additional file globs for languages
#[serde(skip_serializing_if = "Option::is_none")]
pub language_globs: Option<LanguageGlobs>,
/// injection config for embedded languages
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub language_injections: Vec<SerializableInjection>,
}
#[derive(Clone)]
pub struct ProjectConfig {
pub project_dir: PathBuf,
/// YAML rule directories
pub rule_dirs: Vec<PathBuf>,
/// test configurations
pub test_configs: Option<Vec<TestConfig>>,
/// util rules directories
pub util_dirs: Option<Vec<PathBuf>>,
}
impl ProjectConfig {
// return None if config file does not exist
fn discover_project(config_path: Option<PathBuf>) -> Result<Option<(PathBuf, AstGrepConfig)>> {
let config_path = find_config_path_with_default(config_path).context(EC::ProjectNotExist)?;
// NOTE: if config file does not exist, return None
let Some(config_path) = config_path else {
return Ok(None);
};
let config_str = read_to_string(&config_path).context(EC::ReadConfiguration)?;
let sg_config: AstGrepConfig = from_str(&config_str).context(EC::ParseConfiguration)?;
let project_dir = config_path
.parent()
.expect("config file must have parent directory")
.to_path_buf();
Ok(Some((project_dir, sg_config)))
}
pub fn find_rules(
&self,
rule_overwrite: RuleOverwrite,
) -> Result<(RuleCollection<SgLang>, RuleTrace)> {
let global_rules = find_util_rules(self)?;
read_directory_yaml(self, global_rules, rule_overwrite)
}
/// returns a Result of Result.
/// The inner Result is for configuration not found, or ProjectNotExist
/// The outer Result is for definitely wrong config.
pub fn setup(config_path: Option<PathBuf>) -> Result<Result<Self>> {
let Some((project_dir, mut sg_config)) = Self::discover_project(config_path)? else {
return Ok(Err(anyhow::anyhow!(EC::ProjectNotExist)));
};
let config = ProjectConfig {
project_dir,
rule_dirs: sg_config.rule_dirs.drain(..).collect(),
test_configs: sg_config.test_configs.take(),
util_dirs: sg_config.util_dirs.take(),
};
// sg_config will not use rule dirs and test configs anymore
register_custom_language(&config.project_dir, sg_config)?;
Ok(Ok(config))
}
}
fn register_custom_language(project_dir: &Path, sg_config: AstGrepConfig) -> Result<()> {
if let Some(custom_langs) = sg_config.custom_languages {
SgLang::register_custom_language(project_dir, custom_langs)?;
}
if let Some(globs) = sg_config.language_globs {
SgLang::register_globs(globs)?;
}
SgLang::register_injections(sg_config.language_injections)?;
Ok(())
}
fn build_util_walker(base_dir: &Path, util_dirs: &Option<Vec<PathBuf>>) -> Option<WalkBuilder> {
let mut util_dirs = util_dirs.as_ref()?.iter();
let first = util_dirs.next()?;
let mut walker = WalkBuilder::new(base_dir.join(first));
for dir in util_dirs {
walker.add(base_dir.join(dir));
}
Some(walker)
}
fn find_util_rules(config: &ProjectConfig) -> Result<GlobalRules> {
let ProjectConfig {
project_dir,
util_dirs,
..
} = config;
let Some(mut walker) = build_util_walker(project_dir, util_dirs) else {
return Ok(GlobalRules::default());
};
let mut utils = vec![];
let walker = walker.types(config_file_type()).build();
for dir in walker {
let config_file = dir.with_context(|| EC::WalkRuleDir(PathBuf::new()))?;
// file_type is None only if it is stdin, safe to panic here
if !config_file
.file_type()
.expect("file type should be available for non-stdin")
.is_file()
{
continue;
}
let path = config_file.path();
let file = read_to_string(path)?;
let new_configs = from_str(&file)?;
utils.push(new_configs);
}
let ret = DeserializeEnv::<SgLang>::parse_global_utils(utils).context(EC::InvalidGlobalUtils)?;
Ok(ret)
}
fn read_directory_yaml(
config: &ProjectConfig,
global_rules: GlobalRules,
rule_overwrite: RuleOverwrite,
) -> Result<(RuleCollection<SgLang>, RuleTrace)> {
let mut configs = vec![];
let ProjectConfig {
project_dir,
rule_dirs,
..
} = config;
for dir in rule_dirs {
let dir_path = project_dir.join(dir);
let walker = WalkBuilder::new(&dir_path)
.types(config_file_type())
.build();
for dir in walker {
let config_file = dir.with_context(|| EC::WalkRuleDir(dir_path.clone()))?;
// file_type is None only if it is stdin, safe to panic here
if !config_file
.file_type()
.expect("file type should be available for non-stdin")
.is_file()
{
continue;
}
let path = config_file.path();
let new_configs = read_rule_file(path, Some(&global_rules))?;
configs.extend(new_configs);
}
}
let total_rule_count = configs.len();
let configs = rule_overwrite.process_configs(configs)?;
let collection = RuleCollection::try_new(configs).context(EC::GlobPattern)?;
let effective_rule_count = collection.total_rule_count();
let trace = RuleTrace {
file_trace: Default::default(),
effective_rule_count,
skipped_rule_count: total_rule_count - effective_rule_count,
};
Ok((collection, trace))
}
pub fn with_rule_stats(
configs: Vec<RuleConfig<SgLang>>,
) -> Result<(RuleCollection<SgLang>, RuleTrace)> {
let total_rule_count = configs.len();
let collection = RuleCollection::try_new(configs).context(EC::GlobPattern)?;
let effective_rule_count = collection.total_rule_count();
let trace = RuleTrace {
file_trace: Default::default(),
effective_rule_count,
skipped_rule_count: total_rule_count - effective_rule_count,
};
Ok((collection, trace))
}
pub fn read_rule_file(
path: &Path,
global_rules: Option<&GlobalRules>,
) -> Result<Vec<RuleConfig<SgLang>>> {
let yaml = read_to_string(path).with_context(|| EC::ReadRule(path.to_path_buf()))?;
let parsed = if let Some(globals) = global_rules {
from_yaml_string(&yaml, globals)
} else {
from_yaml_string(&yaml, &Default::default())
};
parsed.with_context(|| EC::ParseRule(path.to_path_buf()))
}
const CONFIG_FILE: &str = "sgconfig.yml";
/// return None if config file does not exist
fn find_config_path_with_default(config_path: Option<PathBuf>) -> Result<Option<PathBuf>> {
if config_path.is_some() {
return Ok(config_path);
}
let mut path = std::env::current_dir()?;
loop {
let maybe_config = path.join(CONFIG_FILE);
if maybe_config.exists() {
break Ok(Some(maybe_config));
}
if let Some(parent) = path.parent() {
path = parent.to_path_buf();
} else {
break Ok(None);
}
}
}
```
## /crates/cli/src/lang/injection.rs
```rs path="/crates/cli/src/lang/injection.rs"
use super::SgLang;
use crate::utils::ErrorContext as EC;
use ast_grep_config::{DeserializeEnv, RuleCore, SerializableRuleCore};
use ast_grep_core::{
tree_sitter::{LanguageExt, StrDoc, TSRange},
Doc, Node,
};
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::ptr::{addr_of, addr_of_mut};
use std::str::FromStr;
// NB, you should not use SgLang in the (de_serialize interface
// since Injected is used before lang registration in sgconfig.yml
#[derive(Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum Injected {
Static(String),
Dynamic(Vec<String>),
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct SerializableInjection {
#[serde(flatten)]
core: SerializableRuleCore,
/// The host language, e.g. html, contains other languages
host_language: String,
/// Injected language according to the rule
/// It accepts either a string like js for single static language.
/// or an array of string like [js, ts] for dynamic language detection.
injected: Injected,
}
struct Injection {
host: SgLang,
rules: Vec<(RuleCore, Option<String>)>,
injectable: HashSet<String>,
}
impl Injection {
fn new(lang: SgLang) -> Self {
Self {
host: lang,
rules: vec![],
injectable: Default::default(),
}
}
}
pub unsafe fn register_injetables(injections: Vec<SerializableInjection>) -> Result<()> {
let mut injectable = HashMap::new();
for injection in injections {
register_injetable(injection, &mut injectable)?;
}
merge_default_injecatable(&mut injectable);
*addr_of_mut!(LANG_INJECTIONS) = injectable.into_values().collect();
let injects = unsafe { &*addr_of!(LANG_INJECTIONS) as &'static Vec<Injection> };
*addr_of_mut!(INJECTABLE_LANGS) = injects
.iter()
.map(|inj| {
(
inj.host,
inj.injectable.iter().map(|s| s.as_str()).collect(),
)
})
.collect();
Ok(())
}
fn merge_default_injecatable(ret: &mut HashMap<SgLang, Injection>) {
for (lang, injection) in ret {
let langs = match lang {
SgLang::Builtin(b) => b.injectable_languages(),
SgLang::Custom(c) => c.injectable_languages(),
};
let Some(langs) = langs else {
continue;
};
injection
.injectable
.extend(langs.iter().map(|s| s.to_string()));
}
}
fn register_injetable(
injection: SerializableInjection,
injectable: &mut HashMap<SgLang, Injection>,
) -> Result<()> {
let lang = SgLang::from_str(&injection.host_language)?;
let env = DeserializeEnv::new(lang);
let rule = injection.core.get_matcher(env).context(EC::LangInjection)?;
let default_lang = match &injection.injected {
Injected::Static(s) => Some(s.clone()),
Injected::Dynamic(_) => None,
};
let entry = injectable
.entry(lang)
.or_insert_with(|| Injection::new(lang));
match injection.injected {
Injected::Static(s) => {
entry.injectable.insert(s);
}
Injected::Dynamic(v) => entry.injectable.extend(v),
}
entry.rules.push((rule, default_lang));
Ok(())
}
static mut LANG_INJECTIONS: Vec<Injection> = vec![];
static mut INJECTABLE_LANGS: Vec<(SgLang, Vec<&'static str>)> = vec![];
pub fn injectable_languages(lang: SgLang) -> Option<&'static [&'static str]> {
// NB: custom injection and builtin injections are resolved in INJECTABLE_LANGS
let injections =
unsafe { &*addr_of!(INJECTABLE_LANGS) as &'static Vec<(SgLang, Vec<&'static str>)> };
let Some(injection) = injections.iter().find(|i| i.0 == lang) else {
return match lang {
SgLang::Builtin(b) => b.injectable_languages(),
SgLang::Custom(c) => c.injectable_languages(),
};
};
Some(&injection.1)
}
pub fn extract_injections<L: LanguageExt>(
lang: &SgLang,
root: Node<StrDoc<L>>,
) -> HashMap<String, Vec<TSRange>> {
let mut ret = match lang {
SgLang::Custom(c) => c.extract_injections(root.clone()),
SgLang::Builtin(b) => b.extract_injections(root.clone()),
};
let injections = unsafe { &*addr_of!(LANG_INJECTIONS) };
extract_custom_inject(lang, injections, root, &mut ret);
ret
}
fn extract_custom_inject<L: LanguageExt>(
lang: &SgLang,
injections: &[Injection],
root: Node<StrDoc<L>>,
ret: &mut HashMap<String, Vec<TSRange>>,
) {
let Some(rules) = injections.iter().find(|n| n.host == *lang) else {
return;
};
for (rule, default_lang) in &rules.rules {
for m in root.find_all(rule) {
let env = m.get_env();
let Some(region) = env.get_match("CONTENT") else {
continue;
};
let Some(lang) = env
.get_match("LANG")
.map(|n| n.text().to_string())
.or_else(|| default_lang.clone())
else {
continue;
};
let range = node_to_range(region);
ret.entry(lang).or_default().push(range);
}
}
}
fn node_to_range<D: Doc>(node: &Node<D>) -> TSRange {
let r = node.range();
let start = node.start_pos();
let sp = start.byte_point();
let sp = tree_sitter::Point::new(sp.0, sp.1);
let end = node.end_pos();
let ep = end.byte_point();
let ep = tree_sitter::Point::new(ep.0, ep.1);
TSRange {
start_byte: r.start,
end_byte: r.end,
start_point: sp,
end_point: ep,
}
}
#[cfg(test)]
mod test {
use super::*;
use ast_grep_config::from_str;
use ast_grep_language::SupportLang;
const DYNAMIC: &str = "
hostLanguage: js
rule:
pattern: styled.$LANG`$CONTENT`
injected: [css]";
const STATIC: &str = "
hostLanguage: js
rule:
pattern: styled`$CONTENT`
injected: css";
#[test]
fn test_deserialize() {
let inj: SerializableInjection = from_str(STATIC).expect("should ok");
assert!(matches!(inj.injected, Injected::Static(_)));
let inj: SerializableInjection = from_str(DYNAMIC).expect("should ok");
assert!(matches!(inj.injected, Injected::Dynamic(_)));
}
const BAD: &str = "
hostLanguage: HTML
rule:
kind: not_exist
injected: [js, ts, tsx]";
#[test]
fn test_bad_inject() {
let mut map = HashMap::new();
let inj: SerializableInjection = from_str(BAD).expect("should ok");
let ret = register_injetable(inj, &mut map);
assert!(ret.is_err());
let ec = ret.unwrap_err().downcast::<EC>().expect("should ok");
assert!(matches!(ec, EC::LangInjection));
}
#[test]
fn test_good_injection() {
let mut map = HashMap::new();
let inj: SerializableInjection = from_str(STATIC).expect("should ok");
let ret = register_injetable(inj, &mut map);
assert!(ret.is_ok());
let inj: SerializableInjection = from_str(DYNAMIC).expect("should ok");
let ret = register_injetable(inj, &mut map);
assert!(ret.is_ok());
assert_eq!(map.len(), 1);
let injections: Vec<_> = map.into_values().collect();
let mut ret = HashMap::new();
let lang = SgLang::from(SupportLang::JavaScript);
let sg = lang.ast_grep("const a = styled`.btn { margin: 0; }`");
let root = sg.root();
extract_custom_inject(&lang, &injections, root, &mut ret);
assert_eq!(ret.len(), 1);
assert_eq!(ret["css"].len(), 1);
assert!(!ret.contains_key("js"));
ret.clear();
let sg = lang.ast_grep("const a = styled.css`.btn { margin: 0; }`");
let root = sg.root();
extract_custom_inject(&lang, &injections, root, &mut ret);
assert_eq!(ret.len(), 1);
assert_eq!(ret["css"].len(), 1);
assert!(!ret.contains_key("js"));
}
}
```
## /crates/cli/src/lang/lang_globs.rs
```rs path="/crates/cli/src/lang/lang_globs.rs"
use super::SgLang;
use ignore::types::{Types, TypesBuilder};
use std::collections::HashMap;
use std::path::Path;
use std::ptr::{addr_of, addr_of_mut};
use std::str::FromStr;
use crate::utils::ErrorContext as EC;
use anyhow::{Context, Result};
// both use vec since lang will be small
static mut LANG_GLOBS: Vec<(SgLang, Types)> = vec![];
pub type LanguageGlobs = HashMap<String, Vec<String>>;
pub unsafe fn register(regs: LanguageGlobs) -> Result<()> {
debug_assert! {
(*addr_of!(LANG_GLOBS)).is_empty()
};
let lang_globs = register_impl(regs)?;
_ = std::mem::replace(&mut *addr_of_mut!(LANG_GLOBS), lang_globs);
Ok(())
}
fn register_impl(regs: LanguageGlobs) -> Result<Vec<(SgLang, Types)>> {
let mut lang_globs = vec![];
for (lang, globs) in regs {
let lang = SgLang::from_str(&lang).with_context(|| EC::UnrecognizableLanguage(lang))?;
// Note: we have to use lang.to_string() for normalized language name
// TODO: add test
let lang_name = lang.to_string();
let types = build_types(&lang_name, globs)?;
lang_globs.push((lang, types));
}
Ok(lang_globs)
}
fn build_types(lang: &str, globs: Vec<String>) -> Result<Types> {
let mut builder = TypesBuilder::new();
for glob in globs {
// builder add will only trigger error when lang name is `all`
builder
.add(lang, &glob)
.with_context(|| EC::UnrecognizableLanguage(lang.into()))?;
}
builder.select(lang);
builder.build().context(EC::ParseConfiguration)
}
fn add_types(builder: &mut TypesBuilder, types: &Types) {
for def in types.definitions() {
let name = def.name();
for glob in def.globs() {
builder.add(name, glob).expect(name);
}
}
}
fn get_types(lang: &SgLang) -> Option<&Types> {
for (l, types) in unsafe { &*addr_of!(LANG_GLOBS) } {
if l == lang {
return Some(types);
}
}
None
}
pub fn merge_types(types_vec: impl Iterator<Item = Types>) -> Types {
let mut builder = TypesBuilder::new();
for types in types_vec {
for def in types.definitions() {
let name = def.name();
for glob in def.globs() {
builder.add(name, glob).expect(name);
}
builder.select(name);
}
}
builder.build().expect("file types must be valid")
}
pub fn merge_globs(lang: &SgLang, type1: Types) -> Types {
let Some(type2) = get_types(lang) else {
return type1;
};
let mut builder = TypesBuilder::new();
add_types(&mut builder, &type1);
add_types(&mut builder, type2);
builder.select(&lang.to_string());
builder.build().expect("file type must be valid")
}
pub fn from_path(p: &Path) -> Option<SgLang> {
for (lang, types) in unsafe { &*addr_of!(LANG_GLOBS) } {
if types.matched(p, false).is_whitelist() {
return Some(*lang);
}
}
None
}
#[cfg(test)]
mod test {
use super::*;
use ast_grep_language::SupportLang;
use serde_yaml::from_str;
const YAML: &str = r"
js: ['.eslintrc']
html: ['*.vue', '*.svelte']";
fn get_globs() -> LanguageGlobs {
from_str(YAML).expect("should parse")
}
#[test]
fn test_parse_globs() {
let globs = get_globs();
assert_eq!(globs["js"], &[".eslintrc"]);
assert_eq!(globs["html"], &["*.vue", "*.svelte"]);
}
#[test]
fn test_register() -> Result<()> {
let globs = get_globs();
let lang_globs = register_impl(globs)?;
assert_eq!(lang_globs.len(), 2);
Ok(())
}
#[test]
fn test_invalid_language() {
let mut globs = get_globs();
globs.insert("php-exp".into(), vec!["bestlang".into()]);
let ret = register_impl(globs);
let err = ret.expect_err("should wrong");
assert!(matches!(
err.downcast::<EC>(),
Ok(EC::UnrecognizableLanguage(_))
));
}
#[test]
fn test_merge_types() {
let lang: SgLang = SupportLang::Rust.into();
let default_types = lang.file_types();
let rust_types = merge_globs(&lang, default_types);
assert!(rust_types.matched("a.php", false).is_ignore());
assert!(rust_types.matched("a.rs", false).is_whitelist());
}
#[test]
fn test_merge_with_globs() -> Result<()> {
let globs = get_globs();
unsafe {
// cleanup
std::mem::take(&mut *addr_of_mut!(LANG_GLOBS));
register(globs)?;
assert_eq!((*addr_of!(LANG_GLOBS)).len(), 2);
}
let lang: SgLang = SupportLang::Html.into();
let default_types = lang.file_types();
let html_types = merge_globs(&lang, default_types);
assert!(html_types.matched("a.php", false).is_ignore());
assert!(html_types.matched("a.html", false).is_whitelist());
assert!(html_types.matched("a.vue", false).is_whitelist());
assert!(html_types.matched("a.svelte", false).is_whitelist());
Ok(())
}
}
```
## /crates/cli/src/lang/mod.rs
```rs path="/crates/cli/src/lang/mod.rs"
mod injection;
mod lang_globs;
use crate::utils::ErrorContext as EC;
use anyhow::{Context, Result};
use ast_grep_core::matcher::{Pattern, PatternBuilder, PatternError};
use ast_grep_core::{
tree_sitter::{StrDoc, TSLanguage, TSRange},
Node,
};
use ast_grep_dynamic::DynamicLang;
use ast_grep_language::{Language, LanguageExt, SupportLang};
use ignore::types::Types;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::{Debug, Display, Formatter};
use std::path::Path;
use std::str::FromStr;
pub use ast_grep_dynamic::CustomLang;
pub use injection::SerializableInjection;
pub use lang_globs::LanguageGlobs;
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(untagged)]
pub enum SgLang {
// inlined support lang expando char
Builtin(SupportLang),
Custom(DynamicLang),
}
impl SgLang {
pub fn file_types(&self) -> Types {
let default_types = match self {
Builtin(b) => b.file_types(),
Custom(c) => c.file_types(),
};
lang_globs::merge_globs(self, default_types)
}
// register_globs must be called after register_custom_language
pub fn register_custom_language(base: &Path, langs: HashMap<String, CustomLang>) -> Result<()> {
CustomLang::register(base, langs).context(EC::CustomLanguage)
}
// TODO: add tests
// register_globs must be called after register_custom_language
pub fn register_globs(langs: LanguageGlobs) -> Result<()> {
unsafe {
lang_globs::register(langs)?;
}
Ok(())
}
pub fn register_injections(injections: Vec<SerializableInjection>) -> Result<()> {
unsafe { injection::register_injetables(injections) }
}
pub fn all_langs() -> Vec<Self> {
let builtin = SupportLang::all_langs().iter().copied().map(Self::Builtin);
let customs = DynamicLang::all_langs().into_iter().map(Self::Custom);
builtin.chain(customs).collect()
}
pub fn injectable_sg_langs(&self) -> Option<impl Iterator<Item = Self>> {
let langs = self.injectable_languages()?;
// TODO: handle injected languages not found
// e.g vue can inject scss which is not supported by sg
// we should report an error here
let iter = langs.iter().filter_map(|s| SgLang::from_str(s).ok());
Some(iter)
}
pub fn augmented_file_type(&self) -> Types {
let self_type = self.file_types();
let injector = Self::all_langs().into_iter().filter_map(|lang| {
lang
.injectable_sg_langs()?
.any(|l| l == *self)
.then_some(lang)
});
let injector_types = injector.map(|lang| lang.file_types());
let all_types = std::iter::once(self_type).chain(injector_types);
lang_globs::merge_types(all_types)
}
pub fn file_types_for_langs(langs: impl Iterator<Item = Self>) -> Types {
let types = langs.map(|lang| lang.augmented_file_type());
lang_globs::merge_types(types)
}
}
impl Display for SgLang {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self {
Builtin(b) => write!(f, "{b}"),
Custom(c) => write!(f, "{}", c.name()),
}
}
}
impl Debug for SgLang {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self {
Builtin(b) => write!(f, "{b:?}"),
Custom(c) => write!(f, "{:?}", c.name()),
}
}
}
#[derive(Debug)]
pub enum SgLangErr {
LanguageNotSupported(String),
}
impl Display for SgLangErr {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
use SgLangErr::*;
match self {
LanguageNotSupported(lang) => write!(f, "{lang} is not supported!"),
}
}
}
impl std::error::Error for SgLangErr {}
impl FromStr for SgLang {
type Err = SgLangErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(b) = SupportLang::from_str(s) {
Ok(SgLang::Builtin(b))
} else if let Ok(c) = DynamicLang::from_str(s) {
Ok(SgLang::Custom(c))
} else {
Err(SgLangErr::LanguageNotSupported(s.into()))
}
}
}
impl From<SupportLang> for SgLang {
fn from(value: SupportLang) -> Self {
Self::Builtin(value)
}
}
impl From<DynamicLang> for SgLang {
fn from(value: DynamicLang) -> Self {
Self::Custom(value)
}
}
use SgLang::*;
impl Language for SgLang {
fn pre_process_pattern<'q>(&self, query: &'q str) -> Cow<'q, str> {
match self {
Builtin(b) => b.pre_process_pattern(query),
Custom(c) => c.pre_process_pattern(query),
}
}
#[inline]
fn meta_var_char(&self) -> char {
match self {
Builtin(b) => b.meta_var_char(),
Custom(c) => c.meta_var_char(),
}
}
#[inline]
fn expando_char(&self) -> char {
match self {
Builtin(b) => b.expando_char(),
Custom(c) => c.expando_char(),
}
}
fn kind_to_id(&self, kind: &str) -> u16 {
match self {
Builtin(b) => b.kind_to_id(kind),
Custom(c) => c.kind_to_id(kind),
}
}
fn field_to_id(&self, field: &str) -> Option<u16> {
match self {
Builtin(b) => b.field_to_id(field),
Custom(c) => c.field_to_id(field),
}
}
fn from_path<P: AsRef<Path>>(path: P) -> Option<Self> {
// respect user overriding like languageGlobs and custom lang
// TODO: test this preference
let path = path.as_ref();
lang_globs::from_path(path)
.or_else(|| DynamicLang::from_path(path).map(Custom))
.or_else(|| SupportLang::from_path(path).map(Builtin))
}
fn build_pattern(&self, builder: &PatternBuilder) -> std::result::Result<Pattern, PatternError> {
builder.build(|src| StrDoc::try_new(src, *self))
}
}
impl LanguageExt for SgLang {
fn get_ts_language(&self) -> TSLanguage {
match self {
Builtin(b) => b.get_ts_language(),
Custom(c) => c.get_ts_language(),
}
}
fn injectable_languages(&self) -> Option<&'static [&'static str]> {
injection::injectable_languages(*self)
}
fn extract_injections<L: LanguageExt>(
&self,
root: Node<StrDoc<L>>,
) -> HashMap<String, Vec<TSRange>> {
injection::extract_injections(self, root)
}
}
#[cfg(test)]
mod test {
use super::*;
use std::mem::size_of;
#[test]
fn test_sg_lang_size() {
assert_eq!(size_of::<SgLang>(), size_of::<DynamicLang>());
}
}
```
## /crates/cli/src/lib.rs
```rs path="/crates/cli/src/lib.rs"
mod completions;
mod config;
mod lang;
mod lsp;
mod new;
mod print;
mod run;
mod scan;
mod utils;
mod verify;
use anyhow::Result;
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use completions::{run_shell_completion, CompletionsArg};
use config::ProjectConfig;
use lsp::{run_language_server, LspArg};
use new::{run_create_new, NewArg};
use run::{run_with_pattern, RunArg};
use scan::{run_with_config, ScanArg};
use utils::exit_with_error;
use verify::{run_test_rule, TestArg};
const LOGO: &str = r#"
Search and Rewrite code at large scale using AST pattern.
__
____ ______/ /_ ____ _________ ____
/ __ `/ ___/ __/_____/ __ `/ ___/ _ \/ __ \
/ /_/ (__ ) /_/_____/ /_/ / / / __/ /_/ /
\__,_/____/\__/ \__, /_/ \___/ .___/
/____/ /_/
"#;
#[derive(Parser)]
#[clap(author, version, about, long_about = LOGO)]
/**
* TODO: add some description for ast-grep: sg
* Example:
* sg -p "$PATTERN.to($MATCH)" -l ts --rewrite "use($MATCH)"
*/
struct App {
#[clap(subcommand)]
command: Commands,
/// Path to ast-grep root config, default is sgconfig.yml.
#[clap(short, long, global = true, value_name = "CONFIG_FILE")]
config: Option<PathBuf>,
}
#[derive(Subcommand)]
enum Commands {
/// Run one time search or rewrite in command line. (default command)
Run(RunArg),
/// Scan and rewrite code by configuration.
Scan(ScanArg),
/// Test ast-grep rules.
Test(TestArg),
/// Create new ast-grep project or items like rules/tests.
New(NewArg),
/// Start language server.
Lsp(LspArg),
/// Generate shell completion script.
Completions(CompletionsArg),
/// Generate rule docs for current configuration. (Not Implemented Yet)
#[cfg(debug_assertions)]
Docs,
}
pub fn execute_main() -> Result<()> {
match main_with_args(std::env::args()) {
Err(error) => exit_with_error(error),
ok => ok,
}
}
fn is_command(arg: &str, command: &str) -> bool {
let arg = arg.split('=').next().unwrap_or(arg);
if arg.starts_with("--") {
let arg = arg.trim_start_matches("--");
arg == command
} else if arg.starts_with('-') {
let arg = arg.trim_start_matches('-');
arg == &command[..1]
} else {
false
}
}
fn try_default_run(args: &[String]) -> Result<Option<RunArg>> {
// use `run` if there is at lease one pattern arg with no user provided command
let should_use_default_run_command =
args.iter().skip(1).any(|p| is_command(p, "pattern")) && args[1].starts_with('-');
if should_use_default_run_command {
// handle no subcommand
let arg = RunArg::try_parse_from(args)?;
Ok(Some(arg))
} else {
Ok(None)
}
}
/// finding project and setup custom language configuration
fn setup_project_is_possible(args: &[String]) -> Result<Result<ProjectConfig>> {
let mut config = None;
for i in 0..args.len() {
let arg = &args[i];
if !is_command(arg, "config") {
continue;
}
// handle --config=config.yml, see ast-grep/ast-grep#1617
if arg.contains('=') {
let config_file = arg.split('=').nth(1).unwrap().into();
config = Some(config_file);
break;
}
// handle -c config.yml, arg value should be next
if i + 1 >= args.len() || args[i + 1].starts_with('-') {
return Err(anyhow::anyhow!("missing config file after -c"));
}
let config_file = (&args[i + 1]).into();
config = Some(config_file);
}
ProjectConfig::setup(config)
}
// this wrapper function is for testing
pub fn main_with_args(args: impl Iterator<Item = String>) -> Result<()> {
let args: Vec<_> = args.collect();
// do not unwrap project before cmd parsing
// sg help does not need a valid sgconfig.yml
let project = setup_project_is_possible(&args);
if let Some(arg) = try_default_run(&args)? {
return run_with_pattern(arg, project?);
}
let app = App::try_parse_from(args)?;
let project = project?; // unwrap here to report invalid project
match app.command {
Commands::Run(arg) => run_with_pattern(arg, project),
Commands::Scan(arg) => run_with_config(arg, project),
Commands::Test(arg) => run_test_rule(arg, project),
Commands::New(arg) => run_create_new(arg, project),
Commands::Lsp(arg) => run_language_server(arg, project),
Commands::Completions(arg) => run_shell_completion::<App>(arg),
#[cfg(debug_assertions)]
Commands::Docs => todo!("todo, generate rule docs based on current config"),
}
}
#[cfg(test)]
mod test_cli {
use super::*;
fn sg(args: &str) -> Result<App> {
let app = App::try_parse_from(
std::iter::once("sg".into()).chain(args.split(' ').map(|s| s.to_string())),
)?;
Ok(app)
}
fn ok(args: &str) -> App {
sg(args).expect("should parse")
}
fn error(args: &str) -> clap::Error {
let Err(err) = sg(args) else {
panic!("app parsing should fail!")
};
err
.downcast::<clap::Error>()
.expect("should have clap::Error")
}
#[test]
fn test_wrong_usage() {
error("");
error("Some($A) -l rs");
error("-l rs");
}
#[test]
fn test_version_and_help() {
let version = error("--version");
assert!(version.to_string().starts_with("ast-grep"));
let version = error("-V");
assert!(version.to_string().starts_with("ast-grep"));
let help = error("--help");
assert!(help.to_string().contains("Search and Rewrite code"));
}
fn default_run(args: &str) {
let args: Vec<_> = std::iter::once("sg".into())
.chain(args.split(' ').map(|s| s.to_string()))
.collect();
assert!(matches!(try_default_run(&args), Ok(Some(_))));
}
#[test]
fn test_no_arg_run() {
let ret = main_with_args(["sg".to_owned()].into_iter());
let err = ret.unwrap_err();
assert!(err.to_string().contains("sg [OPTIONS] <COMMAND>"));
}
#[test]
fn test_default_subcommand() {
default_run("-p Some($A) -l rs");
default_run("-p Some($A)");
default_run("-p Some($A) -l rs -r $A.unwrap()");
}
#[test]
fn test_run() {
ok("run -p test -i");
ok("run -p test --interactive dir");
ok("run -p test -r Test dir");
ok("run -p test -l rs --debug-query");
ok("run -p test -l rs --debug-query not");
ok("run -p test -l rs --debug-query=ast");
ok("run -p test -l rs --debug-query=cst");
ok("run -p test -l rs --color always");
ok("run -p test -l rs --heading always");
ok("run -p test dir1 dir2 dir3"); // multiple paths
ok("run -p testm -r restm -U"); // update all
ok("run -p testm -r restm --update-all"); // update all
ok("run -p test --json compact"); // argument after --json should not be parsed as JsonStyle
ok("run -p test --json=pretty dir");
ok("run -p test --json dir"); // arg after --json should not be parsed as JsonStyle
ok("run -p test --strictness ast");
ok("run -p test --strictness relaxed");
ok("run -p test --selector identifier"); // pattern + selector
ok("run -p test --selector identifier -l js");
ok("run -p test --follow");
ok("run -p test --globs '*.js'");
ok("run -p test --globs '*.{js, ts}'");
ok("run -p test --globs '*.js' --globs '*.ts'");
ok("run -p fubuki -j8");
ok("run -p test --threads 12");
ok("run -p test -l rs -c config.yml"); // global config arg
error("run test");
error("run --debug-query test"); // missing lang
error("run -r Test dir");
error("run -p test -i --json dir"); // conflict
error("run -p test -U");
error("run -p test --update-all");
error("run -p test --strictness not");
error("run -p test -l rs --debug-query=not");
error("run -p test --selector");
error("run -p test --threads");
}
#[test]
fn test_scan() {
ok("scan");
ok("scan dir");
ok("scan -r test-rule.yml dir");
ok("scan -c test-rule.yml dir");
ok("scan -c test-rule.yml");
ok("scan --report-style short"); // conflict
ok("scan dir1 dir2 dir3"); // multiple paths
ok("scan -r test.yml --format github");
ok("scan --format github");
ok("scan --interactive");
ok("scan --follow");
ok("scan --json --include-metadata");
ok("scan -r test.yml -c test.yml --json dir"); // allow registering custom lang
ok("scan --globs '*.js'");
ok("scan --globs '*.{js, ts}'");
ok("scan --globs '*.js' --globs '*.ts'");
ok("scan -j 12");
ok("scan --threads 12");
ok("scan -A 12");
ok("scan --after 12");
ok("scan --context 1");
error("scan -i --json dir"); // conflict
error("scan --report-style rich --json dir"); // conflict
error("scan -r test.yml --inline-rules '{}'"); // conflict
error("scan --format gitlab");
error("scan --format github -i");
error("scan --format local");
error("scan --json=dir"); // wrong json flag
error("scan --json= not-pretty"); // wrong json flag
error("scan -j");
error("scan --include-metadata"); // requires json
error("scan --threads");
}
#[test]
fn test_test() {
ok("test");
ok("test -c sgconfig.yml");
ok("test --skip-snapshot-tests");
ok("test -U");
ok("test --update-all");
error("test --update-all --skip-snapshot-tests");
}
#[test]
fn test_new() {
ok("new");
ok("new project");
ok("new -c sgconfig.yml rule");
ok("new rule -y");
ok("new test -y");
ok("new util -y");
ok("new rule -c sgconfig.yml");
error("new --base-dir");
}
#[test]
fn test_shell() {
ok("completions");
ok("completions zsh");
ok("completions fish");
error("completions not-shell");
error("completions --shell fish");
}
}
```
## /crates/cli/src/lsp.rs
```rs path="/crates/cli/src/lsp.rs"
use crate::config::ProjectConfig;
use crate::utils::{ErrorContext as EC, RuleOverwrite};
use anyhow::{Context, Result};
use ast_grep_lsp::{Backend, LspService, Server};
use clap::Args;
#[derive(Args)]
pub struct LspArg {}
async fn run_language_server_impl(_arg: LspArg, project: Result<ProjectConfig>) -> Result<()> {
// env_logger::init();
// TODO: move this error to client
let project_config = project?;
let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();
let config_base = project_config.project_dir.clone();
// Create a rule finder closure that uses the CLI logic
let rule_finder = move || {
let (collection, _trace) = project_config.find_rules(RuleOverwrite::default())?;
Ok(collection)
};
let (service, socket) =
LspService::build(|client| Backend::new(client, config_base, rule_finder)).finish();
Server::new(stdin, stdout, socket).serve(service).await;
Ok(())
}
pub fn run_language_server(arg: LspArg, project: Result<ProjectConfig>) -> Result<()> {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.context(EC::StartLanguageServer)?
.block_on(async { run_language_server_impl(arg, project).await })
}
#[cfg(test)]
mod test {
use super::*;
#[test]
#[ignore = "test lsp later"]
fn test_lsp_start() {
let arg = LspArg {};
assert!(run_language_server(arg, Err(anyhow::anyhow!("error"))).is_err())
}
}
```
## /crates/cli/src/main.rs
```rs path="/crates/cli/src/main.rs"
use anyhow::Result;
use ast_grep::execute_main;
fn main() -> Result<()> {
execute_main()
}
```
## /crates/cli/src/new.rs
```rs path="/crates/cli/src/new.rs"
use crate::config::{AstGrepConfig, ProjectConfig, TestConfig};
use crate::lang::SgLang;
use crate::utils::ErrorContext as EC;
use anyhow::Result;
use clap::{Parser, Subcommand};
use inquire::validator::ValueRequiredValidator;
use std::fmt::Display;
use std::fs::{self, File};
use std::path::{Path, PathBuf};
#[derive(Parser)]
pub struct NewArg {
/// The ast-grep item type to create. Available options: project/rule/test/utils.
#[clap(subcommand)]
entity: Option<Entity>,
/// The id of the item to create.
#[arg(value_parser, global = true)]
name: Option<String>,
/// The language of the item to create.
///
/// This option is only available when creating rule and util.
#[arg(short, long, global = true)]
lang: Option<SgLang>,
/// Accept all default options without interactive input during creation.
///
/// You need to provide all required arguments via command line if this flag is true.
/// Please see the command description for the what arguments are required.
#[arg(short, long, global = true)]
yes: bool,
}
fn create_dir(project_dir: &Path, dir: &str) -> Result<PathBuf> {
let path = project_dir.join(dir);
fs::create_dir_all(&path)?;
// create a .gitkeep file to keep the folder in git
// https://github.com/ast-grep/ast-grep/issues/1273
let gitkeep = path.join(".gitkeep");
File::create(gitkeep)?;
Ok(path)
}
impl NewArg {
fn ask_dir(&self, prompt: &str, default: &str) -> Result<String> {
let dir = if self.yes {
default.to_owned()
} else {
inquire::Text::new(prompt).with_default(default).prompt()?
};
Ok(dir)
}
fn confirm(&self, prompt: &str) -> Result<bool> {
if self.yes {
return Ok(true);
}
Ok(inquire::Confirm::new(prompt).with_default(true).prompt()?)
}
fn ask_entity_type(&self) -> Result<Entity> {
if self.yes {
self
.entity
.clone()
.map(Ok)
.unwrap_or_else(|| Err(anyhow::anyhow!(EC::InsufficientCLIArgument("entity"))))
} else {
let entity = inquire::Select::new(
"Select the item you want to create:",
vec![Entity::Rule, Entity::Test, Entity::Util],
)
.prompt()?;
Ok(entity)
}
}
fn choose_language(&self) -> Result<SgLang> {
if let Some(lang) = self.lang {
Ok(lang)
} else if self.yes {
Err(anyhow::anyhow!(EC::InsufficientCLIArgument("lang")))
} else {
Ok(inquire::Select::new("Choose rule's language", SgLang::all_langs()).prompt()?)
}
}
fn ask_name(&self, entity: &'static str) -> Result<String> {
if let Some(name) = &self.name {
Ok(name.to_string())
} else if self.yes {
Err(anyhow::anyhow!(EC::InsufficientCLIArgument("name")))
} else {
Ok(
inquire::Text::new(&format!("What is your {entity}'s name?"))
.with_validator(ValueRequiredValidator::default())
.prompt()?,
)
}
}
}
/// The ast-grep item type to create.
#[derive(Subcommand, Debug, PartialEq, Eq, Clone)]
enum Entity {
/// Create an new project by scaffolding.
///
/// By default, this command will create a root config file `sgconfig.yml`,
/// a rule folder `rules`, a test case folder `rule-tests` and a utility rule folder `utils`.
/// You can customize the folder names during the creation.
Project,
/// Create a new rule.
///
/// This command will create a new rule in one of the `rule_dirs`.
/// You need to provide `name` and `language` either by interactive input or via command line arguments.
/// ast-grep will ask you which `rule_dir` to use if multiple ones are configured in the `sgconfig.yml`.
/// If `-y, --yes` flag is true, ast-grep will choose the first `rule_dir` to create the new rule.
Rule,
/// Create a new test case.
///
/// This command will create a new test in one of the `test_dirs`.
/// You need to provide `name` either by interactive input or via command line arguments.
/// ast-grep will ask you which `test_dir` to use if multiple ones are configured in the `sgconfig.yml`.
/// If `-y, --yes` flag is true, ast-grep will choose the first `test_dir` to create the new test.
Test,
/// Create a new global utility rule.
///
/// This command will create a new global utility rule in one of the `utils` folders.
/// You need to provide `name` and `language` either by interactive input or via command line arguments.
/// ast-grep will ask you which `util_dir` to use if multiple ones are configured in the `sgconfig.yml`.
/// If `-y, --yes` flag is true, ast-grep will choose the first `util_dir` to create the new item.
Util,
}
impl Display for Entity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use Entity::*;
match self {
Project => f.write_str("Project"),
Rule => f.write_str("Rule"),
Test => f.write_str("Test"),
Util => f.write_str("Util"),
}
}
}
pub fn run_create_new(mut arg: NewArg, project: Result<ProjectConfig>) -> Result<()> {
if let Some(entity) = arg.entity.take() {
run_create_entity(entity, arg, project)
} else {
ask_entity_type(arg, project)
}
}
fn run_create_entity(entity: Entity, arg: NewArg, project: Result<ProjectConfig>) -> Result<()> {
// check if we are under a project dir
if let Ok(found) = project {
return do_create_entity(entity, found, arg);
}
// check if we creating a project
if entity == Entity::Project {
create_new_project(arg, std::env::current_dir()?.as_path())
} else {
// if not, return error
Err(anyhow::anyhow!(EC::ProjectNotExist))
}
}
fn do_create_entity(entity: Entity, found: ProjectConfig, arg: NewArg) -> Result<()> {
// ask user what destination to create if multiple dirs exist
match entity {
Entity::Rule => create_new_rule(found, arg),
Entity::Test => create_new_test(found.test_configs, arg.name),
Entity::Util => create_new_util(found, arg),
Entity::Project => Err(anyhow::anyhow!(EC::ProjectAlreadyExist)),
}
}
fn ask_entity_type(arg: NewArg, project: Result<ProjectConfig>) -> Result<()> {
// 1. check if we are under a sgconfig.yml
if let Ok(found) = project {
// 2. ask users what to create if yes
let entity = arg.ask_entity_type()?;
do_create_entity(entity, found, arg)
} else {
// 3. ask users to provide project info if no sgconfig found
print!("No sgconfig.yml found. ");
let current_dir = std::env::current_dir()?;
create_new_project(arg, ¤t_dir)
}
}
fn create_new_project(arg: NewArg, project_dir: &Path) -> Result<()> {
println!("Creating a new ast-grep project...");
let ask_dir_and_create = |prompt: &str, default: &str| -> Result<PathBuf> {
let dir = arg.ask_dir(prompt, default)?;
create_dir(project_dir, &dir)
};
let rule_dirs = ask_dir_and_create("Where do you want to have your rules?", "rules")?;
let test_dirs = if arg.confirm("Do you want to create rule tests?")? {
let test_dirs = ask_dir_and_create("Where do you want to have your tests?", "rule-tests")?;
Some(TestConfig::from(test_dirs))
} else {
None
};
let utils = if arg.confirm("Do you want to create folder for utility rules?")? {
let util_dirs = ask_dir_and_create("Where do you want to have your utilities?", "utils")?;
Some(util_dirs)
} else {
None
};
let root_config = AstGrepConfig {
rule_dirs: vec![rule_dirs],
test_configs: test_dirs.map(|t| vec![t]),
util_dirs: utils.map(|u| vec![u]),
custom_languages: None, // advanced feature, skip now
language_globs: None, // advanced feature, skip now
language_injections: vec![], // advanced feature
};
let config_path = project_dir.join("sgconfig.yml");
let f = File::create(config_path)?;
serde_yaml::to_writer(f, &root_config)?;
println!("Your new ast-grep project has been created!");
Ok(())
}
fn default_rule(id: &str, lang: SgLang) -> String {
format!(
r#"# yaml-language-server: $schema=https://raw.githubusercontent.com/ast-grep/ast-grep/main/schemas/rule.json
id: {id}
message: Add your rule message here....
severity: error # error, warning, info, hint
language: {lang}
rule:
pattern: Your Rule Pattern here...
# utils: Extract repeated rule as local utility here.
# note: Add detailed explanation for the rule."#
)
}
fn create_new_rule(found: ProjectConfig, arg: NewArg) -> Result<()> {
let ProjectConfig {
project_dir,
rule_dirs,
test_configs,
..
} = found;
let name = arg.ask_name("rule")?;
let rule_dir = if rule_dirs.len() > 1 {
let dirs = rule_dirs.iter().map(|p| p.display()).collect();
let display =
inquire::Select::new("Which rule dir do you want to save your rule?", dirs).prompt()?;
project_dir.join(display.to_string())
} else {
project_dir.join(&rule_dirs[0])
};
let path = rule_dir.join(format!("{name}.yml"));
if path.exists() {
return Err(anyhow::anyhow!(EC::FileAlreadyExist(path)));
}
let lang = arg.choose_language()?;
fs::write(&path, default_rule(&name, lang))?;
println!("Created rules at {}", path.display());
let need_test = arg.confirm("Do you also need to create a test for the rule?")?;
if need_test {
create_new_test(test_configs, Some(name))?;
}
Ok(())
}
fn default_test(id: &str) -> String {
format!(
r#"id: {id}
valid:
- "valid code"
invalid:
- "invalid code"
"#
)
}
fn create_new_test(test_configs: Option<Vec<TestConfig>>, name: Option<String>) -> Result<()> {
let Some(tests) = test_configs else {
return Err(anyhow::anyhow!(EC::NoTestDirConfigured));
};
if tests.is_empty() {
return Err(anyhow::anyhow!(EC::NoTestDirConfigured));
}
let test_dir = if tests.len() > 1 {
let dirs = tests.iter().map(|t| t.test_dir.display()).collect();
let display = inquire::Select::new("Which test dir do you want to use?", dirs).prompt()?;
PathBuf::from(display.to_string())
} else {
tests[0].test_dir.clone()
};
let name = if let Some(name) = name {
name
} else {
inquire::Text::new("What is the rule's id that you want to test?")
.with_validator(ValueRequiredValidator::default())
.prompt()?
};
let path = test_dir.join(format!("{name}-test.yml"));
if path.exists() {
return Err(anyhow::anyhow!(EC::FileAlreadyExist(path)));
}
fs::write(&path, default_test(&name))?;
println!("Created test at {}", path.display());
Ok(())
}
fn default_util(id: &str, lang: SgLang) -> String {
format!(
r#"id: {id}
language: {lang}
rule:
pattern: Your Rule Pattern here...
# utils: Extract repeated rule as local utility here."#
)
}
fn create_new_util(found: ProjectConfig, arg: NewArg) -> Result<()> {
let ProjectConfig {
project_dir,
util_dirs,
..
} = found;
let Some(utils) = util_dirs else {
return Err(anyhow::anyhow!(EC::NoUtilDirConfigured));
};
if utils.is_empty() {
return Err(anyhow::anyhow!(EC::NoUtilDirConfigured));
}
let util_dir = if utils.len() > 1 {
let dirs = utils.iter().map(|p| p.display()).collect();
let display =
inquire::Select::new("Which util dir do you want to save your rule?", dirs).prompt()?;
project_dir.join(display.to_string())
} else {
project_dir.join(&utils[0])
};
let name = arg.ask_name("util")?;
let path = util_dir.join(format!("{name}.yml"));
if path.exists() {
return Err(anyhow::anyhow!(EC::FileAlreadyExist(path)));
}
let lang = arg.choose_language()?;
fs::write(&path, default_util(&name, lang))?;
println!("Created util at {}", path.display());
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use ast_grep_language::SupportLang;
use std::path::Path;
use tempfile::TempDir;
fn create_project(tempdir: &Path) -> Result<()> {
let arg = NewArg {
entity: None,
name: None,
lang: None,
yes: true,
};
create_new_project(arg, tempdir)?;
assert!(tempdir.join("sgconfig.yml").exists());
Ok(())
}
fn create_rule(temp: &Path) -> Result<()> {
let project = ProjectConfig::setup(Some(temp.join("sgconfig.yml")))?;
let arg = NewArg {
entity: Some(Entity::Rule),
name: Some("test-rule".into()),
lang: Some(SupportLang::Rust.into()),
yes: true,
};
run_create_new(arg, project)?;
assert!(temp.join("rules/test-rule.yml").exists());
Ok(())
}
fn create_util(temp: &Path) -> Result<()> {
let project = ProjectConfig::setup(Some(temp.join("sgconfig.yml")))?;
let arg = NewArg {
entity: Some(Entity::Util),
name: Some("test-utils".into()),
lang: Some(SupportLang::Rust.into()),
yes: true,
};
run_create_new(arg, project)?;
assert!(temp.join("utils/test-utils.yml").exists());
Ok(())
}
#[test]
fn test_create_new() -> Result<()> {
let dir = TempDir::new()?;
create_project(dir.path())?;
create_rule(dir.path())?;
drop(dir); // drop at the end since temp dir clean up is done in Drop
Ok(())
}
#[test]
fn test_create_util() -> Result<()> {
let dir = TempDir::new()?;
create_project(dir.path())?;
create_util(dir.path())?;
drop(dir); // drop at the end since temp dir clean up is done in Drop
Ok(())
}
}
```
## /crates/cli/src/print/cloud_print.rs
```rs path="/crates/cli/src/print/cloud_print.rs"
use super::{Diff, NodeMatch, PrintProcessor, Printer};
use crate::lang::SgLang;
use ast_grep_config::{RuleConfig, Severity};
use clap::ValueEnum;
use anyhow::Result;
use codespan_reporting::files::SimpleFile;
use std::io::{Stdout, Write};
use std::borrow::Cow;
use std::path::{Path, PathBuf};
#[derive(PartialEq, Eq, Clone, ValueEnum)]
#[clap(rename_all = "lower")]
pub enum Platform {
GitHub,
}
pub struct CloudPrinter<W: Write> {
writer: W,
}
impl<W: Write> CloudPrinter<W> {
pub fn new(writer: W) -> Self {
Self { writer }
}
}
impl CloudPrinter<Stdout> {
pub fn stdout() -> Self {
Self::new(std::io::stdout())
}
}
impl<W: Write> Printer for CloudPrinter<W> {
type Processed = Vec<u8>;
type Processor = CloudProcessor;
fn get_processor(&self) -> Self::Processor {
CloudProcessor
}
fn process(&mut self, processed: Self::Processed) -> Result<()> {
self.writer.write_all(&processed)?;
Ok(())
}
}
pub struct CloudProcessor;
impl PrintProcessor<Vec<u8>> for CloudProcessor {
fn print_rule(
&self,
matches: Vec<NodeMatch>,
file: SimpleFile<Cow<str>, &str>,
rule: &RuleConfig<SgLang>,
) -> Result<Vec<u8>> {
let mut ret = vec![];
let path = PathBuf::from(file.name().to_string());
for m in matches {
print_rule(&mut ret, m, &path, rule)?;
}
Ok(ret)
}
fn print_matches(&self, _m: Vec<NodeMatch>, _p: &Path) -> Result<Vec<u8>> {
unreachable!("cloud printer does not support pattern search")
}
fn print_diffs(&self, _d: Vec<Diff>, _p: &Path) -> Result<Vec<u8>> {
unreachable!("cloud printer does not support pattern rewrite")
}
fn print_rule_diffs(
&self,
diffs: Vec<(Diff<'_>, &RuleConfig<SgLang>)>,
path: &Path,
) -> Result<Vec<u8>> {
let mut ret = vec![];
for (diff, rule) in diffs {
print_rule(&mut ret, diff.node_match, path, rule)?;
}
Ok(ret)
}
}
fn print_rule<W: Write>(
writer: &mut W,
m: NodeMatch,
path: &Path,
rule: &RuleConfig<SgLang>,
) -> Result<()> {
let level = match rule.severity {
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Info => "notice",
Severity::Hint => return Ok(()),
Severity::Off => unreachable!("turned-off rule should not have match."),
};
let title = &rule.id;
let name = path.display();
let line = m.start_pos().line() + 1;
let end_line = m.end_pos().line() + 1;
let message = rule.get_message(&m);
writeln!(
writer,
"::{level} file={name},line={line},endLine={end_line},title={title}::{message}"
)?;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use ast_grep_config::{from_yaml_string, GlobalRules};
use ast_grep_language::{LanguageExt, SupportLang};
use codespan_reporting::term::termcolor::Buffer;
fn make_test_printer() -> CloudPrinter<Buffer> {
CloudPrinter::new(Buffer::no_color())
}
fn get_text(printer: &mut CloudPrinter<Buffer>) -> String {
let buffer = &mut printer.writer;
let bytes = buffer.as_slice();
std::str::from_utf8(bytes)
.expect("buffer should be valid utf8")
.to_owned()
}
fn make_rule(rule: &str) -> RuleConfig<SgLang> {
let globals = GlobalRules::default();
from_yaml_string(
&format!(
r"
id: test
message: test rule
language: TypeScript
{rule}"
),
&globals,
)
.unwrap()
.pop()
.unwrap()
}
fn test_output(src: &str, rule_str: &str, expect: &str) {
let mut printer = make_test_printer();
let grep = SgLang::from(SupportLang::Tsx).ast_grep(src);
let rule = make_rule(rule_str);
let matches = grep.root().find_all(&rule.matcher).collect();
let file = SimpleFile::new(Cow::Borrowed("test.tsx"), src);
let buffer = printer
.get_processor()
.print_rule(matches, file, &rule)
.unwrap();
printer.process(buffer).expect("should work");
let actual = get_text(&mut printer);
assert_eq!(actual, expect);
}
#[test]
fn test_no_match_output() {
test_output("let a = 123", "rule: { pattern: console }", "");
test_output(
"let a = 123",
"
rule: { pattern: console }
severity: error",
"",
);
}
#[test]
fn test_hint_output() {
test_output(
"console.log(123)",
"
rule: { pattern: console }
severity: hint
",
"",
);
}
#[test]
fn test_info_output() {
test_output(
"console.log(123)",
"
rule: { pattern: console }
severity: info
",
"::notice file=test.tsx,line=1,endLine=1,title=test::test rule\n",
);
}
#[test]
fn test_warning_output() {
test_output(
"console.log(123)",
"
rule: { pattern: console }
severity: warning
",
"::warning file=test.tsx,line=1,endLine=1,title=test::test rule\n",
);
}
#[test]
fn test_error_output() {
test_output(
"console.log(123)",
"
rule: { pattern: console }
severity: error
",
"::error file=test.tsx,line=1,endLine=1,title=test::test rule\n",
);
}
}
```
## /crates/cli/src/print/colored_print.rs
```rs path="/crates/cli/src/print/colored_print.rs"
use super::{Diff, NodeMatch, PrintProcessor, Printer};
use crate::lang::SgLang;
use crate::utils::DiffStyles;
use anyhow::Result;
use ast_grep_config::{RuleConfig, Severity};
use ast_grep_core::Doc;
use clap::ValueEnum;
use codespan_reporting::diagnostic::{self, Diagnostic, Label};
use codespan_reporting::files::SimpleFile;
use codespan_reporting::term::termcolor::{Buffer, ColorChoice, StandardStream, WriteColor};
use codespan_reporting::term::{self, DisplayStyle};
use std::borrow::Cow;
use std::io::Write;
use std::path::Path;
mod markdown;
mod match_merger;
mod styles;
mod test;
use markdown::Markdown;
use match_merger::MatchMerger;
pub use styles::should_use_color;
use styles::{PrintStyles, RuleStyle};
#[derive(Clone, Copy, ValueEnum)]
pub enum ReportStyle {
/// Output a richly formatted diagnostic, with source code previews.
Rich,
/// Output a condensed diagnostic, with a line number, severity, message and notes (if any).
Medium,
/// Output a short diagnostic, with a line number, severity, and message.
Short,
}
#[derive(Clone, Copy, ValueEnum)]
pub enum Heading {
/// Print heading for terminal tty but not for piped output
Auto,
/// Always print heading regardless of output type.
Always,
/// Never print heading regardless of output type.
Never,
}
impl Heading {
fn should_print(&self) -> bool {
use Heading as H;
match self {
H::Always => true,
H::Never => false,
H::Auto => atty::is(atty::Stream::Stdout),
}
}
}
pub struct ColoredPrinter<W: WriteColor> {
writer: W,
config: term::Config,
styles: PrintStyles,
heading: Heading,
context: (u16, u16),
}
impl<W: WriteColor> ColoredPrinter<W> {
pub fn new(writer: W) -> Self {
Self {
writer,
styles: PrintStyles::from(ColorChoice::Auto),
config: term::Config::default(),
heading: Heading::Auto,
context: (0, 0),
}
}
pub fn color<C: Into<ColorChoice>>(mut self, color: C) -> Self {
let color = color.into();
self.styles = PrintStyles::from(color);
self
}
pub fn style(mut self, style: ReportStyle) -> Self {
let display_style = match style {
ReportStyle::Rich => DisplayStyle::Rich,
ReportStyle::Medium => DisplayStyle::Medium,
ReportStyle::Short => DisplayStyle::Short,
};
self.config.display_style = display_style;
self
}
pub fn heading(mut self, heading: Heading) -> Self {
self.heading = heading;
self
}
pub fn context(mut self, context: (u16, u16)) -> Self {
self.context = context;
self.config.start_context_lines = context.0 as usize;
self.config.end_context_lines = context.1 as usize;
self
}
}
impl<W: WriteColor> Printer for ColoredPrinter<W> {
type Processed = Buffer;
type Processor = ColoredProcessor;
fn get_processor(&self) -> Self::Processor {
let color = self.writer.supports_color();
let markdown = Markdown::new(color);
ColoredProcessor {
color,
config: self.config.clone(),
styles: self.styles.clone(),
heading: self.heading,
context: self.context,
markdown,
}
}
fn process(&mut self, buffer: Buffer) -> Result<()> {
self.writer.write_all(buffer.as_slice())?;
Ok(())
}
}
impl ColoredPrinter<StandardStream> {
pub fn stdout<C: Into<ColorChoice>>(color: C) -> Self {
let color = color.into();
ColoredPrinter::new(StandardStream::stdout(color)).color(color)
}
/// Print the title of fixes
pub fn print_diff_title(&mut self, diffs: &[Option<&str>], index: usize) -> Result<()> {
if diffs.len() <= 1 {
return Ok(());
}
let note_style = self.styles.rule.note;
let hunk_style = self.styles.diff.hunk_header;
let select_style = self.styles.diff.select_fix;
writeln!(self.writer, "{}", note_style.paint("Switch fix by [tab]:"))?;
for (i, title) in diffs.iter().enumerate() {
let title = title.unwrap_or("No title");
if i == index {
let arrow = hunk_style.paint("⇥");
let title = select_style.paint(title);
writeln!(self.writer, "{arrow} {title}")?;
} else {
writeln!(self.writer, " {title}")?;
}
}
writeln!(self.writer)?;
Ok(())
}
}
fn create_buffer(color: bool) -> Buffer {
if color {
Buffer::ansi()
} else {
Buffer::no_color()
}
}
pub struct ColoredProcessor {
color: bool,
config: term::Config,
styles: PrintStyles,
heading: Heading,
markdown: Markdown,
context: (u16, u16),
}
impl ColoredProcessor {
fn context_span(&self) -> usize {
(self.context.0 + self.context.1) as usize
}
fn diff_context(&self) -> usize {
if self.context.0 == 0 {
3
} else {
self.context.0 as usize
}
}
}
fn sg_label_to_code_span_label(label: ast_grep_config::Label<impl Doc>) -> Label<()> {
let ret = match label.style {
ast_grep_config::LabelStyle::Primary => Label::primary((), label.range()),
ast_grep_config::LabelStyle::Secondary => Label::secondary((), label.range()),
};
if let Some(message) = label.message {
ret.with_message(message)
} else {
ret
}
}
impl PrintProcessor<Buffer> for ColoredProcessor {
fn print_rule(
&self,
matches: Vec<NodeMatch>,
file: SimpleFile<Cow<str>, &str>,
rule: &RuleConfig<SgLang>,
) -> Result<Buffer> {
let config = &self.config;
let mut buffer = create_buffer(self.color);
let writer = &mut buffer;
let severity = match rule.severity {
Severity::Error => diagnostic::Severity::Error,
Severity::Warning => diagnostic::Severity::Warning,
Severity::Info => diagnostic::Severity::Note,
Severity::Hint => diagnostic::Severity::Help,
Severity::Off => unreachable!("turned-off rule should not have match."),
};
let notes = self.markdown.render_note(rule);
for m in matches {
let labels = rule
.get_labels(&m)
.into_iter()
.map(sg_label_to_code_span_label)
.collect();
let diagnostic = Diagnostic::new(severity)
.with_code(&rule.id)
.with_message(rule.get_message(&m))
.with_notes(notes.clone().into_iter().collect())
.with_labels(labels);
term::emit_to_write_style(&mut *writer, config, &file, &diagnostic)?;
}
Ok(buffer)
}
fn print_matches(&self, matches: Vec<NodeMatch>, path: &Path) -> Result<Buffer> {
if self.heading.should_print() {
print_matches_with_heading(matches, path, self)
} else {
print_matches_with_prefix(matches, path, self)
}
}
fn print_diffs(&self, diffs: Vec<Diff>, path: &Path) -> Result<Buffer> {
let context = self.diff_context();
let mut buffer = create_buffer(self.color);
let writer = &mut buffer;
print_diffs(diffs, path, &self.styles, writer, context)?;
Ok(buffer)
}
fn print_rule_diffs(
&self,
diffs: Vec<(Diff<'_>, &RuleConfig<SgLang>)>,
path: &Path,
) -> Result<Buffer> {
let context = self.diff_context();
let mut buffer = create_buffer(self.color);
let writer = &mut buffer;
let mut start = 0;
let display_style = &self.config.display_style;
for (diff, rule) in diffs {
let range = &diff.range;
// skip overlapping diff
if range.start < start {
continue;
}
start = range.end;
if matches!(display_style, DisplayStyle::Rich) {
self.styles.print_prelude(path, writer)?;
} else {
let pos = diff.node_match.start_pos();
write!(
writer,
"{}:{}:{}: ",
path.display(),
pos.line() + 1,
pos.column(&*diff.node_match) + 1
)?;
}
print_rule_title(rule, &diff.node_match, &self.styles.rule, writer)?;
if matches!(display_style, DisplayStyle::Rich) {
let source = diff.get_root_text();
let new_str = format!(
"{}{}{}",
&source[..range.start],
diff.replacement,
&source[start..],
);
self
.styles
.diff
.print_diff(source, &new_str, writer, context)?;
}
if matches!(display_style, DisplayStyle::Medium | DisplayStyle::Rich) {
let notes = self.markdown.render_note(rule);
if let Some(note) = notes {
writeln!(writer, "{}", self.styles.rule.note.paint("Note:"))?;
writeln!(writer, "{note}")?;
}
}
}
Ok(buffer)
}
}
fn print_rule_title<W: WriteColor>(
rule: &RuleConfig<SgLang>,
nm: &NodeMatch,
style: &RuleStyle,
writer: &mut W,
) -> Result<()> {
let (level, level_style) = match rule.severity {
Severity::Error => ("error", style.error),
Severity::Warning => ("warning", style.warning),
Severity::Info => ("note", style.info),
Severity::Hint => ("help", style.hint),
Severity::Off => unreachable!("turned-off rule should not have match."),
};
let header = format!("{level}[{}]:", &rule.id);
let header = level_style.paint(header);
let message = style.message.paint(rule.get_message(nm));
writeln!(writer, "{header} {message}")?;
Ok(())
}
fn print_matches_with_heading(
matches: Vec<NodeMatch>,
path: &Path,
printer: &ColoredProcessor,
) -> Result<Buffer> {
let mut matches = matches.into_iter();
let styles = &printer.styles;
let context_span = printer.context_span();
let mut buffer = create_buffer(printer.color);
let writer = &mut buffer;
styles.print_prelude(path, writer)?;
let Some(first_match) = matches.next() else {
return Ok(buffer);
};
let source = first_match.root().get_text();
let mut merger = MatchMerger::new(&first_match, printer.context);
let display = merger.display(&first_match);
let mut ret = display.leading.to_string();
styles.push_matched_to_ret(&mut ret, &display.matched)?;
for nm in matches {
if merger.check_overlapping(&nm) {
continue;
}
let display = merger.display(&nm);
// merge adjacent matches
if let Some(last_end_offset) = merger.merge_adjacent(&nm) {
ret.push_str(&source[last_end_offset..nm.range().start]);
styles.push_matched_to_ret(&mut ret, &display.matched)?;
continue;
}
ret.push_str(merger.last_trailing);
let num = merger.last_start_line;
let width = styles.print_highlight(&ret, num, writer)?;
if context_span > 0 {
writeln!(writer, "{:╴>width$}┤", "")?; // make separation
}
merger.conclude_match(&nm);
ret = display.leading.to_string();
styles.push_matched_to_ret(&mut ret, &display.matched)?;
}
ret.push_str(merger.last_trailing);
let num = merger.last_start_line;
styles.print_highlight(&ret, num, writer)?;
writeln!(writer)?; // end
Ok(buffer)
}
fn print_matches_with_prefix(
matches: Vec<NodeMatch>,
path: &Path,
printer: &ColoredProcessor,
) -> Result<Buffer> {
let mut matches = matches.into_iter();
let styles = &printer.styles;
let context_span = printer.context_span();
let mut buffer = create_buffer(printer.color);
let writer = &mut buffer;
let path = path.display();
let Some(first_match) = matches.next() else {
return Ok(buffer);
};
let source = first_match.root().get_text();
let mut merger = MatchMerger::new(&first_match, printer.context);
let display = merger.display(&first_match);
let mut ret = display.leading.to_string();
styles.push_matched_to_ret(&mut ret, &display.matched)?;
for nm in matches {
if merger.check_overlapping(&nm) {
continue;
}
let display = merger.display(&nm);
// merge adjacent matches
if let Some(last_end_offset) = merger.merge_adjacent(&nm) {
ret.push_str(&source[last_end_offset..nm.range().start]);
styles.push_matched_to_ret(&mut ret, &display.matched)?;
continue;
}
ret.push_str(merger.last_trailing);
for (n, line) in ret.lines().enumerate() {
let num = merger.last_start_line + n;
writeln!(writer, "{path}:{num}:{line}")?;
}
if context_span > 0 {
writeln!(writer, "--")?; // make separation
}
merger.conclude_match(&nm);
ret = display.leading.to_string();
styles.push_matched_to_ret(&mut ret, &display.matched)?;
}
ret.push_str(merger.last_trailing);
for (n, line) in ret.lines().enumerate() {
let num = merger.last_start_line + n;
writeln!(writer, "{path}:{num}:{line}")?;
}
Ok(buffer)
}
fn print_diffs<W: WriteColor>(
diffs: Vec<Diff>,
path: &Path,
styles: &PrintStyles,
writer: &mut W,
context: usize,
) -> Result<()> {
let mut diffs = diffs.into_iter();
styles.print_prelude(path, writer)?;
let Some(first_diff) = diffs.next() else {
return Ok(());
};
let source = first_diff.get_root_text();
let range = first_diff.range;
let mut start = range.end;
let mut new_str = format!("{}{}", &source[..range.start], first_diff.replacement);
for diff in diffs {
let range = diff.range;
// skip overlapping diff
if range.start < start {
continue;
}
new_str.push_str(&source[start..range.start]);
new_str.push_str(&diff.replacement);
start = range.end;
}
new_str.push_str(&source[start..]);
styles.diff.print_diff(source, &new_str, writer, context)?;
Ok(())
}
```
## /crates/cli/src/print/colored_print/markdown.rs
```rs path="/crates/cli/src/print/colored_print/markdown.rs"
use crate::lang::SgLang;
use ast_grep_config::RuleConfig;
use dashmap::DashMap;
use termimad::MadSkin;
/// A Markdown renderer that caches rendered notes to avoid recomputing.
pub struct Markdown {
cache: DashMap<String, String>,
skin: MadSkin,
}
impl Markdown {
pub fn new(color: bool) -> Self {
Self {
cache: DashMap::new(),
skin: Self::skin(color),
}
}
pub fn render_note(&self, rule: &RuleConfig<SgLang>) -> Option<String> {
let note = rule.note.as_ref()?;
if let Some(cached) = self.cache.get(&rule.id) {
return Some(cached.clone());
}
let rendered = self.skin.text(note, None).to_string();
self.cache.insert(rule.id.clone(), rendered.clone());
Some(rendered)
}
fn skin(color: bool) -> MadSkin {
if !color {
return MadSkin::no_style();
}
let is_light = is_light_terminal();
if is_light {
MadSkin::default_light()
} else {
MadSkin::default_dark()
}
}
}
fn is_light_terminal() -> bool {
use terminal_light as tl;
// prefer using env instead of escape sequences
// https://github.com/ast-grep/ast-grep/issues/2114
if let Ok(color) = tl::env::bg_color() {
tl::Color::Ansi(color).luma() > 0.6
} else {
tl::luma().is_ok_and(|l| l > 0.6)
}
}
```
## /crates/cli/src/print/colored_print/match_merger.rs
```rs path="/crates/cli/src/print/colored_print/match_merger.rs"
use super::NodeMatch;
use ast_grep_core::tree_sitter::DisplayContext;
/// merging overlapping/adjacent matches
/// adjacent matches: matches that starts or ends on the same line
pub struct MatchMerger<'a> {
pub last_start_line: usize,
pub last_end_line: usize,
pub last_trailing: &'a str,
pub last_end_offset: usize,
pub context: (u16, u16),
}
impl<'a> MatchMerger<'a> {
pub fn new(nm: &NodeMatch<'a>, (before, after): (u16, u16)) -> Self {
let display = nm.display_context(before as usize, after as usize);
let last_start_line = display.start_line + 1;
let last_end_line = nm.end_pos().line() + 1;
let last_trailing = display.trailing;
let last_end_offset = nm.range().end;
Self {
last_start_line,
last_end_line,
last_end_offset,
last_trailing,
context: (before, after),
}
}
// merge non-overlapping matches but start/end on the same line
pub fn merge_adjacent(&mut self, nm: &NodeMatch<'a>) -> Option<usize> {
let display = self.display(nm);
let start_line = display.start_line;
if start_line <= self.last_end_line + self.context.1 as usize {
let last_end_offset = self.last_end_offset;
self.last_end_offset = nm.range().end;
self.last_trailing = display.trailing;
Some(last_end_offset)
} else {
None
}
}
pub fn conclude_match(&mut self, nm: &NodeMatch<'a>) {
let display = self.display(nm);
self.last_start_line = display.start_line + 1;
self.last_end_line = nm.end_pos().line() + 1;
self.last_trailing = display.trailing;
self.last_end_offset = nm.range().end;
}
#[inline]
pub fn check_overlapping(&self, nm: &NodeMatch<'a>) -> bool {
let range = nm.range();
// merge overlapping matches.
// N.B. range.start == last_end_offset does not mean overlapping
if range.start < self.last_end_offset {
// guaranteed by pre-order
debug_assert!(range.end <= self.last_end_offset);
true
} else {
false
}
}
pub fn display(&self, nm: &NodeMatch<'a>) -> DisplayContext<'a> {
let (before, after) = self.context;
nm.display_context(before as usize, after as usize)
}
}
```
## /crates/cli/src/utils/rule_overwrite.rs
```rs path="/crates/cli/src/utils/rule_overwrite.rs"
use super::OverwriteArgs;
use crate::lang::SgLang;
use crate::utils::ErrorContext as EC;
use anyhow::Result;
use ast_grep_config::{RuleConfig, Severity};
use ast_grep_core::Language;
use regex::Regex;
use std::collections::HashMap;
#[derive(Default)]
pub struct RuleOverwrite {
default_severity: Option<Severity>,
by_rule_id: HashMap<String, Severity>,
rule_filter: Option<Regex>,
}
fn read_severity(
severity: Severity,
ids: &Option<Vec<String>>,
by_rule_id: &mut HashMap<String, Severity>,
default_severity: &mut Option<Severity>,
) {
let Some(ids) = ids.as_ref() else { return };
if ids.is_empty() {
*default_severity = Some(severity);
return;
}
for id in ids {
by_rule_id.insert(id.clone(), severity.clone());
}
}
impl RuleOverwrite {
pub fn new_for_verify(filter: Option<&Regex>, include_off: bool) -> Self {
Self {
default_severity: if include_off {
Some(Severity::Hint)
} else {
None
},
by_rule_id: HashMap::new(),
rule_filter: filter.cloned(),
}
}
pub fn new(cli: &OverwriteArgs) -> Result<Self> {
let mut default_severity = None;
let mut by_rule_id = HashMap::new();
read_severity(
Severity::Error,
&cli.error,
&mut by_rule_id,
&mut default_severity,
);
read_severity(
Severity::Warning,
&cli.warning,
&mut by_rule_id,
&mut default_severity,
);
read_severity(
Severity::Info,
&cli.info,
&mut by_rule_id,
&mut default_severity,
);
read_severity(
Severity::Hint,
&cli.hint,
&mut by_rule_id,
&mut default_severity,
);
read_severity(
Severity::Off,
&cli.off,
&mut by_rule_id,
&mut default_severity,
);
Ok(Self {
default_severity,
by_rule_id,
rule_filter: cli.filter.clone(),
})
}
pub fn process_configs(
&self,
configs: Vec<RuleConfig<SgLang>>,
) -> Result<Vec<RuleConfig<SgLang>>> {
let mut configs = if let Some(filter) = &self.rule_filter {
filter_rule_by_regex(configs, filter)?
} else {
configs
};
for config in &mut configs {
let overwrite = self.find(&config.id);
overwrite.overwrite(config);
}
Ok(configs)
}
pub fn find(&self, id: &str) -> OverwriteResult {
let severity = self
.by_rule_id
.get(id)
.cloned()
.or_else(|| self.default_severity.clone());
OverwriteResult { severity }
}
}
fn filter_rule_by_regex(
configs: Vec<RuleConfig<SgLang>>,
filter: &Regex,
) -> Result<Vec<RuleConfig<SgLang>>> {
let selected: Vec<_> = configs
.into_iter()
.filter(|c| filter.is_match(&c.id))
.collect();
if selected.is_empty() {
Err(anyhow::anyhow!(EC::RuleNotFound(filter.to_string())))
} else {
Ok(selected)
}
}
pub struct OverwriteResult {
pub severity: Option<Severity>,
}
impl OverwriteResult {
fn overwrite<L>(&self, rule: &mut RuleConfig<L>)
where
L: Language,
{
if let Some(severity) = &self.severity {
rule.severity = severity.clone();
}
}
}
```
## /crates/napi/.gitignore
```gitignore path="/crates/napi/.gitignore"
# I cannot disable napi-derive's typedef
ignore.d.ts
lang/*.d.ts
```
## /crates/napi/.npmignore
```npmignore path="/crates/napi/.npmignore"
```
The content has been capped at 50000 tokens. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.