[[golangci-lint]]を使ってみる。目的は[[go-exhaustruct]]を[[Neovim]]で使うこと。 ## golangci-lintのインストール [[mise]]でインストール。 ```console mise use -g golangci-lint ``` ## 実行 クイックスタートに記載されているとおりに。 <div class="link-card"> <div class="link-card-header"> <img src="https://golangci-lint.run/favicon-32x32.png?v=6f2f64f27c627571f538b670491b3f23" class="link-card-site-icon"/> <span class="link-card-site-name">golangci-lint</span> </div> <div class="link-card-body"> <div class="link-card-content"> <p class="link-card-title">Quick Start | golangci-lint</p> <p class="link-card-description">Fast Go linters runner golangci-lint.</p> </div> <img src="https://golangci-lint.run/logo.png" class="link-card-image" /> </div> <a href="https://golangci-lint.run/welcome/quick-start/"></a> </div> `main.go` ```go package main import ( "log" ) type human struct { Id int Name string } func sum(x int, y int) int { return x + y } func main() { total := sum(1, 10) log.Printf("x + y = %d", total) hoge := "hoge" tadashi := human{ Name: "tadashi", } log.Panicf("%s", tadashi.Name) } ``` 1つ引っかかればOK。 ```console $ golangci-lint run main.go:1: : # sandbox/go-sandbox ./main.go:20:2: hoge declared and not used (typecheck) package main ``` ## 設定ファイルの追加 [[go-exhaustruct]]を使いたいが、デフォルトでは無効なので設定ファイルを作成して有効にする。 `.golangci.yaml` ```yaml linters: enable: - exhaustruct ``` `main.go`も少しシンプルにする。 `main.go` ```go package main import ( "log" ) type human struct { Id int Name string } func main() { tadashi := human{ Name: "tadashi", } log.Panicf("%s", tadashi.Name) } ``` 初期化していない`Id`を検知してエラーを出してくれた。 ```console $ golangci-lint run main.go:13:13: main.human is missing field Id (exhaustruct) tadashi := human{ ^ ``` ## Neovimで利用する [[nvim-lspconfig]]で検知できるようにする。公式ドキュメントでは[[golangci-lint-langserver]]の利用を推奨している。 <div class="link-card"> <div class="link-card-header"> <img src="https://golangci-lint.run/favicon-32x32.png?v=6f2f64f27c627571f538b670491b3f23" class="link-card-site-icon"/> <span class="link-card-site-name">golangci-lint</span> </div> <div class="link-card-body"> <div class="link-card-content"> <p class="link-card-title">Integrations | golangci-lint</p> <p class="link-card-description">Fast Go linters runner golangci-lint.</p> </div> <img src="https://golangci-lint.run/logo.png" class="link-card-image" /> </div> <a href="https://golangci-lint.run/welcome/integrations/"></a> </div> [[golangci-lint-langserver]]をインストール。 ```console go install github.com/nametake/golangci-lint-langserver@latest ``` [[nvim-lspconfig]]の設定を追加。 <div class="link-card"> <div class="link-card-header"> <img src="https://github.githubassets.com/favicons/favicon.svg" class="link-card-site-icon"/> <span class="link-card-site-name">GitHub</span> </div> <div class="link-card-body"> <div class="link-card-content"> <p class="link-card-title">nvim-lspconfig/doc/server_configurations.md at master · neovim/nvim-lspconfig</p> <p class="link-card-description">Quickstart configs for Nvim LSP. Contribute to neovim/nvim-lspconfig development by creating a ... </p> </div> <img src="https://opengraph.githubassets.com/798c64dba28fd528e0109d727e312d6a4586d9604d35d9cf89ea8c8c3956991f/neovim/nvim-lspconfig" class="link-card-image" /> </div> <a href="https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#golangci_lint_ls"></a> </div> ```lua lspconfig.golangci_lint_ls.setup({ capabilities = capabilities }) ``` COOOOOL! ![[Pasted image 20240601165504.png]] ### Staticcheck [[gopls]]で[[Staticcheckを有効 (gopls)|Staticcheckを有効]]にしていたが、[[golangci-lint]]の方がしっかり動いている気がするので無効化する。 ```diff lspconfig.gopls.setup({ capabilities = capabilities, - settings = { - gopls = { - staticcheck = true, - }, - }, }) ``` ## exhaustructのdiagnosticをエラーにする 基本的に認めたくないのでエラーにしたい。`exhaustruct`に対してseverityを設定する方法もあるが、基本的にwarningという中途半端なレベルは利用したくないので、全体のデフォルトをerrorにしてしまう。 `.golangci.yaml` ```yaml linters: enable: - exhaustruct severity: default-severity: error ``` ![[Pasted image 20240601170718.png]] ## リンターを明示的に選択する デフォルトでは色々な[[リンター]]が暗黙的にONになっているが、[[gopls]]と重なる部分もあり冗長に感じる。明示的に有効にしたいものだけ有効にしてみる。 <div class="link-card"> <div class="link-card-header"> <img src="https://golangci-lint.run/favicon-32x32.png?v=6f2f64f27c627571f538b670491b3f23" class="link-card-site-icon"/> <span class="link-card-site-name">golangci-lint</span> </div> <div class="link-card-body"> <div class="link-card-content"> <p class="link-card-title">Linters | golangci-lint</p> <p class="link-card-description">Fast Go linters runner golangci-lint.</p> </div> <img src="https://golangci-lint.run/logo.png" class="link-card-image" /> </div> <a href="https://golangci-lint.run/usage/linters/#list-item-staticcheck"></a> </div> ```yaml linters: disable-all: true enable: - govet - errcheck - gosimple - ineffassign - staticcheck - unused # 以降は明示的に追加 - errorlint - exhaustive - exhaustruct - goimports # コード変更はgoplsで行う - nakedret - revive linters-settings: nakedret: max-func-lines: 0 # Naked returnは認めない severity: default-severity: error ``` それぞれの[[リンター]]については以下。 ### govet [[govet]]を使う設定。 ### errcheck [[errcheck]]を使う設定。 ```go package main import "errors" func maybeError(x *string) error { *x = "hoge" return errors.New("エラー") } func main() { x := "hoge" maybeError(&x) // errcheck: Error return value is not checked errcheck } ``` ### gosimple [go-tools/simple](https://github.com/dominikh/go-tools/tree/master/simple) 配下のルールに基づいて、よりシンプルに書ける場合にエラーを出す。 ```go package main func isXXX() bool { return false } func main() { if isXXX() == true { // gosimple: S1002: should omit comparison to bool constant, can be simplified to `isXXX()` print("true") } } ``` ### ineffassign [[ineffassign]]を使う設定。 ```go package main func increment(x int) { x++ // ineffassign: ineffectual assignment to x } func main() { x := 1 increment(x) print(x) } ``` > [!hint] > [[仮引数]]が[[ポインタ]]でない場合、関数内部で値を変更しても[[実引数]]には影響がない。(実装ミス) ### Staticcheck [[Staticcheck]]を使う設定。確認項目の数が多いので省略。 ### unused 使用されていない変数・関数などがあったらエラーにする設定。 ```go package main const staticValue = "hoge" // unused: const `staticValue` is unused unused type hoge = int // unused: type `hoge` is unused unused func noUseFunc() { // unused: func `noUseFunc` is unused unused } func main() { } ``` > [!hint] > exportされていないモノ(小文字始まりのモノ)に限る。また、関数の引数が利用されていないケースは拾えない。 ### errorlint [[go-errorlint]]を使う設定。 ```go package main import ( "errors" "fmt" ) func main() { originalErr := errors.New("original error") wrappedErr := fmt.Errorf("another error: %v", originalErr) // errorlint: non-wrapping format verb for fmt.Errorf. Use `%w` to format errors if errors.Is(wrappedErr, originalErr) { fmt.Println("They are same!") } } ``` > [!hint] > `%v`を使うと元エラー(`originalErr`)の情報が失われるため、if文は`false`になってしまう。`%w`を使うと元エラーの情報が保持されるのでif文は`true`になる。そのための警告。 > > なお、[[errors.Is]]の代わりに`==`を使おうとした場合などにもエラーを出してくれる。 > ```go > if wrappedErr == originalErr { > fmt.Println("They are same!") // errorlint: comparing with == will fail on wrapped errors. Use errors.Is to check for a specific error > } > ``` ### exhaustive [[exhaustive]]を使う設定。 ```go package main type Fruit int const ( Apple Fruit = iota Orange ) func main() { var fruit Fruit switch fruit { // exhaustive: missing cases in switch of type main.Fruit: main.Orange case Apple: print("apple") } } ``` > [!hint] > switch文には`Orange`のcaseがないので、その場合はswitch文をすり抜けてしまう。それを警告してくれる。 ### exhaustruct [[go-exhaustruct]]を使う設定。 ```go package main import "fmt" type Human struct { Id int Name string Favorite *string `exhaustruct:"optional"` } func main() { human := Human{ Name: "", } fmt.Print(human) } ``` > [!hint] > `Favorite`はポインタだが、[[go-exhaustruct]]では通常明示的な指定が必要になる。しかし、`exhaustruct:"optional"`の[[構造体タグ]]を付与すれば、未指定でも警告されない。面倒だが、ポインタには都度つけた方がいいかも。 ### goimports [[goimports]]の結果と正しくない場合に警告を表示する設定。変更は[[IDE]]などの機能から[[goimports]]を呼び出したときに行う想定。 ```go package main import ( "fmt" // "fmt" imported and not used [UnusedImport] ) func main() { } ``` ### nakedret [[nakedret]]を使う設定。 ```go package main import "fmt" func sum(x int, y int) (total int) { total = x + y return // nakedret: naked return in func `sum` with 3 lines of code } func main() { r := sum(1, 2) fmt.Println(r) } ``` > [!hint] > `max-func-lines: 0`としているため、[[Naked return]]を使っているすべての関数は警告される。 ## 訳ありで使わなかったリンター ### gofumpt [[gofumpt]]の結果と正しくない場合に警告を表示する設定。変更は[[IDE]]などの機能から[[gofumpt]]を呼び出したときに行う想定。 ```go package main import ( "fmt" // gofumpt: File is not `gofumpt`-ed "sandbox/go-sandbox/sub" ) func main() { var a = 6 // gofumpt: File is not `gofumpt`-ed fmt.Println(a) _ = sub.Sum(1, 2) } ``` > [!hint] > 1つ目は空行が不要。2つ目は `a := 6` を提案される。 > [!question] > [[Neovim]]の[[nvim-lspconfig]]で設定するとき、import内に空行が入る/入らないが設定方法によって変わるという怪奇現象が起きている。 > > - [[none-lsでgofumptを使用]]すると空行は発生せず、[[golangci-lint]]の結果と同じ > - [[nvim-lspconfigでgoplsのフォーマッターをgofumptにする]]と、空行が発生し、[[golangci-lint]]からはエラー ### unparam [[unparam]]を使う設定。だけど動かなかった。。 ## 気になること [[🦉gowl]]をクローンして実行してみたら少し遅かった (2秒程度のラグ) ので、大きなプロジェクトだと重くなるかもと思った。ボトルネックになる[[リンター]]を減らせば改善するかもしれないが、並列実行しているならば数を減らすのは効果がなさそう。 まあ、1~2週間くらい様子を見たい。