https://tenntenn.dev/ja/posts/2021-04-11-gqlanalysis/
副業をしているAppify TechnologiesにてGraphQLの静的解析ツールをGoで書けるライブラリgqlanalysisを作りました。またそれに合わせクエリのセレクションにid
の追加忘れを指摘するlackidとgqlgoオーガナイゼーションで開発した静的解析ツールをまとめて実行できるgqlintも公開されています。
gqlanalysisを用いるとGraphQLのスキーマやクエリファイルに対するLinterを簡単に作ることができます。gqlanalysisはGoの静的解析ツールライブラリのgo/analysisに似た構造で作ってあります。
go/analysisと同様にAnalyzerという単位で解析を行います。Analyzerは別のAnalyzerの解析結果を用いることができるため、静的解析ツールをモジュール化できます。各Analyzerはゴルーチンで1度だけ実行されます。
.graphql
ファイル(.gql
や.ql
などの拡張子の場合もある)のパースはgqlanalysisがgqlparserを用いて自動で行います。そのため、Analyzerの開発者は、作りたい静的解析ツールのロジックのみに集中して開発できます。
Analyzerはgqlanalysis.Analyzer
型という構造体として定義されています。Analyzer構造体のフィールドを埋めることにより静的解析ツールを作成できます。
例えば、名前が”Gopher”で始まるクエリを検出するAnalyzerは以下のように書けます。
packge findgopher
import (
"strings"
"github.com/gqlgo/gqlanalysis"
)
var Analyzer = &gqlanalysis.Analyzer{
Name: "findgopher",
Doc: `find a query which name begin "Gopher"`,
Run: run,
}
func run(pass *gqlanalysis.Pass) (interface{}, error) {
for _, q := pass.Queries {
for _, op := range Operations {
if op.Operation == ast.Query &&
strings.HasPrefix(op.Name, "Gopher") {
pass.Reportf(op.Position, "NG")
}
}
}
return nil, nil
}
Pass
型には依存するAnalyzerの解析結果やクエリやスキーマのパース結果が格納されています。(*gqlanalysis.Pass).Reportf
メソッドを用いることで該当箇所を指定してレポートできます。
より実践的な実装例は筆者が開発したlackidを参考にすると良いでしょう。
go/analysisと同様にテストも簡単に作成できます。以下のようにテストデータを生成し、コメントでレポートされるであろう箇所にメッセージをwant
コメントともに正規表現で記述するだけです。
query Gopher { # want "NG" ...}
テストコードは以下のようにanalysistest
パッケージを用いるだけです。
package findgopher_test
import (
"testing"
"github.com/gqlgo/gqlanalysis/analysistest"
"example.com/findgopher"
)
func Test(t *testing.T) {
testdata := analysistest.TestData(t)
analysistest.Run(t, testdata, findgopher.Analyzer, "a")
}
テストに使用する.graphql
ファイルはtestdata
以下に配置します。analysistest.Run
関数が指定したディレクトリ以下の*.graphql
ファイルを解析対象としてテストを行います。
testdata
└── a
├── query
│ ├── mutation.graphql
│ ├── query.graphql
│ └── subscription.graphql
└── schema
├── model.graphql
└── schema.graphql