Chapter 03

golangci-lint に搭載されている linter を学ぶ

さんぽし
さんぽし
2021.07.20に更新

golangci-lintにはかなりの数のlinterが搭載されています。この章では各linterの紹介を行います。(非推奨のlinterの紹介は行いません。)

一覧は以下から閲覧できます
https://golangci-lint.run/usage/linters/

Presetsという列を見ることでそのlinterがどのような種類の報告をするものなのかを確認できます。また、AutoFixという列にチェックマークが入っている場合は--fixオプションで報告を自動的に修正する機能をlinterが持っていることを意味しています。

また、ソースコードの/test/testdataを覗くと実際にどのような報告が行われるのかを見ることができます。linterの細かい動きを確認したい時におすすめです。
(この記事内のサンプルのほとんどもここから取ってきています)

[ちょっとその前に]非推奨のlinterって?

golangci-lintではリポジトリがアーカイブされるなどの理由によって搭載されていたlinterが非推奨の扱いになることがあります。非推奨のlinterも一覧から確認することができます。

https://golangci-lint.run/usage/linters/

非推奨になったlinterは将来的にgolangci-lintから削除される予定です。
具体的に非推奨になったlinterをこれからどのように扱っていくかは以下のissueで議論が行われています。

https://github.com/golangci/golangci-lint/issues/1987

デフォルトで有効なlinter

deadcode

Finds unused code

以下のように使用していない関数や変数を報告します

var unused int // ERROR "`unused` is unused"

func g(x int) { // ERROR "`g` is unused"
}

errcheck

Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases

以下のようにエラーの戻り値が確認されていない際に報告を行います。
Goではエラーはpanicではなく戻り値で呼び出し元に知らせるのが良いとされています。

func MissedErrorCheck() {
	RetErr() // ERROR "Error return value is not checked"
}

gosimple

Linter for Go source code that specializes in simplifying a code

必要のないいくつかのパターンのコードを教えてくれます。賢い

func Gosimple(ss []string) {
	if ss != nil { // ERROR "S1031: unnecessary nil check around range"
		for _, s := range ss {
			log.Printf(s)
		}
	}
}

govet

Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string

公式のgo vetです
https://golang.org/cmd/vet/

ineffassign

Detects when assignments to existing variables are not used

不要な代入が行われている箇所を報告します。以下だと直後に代入されているので不要ですね。賢い

func _() {
	x := math.MinInt8
	for {
		_ = x
		x = 0 // ERROR "ineffectual assignment to x"
		x = 0
	}
}

staticcheck

Staticcheck is a go vet on steroids, applying a ton of static analysis checks

以下です。複数のチェック項目があります。

https://staticcheck.io/

structcheck

Finds unused struct fields

使用していないフィールドを報告します

type t struct {
	unusedField int // ERROR "`unusedField` is unused"
}

typecheck

Like the front-end of a Go compiler, parses and type-checks Go code

コンパイルが通るかをチェックします。

https://github.com/golangci/golangci-lint/blob/master/pkg/golinters/typecheck.go#L16

unused

Checks Go code for unused constants, variables, functions and types

使用されていない変数や定数、関数や型などを報告します


func fn1() {} // ERROR "func `fn1` is unused"

varcheck

Finds unused global variables and constants

使用されていないグローバル変数、定数を報告します

var v string // ERROR "`v` is unused"

デフォルトでは有効ではないlinter

asciicheck

Simple linter to check that your code does not contain non-ASCII identifiers

non-ASCIIな識別子を見つけて報告します。

type AsciicheckTеstStruct struct { // ERROR `identifier "AsciicheckTеstStruct" contain non-ASCII character: U\+0435 'е'`
	Date time.Time
}

bodyclose

checks whether HTTP response body is closed successfully

responseがcloseされているかどうかを確認します。

func BodycloseNotClosed() {
	resp, _ := http.Get("https://google.com") // ERROR "response body must be closed"
	_, _ = ioutil.ReadAll(resp.Body)
}

cyclop

checks function and package cyclomatic complexity

関数やパッケージのcyclomatic complexityを確認します。

func cyclopComplexFunc(s string) { // ERROR "calculated cyclomatic complexity for function cyclopComplexFunc is 22, max is 15"
	if s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" {
		return
	}
	if s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" {
		return
	}
	if s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" {
		return
	}
}

depguard

Go linter that checks if package imports are in a list of acceptable packages

設定に使用しないパッケージなどを記載しておき、その確認を行います

import (
	"compress/gzip" // ERROR "`compress/gzip` is in the blacklist"
	"log"           // ERROR "`log` is in the blacklist: don't use log"
)
linters-settings:
  depguard:
    include-go-root: true
    packages: 
      - compress/*
    packages-with-error-message:
      log: "don't use log"

dogsled

Checks assignments with too many blank identifiers (e.g. x, , , _, := f())

多くのブランク演算子(_)を使用している箇所を報告します

func Dogsled() {
	_ = ret1()
	_, _ = ret2()
	_, _, _ = ret3()    // ERROR "declaration has 3 blank identifiers"
	_, _, _, _ = ret4() // ERROR "declaration has 4 blank identifiers"
}

dupl

Tool for code clone detection

コードが重複している箇所を報告します。

func (logger *DuplLogger) First(args ...interface{}) { // ERROR "14-23 lines are duplicate of `.*dupl.go:25-34`"
	if logger.level() >= 0 {
		logger.Debug(args...)
		logger.Debug(args...)
		logger.Debug(args...)
		logger.Debug(args...)
		logger.Debug(args...)
		logger.Debug(args...)
	}
}

func (logger *DuplLogger) Second(args ...interface{}) { // ERROR "25-34 lines are duplicate of `.*dupl.go:14-23`"
	if logger.level() >= 1 {
		logger.Info(args...)
		logger.Info(args...)
		logger.Info(args...)
		logger.Info(args...)
		logger.Info(args...)
		logger.Info(args...)
	}
}

durationcheck

check for two durations multiplied together

time.Duration型同士の掛け算を報告します。

func durationcheckCase05() {
	someDuration := 2 * time.Second
	timeToWait := someDuration * time.Second // ERROR "Multiplication of durations: `someDuration \\* time.Second`"
	time.Sleep(timeToWait)
}

errorlint

errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13.

error型はerrors.Isで比較することや、fmt.Errorfでのerror型のwrapでは%wを使うことや、errorの型の変換にはerrors.Asを使用することなどをチェックします。

func errorLintAll() {
	err := func() error { return nil }()
	if err == errLintFoo { // ERROR "comparing with == will fail on wrapped errors. Use errors.Is to check for a specific error"
		log.Println("errCompare")
	}

	err = errors.New("oops")
	fmt.Errorf("error: %v", err) // ERROR "non-wrapping format verb for fmt.Errorf. Use `%w` to format errors"

	switch err.(type) { // ERROR "type switch on error will fail on wrapped errors. Use errors.As to check for specific errors"
	case *errLintBar:
		log.Println("errLintBar")
	}
}

Go 1.13からerror型はfmt.Errorf()を使用してwrapすることができ、==などで比較するとwrapされているerrorをうまく比較できない場合があるためです。

詳しくは→ https://golang.org/pkg/errors/

exhaustive

check exhaustiveness of enum switch statements

Goではiotaを使用してenumを表現することがありますが、switch-case文でその全ての場合が確認されているかどうかをチェックします

type Direction int

const (
	North Direction = iota
	East
	South
	West
)

func processDirection(d Direction) {
	switch d { // ERROR "missing cases in switch of type Direction: East, West"
	case North, South:
	}
}

exhaustivestruct

Checks if all struct's fields are initialized

全てのフィールドが初期化されているかどうかを確認します。

type Test struct {
	A string
	B int
	c bool // private field inside the same package are not ignored
	D float64
	E time.Time
}

var failPrivate = Test{ // ERROR "c is missing in Test"
	A: "a",
	B: 0,
	D: 1.0,
	E: time.Now(),
}

exportloopref

checks for pointers to enclosing loop variables

ループ変数の使用のされ方を確認します。ループ変数と読んでいるのはfor i, p := range slice {}でいうところのipのことです。

以下の例は少し複雑ですね。pのポインタをループの外のスコープのスライスや配列にappendしているのが誤りで、これだとpは最後に13になるので、10, 11, 12, 13と表示されて欲しいところが全て13が表示されます。
実行: https://play.golang.org/p/3JP6CF-4PEn

func dummyFunction() {
	var array [4]*int
	var slice []*int
	var ref *int
	var str struct{ x *int }

	fmt.Println("loop expecting 10, 11, 12, 13")
	for i, p := range []int{10, 11, 12, 13} {
		printp(&p)
		slice = append(slice, &p) // ERROR "exporting a pointer for the loop variable p"
		array[i] = &p             // ERROR "exporting a pointer for the loop variable p"
		if i%2 == 0 {
			ref = &p   // ERROR "exporting a pointer for the loop variable p"
			str.x = &p // ERROR "exporting a pointer for the loop variable p"
		}
		var vStr struct{ x *int }
		var vArray [4]*int
		var v *int
		if i%2 == 0 {
			v = &p
			vArray[1] = &p
			vStr.x = &p
		}
		_ = v
	}

	fmt.Println(`slice expecting "10, 11, 12, 13" but "13, 13, 13, 13"`)
	for _, p := range slice {
		printp(p)
	}
	fmt.Println(`array expecting "10, 11, 12, 13" but "13, 13, 13, 13"`)
	for _, p := range array {
		printp(p)
	}
	fmt.Println(`captured value expecting "12" but "13"`)
	printp(ref)
}

func printp(p *int) {
	fmt.Println(*p)
}

forbidigo

Forbids identifiers

特定の表現の使用を禁止することができます
例だとfmt\.Print.*にマッチする表現を全て禁止していますね

func Forbidigo() {
	fmt.Printf("too noisy!!!") // ERROR "use of `fmt\\.Printf` forbidden by pattern `fmt\\\\.Print\\.\\*`"
}
linters-settings:
  forbidigo:
    forbid:
      - fmt\.Print.*

forcetypeassert

finds forced type assertions

ai, ok := a.(int)このような形で確認をしていないtype-assertを報告します。panicの原因になります

func forcetypeassertInvalid() {
	var a interface{}
	_ = a.(int) // ERROR "type assertion must be checked"

	var b interface{}
	bi := b.(int) // ERROR "type assertion must be checked"
	fmt.Println(bi)
}

funlen

Tool for detection of long functions

関数bodyの行数が長すぎる関数やstatementが多すぎる関数を報告します。

func TooManyLines() { // ERROR `Function 'TooManyLines' is too long \(22 > 20\)`
	t := struct {
		A string
		B string
		C string
		D string
		E string
		F string
		G string
		H string
		I string
	}{
		`a`,
		`b`,
		`c`,
		`d`,
		`e`,
		`f`,
		`g`,
		`h`,
		`i`,
	}
	_ = t
}

func TooManyStatements() { // ERROR `Function 'TooManyStatements' has too many statements \(11 > 10\)`
	a := 1
	b := a
	c := b
	d := c
	e := d
	f := e
	g := f
	h := g
	i := h
	j := i
	_ = j
}

gci

Gci control golang package import order and make it always deterministic.

goimportsみたいなやつです。autofixに対応しており、gciがちゃんとかけられているかどうかの確認もすることができます。
個人的には、結果が一意に決まるのでgoimportsではなく、gciを使うことが多いです。
例は公式のREADMEを見るのが分かりやすいです

https://github.com/daixiang0/gci

package main
import (
  "golang.org/x/tools"

  "fmt"

  "github.com/daixiang0/gci"
)

package main
import (
  "fmt"

  "golang.org/x/tools"

  "github.com/daixiang0/gci"
)

gochecknoglobals

check that no global variables exist
This analyzer checks for global variables and errors on any found.
A global variable is a variable declared in package scope and that can be read and written to by any function within the package. Global variables can cause side effects which are difficult to keep track of. A code in one function may change the variables state while another unrelated chunk of code may be effected by it.

グローバル変数を報告します。しかし以下の例にもあるように特定の表現に関しては報告を行いません。
READMEを見ると具体的にどのような表現には報告を行わないか記載があります

https://github.com/leighmcculloch/gochecknoglobals

var noGlobalsVar int // ERROR "noGlobalsVar is a global variable"
var ErrSomeType = errors.New("test that global erorrs aren't warned")

var (
	OnlyDigites = regexp.MustCompile(`^\d+$`)
	BadNamedErr = errors.New("this is bad") // ERROR "BadNamedErr is a global variable"
)

gochecknoinits

Checks that no init functions are present in Go code

init関数を使用している箇所を報告します

func init() { // ERROR "don't use `init` function"
	fmt.Println()
}

gocognit

Computes and checks the cognitive complexity of functions

cognitive complexityを計算して設定値よりcognitive complexityが高くなっている箇所を報告します。

// 設定値は2になっています
func GoCognit_CC3_Fact(n int) int { // ERROR "cognitive complexity 3 of func .* is high .*"
	if n <= 1 { // +1
		return 1
	} else { // +1
		return n + GoCognit_CC3_Fact(n-1) // +1
	}
} // total complexity = 3

goconst

Finds repeated strings that could be replaced by a constant

定数になっておらず、何度も繰り返し登場しているstring型の値を報告します。

func GoconstA() {
	a := "needconst" // ERROR "string `needconst` has 5 occurrences, make it a constant"
	fmt.Print(a)
	b := "needconst"
	fmt.Print(b)
	c := "needconst"
	fmt.Print(c)
}

func GoconstB() {
	a := "needconst"
	fmt.Print(a)
	b := "needconst"
	fmt.Print(b)
}

const AlreadyHasConst = "alreadyhasconst"

func GoconstC() {
	a := "alreadyhasconst" // ERROR "string `alreadyhasconst` has 3 occurrences, but such constant `AlreadyHasConst` already exists"
	fmt.Print(a)
	b := "alreadyhasconst"
	fmt.Print(b)
	c := "alreadyhasconst"
	fmt.Print(c)
	fmt.Print("alreadyhasconst")
}

gocritic

Provides many diagnostics that check for bugs, performance and style issues.
Extensible without recompilation through dynamic rules.
Dynamic rules are written declaratively with AST patterns, filters, report message and optional suggestion.

多くのチェックを搭載している系のやつです。
チェック項目は↓
https://go-critic.github.io/overview

gocyclo

Computes and checks the cyclomatic complexity of functions

cyclomatic complexityを計算して設定値よりcyclomatic complexityが高くなっている箇所を報告します。

func GocycloBigComplexity(s string) { // ERROR "cyclomatic complexity .* of func .* is high .*"
	if s == http.MethodGet || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" {
		return
	}

	if s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" {
		return
	}

	if s == "1" || s == "2" || s == "3" || s == "4" || s == "5" || s == "6" || s == "7" {
		return
	}
}

godot

Check if comments end in a period

コメントがピリオドで終わっているかを確認します。autofixに対応しています

godox

Tool for detection of FIXME, TODO and other comment keywords

TODO, FIXMEなどのキーワードのついたコメントを発見します

goerr113

Golang linter to check the errors handling expressions

errorlintと似ていますね。比較がerrors.Isで行われているかを確認します。また、error型が動的に生成されている箇所も検出します。

https://github.com/Djarvur/go-err113

func SimpleEqual(e1, e2 error) bool {
	return e1 == e2 // ERROR `err113: do not compare errors directly "e1 == e2", use "errors.Is\(e1, e2\)" instead`
}

func SimpleNotEqual(e1, e2 error) bool {
	return e1 != e2 // ERROR `err113: do not compare errors directly "e1 != e2", use "!errors.Is\(e1, e2\)" instead`
}

gofmt

Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification

go fmtがかけられているかを報告します。Autofixに対応しています

gofumpt

Gofumpt checks whether code was gofumpt-ed.

gofmtに幾つかのルールを追加した感じのものです。Autofixに対応しています

https://github.com/mvdan/gofumpt

goheader

Checks is file header matches to pattern

fileのヘッダーがパターンとマッチしているかどうかを確認します。コピーライトを全てのファイルに記載しているプロジェクトで有効なlinterだと思います。

https://github.com/denis-tingaikin/go-header

goimports

Goimports does everything that gofmt does. Additionally it checks unused imports

言わずと知れたgoimportsです。

gomnd

An analyzer to detect magic numbers.

マジックナンバーを検出します。

func UseMagicNumber() {
	c := &http.Client{
		Timeout: 2 * time.Second, // ERROR "Magic number: 2, in <assign> detected"
	}

	res, err := c.Get("http://www.google.com")
	if err != nil {
		log.Fatal(err)
	}
	if res.StatusCode != 200 { // ERROR "Magic number: 200, in <condition> detected"
		log.Println("Something went wrong")
	}
}

gomoddirectives

Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod.

go.mod内のreplace, retract, excludesの使用方法に関して制限をかけたりするlinterです。

詳しくは公式のREADMEを確認してください。
https://github.com/ldez/gomoddirectives

gomodguard

Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations.

使用するパッケージに関してホワイトリストやブラックリスト形式で設定することができます

import (
	"log"

	"golang.org/x/mod/modfile"
	"gopkg.in/yaml.v3" // ERROR "import of package `gopkg.in/yaml.v3` is blocked because the module is in the blocked modules list. `github.com/kylelemons/go-gypsy` is a recommended module. This is an example of recommendations."
)

goprintffuncname

Checks that printf-like functions are named with f at the end

fmt.Printfのようにフォーマットを受け取る関数の名前の終わりがfで終了しているかどうかを確認します。

func PrintfLikeFuncWithBadName(format string, args ...interface{}) { // ERROR "printf-like formatting function 'PrintfLikeFuncWithBadName' should be named 'PrintfLikeFuncWithBadNamef'"
}

gosec

Inspects source code for security problems

セキュリティという観点で様々な確認をします
https://securego.io/

ifshort

Checks that your code uses short syntax for if-statements whenever possible

Goではif文でのみ使用されている変数はif文の中で定義することができます。ifshortはそれをやってない箇所を判定します。

err := otherFunc1() // ERROR "variable 'err' is only used in the if-statement .*"
if err != nil {
	otherFunc2(err)
}

↓このように書くべきです

if err := otherFunc1(); err != nil {
		otherFunc2(err)
}

importas

Enforces consistent import aliases

aliasを張る際の名前が設定した名前になっているかを確認します。

import (
	wrong_alias "fmt" // ERROR `import "fmt" imported as "wrong_alias" but must be "fff" according to config`
	"os"
	wrong_alias_again "os" // ERROR `import "os" imported as "wrong_alias_again" but must be "std_os" according to config`

	wrong "github.com/pkg/errors" // ERROR `import "github.com/pkg/errors" imported as "wrong" but must be "pkgerr" according to config`
)
linters-settings:
  importas:
    alias:
      - pkg: fmt
        alias: fff
      - pkg: os
        alias: std_os
      - pkg: github.com/pkg/errors
        alias: pkgerr

lll

Reports long lines

一行がとても長くなっている部分を確認します。

func Lll() {
	// In my experience, long lines are the lines with comments, not the code. So this is a long comment // ERROR "line is 138 characters"
}

makezero

Finds slice declarations with non-zero initial length

makeの第二引数はスライスの長さを表します。makezeroは長さが0ではない数で初期化されたスライスに対するappendを検出します。

func Makezero() []int {
	x := make([]int, math.MaxInt8)
	return append(x, 1) // ERROR "append to slice `x` with non-zero initialized length"
}

misspell

Finds commonly misspelled English words in comments

スペルを間違えている箇所を検出します。
こちらはauto fixに対応していますが、個人的には固有名詞をスペルが違うと判定することがあるので注意が必要だと思っています。

nakedret

Finds naked returns in functions greater than a specified function length

行数が長い関数で名前付き戻り値が使用されているかつnaked return(名前付き戻り値を使っているときに、returnだけを書くだけでその時の名前付き戻り値の値を返せると言うやり方)が使用されている箇所を検出します

func NakedretIssue() (a int, b string) {
	if a > 0 {
		return
	}

	if b == "" {
		return 0, "0"
	}

	// ...
	// ...
	// ...
	// ...
	// ...
	// ...
	// ...
	// ...
	// ...
	// ...
	// ...
	// ...
	// ...
	// ...
	// ...
	// ...
	// ...
	// ...
	// ...

	// len of this function is 31
	return // ERROR "naked return in func `NakedretIssue` with 31 lines of code"
}

nestif

Reports deeply nested if statements

深くネストしているif文を報告します。設定でどのレベルの深さのネストを検出するかを指定することができます。

if b1 { // ERROR "`if b1` is deeply nested \\(complexity: 5\\)"
	if b2 { // +1
	} else if b3 { // +1
		if b4 { // +2
		}
	} else { // +1
	}
}

nilerr

Finds the code that returns nil even if it checks that the error is not nil.

err != nilの確認をした後にerrorを返却していない箇所を検出します。

err := nilErrDo()
if err != nil {
	return nil // ERROR `error is not nil \(line 25\) but it returns nil`
}

nlreturn

nlreturn checks for a new line before return and branch statements to increase code clarity

return文やbreak文などの前に空行がない箇所を検出します。

_ = a
return nil // ERROR "return with no blank line before"

noctx

noctx finds sending http request without context.Context

httpリクエストをcontextなしで送信している箇所を検出します。

http.Get(url) // ERROR `net/http\.Get must not be called`

nolintlint

Reports ill-formed or insufficient nolint directives

nolintを指定した箇所になぜnolintであるかの理由などを書いていない箇所などのnolintの使用上よろしくない点を検出します

詳しくはREADMEを確認してください。

https://github.com/golangci/golangci-lint/tree/master/pkg/golinters/nolintlint

paralleltest

paralleltest detects missing usage of t.Parallel() method in your Go test

Goではt.Parallel()を使用してテストを並列に実行することができます。paralleltestはt.Parallel()を使用していない箇所を見つけます

func TestFunctionMissingCallToParallel(t *testing.T) {} // ERROR "Function TestFunctionMissingCallToParallel missing the call to method parallel"

prealloc

Finds slice declarations that could potentially be preallocated

forループ内でappendしてしまうとアロケーションが走ってしまう可能性があります。初期化する時点でcapacityを設定することでこれを回避することができます。

var dest []int // ERROR "Consider preallocating `dest`"
for _, v := range source {
	dest = append(dest, v)
}

以下のように改修できます

dest := make([]int, 0, len(source))
for _, v := range source {
	dest = append(dest, v)
}

predeclared

find code that shadows one of Go's predeclared identifiers

goのbuildinで使用されている識別子を使用している箇所を検出します。以下では、recoverがすでにbuildinの関数として存在しているため報告が行われます。

func recover() {} // ERROR "function recover has same name as predeclared identifier"

promlinter

Check Prometheus metrics naming via promlint

Prometheusのメトリクスのネーミングに関するチェックをします

_ = promauto.NewCounterVec(
	prometheus.CounterOpts{ // ERROR `Metric: test_metric_name Error: counter metrics should have "_total" suffix`
		Name: "test_metric_name",
		Help: "test help text",
	}, []string{},
)

_ = promauto.NewCounterVec(
	prometheus.CounterOpts{ // ERROR "Metric: test_metric_total Error: no help text"
		Name: "test_metric_total",
	}, []string{},
)

revive

Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint.

非推奨になってしまったかつてのGo公式linterである、golintと似たチェックを行います。

golangci-lint上ではreviveがgolintの代替として推奨されています。
https://zenn.dev/sanpo_shiho/articles/09d1da9af91998#revive

rowserrcheck

checks whether Err of rows is checked successfully

database/sqlのRowsのエラーが正しく処理されているかを確認します。
https://golang.org/pkg/database/sql/#Rows.Err

sqlclosecheck

Checks that sql.Rows and sql.Stmt are closed.

sql.Rowsやsql.Stmtがcloseされてるかどうかを確認します。

stylecheck

Stylecheck is a replacement for golint

golintの代替を目指したlinterのようです。

tagliatelle

Checks the struct tags.

structのタグの命名のチェックします。

https://github.com/ldez/tagliatelle

type TglFoo struct {
	ID     string        `json:"ID"`     // ERROR `json\(camel\): got 'ID' want 'id'`
	UserID string        `json:"UserID"` // ERROR `json\(camel\): got 'UserID' want 'userId'`
	Name   string        `json:"name"`
	Value  time.Duration `json:"value,omitempty"`
	Bar    TglBar        `json:"bar"`
	Bur    `json:"bur"`
}

testpackage

linter that makes you use a separate _test package

Goではtestはパッケージ名_testという名前に変更することができます。このlinterではtestのパッケージを分けていることを確認し、分けていないパッケージがあればそれを検出します。
導入するかどうかはプロジェクトの方針によりそうですね。

thelper

thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers

testのhelper関数にt.Helper()をつけているかどうかを確認するlinterです。その他、testのhelper関数に関するチェックも行います

チェック項目はREADMEに一覧があります。

https://github.com/kulti/thelper

tparallel

tparallel detects inappropriate usage of t.Parallel() method in your Go test codes

tparallelはt.Parallel()を使用していない箇所を見つけます. 前述のparalleltestと似ていますね。

unconvert

Remove unnecessary type conversions

必要のないtypeの変換を検出します。

func Unconvert() {
	a := 1
	b := int(a) // ERROR "unnecessary conversion"
	log.Print(b)
}

unparam

Reports unused function parameters

使用していない引数を検出します。

func unparamUnused(a, b uint) uint { // ERROR "`unparamUnused` - `b` is unused"
	a++
	return a
}

wastedassign

wastedassign finds wasted assignment statements.

不要な代入を検出するlinterです。
ちなみに筆者が作りました。

https://github.com/sanposhiho/wastedassign

func f() int {
	a := 0 
        b := 0
        fmt.Print(a)
        fmt.Print(b)
        a = 1  // ここより後にaの値が使用されることはないのでここでの代入は不要です。

        b = 1  // この次の行ですぐに数値が書き換えられているのでここでの代入は不要です。
        b = 2
        fmt.Print(b)
        
	return 1 + 2
}

whitespace

Tool for detection of leading and trailing whitespace

関数やif, forなどの最初や最後に不要な改行がないかを確認します

wrapcheck

Checks that errors returned from external packages are wrapped

関数から帰ってきたerrorをif err != nilの時にそのままreturnしている箇所を検出します。

func do() error {
	_, err := json.Marshal(struct{}{})
	if err != nil {
		return err // ERROR "error returned from external package is unwrapped"
	}

	return nil
}

以下のようにfmt.Errorf("json marchal: %w", err)という形でerrorをwrapしてreturnするべきです

func do() error {
	_, err := json.Marshal(struct{}{})
	if err != nil {
		return fmt.Errorf("json marchal: %w", err) // ERROR "error returned from external package is unwrapped"
	}

	return nil
}

wsl

Whitespace Linter - Forces you to use empty lines!

適切な箇所に空行を入れることを目的としたlinterです。

具体的な検出箇所に関してはREADMEに記載があります。

https://github.com/bombsimon/wsl

golangci-lintに載っていないLinterを実行する

golangci-lintは自身に搭載していないlinterをcustom linterとして実行する機能を持っています。

https://golangci-lint.run/contributing/new-linters/#how-to-add-a-private-linter-to-golangci-lint

しかし、brewやdockerからgolangci-lintをinstallした場合はこの機能は使用できません。

golangci-lintはCGO_ENABLED=0でビルドされていますが、pluginのロードのためはCGO_ENABLED=1でビルドされる必要があるためです。現状pluginを使用するにはgolangci-lintをソースコードからビルドする必要があります。

関連するissueは以下になります。

https://github.com/golangci/golangci-lint/issues/1276
https://github.com/golang/go/issues/19569

終わりに

次の章では少し視点を変え、golangci-lintの内部実装から理解を進めていきます。