- [ちょっとその前に]非推奨のlinterって?
- デフォルトで有効なlinter
- deadcode
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- structcheck
- typecheck
- unused
- varcheck
- デフォルトでは有効ではないlinter
- asciicheck
- bodyclose
- cyclop
- depguard
- dogsled
- dupl
- durationcheck
- errorlint
- exhaustive
- exhaustivestruct
- exportloopref
- forbidigo
- forcetypeassert
- funlen
- gci
- gochecknoglobals
- gochecknoinits
- gocognit
- goconst
- gocritic
- gocyclo
- godot
- godox
- goerr113
- gofmt
- gofumpt
- goheader
- goimports
- gomnd
- gomoddirectives
- gomodguard
- goprintffuncname
- gosec
- ifshort
- importas
- lll
- makezero
- misspell
- nakedret
- nestif
- nilerr
- nlreturn
- noctx
- nolintlint
- paralleltest
- prealloc
- predeclared
- promlinter
- revive
- rowserrcheck
- sqlclosecheck
- stylecheck
- tagliatelle
- testpackage
- thelper
- tparallel
- unconvert
- unparam
- wastedassign
- whitespace
- wrapcheck
- wsl
- golangci-lintに載っていないLinterを実行する
- 終わりに
golangci-lintにはかなりの数のlinterが搭載されています。この章では各linterの紹介を行います。(非推奨のlinterの紹介は行いません。)
一覧は以下から閲覧できます
Presetsという列を見ることでそのlinterがどのような種類の報告をするものなのかを確認できます。また、AutoFixという列にチェックマークが入っている場合は--fix
オプションで報告を自動的に修正する機能をlinterが持っていることを意味しています。
また、ソースコードの/test/testdataを覗くと実際にどのような報告が行われるのかを見ることができます。linterの細かい動きを確認したい時におすすめです。
(この記事内のサンプルのほとんどもここから取ってきています)
[ちょっとその前に]非推奨のlinterって?
golangci-lintではリポジトリがアーカイブされるなどの理由によって搭載されていたlinterが非推奨の扱いになることがあります。非推奨のlinterも一覧から確認することができます。
非推奨になったlinterは将来的にgolangci-lintから削除される予定です。
具体的に非推奨になったlinterをこれからどのように扱っていくかは以下のissueで議論が行われています。
デフォルトで有効な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
です
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
以下です。複数のチェック項目があります。
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
コンパイルが通るかをチェックします。
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 {}
でいうところのi
とp
のことです。
以下の例は少し複雑ですね。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を見るのが分かりやすいです
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を見ると具体的にどのような表現には報告を行わないか記載があります
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.
多くのチェックを搭載している系のやつです。
チェック項目は↓
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型が動的に生成されている箇所も検出します。
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に対応しています
goheader
Checks is file header matches to pattern
fileのヘッダーがパターンとマッチしているかどうかを確認します。コピーライトを全てのファイルに記載しているプロジェクトで有効なlinterだと思います。
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を確認してください。
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
セキュリティという観点で様々な確認をします
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を確認してください。
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の代替として推奨されています。
rowserrcheck
checks whether Err of rows is checked successfully
database/sqlのRowsのエラーが正しく処理されているかを確認します。
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のタグの命名のチェックします。
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に一覧があります。
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です。
ちなみに筆者が作りました。
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に記載があります。
golangci-lintに載っていないLinterを実行する
golangci-lintは自身に搭載していないlinterをcustom linterとして実行する機能を持っています。
しかし、brewやdockerからgolangci-lintをinstallした場合はこの機能は使用できません。
golangci-lintはCGO_ENABLED=0
でビルドされていますが、pluginのロードのためはCGO_ENABLED=1
でビルドされる必要があるためです。現状pluginを使用するにはgolangci-lintをソースコードからビルドする必要があります。
関連するissueは以下になります。
終わりに
次の章では少し視点を変え、golangci-lintの内部実装から理解を進めていきます。