11# golangci-lint configuration.
22#
3- # We run golangci-lint v2 (`brew install golangi -lint` on macOS).
3+ # We run golangci-lint v2 (`brew install golangci -lint` on macOS).
44# v2 changed the schema: linters live under `linters.enable`, and
55# per-rule exclusions live under `linters.exclusions.rules`.
66#
99# reproducible across contributors and CI (no "default changed
1010# in v2.13"). `default: none` means ONLY the linters listed below
1111# run — nothing implicit.
12- # 2. Suppress errcheck noise inside *_test.go where the standard
13- # pattern is os.Setenv / w.Close / buf.ReadFrom / os.MkdirAll
14- # without explicit error handling. Production code still gets
15- # the full check.
12+ # 2. Suppress noise inside *_test.go where the standard pattern is
13+ # os.Setenv / w.Close / buf.ReadFrom / os.MkdirAll without explicit
14+ # error handling. Production code still gets the full check.
1615#
1716# Linter philosophy:
18- # - Real-bug catchers (errcheck, gosec, errorlint, nilerr, …) are
19- # ON at default thresholds. Every finding they surface is either a
20- # bug we fix or a documented false positive (`// #nosec`).
17+ # - Real-bug catchers (errcheck, gosec, errorlint, nilerr, exhaustive,
18+ # …) are ON at default thresholds. Every finding they surface is
19+ # either a bug we fix or a documented false positive (`// #nosec`).
2120# - gocyclo is kept at a deliberately high threshold (70) because the
2221# three CLI dispatchers (RunGuard/RunInit/RunUndo) are inherently
2322# big switch-on-subcommand functions; the value is in preventing NEW
2423# complexity, not flagging what we already know about.
25- # - misspell / gocritic / unconvert / predeclared / forcetypeassert
26- # currently produce zero findings on this codebase, but we keep them
27- # enabled as a regression fence: the day someone adds a typo or an
24+ # - Modernisation linters (modernize, intrange, copyloopvar) push us
25+ # toward Go 1.22+ idioms (CutPrefix, SplitSeq, slices.Contains,
26+ # min(), interface{}→any, range-over-int).
27+ # - misspell / gocritic / unconvert / predeclared / forcetypeassert /
28+ # dupword / godot / asciicheck / bidichk / dupl / etc. currently
29+ # produce zero findings on this codebase, but we keep them enabled
30+ # as a regression fence: the day someone adds a typo or an
2831# unnecessary conversion, CI catches it instead of a reviewer.
2932#
33+ # Deliberately NOT enabled:
34+ # - goconst — 46 findings, almost all test data ("--bogus", "rm",
35+ # "list"…). Extracting test literals to constants harms readability.
36+ # - gocognit — duplicates gocyclo with a different complexity metric.
37+ # - nestif — duplicates gocyclo for nesting depth.
38+ # - Various framework-specific linters (bodyclose, sqlclosecheck,
39+ # protogetter, …) — cmdguard has no HTTP / SQL / protobuf.
40+ #
3041# Run: golangci-lint run ./...
3142# Update: golangci-lint config verify
3243version : " 2"
3344
3445linters :
3546 default : none
3647 enable :
37- # --- Built-ins that always pay off ---
38- - errcheck # unchecked error returns (the most frequent real bug)
39- - govet # the official `go vet` rules
40- - ineffassign # assignments whose result is never read
41- - staticcheck # SA-/ST-/QF- rules; the single most valuable set
42- - unused # dead code (functions, vars, types, consts)
48+ # ── Built-ins that always pay off ──────────────────────────
49+ - errcheck # unchecked error returns (the most frequent real bug)
50+ - govet # the official `go vet` rules
51+ - ineffassign # assignments whose result is never read
52+ - staticcheck # SA-/ST-/QF- rules; the single most valuable set
53+ - unused # dead code (functions, vars, types, consts)
4354
44- # --- Security & correctness ---
45- - gosec # G1xx/G3xx/G7xx security checks (path traversal, hardcoded creds, …)
46- - errorlint # use errors.As/Is/wrap instead of == / type-assertion
55+ # ── Security & correctness ─────────────────────────────────
56+ - gosec # G1xx/G3xx/G7xx security checks (path traversal, hardcoded creds, …)
57+ - errorlint # use errors.As/Is/wrap instead of == / type-assertion
4758 - nilerr # detect `if err != nil { return nil }` patterns that drop errors
59+ - nilnil # simultaneous return of nil error + invalid value
60+ - exhaustive # missing cases in switch / type switch
61+ - gochecksumtype # exhaustiveness for sum-type-style type switches
62+ - wastedassign # assignments that are overwritten before being read
63+ - reassign # package-level variables that get reassigned
64+ - unparam # unused function parameters
65+
66+ # ── Input validation & safety ──────────────────────────────
67+ - bidichk # dangerous Unicode (trojan source / bidi override)
68+ - asciicheck # non-ASCII identifiers
69+ - gocheckcompilerdirectives # malformed //go: directives
70+
71+ # ── Modernisation (Go 1.22–1.26 idioms) ───────────────────
72+ - modernize # CutPrefix/CutSuffix, SplitSeq, slices.Contains, min(), any, …
73+ - intrange # for i := range N instead of for i := 0; i < N; i++
74+ - copyloopvar # Go 1.22+ loop variable capture semantics
4875
49- # --- Performance micro-optimisation ---
50- - prealloc # preallocate slices when size is known upfront
76+ # ── Performance micro-optimisation ─────────────────────────
77+ - prealloc # preallocate slices when size is known upfront
78+ - perfsprint # fmt.Sprintf → strconv/errors.New where applicable
5179
52- # --- Style & polish (zero findings today, fence for tomorrow) ---
53- - misspell # typos in comments/strings
54- - gocritic # style/perf/bug-prone patterns
55- - unconvert # unnecessary type conversions
56- - predeclared # shadowing predeclared identifiers (e.g. `cap`, `len`)
57- - forcetypeassert # unchecked type assertions (panic risk)
80+ # ── Meta-linter ────────────────────────────────────────────
81+ - nolintlint # validate //nolint / #nosec directives are well-formed
5882
59- # --- Complexity (high threshold — see comment above) ---
83+ # ── Style & polish (regression fence — 0 findings today) ──
84+ - misspell # typos in comments/strings
85+ - gocritic # style/perf/bug-prone patterns
86+ - unconvert # unnecessary type conversions
87+ - predeclared # shadowing predeclared identifiers (e.g. `cap`, `len`)
88+ - forcetypeassert # unchecked type assertions (panic risk)
89+ - dupword # duplicate words ("the the") in comments/strings
90+ - godot # comments should end with a period
91+ - errname # error variables should be named ErrXxx
92+ - dupl # duplicate code blocks (copy-paste detection)
93+ - errchkjson # unsupported types passed to json.Marshal/Encode
94+ - durationcheck # two time.Duration values multiplied together
95+ - makezero # make() with non-zero length followed by append
96+
97+ # ── Complexity (high threshold — see header comment) ──────
6098 - gocyclo
6199
62100 settings :
@@ -68,10 +106,8 @@ linters:
68106 # nagging about the existing ones.
69107 min-complexity : 70
70108 gosec :
71- # #nosec directives already cover the genuine false positives
72- # (user-supplied command launching, PATH-walk file reads, …).
73- # No global exclusion rules — every suppression is per-site with
74- # a justification comment.
109+ # #nosec directives cover genuine false positives per-site.
110+ # No global exclusions — keeping it explicit.
75111 excludes : []
76112
77113 exclusions :
@@ -86,7 +122,6 @@ linters:
86122 - gosec
87123 - forcetypeassert
88124 - prealloc
89- # gocyclo is meaningless in test table-drivers and helpers.
90- - path : _test\.go
91- linters :
92125 - gocyclo
126+ - wastedassign
127+ - unparam
0 commit comments