Problem
nolintlint (allow-unused: false) fails the gate on a //nolint directive that suppresses
nothing — but only when the linter it names is enabled. A directive naming a linter that is
disabled, or that does not exist at all, is silently ignored by golangci-lint's nolint processor
and reported by nothing in the gate.
Verified by execution on golangci-lint 2.12.2 / go1.26.4, planting each form on one line of
pass/validate_propids_test.go and running golangci-lint run with a cleaned cache:
| planted directive |
reported? |
//nolint:unparam // … (enabled, nothing to suppress) |
yes — is unused for linter "unparam" (nolintlint) |
//nolint:forcetypeassert // … (real linter, not enabled) |
no — 0 issues. |
//nolint:notarealinter // … (no such linter) |
no — 0 issues. |
This is not hypothetical: the //nolint:forcetypeassert directive that stood at
pass/validate_propids_test.go:86 was of exactly the second kind. forcetypeassert has never been
in the enabled set, so the directive never suppressed anything from the day it was written, and no
gate step could have said so.
The failure mode is the one a suppression comment is worst at: it reads as a live constraint on the
code under it, and re-checking it by hand is the only thing that can tell you otherwise.
Proposed direction
Cross-check the linter names appearing in //nolint directives against the enabled set, and fail
when a directive names one that is not enabled. The enabled set is derivable
(golangci-lint linters --json, or parsing .golangci.yml against
golangci-lint help linters), and the directives are a git ls-files '*.go' grep away, so this
fits the existing bundled gate job as one more step rather than a new workflow.
Acceptance
- A directive naming a disabled or unknown linter fails the gate.
- The check is proven to bite by planting one of each form and watching it go red.
Problem
nolintlint(allow-unused: false) fails the gate on a//nolintdirective that suppressesnothing — but only when the linter it names is enabled. A directive naming a linter that is
disabled, or that does not exist at all, is silently ignored by golangci-lint's nolint processor
and reported by nothing in the gate.
Verified by execution on golangci-lint 2.12.2 / go1.26.4, planting each form on one line of
pass/validate_propids_test.goand runninggolangci-lint runwith a cleaned cache://nolint:unparam // …(enabled, nothing to suppress)is unused for linter "unparam" (nolintlint)//nolint:forcetypeassert // …(real linter, not enabled)0 issues.//nolint:notarealinter // …(no such linter)0 issues.This is not hypothetical: the
//nolint:forcetypeassertdirective that stood atpass/validate_propids_test.go:86was of exactly the second kind.forcetypeasserthas never beenin the enabled set, so the directive never suppressed anything from the day it was written, and no
gate step could have said so.
The failure mode is the one a suppression comment is worst at: it reads as a live constraint on the
code under it, and re-checking it by hand is the only thing that can tell you otherwise.
Proposed direction
Cross-check the linter names appearing in
//nolintdirectives against the enabled set, and failwhen a directive names one that is not enabled. The enabled set is derivable
(
golangci-lint linters --json, or parsing.golangci.ymlagainstgolangci-lint help linters), and the directives are agit ls-files '*.go'grep away, so thisfits the existing bundled
gatejob as one more step rather than a new workflow.Acceptance