Summary
sprintferrdot (the newly-landed 30th custom linter; not yet in pkg/linters/doc.go) flags redundant .Error() calls on error arguments passed to fmt format functions, because fmt invokes Error() automatically. Its verb gate only recognizes %s and %v (pkg/linters/sprintferrdot/sprintferrdot.go:89), which is wrong in both directions relative to the fmt specification.
The fmt rule (authoritative)
Per the fmt package docs: the Error() method is invoked when the format “is valid for a string (%s %q %x %X), or is %v but not %#v.” So Error() is auto-called for %s %q %v %x %X, and is not called for %#v (which uses the GoStringer/Go-syntax representation instead).
Two defects, one root cause
The verb allow-list is {s, v} and parseSimpleFormatVerbs discards the # flag (it is skipped along with the other flags at pkg/linters/sprintferrdot/sprintferrdot.go:156), so %#v collapses to bare verb v.
1. False positive on %#v — fmt.Sprintf("%#v", err.Error()) is flagged as redundant, but %#v does not invoke Error(). Dropping .Error() changes output: %#v on the string prints a quoted Go string literal, while %#v on the error value prints the Go-syntax representation (e.g. &errors.errorString{s:"..."}). The suggestion is behavior-changing and incorrect.
2. False negatives on %q, %x, %X — fmt.Errorf("%q", err.Error()) (and %x/%X) are genuinely redundant per the same rule, but the verb gate skips them (verbs[i] != 's' && verbs[i] != 'v' at :89).
Evidence
pkg/linters/sprintferrdot/sprintferrdot.go:89 — if verbs[i] != 's' && verbs[i] != 'v' { continue }
pkg/linters/sprintferrdot/sprintferrdot.go:156 — flag-skip loop consumes #, so the # of %#v is lost before the verb rune is captured at :183
parseSimpleFormatVerbs (:139-187) stores only the verb rune, never the flag, so %#v and %v are indistinguishable downstream
Impact
The .Error() detection itself is sound (isErrorDotCall uses pass.TypesInfo + types.Implements, :191-208). The defect is purely in verb classification. The FP is the higher-severity half: if this linter is ever added to the CI-enforced set, a %#v site becomes a build-blocker with an incorrect fix. No production sites of either case exist today (grep of pkg/ non-test for %#v ... .Error() and %q/%x/%X ... .Error() returns zero), so this is latent — but it is a correctness gap on documented fmt semantics.
Recommendation
- Preserve the
# flag in parseSimpleFormatVerbs (track sharp-v distinctly from plain v).
- Widen the auto-
Error() verb set to {s, q, v, x, X} while excluding %#v.
- Add testdata cases:
%#v (no report), and %q/%x/%X (report), mirroring the existing %s/%v cases.
Validation checklist
Effort: small (single file + testdata).
References: §27860925409
Generated by 🤖 Sergo - Serena Go Expert · 276.8 AIC · ⌖ 11.6 AIC · ⊞ 5.8K · ◷
Summary
sprintferrdot(the newly-landed 30th custom linter; not yet inpkg/linters/doc.go) flags redundant.Error()calls on error arguments passed tofmtformat functions, becausefmtinvokesError()automatically. Its verb gate only recognizes%sand%v(pkg/linters/sprintferrdot/sprintferrdot.go:89), which is wrong in both directions relative to thefmtspecification.The fmt rule (authoritative)
Per the
fmtpackage docs: theError()method is invoked when the format “is valid for a string (%s %q %x %X), or is %v but not %#v.” SoError()is auto-called for %s %q %v %x %X, and is not called for %#v (which uses theGoStringer/Go-syntax representation instead).Two defects, one root cause
The verb allow-list is
{s, v}andparseSimpleFormatVerbsdiscards the#flag (it is skipped along with the other flags atpkg/linters/sprintferrdot/sprintferrdot.go:156), so%#vcollapses to bare verbv.1. False positive on
%#v—fmt.Sprintf("%#v", err.Error())is flagged as redundant, but%#vdoes not invokeError(). Dropping.Error()changes output:%#von the string prints a quoted Go string literal, while%#von the error value prints the Go-syntax representation (e.g.&errors.errorString{s:"..."}). The suggestion is behavior-changing and incorrect.2. False negatives on
%q,%x,%X—fmt.Errorf("%q", err.Error())(and%x/%X) are genuinely redundant per the same rule, but the verb gate skips them (verbs[i] != 's' && verbs[i] != 'v'at:89).Evidence
pkg/linters/sprintferrdot/sprintferrdot.go:89—if verbs[i] != 's' && verbs[i] != 'v' { continue }pkg/linters/sprintferrdot/sprintferrdot.go:156— flag-skip loop consumes#, so the#of%#vis lost before the verb rune is captured at:183parseSimpleFormatVerbs(:139-187) stores only the verb rune, never the flag, so%#vand%vare indistinguishable downstreamImpact
The
.Error()detection itself is sound (isErrorDotCallusespass.TypesInfo+types.Implements,:191-208). The defect is purely in verb classification. The FP is the higher-severity half: if this linter is ever added to the CI-enforced set, a%#vsite becomes a build-blocker with an incorrect fix. No production sites of either case exist today (grepofpkg/non-test for%#v ... .Error()and%q/%x/%X ... .Error()returns zero), so this is latent — but it is a correctness gap on documentedfmtsemantics.Recommendation
#flag inparseSimpleFormatVerbs(track sharp-vdistinctly from plainv).Error()verb set to{s, q, v, x, X}while excluding%#v.%#v(no report), and%q/%x/%X(report), mirroring the existing%s/%vcases.Validation checklist
%#vwitherr.Error()produces no diagnostic%q,%x,%Xwitherr.Error()each produce a diagnostic%s,%vbehavior unchanged%T,%pnever flagged (already correct — they bypass the verb gate)Effort: small (single file + testdata).
References: §27860925409