GritQL について基本的な使い方を調べてみた
はじめに
GritQL を知ったのは去年 Biome のプラグインに関する RFC に少し目を通した時に、GritQL の提案があったのがきっかけだったと思います。Biome のドキュメントには現在 GritQL [実験的機能] として載っています。そして、少し前のポスト Roadmap 2025 and Biome 2.0 で以下のような記述がありました。
プラグイン 2024 年 1 月に始まった RFC プロセスを経て、Biome プラグインの開発が始まった。Biome 2.0 では、その最初の成果が搭載される: ユーザーは GritQL を使って独自のリントルールを作成できるようになる。
そこで以前 GritQL について軽く調べたんですが完全に忘れているので再度キャッチアップし直してみました。
GritQL とは?
GritQL はソースコードの検索と修正のために設計されたクエリ言語で、ざっと以下のような特徴があります。
- Rust 製でパフォーマンスに優れている
- 構文解析ツールとして tree-sitter を採用
- 複数の言語に対応している(target-languages)
- SQL ライクな文法を採用しており、より直感的で自然な形で書ける
- AST の詳細な知識は不要で、たとえば jscodeshift などの codemod のように複雑な AST の操作を直接書く必要がない
- 組み込みモジュールのシステムを使用して、たくさんの標準パターンを再利用したり(standard patterns)、独自のパターンを共有したりすることができる(share your own)
ちなみに現時点ではまだアルファ版のようです。v0.1.0-alpha.1738896512
Grit と GritQL の違いについて
Grit は、GritQL や CLI を含む開発ツールの総称という感じのようです。
クエリの書き方
以下は GritQL Tutorial をベースに書いてます。
また、Grit にはクエリをブラウザで実行して試せる Grit studio が用意されています。
パターンで検索する
GritQL はパターン(patterns)を用いてコードベースから検索を行います。検索したいコード(コードスニペット)をパターンとして記述するには「`」で囲むのがベーシックな方法です(#code-snippets)。
これは console.log("Hello world!") にマッチします。ただしコメント内のものは無視されています。これは GritQL が単なる文字列マッチングではなく、構造マッチングを行うように設計されているためです。すべてのコードスニペットは、コードベースと照合される前に、自動的に構文ツリーに変換されます。

変数を使った検索
引数などの特定の要素を変数として定義できます。接頭辞で$をつけることで変数として扱われます。

書き換える
⇒(rewrite-operator)を使うと検索対象を書き換えることができます。「検索対象のコードスニペット ⇒ 書き換えるコードスニペット」のように記述します。

検索条件を追加する
Grit にはコンディションオペレーター(conditions)が用意されていて複雑な条件を定義できます。
where句で検索条件を定義する
where 句はその前に書かれたパターンを実行するために真である必要のある条件を書きます。where 句の後には、1 つの条件か、中括弧で囲んでカンマで区切った条件のリスト({ cond1, cond2 })を続けることができます。

<:はマッチ演算子でこの場合 $my_message の文字列が "This is a user-facing message" と一致するもの、という条件を意味します。
つまり上記はconsole.log()をalert()に書き換えますが、その際 $my_message の文字列が "This is a user-facing message" と一致するものだけに絞り込まれています。
!で否定の条件を定義する

上記はconsole.log() を winston.info() に書き換えますが、! を使って引数の文字列にuser-facingを含まないものを対象にしています。r".+user-facing.+"は正規表現の記法になっています。先頭にrをつけることで正規表現として解釈されます。
代替パターン
これまでの例で使用したパターン以外にも、AST ノードやプリミティブタイプをパターンとして使用することもできます。たとえば以下は、console.log() を winston.info() に書き換えますが、
変数の条件にstring()AST ノードを参照することで、引数が string 型のconsole.log()のみを書き換えています。console.log(42)は除外されています。

複合パターン
and や or のブール句は、複数のパターンを 1 つのパターンにまとめるために使うこともできます。以下は console.log() と console.error() を winston.info() に変換してしています。

リライトはパターンでもあるので、条件節の中でも使えます。これにより、入れ子のリライトを構築できます。以下は、最初に親条件でconsoleをwinstonに書き換える定義をします。その際、メタ変数を使い $method でメソッド部分を参照しておいて、where 句の条件で $method が log の場合は debug に、error の場合は warn に書き換えるように切り替えています。

パターン修飾子
さきほどの and や or 以外にも状況に応じたパターン修飾子が複数用意されています。たとえば、within修飾子を使うと、構文ツリーを上方向にたどり、console.log が try/catchブロック内にある場合のみマッチさせることができます。

パターン呼び出し
プログラミング言語の関数のようにパターンを定義しておき、それを呼び出すことで再利用できます。
多くのパターンが標準ライブラリとして用意されています。以下のサンプルではliteral(value="42")の部分が、パターン呼び出しになっていて、標準ライブラリの Literals を呼び出しています。これにより42というリテラルを持つものだけを書き換えています。

ローカル環境で使ってみる
サンプル用のプロジェクトを作成する
実際に作成したリポジトリはこちら。
grit-playground
ディレエクトリを作成する。
mkdir grit-playground
cd grit-playground
なんでもいいんですが簡単なので今回は Vite で TypeScript のプロジェクトを作成します。
pnpm create vite ./
grit-playground
┣ public
┃ ┗ vite.svg
┣ src
┃ ┣ counter.ts
┃ ┣ main.ts
┃ ┣ style.css
┃ ┣ typescript.svg
┃ ┗ vite-env.d.ts
┣ .gitignore
┣ index.html
┣ package.json
┣ pnpm-lock.yaml
┗ tsconfig.json
Grit のインストール
ドキュメントではグローバルインストールの例になっていますが、今回はプロジェクトのローカルにインストールしてみます。(特に理由はないですが、あまりグローバルに入れるのが好きじゃないのでなんとなくです。)
pnpm add -D @getgrit/cli
確認で grit のコマンドを実行してみます。
grit-playground $ pnpm grit help
Software maintenance on autopilot, from grit.io
Usage: grit [OPTIONS] <COMMAND>
Commands:
check Check the current directory for pattern violations
list List everything that can be applied to the current directory
apply Apply a pattern or migration to a set of files
doctor Print diagnostic information about the current environment
blueprints Manage blueprints for the Grit Agent
auth Authentication commands, run `grit auth --help` for more information
install Install supporting binaries
init Install grit modules
workflows Workflow commands, run `grit workflows --help` for more information
patterns Patterns commands, run `grit patterns --help` for more information
version Display version information about the CLI and agents
format Format grit files under current directory
help Print this message or the help of the given subcommand(s)
Options:
--json Enable JSON output, only supported on some commands
--jsonl Enable JSONL output, only supported on some commands
--log-level <LOG_LEVEL> Override the default log level (info)
--grit-dir <GRIT_DIR> Override the default .grit directory location
-h, --help Print help
-V, --version Print version
For help with a specific command, run `grit help <command>`.
Grit の初期化
init コマンドを実行してみます。
grit-playground $ pnpm grit init
Initialized grit config at /grit-playground/.grit/grit.yaml
プロジェクトルートに.gritディレクトリが作成され、その中の.gritmodulesには標準ライブラリが格納されています。
┣ .grit
┃ ┣ .gritmodules
┃ ┃ ┗ github.com
┃ ┃ ┃ ┗ getgrit
┃ ┃ ┃ ┃ ┗ stdlib
┃ ┃ ┃ ┃ ┃ ┣ .devcontainer
┃ ┃ ┃ ┃ ┃ ┃ ┗ devcontainer.json
┃ ┃ ┃ ┃ ┃ ┣ .git
┃ ┃ ┃ ┃ ┃ ┣ .grit
┃ ┃ ┃ ┃ ┃ ┃ ┣ patterns
┃ ┃ ┃ ┃ ┃ ┣ .gitignore
┃ ┃ ┃ ┃ ┃ ┣ .prettierrc
┃ ┃ ┃ ┃ ┃ ┣ CONTRIBUTING.md
┃ ┃ ┃ ┃ ┃ ┗ README.md
┃ ┣ .gitignore
┃ ┗ grit.yaml
.grit/grit.yamlは Grit のパターン定義用のファイルです。patterns.name に github.com/getgrit/stdlib#* と書かれていて、グラブパターンで標準ライブラリのすべての名前が指定されています。(Imported Remote Patterns)
version: 0.0.1
patterns:
- name: github.com/getgrit/stdlib#*
grit listを実行してみます。標準ライブラリにあるパターンリストが表示されます。
grit-playground $ pnpm grit list
STANDARD LIBRARY PATTERNS
CSS patterns (getgrit/stdlib)
✔ aspect_ratio
Go patterns (getgrit/stdlib)
✔ after_each_file
✔ after_each_file_handle_imports
✔ before_each_file
✔ before_each_file_prep_imports
✔ channel_guarded_with_mutex fix best-practice
✔ cloudflare_go_v2
✔ ensure_import
✔ exported_loop_pointer fix correctness
✔ go_importing
✔ hidden_goroutine correctness best-practice
✔ import_of
✔ jwt_go_none_algorithm fix security
✔ mux_go_v5 mux stainless sdk
✔ no_strconv_atoi fix correctness
✔ no_unnecessary_conditionals fix warning
✔ path_to_filepath fix correctness
✔ useless_if_else_body fix correctness
長いので省略
標準ライブラリのパターンを使ってみる
パターンを適用するファイルをプロジェクトに追加します。
const a = "a"
console.log(a)
const b = "b"
console.log(b)
適用してみる
no_console_log を適用してみます。
grit-playground $ pnpm grit apply no_console_log
Your working tree currently has untracked changes and Grit will rewrite files in place. Do you want to proceed? yes
./src/test.ts
const a = "a"
-console.log(a)
const b = "b"
-console.log(b)
Processed 4 files and found 2 matches
console.log()が削除されました。
const a = "a"
const b = "b"
オリジナルのパターンを作ってみる(yaml)
コンフィグファイルを使ってオリジナルで Grit 用のクエリパターンを定義できます。console_errorというパターンを追加してみます。
yaml ファイルを追加
version: 0.0.1
patterns:
- name: github.com/getgrit/stdlib#*
+ - name: console_error
+ tags: ["console"]
+ level: info
+ body: |
+ `console.log($my_message)` => `console.error($my_message)`
+ description: |
+ console.log() を console.error() に書き直す
+ samples:
+ - input: |
+ console.log("test")
+ output: |
+ console.error("test")
+
この状態でリストコマンドを実行すると以下のように出力されます。ローカルパターンとしてconsole_errorがリストに追加されています。
grit-playground $ pnpm grit list --source local
LOCAL PATTERNS
TSX patterns
✔ console_error console
フィールドについて
-
name: パターン名。慣例的にスネークケース。(必須) -
title: パターンのタイトル。未設定の場合は name の値が使われる。(オプショナル) -
body: クエリの定義。別ファイルの定義をインポートも可能。(オプショナル) -
description: パターンの説明。(オプショナル) -
level:grit checkで診断を実行するパターンの執行レベル。none, info, warn, error のいずれか、デフォルトは info。(オプショナル) -
tags: パターンのフィルタリング用。(オプショナル)
テストの実行
grit-patterns-test コマンドでテストを実行してみます。
grit-playground $ pnpm grit patterns test
Found 1 testable patterns.
✓ console_error
✓ All 1 samples passed.
パターンの適用
追加したパターンを実行して適用みます。デモ用のコードを用意します。
console.log("a")
console.log("b")
grit-playground $ pnpm grit apply console_error
Your working tree currently has untracked changes and Grit will rewrite files in place. Do you want to proceed? yes
./src/sample1.ts
-console.log("a")
-console.log("b")
+console.error("a")
+console.error("b")
Processed 4 files and found 2 matches
変更されました。
console.error("a")
console.error("b")
オリジナルのパターンを作ってみる(markdown)
Markdown patterns でもパターンの定義ができます。md ファイルは Grit パターンのオーサリングに便利です。ドキュメント、GritQL のパターン、パターンのテストケースを 1 つのファイルにまとめることができます。また、VSCode のエクステンションも用意されていて、Grit パターンのシンタックスハイライト機能などが提供されています。
md ファイルを追加
md ファイルは.grit/patternsに配置します。
.grit
┣ patterns
┃ ┗ remove_console_log.md
---
tags: [optional, tags, here]
---
# Console.log message foo to bar
console.log()のメッセージを"foo"を"bar"に書き換えます。
```grit
`console.log('$message')` => `console.log('bar')` where {
$message <: "foo"
}
```
## テストケース
入力用のコードを書きます。
```typescript
console.log('hello');
console.log('foo');
```
期待するアウトプットのコードを書きます。
```typescript
console.log('hello');
console.log('bar');
```
実際のエディタでの表示
bodyのクエリ定義にシンタックスハイライトが効いてます
拡張機能あり

拡張機能なし

フィールドとの対応
-
name: ファイル名がパターン名として使われる。 -
title: パターンのタイトルは、ファイルの最初の見出しから取得される。フロントマターにタイトルフィールドを追加することで上書きも可能。 -
description: パターンの説明は、ファイル内の見出し以外の最初の段落から取得される。 -
body: ファイル内の最初のフェンスで囲まれたコードブロックから取得されます。 -
追加のメタデータ: フロントマターに記述
-
level:grit checkで診断を実行するパターンの執行レベル。none, info, warn, error のいずれか、デフォルトは info。(オプショナル) -
tags: パターンのフィルタリング用。(オプショナル)
-
-
各テストケース
- 小見出しに 1 つのコードブロックがある場合、パターンにマッチすべきテストケースを表す。
- 小見出しに 2 つのコードブロックがある場合、1 つ目は入力を表し、2 つ目は期待される出力を表す。
- 否定的なテスト・ケースには、2 つの同じコードブロックが必要。
- パターンの中では、
// @filename: example.jsを使って、グループとしてテストされるべき複数の入出力ファイルを表す。(サンプル)
パターンリストの確認
この状態でリストコマンドを実行するローカルパターンとしてconsole_foo_barがリストに追加されています。
grit-playground $ pnpm grit list --source local
LOCAL PATTERNS
TSX patterns
✔ console_error console
✔ console_foo_bar optional tags here
テストの実行
grit-playground $ pnpm grit patterns test
Found 2 testable patterns.
✓ console_error
✓ console_foo_bar
✓ All 2 samples passed.
パターンの適用
追加したパターンを実行して適用してみます。デモ用のコードを用意します。
console.log("hello");
console.log("foo");
grit-playground $ pnpm grit apply console_foo_bar
Your working tree currently has untracked changes and Grit will rewrite files in place. Do you want to proceed? yes
./src/sample2.ts
console.log("hello");
-console.log("foo");
+console.log('bar');
Processed 5 files and found 1 matches
変更されました。
console.log("hello");
console.log('bar');
おわりに
基本的なところまでしかやってはいないですが、SQL ライクな書き方は直感的でわかりやすい気がしました。jscodeshift のように AST の操作を書くスタイルに比べると、学習コストも低く比較的気軽に使えそうな感じがします。Markdown でファイルとしてパターン定義ができるのも管理がしやくて良さそうです。 Biome での採用も、まだ Grit 自体がアルファ版なので注意が必要ですが、最終的にどうなるか気になるところです。
Discussion