[[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週間くらい様子を見たい。