面對需要修改數百個文件的代碼遷移,你還在手動一個個改嗎?今天介紹一款能讓代碼批量重構像查找替換一樣簡單的工具 —— Grit。
為什麼需要 Grit
作為開發者,我們經常遇到這樣的場景:
- 團隊決定統一使用
lodash-es替代lodash,需要修改幾百個 import 語句 - 項目要從 React 類組件遷移到 Hooks,涉及上千個組件
- 某個廢棄的 API 需要全面替換,但調用位置散落在代碼庫各處
傳統的解決方案各有痛點:
| 方案 | 問題 |
|---|---|
| 手動修改 | 耗時長、易遺漏、容易出錯 |
| 正則替換 | 不理解代碼結構,容易誤傷 |
| jscodeshift | 僅支持 JS/TS,需懂 AST,編寫複雜 |
| find + sed | 脆弱,難以處理複雜場景 |
Grit 就是為了解決這些問題而生的 —— 它用聲明式的語法,支持多種語言,讓任何人都能寫出安全的代碼轉換規則。
快速開始
安裝
# 方式一:官方安裝腳本
curl -fsSL https://docs.grit.io/install | bash
# 方式二:npm 安裝
npm install -g @getgrit/cli
驗證安裝
# 檢查版本
grit version
# 診斷環境
grit doctor
第一個轉換
假設我們要把所有的 console.log 調用刪除:
# 方式一:使用標準庫 pattern(推薦)
grit apply no_console_log src/
# 方式二:使用內聯 pattern
grit apply '`console.log($_)`' src/
# 先預覽會改什麼(dry-run)
grit apply --dry-run no_console_log src/
# 查看某個 pattern 的詳細信息
grit patterns describe no_console_log
核心命令
grit apply - 應用轉換
語法: grit apply [OPTIONS] <PATTERN> [PATHS]...
Pattern 參數形式:
- 標準庫 pattern:
no_console_log - 內聯模式:
'console.log($_)'(用單引號包裹反引號內的 pattern) - Pattern 文件:
./patterns/my_pattern.grit
常用選項:
# 預覽模式(不實際修改文件)
grit apply --dry-run no_console_log file.js
# 指定語言
grit apply --language python print_to_log file.py
# 限制修改數量
grit apply --limit 10 no_console_log src/
# 緊湊輸出
grit apply --output compact no_console_log src/
# 交互式選擇(逐個確認)
grit apply --interactive no_console_log src/
# JSONL 格式輸出
grit apply --jsonl no_console_log src/
grit list - 列出可用 Patterns
# 列出所有 patterns
grit list
# 僅列出 JavaScript/TypeScript patterns
grit list --language js
# 僅列出 Python patterns
grit list --language python
# 僅列出本地 patterns
grit list --source local
grit check - 檢查代碼
# 檢查當前目錄
grit check
# 自動修復
grit check --fix
# 詳細輸出
grit check --verbose
其他有用命令
# 描述某個 pattern
grit patterns describe no_console_log
# 列出 patterns
grit patterns list
# 診斷環境
grit doctor
核心語法
代碼片段模式
最基本的模式是用反引號包裹的代碼片段:
`console.log('Hello')`
結構化匹配意味着 Grit 忽略格式差異,以下都會被匹配:
console.log('Hello')
console.log ( 'Hello' ) // 換行和空格不影響
console.log("Hello") // 單雙引號等價
變量系統
使用 $ 前綴定義變量,匹配任意內容:
`console.$method($msg)` // 匹配 console 的任何方法調用
變量複用規則:同一變量多次出現必須匹配相同值
`$obj && $obj.$prop()` // foo && foo.bar() ✓
// foo && bar.baz() ✗
條件過濾
使用 where 子句精確控制匹配範圍:
`console.$method($msg)` where {
$method <: or { `log`, `warn` } // 只匹配 log 和 warn
}
轉換語法
使用 => 進行代碼轉換:
`舊代碼($args)` => `新代碼($args)`
實戰案例
案例 1:清理 console 調試語句
最簡單的場景 —— 直接刪除匹配的代碼。
// 轉換前
console.log('fetching data...');
console.debug('state:', state);
console.info('API called');
console.error('Critical error'); // 保留
// 轉換後
console.error('Critical error');
使用標準庫 pattern(推薦):
grit apply no_console_log src/
自定義 GritQL 規則:
`console.log($a)` => ``
`console.debug($a)` => ``
`console.info($a)` => ``
注意: 標準庫的 no_console_log 會自動保留 catch 塊中的 console.log,更加安全。
案例 2:Python print 轉 logger
將 Python 中的 print 語句轉換為 logger 調用。
# 轉換前
print("Hello, World")
print(f"User: {username}")
print("Error:", error)
# 轉換後
log("Hello, World")
log(f"User: {username}")
log("Error:", error)
使用標準庫 pattern:
grit apply --language python print_to_log src/
案例 3:移除 debugger 語句
清理開發時留下的 debugger 語句。
// 轉換前
function processData(data) {
debugger; // 調試用
return data.map(x => x * 2);
}
// 轉換後
function processData(data) {
return data.map(x => x * 2);
}
使用標準庫 pattern:
grit apply no_debugger src/
案例 4:import 路徑調整
項目目錄重構時的常見需求 —— 批量更新 import 路徑。
// 轉換前
import { Button } from '@/components/ui/Button'
import { useAuth } from '@/hooks/useAuth'
import { formatDate } from '@/utils/date'
// 轉換後
import { Button } from '@/components/Button'
import { useAuth } from '@/hooks/auth'
import { formatDate } from '@/utils/format'
GritQL 規則:
`from '@/components/ui/$name'` => `from '@/components/$name'`
`from '@/hooks/use$name'` => `from '@/hooks/$name'`
`from '@/utils/date'` => `from '@/utils/format'`
案例 5:箭頭函數簡化
⚠️ 限制:內聯 pattern 對多參數函數的處理有限制,建議僅用於單參數場景。
單行返回的箭頭函數可以省略 return 和大括號。
// 轉換前
const double = x => { return x * 2; }
const getValue = () => { return config.value; }
// 轉換後
const double = x => x * 2
const getValue = () => config.value
異步箭頭函數:可以去除 async 和 await(當直接返回 Promise 時)。
// 轉換前
const getData = async () => { return await fetch('/api/data'); }
// 轉換後
const getData = () => fetch('/api/data')
説明:當函數只是直接返回await的結果時,async/await是冗餘的。
GritQL 規則:
# 單參數箭頭函數(推薦)
`$param => { return $expr }` => `$param => $expr`
# 無參數箭頭函數
`() => { return $expr }` => `() => $expr`
# 異步箭頭函數 - 去除 async/await
`async () => { return await $expr }` => `() => $expr`
注意:多參數函數 (a, b) => ... 的轉換存在括號處理問題,建議手動處理或使用 .grit 文件。
案例 6:框架升級遷移
Grit 提供了豐富的框架升級 patterns,可自動化常見的遷移工作。
React 升級
| 遷移類型 | Pattern | 命令 |
|---|---|---|
| 類組件 → Hooks | react_to_hooks |
grit apply react_to_hooks src/ |
| React Query v3 → v4 | migrating_from_react_query_3_to_react_query_4 |
grit apply migrating_from_react_query_3_to_react_query_4 src/ |
| MUI v4 → v5 | mui4_to_mui5 |
grit apply mui4_to_mui5 src/ |
| Knockout → React | knockout_to_react |
grit apply knockout_to_react src/ |
| Jest → Vitest | jest_to_vitest |
grit apply jest_to_vitest src/ |
| Chai → Jest | chai_to_jest |
grit apply chai_to_jest src/ |
| Enzyme → RTL | enzyme_rtl |
grit apply enzyme_rtl src/ |
React 類組件轉 Hooks 示例:
// 轉換前
class UserList extends React.Component {
state = { users: [], loading: false }
componentDidMount() {
this.fetchUsers()
}
fetchUsers() {
this.setState({ loading: true })
api.getUsers().then(users => this.setState({ users, loading: false }))
}
render() {
if (this.state.loading) return <div>Loading...</div>
return <div>{this.state.users.length} users</div>
}
}
// 轉換後
function UserList() {
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(false)
useEffect(() => {
fetchUsers()
}, [])
function fetchUsers() {
setLoading(true)
api.getUsers().then(data => { setUsers(data); setLoading(false) })
}
if (loading) return <div>Loading...</div>
return <div>{users.length} users</div>
}
使用命令:
# 預覽轉換結果
grit apply --dry-run react_to_hooks src/
# 執行轉換
grit apply react_to_hooks src/
# 交互式選擇(逐個確認)
grit apply --interactive react_to_hooks src/
Vue 升級
Vue 相關的遷移 pattern 較少,建議手動或配合其他工具進行 Vue 2 → Vue 3 的升級。
其他遷移
| 遷移類型 | Pattern | 命令 |
|---|---|---|
| Cypress → Playwright | cypress_to_playwright |
grit apply cypress_to_playwright src/ |
| Protractor → Playwright | protractor_to_playwright |
grit apply protractor_to_playwright src/ |
| CodeceptJS → Playwright | codecept_to_playwright |
grit apply codecept_to_playwright src/ |
| Moment → date-fns | moment_to_datefns |
grit apply moment_to_datefns src/ |
| io-ts → Zod | iots_to_zod |
grit apply iots_to_zod src/ |
| OpenAI v3 → v4 | openai_v4 |
grit apply openai_v4 src/ |
使用示例:
# Jest 遷移到 Vitest
grit apply --dry-run jest_to_vitest src/
grit apply jest_to_vitest src/
# OpenAI SDK 升級
grit apply --dry-run openai_v4 src/
grit apply openai_v4 src/
# 測試框架遷移(Cypress → Playwright)
grit apply --dry-run cypress_to_playwright tests/
grit apply cypress_to_playwright tests/
案例 7:對象簡寫屬性
⚠️ 限制:必須使用 .grit 文件,內聯語法不支持,且轉換後需要人工檢查。
ES6 允許屬性名和變量名相同時省略冒號。
// 轉換前
const user = { name: name, age: age, userId: id }
return { data: data, isLoading: loading }
// 轉換後
const user = { name, age, userId: id }
return { data, isLoading: loading }
創建 shorthand.grit 文件:
# 指定使用的引擎版本(marzano 是 Grit 的匹配引擎)
engine marzano(0.1)
# 指定目標語言
language js
`{ $props }` where {
$props <: some {
`$k: $k` => `$k`
}
}
engine marzano(0.1):Grit 的匹配引擎版本,所有.grit文件都需要在開頭聲明。
使用方式:
# 應用轉換(可能需要運行多次直到所有屬性都轉換完成)
grit apply shorthand.grit src/
測試結果:
- ✅ 可以匹配並轉換
{ name: name }→{ name } - ⚠️ 多個屬性需要運行多次才能全部轉換
- ⚠️ 轉換後建議人工檢查結果
常用標準 Patterns
Grit 提供了豐富的標準 patterns 庫,使用 grit list 查看。
JavaScript/TypeScript 常用 Patterns
| Pattern | 説明 | 命令 |
|---|---|---|
no_console_log |
移除 console.log |
grit apply no_console_log |
no_debugger |
移除 debugger |
grit apply no_debugger |
no_alert |
移除 alert() |
grit apply no_alert |
no_commented_out_code |
移除註釋代碼 | grit apply no_commented_out_code |
es6_imports |
轉換為 ES6 imports | grit apply es6_imports |
es6_exports |
轉換為 ES6 exports | grit apply es6_exports |
jest_to_vitest |
Jest 遷移到 Vitest | grit apply jest_to_vitest |
chai_to_jest |
Chai 遷移到 Jest | grit apply chai_to_jest |
openai_v4 |
OpenAI v4 遷移 | grit apply openai_v4 |
Python 常用 Patterns
| Pattern | 説明 | 命令 |
|---|---|---|
print_to_log |
print 轉 logger | grit apply --language python print_to_log |
py_no_debugger |
移除 pdb.set_trace() |
grit apply py_no_debugger |
no_skipped_tests |
移除跳過的測試 | grit apply no_skipped_tests |
openai |
OpenAI SDK 遷移 | grit apply openai |
支持的語言
Grit 支持以下編程語言(使用 --language 選項指定):
語言列表
| 標識符 | 語言 |
|---|---|
js |
JavaScript / TypeScript / JSX / TSX |
html |
HTML |
css |
CSS |
json |
JSON |
java |
Java |
kotlin |
Kotlin |
csharp |
C# |
python |
Python |
markdown |
Markdown |
go |
Go |
rust |
Rust |
ruby |
Ruby |
elixir |
Elixir |
solidity |
Solidity |
hcl |
HCL |
yaml |
YAML |
sql |
SQL |
vue |
Vue |
toml |
TOML |
php |
PHP |
使用示例
# Python 項目 - 將 print 轉換為 logger
grit apply --language python print_to_log src/
# Go 項目 - 移除 debugger 語句
grit apply --language go no_debugger src/
# Java 項目 - 列出可用的 Java patterns
grit list --language java
# Rust 項目 - 應用自定義 pattern
grit apply --language rust ./patterns/custom.grit src/
# Vue 項目 - 檢查代碼規範
grit check --language vue src/
# 多語言項目 - 同時處理多種文件類型
grit apply --language js no_console_log src/
grit apply --language python print_to_log src/
# 查看某個語言的可用 patterns
grit patterns list --language python
工作流最佳實踐
推薦工作流程
# 1. 列出可用的 patterns
grit list --language js
# 2. 查看 pattern 詳情
grit patterns describe no_console_log
# 3. 預覽模式(不會修改文件)
grit apply --dry-run no_console_log src/
# 4. 限制範圍測試
grit apply no_console_log src/components/
# 5. 交互式應用(逐個確認)
grit apply --interactive no_console_log src/
# 6. 確認後正式應用
grit apply no_console_log src/
使用配置文件
創建 .grit/grit.yaml 管理項目規則:
patterns:
- no_console_log
- no_debugger
- .grit/patterns/**/*.grit
ignored:
- node_modules/**
- dist/**
- build/**
- "**/*.min.js"
enforcement:
level: fix
CI 集成
在 GitHub Actions 中集成代碼檢查:
name: Grit Code Quality
on: [pull_request]
jobs:
grit-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Grit
run: npm install -g @getgrit/cli
- name: Check code patterns
run: grit check --github-actions
工作原理
處理流程
源代碼
↓
┌─────────────────────────────────────┐
│ Tree-sitter 解析器 │
│ (多語言支持,增量解析) │
└─────────────────────────────────────┘
↓ AST
┌─────────────────────────────────────┐
│ Grit 模式匹配引擎 │
│ (變量綁定,條件過濾) │
└─────────────────────────────────────┘
↓ 匹配結果
┌─────────────────────────────────────┐
│ AST 轉換引擎 │
│ (語義保持,結構轉換) │
└─────────────────────────────────────┘
↓ 修改後的 AST
┌─────────────────────────────────────┐
│ 代碼生成器 │
│ (保留格式,生成代碼) │
└─────────────────────────────────────┘
↓
轉換後代碼
為什麼比正則更安全?
| 特性 | 正則表達式 | Grit |
|---|---|---|
| 理解代碼結構 | ❌ | ✅ |
| 忽略空白/引號差異 | ❌ | ✅ |
| 變量綁定與複用 | ❌ | ✅ |
| AST 感知 | ❌ | ✅ |
| 語義安全 | ❌ | ✅ |
常見問題
Q: 轉換會破壞代碼格式嗎?
A: 不會。Grit 保留原始格式,只修改語義結構。
Q: 內聯 pattern 怎麼寫?
A: 使用單引號包裹整個 pattern(反引號內是匹配代碼):
# 正確 - 單引號包裹,shell 不會解析反引號
grit apply '`console.log($_)`' file.js
# 錯誤 - shell 會嘗試執行反引號內的命令
grit apply `console.log($_)` file.js
Q: 如何查看所有可用的 patterns?
A: 使用 grit list 命令:
# 列出所有
grit list
# 按語言過濾
grit list --language js
grit list --language python
Q: --json 和 --jsonl 有什麼區別?
A: --json 目前不被 apply_pattern 支持,使用 --jsonl 獲取 JSON Lines 格式輸出。
Q: 如何限制修改數量?
A: 使用 --limit 選項:
# 只修改前 10 處
grit apply --limit 10 no_console_log src/
Q: 如何安全地進行大規模轉換?
A: 建議按以下步驟:
- 先用
--dry-run預覽 - 用
--limit小範圍測試 - 用
--interactive交互式確認 - 確認無誤後正式應用
總結
Grit 的核心價值在於:
- 簡單 - 用代碼片段寫規則,無需懂 AST
- 安全 - 結構化匹配,不會誤傷代碼
- 高效 - 秒級完成大規模代碼遷移
- 可維護 - 規則即文檔,易於 review
- 豐富 - 內置大量標準 patterns,開箱即用
下次遇到代碼遷移任務,不妨試試 Grit,讓繁瑣的重構工作變得輕鬆高效。
參考資源
- Grit 官網
- Grit 官方文檔
- Grit 標準 Patterns 庫
- Grit GitHub