ast-grep: tìm và refactor code theo cấu trúc cú pháp thay vì regex
Tóm tắt
ast-grep tìm và thay code theo cây AST của tree-sitter, nên không vỡ khi code xuống dòng hay lồng ngoặc. -p cho mẫu, -r để thay, -i để xác nhận từng chỗ; ast-grep scan chạy quy tắc lint riêng của project.
Bạn cần đổi mọi lời gọi oldApi(a, b) thành newApi({ a, b }) trên cả codebase. Tìm và thay bằng regex thì vỡ khi tham số xuống dòng, có comment, hay lồng ngoặc. Sửa tay thì mất cả ngày. ast-grep tìm và thay code theo cấu trúc cú pháp (cây AST do tree-sitter phân tích) thay vì theo chuỗi ký tự. Bạn viết mẫu trông giống code thật, ast-grep hiểu đâu là biểu thức, tham số, khối lệnh, và thay thế chính xác.

Cài đặt
brew install ast-grep
cargo install ast-grep --locked
pip install ast-grep-cli
npm install --global @ast-grep/cli
Công cụ cũng cài kèm alias ngắn sg, nhưng trên Linux sg trùng một lệnh hệ thống có sẵn; dùng tên đầy đủ ast-grep cho chắc.
Tìm kiếm theo cấu trúc
# Tìm mọi console.log, bất kể tham số gì
ast-grep -p 'console.log($$$ARGS)' -l ts
# Tìm useEffect không có mảng phụ thuộc
ast-grep -p 'useEffect($FN)' -l tsx
# Tìm so sánh với chính nó (thường là lỗi)
ast-grep -p '$A == $A' -l py
| Cú pháp mẫu | Khớp với |
|---|---|
$A | Đúng một nút AST (một biểu thức, một tên), có thể tham chiếu lại |
$$$ARGS | Không hoặc nhiều nút (danh sách tham số, câu lệnh) |
$_ | Một nút bất kỳ, không cần nhớ |
$A xuất hiện hai lần | Hai vị trí phải giống hệt nhau |
Thay thế (rewrite)
# Ví dụ trong README: dùng optional chaining
ast-grep -p '$A && $A()' -l ts -r '$A?.()'
# Đổi chữ ký API
ast-grep -p 'oldApi($A, $B)' -r 'newApi({ a: $A, b: $B })' -l ts
# Xem và xác nhận từng thay đổi
ast-grep -p 'oldApi($A, $B)' -r 'newApi({ a: $A, b: $B })' -l ts -i
# Áp dụng tất cả không hỏi (sau khi đã kiểm tra)
ast-grep -p 'oldApi($A, $B)' -r 'newApi({ a: $A, b: $B })' -l ts -U
Biến mẫu thành quy tắc lint
ast-grep new # tạo project quy tắc: sgconfig.yml, thư mục rules/
# rules/no-console-log.yml
id: no-console-log
language: TypeScript
severity: warning
message: Xoá console.log trước khi merge
rule:
pattern: console.log($$$ARGS)
fix: ''
ast-grep scan # chạy mọi quy tắc trong project
ast-grep scan --update-all # áp dụng mọi fix
Lưu ý
- Mẫu phải là code hợp lệ của ngôn ngữ đó; luôn chỉ định
-lđể ast-grep phân tích đúng. - Chạy rewrite trên nhánh riêng và xem
git difftrước khi commit; dùng-icho lần đầu. - ast-grep không hiểu kiểu dữ liệu (type); hai hàm cùng tên ở module khác nhau vẫn bị khớp. Thu hẹp bằng đường dẫn hoặc điều kiện trong quy tắc.
Áp dụng thế nào?
- Dev: Dùng ast-grep cho refactor hàng loạt: đổi API, nâng cấp thư viện có thay đổi chữ ký hàm, xoá code chết theo mẫu.
- Tech Lead: Viết quy tắc ast-grep cho những mẫu cấm riêng của dự án và chạy
ast-grep scantrong CI; quy tắc ngắn, dễ đọc hơn plugin linter tự viết. - PM: Các đợt nâng cấp thư viện lớn trước đây tốn nhiều ngày sửa tay có thể rút ngắn đáng kể, ít lỗi sót hơn.
Nguồn & tham khảo
- ast-grep/ast-grep — ast-grep (GitHub)
- ast-grep documentation — ast-grep