Reviewdogはlinterの結果をPRにコメントしてくれるツール。 差分のみに適用することができるので段階的に改善していくこともできる。
Staticcheck
StaticcheckはdeprecatedになったGolintのリポジトリで移行先として紹介されているlinter。
$ go install honnef.co/go/tools/cmd/staticcheck@latest
$ staticcheck --version
staticcheck 2021.1 (v0.2.0)
典型的なミスや非効率な書き方など様々な項目がチェックされる。
package main
import (
"context"
"encoding/json"
"fmt"
"logging"
"strings"
)
type Color int
const (
Red Color = 1 // main.go:14:2: only the first constant in this group has an explicit type (SA9004)
Blue = 2
)
func f(ctx context.Context, str []byte) error {
var m map[string]string
m["A"] = "default" // main.go:13:2: assignment to nil map (SA5000)
if err := json.Unmarshal(str, m); err != nil { // main.go:14:32: json.Unmarshal expects to unmarshal into a pointer, but the provided value is not a pointer (SA1014)
return err
}
l := *&m // main.go:17:7: *&x will be simplified to x. It will not copy x. (SA4001)
l["A"] = ""
if strings.ToLower(m["A"]) == strings.ToLower(m["X"]) { // main.go:19:5: should use strings.EqualFold instead (SA6005)
fmt.Println(fmt.Sprintf("%v -> %s", m, l)) // main.go:27:3: should use fmt.Printf instead of fmt.Println(fmt.Sprintf(...)) (but don't forget the newline) (S1038)
}
return nil
}
func main() {
f(nil, []byte(`{"A": "B", "X": "Y"}`)) // main.go:33:14: do not pass a nil Context, even if a function permits it; pass context.TODO if you are unsure about which Context to use (SA1012)
}
ReviewdogにStaticcheckのActionがあるので次のような設定で動き出す。
$ cat .github/workflows/reviewdog.yaml
name: reviewdog
on: [pull_request]
jobs:
staticcheck:
name: runner / staticcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: reviewdog/action-staticcheck@v1
with:
filter_mode: diff_context
fail_on_error: true
golangci-lint
golangci-lintは複数のlinterをまとめて適用できるツールで、これにStaticcheckも含まれている。
$ brew install golangci-lint
$ golangci-lint --version
golangci-lint has version 1.40.1 built from 625445b on 2021-05-17T08:47:21Z
全てのlinterを有効にしたところほぼすべての行で指摘が入った。 Staticcheckと重複しているものもあるが、デフォルトで有効なdeadcodeによって未使用コードが、errcheckによってエラーハンドリング漏れが検知されている。
$ golangci-lint run --uniq-by-line=false --enable-all main.go
package main
import (
"context"
"encoding/json"
"fmt"
"strings"
)
type Color int
const (
Red Color = 1
// `Red` is unused (deadcode)
// exported: exported const Red should have comment (or a comment on this block) or be unexported (revive)
// SA9004: only the first constant in this group has an explicit type (staticcheck)
Blue = 2 // `Blue` is unused (deadcode)
)
func f(ctx context.Context, str []byte) error { // `f` - `ctx` is unused (unparam)
var m map[string]string
m["A"] = "default" // SA5000: assignment to nil map (staticcheck)
if err := json.Unmarshal(str, m); err != nil {
// only one cuddle assignment allowed before if statement (wsl)
// unmarshal: call of Unmarshal passes non-pointer as second argument (govet)
// SA1014: json.Unmarshal expects to unmarshal into a pointer, but the provided value is not a pointer (staticcheck)
return err // error returned from external package is unwrapped: sig: func encoding/json.Unmarshal(data []byte, v interface{}) error (wrapcheck)
}
l := *&m
// assignments should only be cuddled with other assignments (wsl)
// SA4001: *&x will be simplified to x. It will not copy x. (staticcheck)
l["A"] = ""
if strings.ToLower(m["A"]) == strings.ToLower(m["X"]) {
// only one cuddle assignment allowed before if statement (wsl)
// SA6005: should use strings.EqualFold instead (staticcheck)
fmt.Println(fmt.Sprintf("%v -> %s", m, l))
// use of `fmt.Println` forbidden by pattern `^fmt\.Print(|f|ln)$` (forbidigo)
// S1038: should use fmt.Printf instead of fmt.Println(fmt.Sprintf(...)) (but don't forget the newline) (gosimple)
}
return nil
// return statements should not be cuddled if block has more than two lines (wsl)
// return with no blank line before (nlreturn)
}
func main() {
f(nil, []byte(`{"A": "B", "X": "Y"}`))
// Error return value is not checked (errcheck)
// SA1012: do not pass a nil Context, even if a function permits it; pass context.TODO if you are unsure about which Context to use (staticcheck)
}
こちらにもReviewdogのActionがある。 追加で関数の循環的複雑度を測るgocycloをテスト以外で有効にしている。 private repositoryへの依存のためにgit configしているが、GITHUB_TOKENにはrepo scopeがないので別にTokenを発行した。
push時にも実行する際はreporterをgithub-checkにする。ただしCode Suggetionsはサポートしていない。
$ cat .github/workflows/reviewdog.yml
name: reviewdog
on: [pull_request]
jobs:
golangci-lint:
name: runner / golangci-lint
runs-on: ubuntu-latest
steps:
- name: git config to get private repositories
run: git config --global url.'https://${{ secrets.REVIEWDOG_TOKEN }}:[email protected]/sambaiz'.insteadOf 'https://github.com/sambaiz'
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: '1.16'
- name: golangci-lint
uses: reviewdog/action-golangci-lint@v1
with:
reporter: github-pr-review
filter_mode: diff_context
fail_on_error: true
gocyclo:
name: runner / gocyclo
runs-on: ubuntu-latest
steps:
- name: git config to get private repositories
run: git config --global url.'https://${{ secrets.REVIEWDOG_TOKEN }}:[email protected]/sambaiz'.insteadOf 'https://github.com/sambaiz'
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: '1.16'
- name: gocyclo
uses: reviewdog/action-golangci-lint@v1
with:
reporter: github-pr-review
filter_mode: diff_context
fail_on_error: true
tool_name: gocyclo
golangci_lint_flags: "--config=.github/.golangci.yml --disable-all --tests=false -E gocyclo"
$ cat .github/.golangci.yml
linters-settings:
gocyclo:
# min-complexity: 10
min-complexity: 1