記事の概要
個人開発中、golangci-lint v1系で ent の自動生成コードに構文エラー扱いが出る → v2.3.0 へ上げたら解消という現象が起きました。その問題について、原因から解決まで順序だててまとめた技術メモです。
ざっくり内容
-
問題点: Go 1.24.x + ent v0.14.4 のプロジェクトで、
golangci-lint v1.64.2を回すと ent の生成コードに対して構文エラーが出る。 -
直接原因: v1系にバンドルされた解析器/アナライザ(
staticcheck/gosimple/govetなど)のバージョンやビルド Go バージョンが、プロジェクト側(Go 1.24.x)に追随しておらず、新しい構文やコード生成パターンに追いつけていなかった。 -
解決策:
golangci-lintを v2.3.0 に上げ、設定ファイルを v2 形式へ移行。これにより解析スタックが更新され、ent 生成コードの誤判定が解消。加えて v2 での書式(formatters)・設定キー変更に対応した。 -
根拠ドキュメント: v2 への移行ガイドと新設定モデル(
version: 2、formatter の導入、goimportsの位置づけ変更など)を参照。 (golangci-lint)
環境
-
Go: 1.24.x
-
ent: v0.14.4
-
golangci-lint:
- 以前: v1.64.2(コンテナ側)。ローカルでは v1.62.0 など古いものも混在
- 以後: v2.3.0(コンテナ/ローカル共に統一)
何が起きていたのか
現れた症状(例)
-
ent の
ent/配下の自動生成コード(Code generated by entc... DO NOT EDIT.などのヘッダ付き)で構文エラー扱いやパーサの失敗が起きる。 -
あるいは
golangci-lint本体が 「ビルドに使われた Go が対象より古い」 と怒られる:the Go language version used to build golangci-lint is lower than the targeted Go version
このチェックは v1系の段階から導入されており、バイナリが古い Go でビルドされていると失敗します。 (GitHub)
背景となる原因
-
golangci-lintは 複数のアナライザ(staticcheck/gosimple/govetなど)を同梱しており、それらがビルドされた Go のバージョンやアナライザ自身の対応範囲に依存します。 - プロジェクト側が新しめの Go(1.24.x)、かつ ent の生成コードが最新の構文/パターン(ジェネリクスのエッジケースや build tag、unsafe の使い方等)を含んでいる場合、古いアナライザがパースできずに壊れる/誤爆することがあります。
- v2 系では
golangci-lint本体・同梱アナライザ群・設定体系が大きく更新され、新しい Go に対する互換性や挙動が改善されています(設定モデル刷新、フォーマッタ分離など)。 (golangci-lint)
解決までの流れ
1) golangci-lint を v2 へアップグレード
-
Homebrew(ローカル):
brew upgrade golangci-lint golangci-lint version # バージョン表示で v2.3.0 を確認 -
Go ツールチェインで明示ピン留め:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v2.3.0 -
Dockerfile(開発用コンテナ):
# 旧: v1.64.2 # RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.2 # 新: v2.3.0 RUN go install github.com/golangci/golangci-lint/cmd/golangci-lint@v2.3.0 -
プロジェクトがターゲットとする Go バージョン(go.mod の go directive)以上の Go でビルドされた golangci-lint を使うこと。古いバイナリだと前述のエラーが出ます。 (GitHub)
2) 設定ファイルを v2 形式に移行
v2 では 設定モデルが変わりました。代表的な変更点:
-
必須: ルートに
version: 2を書く -
goimportsは linter ではなく “formatter” に移動(linters.enableから外してformatters.enableに入れる) - 旧キーが非互換:
output.formats→output.formatなど -
govet.check-shadowingのようなdeprecated 設定は整理(shadow は独立 linter)
公式の Migration Guide と Configuration(v2) ドキュメントが全体像です。 (golangci-lint)
また Formatters の概念/一覧(goimports を含む)は専用ページにまとまっています。 (golangci-lint)
既存の v1 設定から自動変換したい場合は
golangci-lint migrateが用意されています(ただしコメントは落ちます)。 (golangci-lint)
v2 用の最小サンプル(今回のケースに合わせた例)
version: 2
run:
timeout: 5m
issues:
# 生成物/モックを丸ごと外す(正規表現はリポジトリルート相対)
exclude-dirs:
- ^ent($|/) # ent 生成コード
- ^src/mocks($|/) # mockery 生成コード
# 一時的に特定ファイルを外す場合
exclude-files:
- ^src/infrastructure/app_client\.go$
linters-settings:
gocyclo:
min-complexity: 15
govet: {} # 個別設定不要なら空でOK
linters:
enable:
- errcheck
- govet
- ineffassign
- staticcheck
- gocyclo
# - revive # 必要に応じて
# ← v2 から導入: フォーマッタは linters とは別セクション
formatters:
enable:
- goimports
goimports:
local-prefixes: word_app/backend
output:
format: colored-line-number
sort-results: true
- 以前の設定のまま v2 を起動すると、
unsupported version of the configurationやgoimports is a formatterなどのエラーが出ます。これは 設定モデルの変更が原因なので、上記のようにversion: 2を宣言し、goimportsを formatters へ移動するなど、移行作業が必要となります。 (golangci-lint)
3) 除外設定の “効かない” を潰す
-
exclude-dirsは リポジトリルートからの正規表現でマッチします。
ent,src/mocksなどは^ent($|/),^src/mocks($|/)のように先頭・末尾を意識した正規表現にすると安定します。 - それでも個別に出る場合は
issues.exclude-rulesで “ファイルパス + 対象 linter + メッセージ” をピンポイントで抑制できます(テスト用途の自動生成 Mock に対してのみstaticcheckを外す等)。
issues:
exclude-dirs:
- ^ent($|/)
- ^src/mocks($|/)
exclude-rules:
- path: ^src/mocks/
linters:
- staticcheck
- errcheck
- revive
text: ".*" # 生成 Mock は全面除外(必要最小限に)
なお、golangci-lint は 生成コード(ヘッダに “Code generated by … DO NOT EDIT.”) を自動判定してスキップする機構も持っています(
generated_file_filter)。ただしすべての生成物を網羅できるわけではないため、ディレクトリ除外を併用するのが実務的です。
v2 へ上げたらなぜ直ったのか(メカニズム)
-
バンドルされたアナライザ/パーサの更新
v2 系では、staticcheckをはじめとする解析器が刷新・統合され、新しい Go バージョンやエッジな構文に対する互換性が上がっています。ent 生成コードで起きていたパース失敗/誤検知が解消しました。 (golangci-lint) -
ツール自体のビルド Go バージョン整合
「golangci-lint をビルドした Go バージョン < プロジェクトのターゲット Go」だと実行時に弾かれるチェックもあり、ここが揃うことで解析系が正しく動作します。 (GitHub) -
設定モデルの整理
goimportsを linter ではなく formatter として扱うなど、v2 の設計に合わせて設定を整理したことで、設定起因のエラーが消滅。本質的なコード解析に集中できる状態になりました。 (golangci-lint)
付録: よく出たエラーと対処メモ
-
unsupported version of the configuration
→version: 2を設定ファイルの先頭に追加。設定キーも v2 に合わせて見直す。 (golangci-lint) -
goimports is a formatter
→linters.enableからgoimportsを外し、formatters.enableに移動。 (golangci-lint) -
the Go language version used to build golangci-lint is lower than the targeted Go version
→golangci-lintバイナリを更新(Homebrew /go install @v2.3.0/ Docker)。プロジェクトの Go と整合させる。 (GitHub) -
モック/生成コードが除外されない
→exclude-dirsの正規表現をリポジトリルート相対で見直し(^src/mocks($|/)等)。必要に応じてexclude-rulesで個別無効化。
まとめ
- 症状は「v1 系の解析スタックが、Go 1.24 + ent の生成コードに追随できず壊れる/誤爆する」こと。
- 解決は「golangci-lint を v2(今回は v2.3.0)へ上げ、設定を v2 仕様に移行」。
- これで ent 生成コードでの構文エラー相当が消え、lint が安定稼働するようになりました。
- v2 は 設定体系も変わっているため、
version: 2、formatter(goimports)の導入、deprecated 設定の除去を忘れずに。 (golangci-lint)
参考リンク
-
Migration Guide (v2) – 移行の全体像、
migrateコマンド、非互換点一覧
(golangci-lint) -
Configuration (v2) – 新しい設定モデル(
version: 2など)
(golangci-lint) -
Formatters –
goimportsを含むフォーマッタの説明
(golangci-lint) -
Changelog – v2 の主な変更(解析器刷新・統合等)
(golangci-lint) -
Go バージョン整合のチェック(ビルド Go が古いと失敗)
(GitHub)