私が歌川です

@utgwkk が書いている

golangci-lintの //nolint directiveを自動で挿入するCLIツール autonolint を作った

はじめに

GitHub - utgwkk/autonolint: Insert `//nolint` comment automatically for golangci-lint

表題のものを作りました。go install コマンドで直ちにインストールできます。

$ go install github.com/utgwkk/autonolint/cmd/autonolint

golangci-lintの --out-format=json オプションで出力したJSONを標準入力経由で渡す、という使い方をします。つまりこういう感じです。

$ golangci-lint run --out-format=json | autonolint

//nolint directiveの後に書くコメントの内容を -comment オプションで指定できます。

$ golangci-lint run --out-format=json | autonolint -comment "あとでなんとかする"

JSONの Issues フィールドだけ抽出して渡すこともできます。これによって、golangci-lintが出力したJSONを加工して渡す (たとえば、一部のファイルのみ修正対象とする、など) ことが容易になるのを狙っています。

$ golangci-lint run --out-format=json | jq .Issues | autonolint

様子

nilnil linter*1に引っかかるコードに対して、自動で //nolint:nilnil コメントを足している様子です。

gyazo.com

モチベーション

golangci-lintで有効にするlinterを増やして、コードの質をより高めやすくしたい、ということは誰しも考えると思います。その際に問題になるのが、既存のコードでlinterに警告される箇所の処遇です。

既存のコードを直ちに修正できるならよいのですが、すぐには修正できないとか、対応箇所が多すぎることもあると思います。警告が出るのに目をつぶってlinterを有効にすると目が滑ってしまうし、他の問題を見逃す可能性が出るため、警告が出ない状態にするのが望ましいでしょう。

.golangci.ymlファイルによって、特定のファイルでは特定のlinterの警告を無効にする、という設定をすることはできます。しかし、.golangci.ymlファイルでは警告を無効にする最小単位がファイルになってしまい、同じファイル内の別の箇所に新しく書いたコードではlinterによる警告を有効にする、ということが難しいです。

あるファイルの特定の行 (もしくはスコープ) でのみlinterの警告を無効にする、というときには //nolint directiveが使えます。しかし、directiveを追加すべき箇所があまりに多いと骨が折れてしまいます。なんとか自動化できないか?

golangci-lintには検査結果をJSON形式で出力するオプションがあり、これを使うと警告が出た箇所を特定するのは容易そうです。ということで実装しました。

実装のみどころ

この記事を書きはじめた時点では、autonolint.go という1ファイルで・100行ちょっとで実装できています。

github.com

やっていることは非常にシンプルで、警告が出た箇所の直前の行に //nolint:(linter名) というコメントを挿入したあとgofmtをかけています。最初はASTのままコメントを挿入しようとしたのですが、狙った箇所にコメントを挿入するのが非常に難しかったため、1行ずつファイルに書き出す作戦にシフトしました。インデントの深さを //nolint directiveの対象となる行と揃えないとうまく動かない、ということが分かったのでインデントを揃える工夫をしてあります。

テストコードは以下のような実装になっています。 - golangci-lintを実行する - autonolintでコードを修正する - 修正したコードに対してgolangci-lintを走らせて、エラーが出ないことを確かめる

テストが落ちたときにどのようなコードが出力されたのか簡単に確かめられるように、t.Tempdir() メソッドを呼ぶのではなく自分で一時ディレクトリを作成しています。

github.com

現時点での限界

2025/1/16 時点の実装では、同じ行で複数のlinterが警告を出力する場合にうまくコメントを挿入できない可能性があります。たぶん //nolint:a,b のようにlinter名をマージする必要があるんじゃないかと見ています。欲しくなったら実装すると思うけどいつになるかは分かりません。

他にも、ある程度は動くけどまだまだちゃんと動かないケースはいくつかあると思います。

よくある質問

特定のlinterに対してだけnolintを挿入したい

golangci-lintの --enable-only オプションで特定のlinterのみを有効にすることができます。これと組み合わせることで実現できます。

$ golangci-lint run --enable-only=nilnil --out-format=json | autonolint

あわせて読みたい

blog.utgw.net

*1: (T, error) を返す関数・メソッドから nil, nil を返すコードを検出するlinter