😽

Biome2.0 β版でGritQLを使った独自Lintルールを作成する

に公開

はじめに

TimeTreeの公開カレンダーのオーナー本部で開発をしています、Iscoです。現在、TimeTreeのWeb版開発環境ではBiomeを導入しており、日々の開発効率化に取り組んでいます。そんな中、最近Biome2.0についてアナウンスがありました。

https://biomejs.dev/blog/biome-v2-0-beta/

この中の機能として特に注目すべきものが、以下です。

Plugins: You can write custom lint rules using GritQL.

GritQLを使用して、独自のルールを作成することが可能になり、ESLintのようなプラグイン作成が今後増えていきそうです!

https://biomejs.dev/ja/linter/plugins/

GritQLとは?

GritQLは、コードの探索やリンティング、修正を行うためのクエリ言語です。OSSとして公開されており、以下の特徴があります。

https://github.com/honeycombio/gritql

  • ASTの詳細を学ばなくても簡単に始められる。
  • Rustとクエリ最適化を活用し、1000万行以上のリポジトリにも対応可能。
  • Gritの組み込みモジュールシステムを使用し、200以上の標準パターンを再利用したり、自分独自ののパターンを共有が可能。
  • JavaScript/TypeScript、Python、JSON、Java、Terraform、Solidity、CSS、Markdown、YAML、Rust、Go、SQLなど、あらゆる言語のリライトが可能。
  • 自動修正ルールを簡単に含められるため、修正作業を迅速に行えます。(ESLintのようなfixの機能)

個人的に特に興味をそそられたのは、ASTの詳細を学ばなくても簡単に始められる点と、あらゆる言語に対応していることの2点です。

前者に関して、ESLintのプラグインなどを作成する際には、EstreeなどJavaScriptのASTの仕様などをある程度把握して実装する必要がありました。(自分はESLintのプラグインを作成する際にASTについて学習しました。)

https://github.com/estree/estree

ですが、GritQLはその部分を隠蔽しているので、ASTの仕様について理解していなくても誰でもプラグインを作成できることが可能になります。

後者に関しては、今回のBiomeがGritQLを選択したことで、他のライブラリなどもGritQLを採用しよりプラグインの開発がしやすくなるという点に魅力を感じました!こうすることでBiomeだけでなく、他のESLintなどのプラグイン開発もよりしやすくなるのではないかと思っています。
GritQLを使用することでどのような恩恵があるのか?が今言葉だけですと何も説得力がないのでこの後、実際に自分が作成した簡単なBiomeのプラグインを紹介したいと思います🙏

実際にプラグインを作成してみる

今回試した検証用で作成したリポジトリは以下です。とりあえず手を動かして試したい場合は以下を参考にしてみてください。

https://github.com/hayawata3626/biome-plugins-leanring

環境開発

最小限の環境構築をします。

まずは、プロジェクトディレクトリを作成してみます。

mkdir biome-plugins-leanring

こちらを参考に、以下のコマンドを実行します。

npm install --save-dev --save-exact @biomejs/biome@beta

その後、以下のコマンドを実行します。

npx @biomejs/biome init

するとbiome.jsonというファイルが作成されます。自分はindentStyleをtabではなくspaceにしたかったのでformatterの部分を少しいじりました。

{
  "$schema": "https://biomejs.dev/schemas/2.0.0-beta.5/schema.json",
  "vcs": {
    "enabled": false,
    "clientKind": "git",
    "useIgnoreFile": false
  },
  "files": {
    "ignoreUnknown": false,
    "includes": ["src/**/*.{ts,tsx,js,jsx}"]
  },
  "formatter": {
    "indentStyle": "space",
    "indentWidth": 2
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  },
  "javascript": {
    "formatter": {
      "quoteStyle": "double"
    }
  },
  "assist": {
    "enabled": true,
    "actions": {
      "source": {
        "organizeImports": "on"
      }
    }
  }
}

そしてpackage.jsonのscritsの部分を設定します。

{
  "scripts": {
    "lint": "biome check"
  },
  "devDependencies": {
    "@biomejs/biome": "2.0.0-beta.5"
  }
}

最低限の環境は整ったので今回作成するカスタムルールを決めます。

function宣言を禁止して、アロー関数を使用するようにする」という簡単なルールを追加します。

まずは、plugin用のファイルを作成します。
pluginsディレクトリを作成し、そこにno-function-declaration.gritというファイルを作成します。(拡張子が.gritになります)

ファイルの中身は以下になります。

`function $name($params) { $body }` where {
  register_diagnostic(
    span = $name,
    message = "Use arrow functions instead of `function` declarations."
  )
}

こちらはfunctionキーワードで書かれた関数宣言を検出し、関数名の位置でエラーを出すようにするとてもシンプルなルールです。

function $name($params) { $body }

これは、GritQLのパターンマッチ記法で、functionキーワードで始まる構文(つまり関数宣言のこと)を探します。その中で

  • $nameは関数名
  • $paramsは引数のリスト
  • $bodyは関数の中身({ ... }のブロック部分)

上記をキャプチャします。

where {
  register_diagnostic(
    span = $name,
    message = "Use arrow functions instead of `function` declarations."
  )
}

register_diagnostic関数の中で
span = $nameで「エラー箇所は関数名(例えば double)の部分」を指定。
messageは出力するエラーメッセージの内容を指定。

https://biomejs.dev/linter/plugins/#register_diagnostic

このプラグインをBiomeのルールに登録します。先ほどのbiome.jsonに以下を追加。

"plugins": ["./plugins/no-function-declaration.grit"],

lintが機能するか試す

試しにsrc/index.tsを作成し、以下を追加します。

function double(num: number) {
  return num * 2;
}

double(100);

きちんと自分が登録したruleが適用されています🎉

ちなみにですが、名前付き関数の場合は、Biome標準で組み込まれているルールが適用されます。

const greet = function (name: string) {
  return `Hello, ${name}!`;
};

greet('John');

useArrowFunctionというルールが適用されます。

https://biomejs.dev/linter/rules/use-arrow-function

多言語のサポート例(CSSの例)

GritQLはJavaScript/TypeScript以外にも対応しており、例えばCSSのLintルールも簡単に作成できます。以下は、.color-*クラス以外でのcolorの指定を禁止するルールの例です。(こちら公式の例になります)

language css;

`$selector { $props }` where {
    $props <: contains `color: $color` as $rule,
    not $selector <: r"\.color-.*",
    register_diagnostic(
        span = $rule,
        message = "Don't set explicit colors. Use `.color-*` classes instead."
    )
}

language css;で対象言語がCSSであることを明示しています。

https://biomejs.dev/linter/plugins/#register_diagnostic

We currently do not support other target languages than JavaScript and CSS.

まとめ

今回、Biome2.0で導入されるであろうGritQLのプラグイン機能を使って、簡単なLintルールの作成を試してみました。特に、以下の点がGritQLの大きな強みだと感じました。

  • ASTの深い知識が不要なので誰でもパターンベースでルール作成が可能
  • 様々な言語に対応可能しているのでJavaScript/TypeScript だけでなく、他の言語にも応用できる可能性
  • 高速なクエリエンジンなので大規模リポジトリでもスムーズに動作しそう

まだβ版ということもあり、ドキュメントやエコシステムの整備はこれからだと思うので、今後GritQLの利用事例やコミュニティの拡大、公式のプラグイン例の充実に期待したいところです。
GritQLの可能性は非常に大きいので、Biome2.0からBiomeを導入する方々も増えてくるのではないでしょうか?今後の動きに期待です!!

TimeTree Tech Blog

Discussion