textmachine/backend/.golangci.yml

114 lines
6.3 KiB
YAML

# golangci-lint configuration for the backend module.
#
# Every enable/disable is justified by a MEASURED count from the report-only sweep of 2026-08-03
# (golangci-lint 2.12.2, 205 .go files, 115 of them tests). Raw sweep = 435 findings:
# errcheck 413, staticcheck 17, misspell 4, unused 1; govet / ineffassign / nolintlint 0.
# Those counts are the PROVENANCE of each decision below, not the state of the tree: everything they
# name has since been fixed or excluded, so a clean run today reports 0.
#
# Owner's constraint (D39.92) outranks coverage: standards must not slow development or push a
# model into hacks. A rule that produces noise is switched OFF here with its reason on one line —
# never worked around in the code — and nolintlint refuses a bare //nolint.
version: "2"
linters:
default: none
enable:
# --- the standard set ---
- errcheck # 413 raw; the exclusions below account for 401 of them, the rest were fixed
- govet # 0 — already in the manual battery; enabling pins it
- ineffassign # 0
- staticcheck # 17 raw; 6 excluded as QF1001, 11 fixed
- unused # 1 — a real dead method (pipeline.applyBanknote), deleted by this pack
# --- zero-cost bug classes: 0 findings today, so they cost nothing and cannot regress ---
- bodyclose # a leaked provider response body is a transport leak
- copyloopvar
- durationcheck
- makezero
# --- small real tails ---
- errorlint # 3 — wrapped-error comparison is a live correctness class here
- misspell # 4, all one word (see exclusions)
- nilerr # 3
- rowserrcheck # 2
- sqlclosecheck # 1
- nolintlint # 0 today — enforces the anti-hack rule: no silent suppressions
# DISABLED ON PURPOSE (measured, not skipped):
# exhaustruct — 988 findings module-wide. Scoped to the two types it was proposed for
# (config.Stage, pipeline.Request) it is 17, but 16 are test literals and the 17th is the SEAM
# ITSELF (config/internal_call.go Stage()) — the one construction site that is allowed. The
# invariant "Stage is built only through the seam" needs a rule that tells seam from bypass;
# exhaustruct cannot, the analyzer can. Off so it does not compete with that guard.
settings:
staticcheck:
checks:
- all
- -ST1000 # default-off in golangci-lint: package comment form
- -ST1003 # default-off: naming conventions
- -ST1016 # default-off: receiver name consistency
- -ST1020 # default-off: comment form on exported methods
- -ST1021 # default-off: comment form on exported types
- -ST1022 # default-off: comment form on exported vars
- -QF1001 # 6 findings: "apply De Morgan's law". The current shape mirrors the prose above
# each condition ("neither X nor Y"); rewriting for the linter reads worse
# than the rule it encodes. A style opinion, not a defect class.
misspell:
ignore-rules:
# `initals` is a DELIBERATE typo: the langpack tests write it into a fixture to prove the
# parser reports unknown keys instead of silently dropping them. Correcting it deletes the
# test. 1 comment + 3 test sites. NOTE: `locale` is left unset on purpose — setting it to US
# makes misspell rewrite en-GB spellings in the English prose (behaviour, honoured, dialogue):
# measured 0 findings unset against 311 with `locale: US`, none of them real errors.
- initals
nolintlint:
require-explanation: true # a suppression without a reason is the hack this rule exists to stop
require-specific: true # //nolint must name the linter, never blanket-silence a file
allow-unused: false
exclusions:
# Written out instead of the std-error-handling preset: that preset also swallows os.Setenv and
# os.Remove, and one of the two real production findings in this repo is an unchecked os.Setenv.
rules:
# Close/Flush: 294 sites. All 33 production ones are readers or DB handles (listed and
# checked one by one). The repo has exactly ONE flush-on-Close writer — chunktest/epub.go's
# zip.Writer — and its Close IS checked, so this cannot hide a lost write.
- linters: [errcheck]
text: 'Error return value of `[^`]*\.(Close|Flush)` is not checked'
# CLI rendering to stdout: 91 sites. A broken pipe on `tmctl status | head` is not something
# the CLI can act on, and threading it out would rewrite every renderer for nothing.
- linters: [errcheck]
text: 'Error return value of `fmt\.Fprint(f|ln)?` is not checked'
# hash.Hash.Write is documented never to return an error (2 sites, the membank digest). Keyed on
# the METHOD, not on a receiver spelled `h`: a rule that fires for `hasher.Write` but not `h.Write`
# pushes the next contributor toward renaming a variable to appease the linter.
- linters: [errcheck]
text: 'Error return value of `[^`]*\.Write` is not checked'
path: internal/membank/
# `defer tx.Rollback()` — 5 sites. The idiom is "roll back unless committed", so on the happy
# path the error is sql.ErrTxDone BY DESIGN. Wrapping each in a discard closure would add five
# blocks of noise to say nothing. Excluded in config rather than worked around in the code.
- linters: [errcheck]
text: 'Error return value of `[^`]*\.Rollback` is not checked'
# In tests, a response-writer/fixture write that fails cannot be acted on, and a t.Fatal on
# each would bury the assertion the test is actually about.
- linters: [errcheck]
path: _test\.go
text: 'Error return value of `(w\.Write|os\.WriteFile|fmt\.Sscanf)` is not checked'
# runToSignatureStop returns *WaveSignatureStop, which implements error because the stop travels
# the pipeline AS an error — so errcheck sees 12 discarded "errors". The helper has already
# t.Fatal'd on every wrong outcome; the return is there for the tests that want to inspect it.
- linters: [errcheck]
path: internal/pipeline/miningstop_join_test\.go
text: 'Error return value is not checked'
formatters:
enable:
- gofmt # `make fmt` checks this too; the duplicate is deliberate, so a bare
# `golangci-lint run` is still a complete gate on its own
issues:
max-issues-per-linter: 0 # never truncate: a hidden tail reads as "clean"
max-same-issues: 0