コンテンツにスキップ

golangci-lint@静的解析

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. golangci-lintとは

Goの様々な静的解析ツールをまとめて実行できる。


02. CIへの導入

GitLab CI

variables:
  GO_VERSION: "<Goのバージョン>"
  # golangci-lintのイメージレイヤーから、使用しているGoバージョンを確認する必要がある
  # @see https://hub.docker.com/layers/golangci/golangci-lint/v1.50-alpine/images/sha256-9f44001cd4ce1e9749f2f1fb63adb76787b7dfcc77cb7b54e65e74ddac4132d8?context=explore
  GOLANGCI_LINT: "<golangci-lintのバージョン>"

stages:
  - build
  - test

go_build:
  stage: build
  image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/golang:${GO_VERSION}
  script:
    # バージョンを確認する
    - go version
    - go mod tidy

go_lint:
  stage: test
  image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/golangci/golangci-lint:<必要なGoのバージョンを含むイメージ>-alpine
  script:
    - go version
    # 有効化している静的解析の一覧を表示する
    # GitLab CIでは色が無効になってしまうため、有効化する
    - golangci-lint linters --color always
    # 静的解析を実行する
    # GitLab CIでは色が無効になってしまうため、有効化する
    - golangci-lint run --color always --timeout 5m


03. .golangci.yml

run

# 実行のオプションを設定する
run:
  concurrency: 4
  timeout: 5m
  issues-exit-code: 2
  # testファイルがあるか否かを設定する
  tests: false
  build-tags:
    - mytag
  skip-dirs:
    - src/external_libs
    - autogenerated_by_my_lib
  skip-dirs-use-default: false
  skip-files:
    - .*\.my\.go$
    - lib/bad.go
  modules-download-mode: readonly
  allow-parallel-runners: false
  allow-serial-runners: true
  print-resources-usage: true
  # Goのバージョンを指定する
  go: 1.20


output

# 結果の出力形式を設定する
output:
  format: json
  print-issued-lines: false
  print-linter-name: false
  uniq-by-line: false
  path-prefix: ""
  sort-results: true


linters-settings

# 使用するリンターにオプションを設定する
linters-settings:
  # staticcheckにオプションを設定する
  staticcheck:
    go: 1.20
    checks: [ "all" ]

  ...

  custom:
    example:
      path: /path/to/example.so
      description: This is an example usage of a plugin linter.
      original-url: github.com/golangci/example-linter


linters

# 使用するリンターを選ぶ
linters:
  disable-all: false
  enable:

    ...

    - gofmt
    - gosec
    - govet

    # staticcheckを使用する
    - staticcheck

    ...

  enable-all: false
  disable:
    - golint

    ...

  presets:
    - bugs
    - comment
    - complexity
    - error
    - format
    - import
    - metalinter
    - module
    - performance
    - sql
    - style
    - test
    - unused
  fast: true


issues

# 検証するルールを設定する
issues:
  exclude:
    - abcdef
  exclude-rules:
    - path: _test\.go
      linters:
        - gocyclo
        - errcheck
        - dupl
        - gosec
    - path-except: _test\.go
      linters:
        - forbidigo
    - path: internal/hmac/
      text: weak cryptographic primitive
      linters:
        - gosec
    - linters:
        - staticcheck
      text: "SA9003:"
    - linters:
        - lll
      source: "^//go:generate "
  exclude-use-default: false
  exclude-case-sensitive: false
  include:
    - EXC0001
    - EXC0002
    - EXC0003
    - EXC0004
    - EXC0005
    - EXC0006
    - EXC0007
    - EXC0008
    - EXC0009
    - EXC0010
    - EXC0011
    - EXC0012
    - EXC0013
    - EXC0014
    - EXC0015
  max-issues-per-linter: 0
  max-same-issues: 0
  new: true
  new-from-rev: HEAD
  new-from-patch: path/to/patch/file
  fix: true
  whole-files: true


severity

# 重要度を設定する
severity:
  default-severity: error
  case-sensitive: true
  rules:
    - linters:
        - dupl
      severity: info


04. コマンド

グローバル

▼ --color

コマンドの実行結果に色をつける。

CIによっては、実行時に色がなくなってしまうが、alwaysを有効化すると色がつくようになる。

$ golangci-lint linters --color always


run

▼ --go

Goのバージョンを指定して実行する。

$ golangci-lint run --go <バージョン>

▼ --config

設定ファイルを指定する。

$ golangci-lint run --config .golangci.yml


linters

有効/無効になっている解析の一覧を取得できる。

$ golangci-lint linters

# Enabled by your configuration linters:
errcheck: errcheck is a program for checking for unchecked errors in Go code. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false]
gosimple: Linter for Go source code that specializes in simplifying code [fast: false, auto-fix: false]
govet: Vet examines Go source code and reports suspicious constructs. It is roughly the same as 'go vet' and uses its passes. [fast: false, auto-fix: false]
ineffassign: Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
staticcheck: It's a set of rules from staticcheck. It's not the same thing as the staticcheck binary. The author of staticcheck doesn't support or approve the use of staticcheck as a library inside golangci-lint. [fast: false, auto-fix: false]
unused: Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]

# Disabled by your configuration linters:
asasalint: check for pass []any as any in variadic func(...any) [fast: false, auto-fix: false]
asciicheck: checks that all code identifiers does not have non-ASCII symbols in the name [fast: true, auto-fix: false]

...


05. コメントアウト

静的解析の無視

▼ プロジェクト全体

linters-settings:
  staticcheck:
    checks:
      - all
      # マイナスをつけると無視できる
      - "-SA1000"
      - "-SA1004"

▼ 特定のパス

issues:
  exclude-rules:
    - path-except: '(.+)_test\.go'
      linters:
        - staticcheck

▼ 特定のファイル

//nolint:staticcheck
package pkg

▼ 特定のコード

コメントアウトのコードに対して、指定した番号の静的解析を無視する。

注意点として、各ツールの用意しているignoreコメントではなく、golangci-lint専用のコメントである。

特定の番号 (例:SA1019) を無視することは難しそう。

package grpc

import (
    "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
    "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/filters"
    "google.golang.org/grpc"

    grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
)

// ChainUnaryServerInterceptor gRPCサーバー側の計装に必要なUnaryServerInterceptorをチェインする
// NOTE:
// このミドルウェアを実行すると、リクエストを単位としてスパンを自動的に開始/終了できる
func ChainUnaryServerInterceptor(opts ...otelgrpc.Option) grpc.ServerOption {
    return grpc.ChainUnaryInterceptor(
        grpc_recovery.UnaryServerInterceptor(),
        //nolint:staticcheck // NewServerHandlerが推奨となっているが、実装時点 (2027/07/02) のバージョンではNewServerHandlerはオプションが少ないため、止むを得ず非推奨のUnaryServerInterceptorを使う
        otelgrpc.UnaryServerInterceptor(
            append(opts, []otelgrpc.Option{
                InterceptorFilterHealthCheck(),
            }...)...,
        ),
    )
}

// ChainStreamServerInterceptor gRPCサーバー側の計装に必要なStreamServerInterceptorをチェインする
// NOTE:
// このミドルウェアを実行すると、リクエストを単位としてスパンを自動的に開始/終了できる
func ChainStreamServerInterceptor(opts ...otelgrpc.Option) grpc.ServerOption {
    return grpc.ChainStreamInterceptor(
        grpc_recovery.StreamServerInterceptor(),
        //nolint:staticcheck // NewServerHandlerが推奨となっているが、実装時点 (2027/07/02) のバージョンではNewServerHandlerはオプションが少ないため、止むを得ず非推奨のStreamServerInterceptorを使う
        otelgrpc.StreamServerInterceptor(
            append(opts, []otelgrpc.Option{
                InterceptorFilterHealthCheck(),
            }...)...,
        ),
    )
}

// InterceptorFilterHealthCheck ヘルスチェックパスではスパンを作成しない
func InterceptorFilterHealthCheck() otelgrpc.Option {

    //nolint:staticcheck // gRPCのstats handlerが推奨となっているが、実装時点 (2027/07/02) のバージョンではstats handlerはオプションが少ないため、止むを得ず非推奨のWithInterceptorFilterを使う
    return otelgrpc.WithInterceptorFilter(filters.Not(filters.HealthCheck()))
}