Zenn
🏝️

GritQL について基本的な使い方を調べてみた

2025/02/14に公開

はじめに

GritQL を知ったのは去年 Biomeのプラグインに関するRFC に少し目お通した時に、GritQL の提案があったのがきっかけだったと思います。Biome のドキュメントには現在 GritQL [実験的機能] として載っています。そして、少し前のポスト Roadmap 2025 and Biome 2.0 で以下のような記述がありました。

プラグイン 2024年1月に始まったRFCプロセスを経て、Biomeプラグインの開発が始まった。Biome 2.0では、その最初の成果が搭載される: ユーザーはGritQLを使って独自のリントルールを作成できるようになる。

そこで以前 GritQL について軽く調べたんですが完全に忘れているので再度キャッチアップし直してみました。

GritQLとは?

https://github.com/getgrit/gritql

https://docs.grit.io/

GritQL はソースコードの検索と修正のために設計されたクエリ言語で、ざっと以下のような特徴があります。

  • Rust製でパフォーマンスに優れている
  • 構文解析ツールとして tree-sitter を採用
  • 複数の言語に対応している(target-languages
  • SQLライクな文法を採用しており、より直感的で自然な形で書ける
  • ASTの詳細な知識は不要で、たとえば jscodeshift などの codemod のように複雑なASTの操作を直接書く必要がない
  • 組み込みモジュールのシステムを使用して、たくさんの標準パターンを再利用したり(standard patterns)、独自のパターンを共有したりすることができる(share your own

ちなみに現時点ではまだアルファ版のようです。v0.1.0-alpha.1738896512

Grit と GritQL の違いについて
Grit は、GritQLCLI を含む開発ツールの総称という感じのようです。

クエリの書き方

以下は 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)は除外されています。

複合パターン

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

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

パターン修飾子

さきほどの andor 以外にも状況に応じたパターン修飾子が複数用意されています。たとえば、within修飾子を使うと、構文ツリーを上方向にたどり、console.logtry/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のインストール

https://docs.grit.io/cli/quickstart#installation

ドキュメントではグローバルインストールの例になっていますが、今回はプロジェクトのローカルにインストールしてみます。(特に理由はないですが、あまりグローバルに入れるのが好きじゃないのでなんとなくです。)

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

.grit/grit.yaml
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

長いので省略

標準ライブラリのパターンを使ってみる

パターンを適用するファイルをプロジェクトに追加します。

src/test.ts
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()が削除されました。

src/test.ts
const a = "a"
const b = "b"

オリジナルのパターンを作ってみる(yaml)

https://docs.grit.io/guides/config

コンフィグファイルを使ってオリジナルでGrit用のクエリパターンを定義できます。console_errorというパターンを追加してみます。

yamlファイルを追加

.grit/grit.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.

パターンの適用

追加したパターンを実行して適用みます。デモ用のコードを用意します。

src/sample1.ts
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

変更されました。

src/sample1.ts
console.error("a")
console.error("b")

オリジナルのパターンを作ってみる(markdown)

Markdown patterns でもパターンの定義ができます。mdファイルはGritパターンのオーサリングに便利です。ドキュメント、GritQLのパターン、パターンのテストケースを1つのファイルにまとめることができます。また、VSCodeのエクステンションも用意されていて、Gritパターンのシンタックスハイライト機能などが提供されています。
https://docs.grit.io/guides/authoring

mdファイルを追加

mdファイルは.grit/patternsに配置します。

.grit
 ┣ patterns
 ┃ ┗ remove_console_log.md
.grit/patterns/console_foo_bar.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.

パターンの適用

追加したパターンを実行して適用してみます。デモ用のコードを用意します。

src/sample2.ts
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

変更されました。

src/sample2.ts
console.log("hello");
console.log('bar');

おわりに

基本的なところまでしかやってはいないですが、SQLライクな書き方は直感的でわかりやすい気がしました。jscodeshift のようにASTの操作を書くスタイルに比べると、学習コストも低く比較的気軽に使えそうな感じがします。Markdownでファイルとしてパターン定義ができるのも管理がしやくて良さそうです。 Biome での採用も、まだ Grit 自体がアルファ版なので注意が必要ですが、最終的にどうなるか気になるところです。

参考

Discussion

ログインするとコメントできます