Summary
pkg/linters/lenstringzero flags len(s) == 0 → s == "" and len(s) != 0 → s != "", but it only matches the equality operators (token.EQL / token.NEQ). The exactly-equivalent relational forms on strings escape:
| Written |
Equivalent to |
Flagged today? |
len(s) == 0 |
s == "" (empty) |
✅ yes |
len(s) != 0 |
s != "" (non-empty) |
✅ yes |
len(s) > 0 |
s != "" (non-empty) |
❌ no |
len(s) < 1 / len(s) <= 0 |
s == "" (empty) |
❌ no |
len(s) >= 1 |
s != "" (non-empty) |
❌ no |
Since len(s) >= 0 always holds for strings, len(s) > 0 and len(s) != 0 are semantically identical — yet only one is reported. That inconsistency is the gap.
Location
pkg/linters/lenstringzero/lenstringzero.go:40:
nodeFilter := []ast.Node{(*ast.BinaryExpr)(nil)}
insp.Preorder(nodeFilter, func(n ast.Node) {
expr, ok := n.(*ast.BinaryExpr)
if !ok {
return
}
if expr.Op != token.EQL && expr.Op != token.NEQ { // <-- only == and !=
return
}
...
Impact
len(s) > 0 is the most common non-empty idiom in Go, so this is the larger of the two cases by prevalence. Real string-typed sites exist in production, e.g.:
pkg/stringutil/stringutil.go:44 — if len(normalized) > 0 {
pkg/stringutil/sanitize.go:209 — if len(result) > 0 && ...
(The narrow scoping is plausibly intentional precisely because > 0 is so prevalent — see the tradeoff below.)
Recommendation (team to decide scope)
The equivalence is exact, so extending coverage is consistent with the linter's stated goal (prefer direct string comparison). Two viable directions:
- Extend the operator set to the relational forms, mapping to the correct empty/non-empty fixer:
len(s) > 0, len(s) >= 1, 0 < len(s), 1 <= len(s) → s != ""
len(s) < 1, len(s) <= 0, 1 > len(s), 0 >= len(s) → s == ""
- reuse the existing literal-zero / yoda-swap and string-type guard (
lenStringArg), and adjust cmpVerb + the suggested-fix operator accordingly.
- Document the boundary explicitly in the analyzer
Doc and doc.go if > 0 is deliberately excluded as too noisy — so the asymmetry is an intentional, recorded decision rather than an apparent oversight.
This report favors a deliberate choice over the current silent asymmetry where != 0 is flagged but the identical > 0 is not.
Validation checklist
Effort
Small–medium — single file; the type guard, alias tracking, and yoda handling already exist and are reusable; main work is operator mapping + fixer text + testdata.
Not a duplicate of any open sergo issue (no existing lenstringzero issue).
Generated by 🤖 Sergo - Serena Go Expert · 276.1 AIC · ⌖ 11 AIC · ⊞ 5.8K · ◷
Summary
pkg/linters/lenstringzeroflagslen(s) == 0→s == ""andlen(s) != 0→s != "", but it only matches the equality operators (token.EQL/token.NEQ). The exactly-equivalent relational forms on strings escape:len(s) == 0s == ""(empty)len(s) != 0s != ""(non-empty)len(s) > 0s != ""(non-empty)len(s) < 1/len(s) <= 0s == ""(empty)len(s) >= 1s != ""(non-empty)Since
len(s) >= 0always holds for strings,len(s) > 0andlen(s) != 0are semantically identical — yet only one is reported. That inconsistency is the gap.Location
pkg/linters/lenstringzero/lenstringzero.go:40:Impact
len(s) > 0is the most common non-empty idiom in Go, so this is the larger of the two cases by prevalence. Real string-typed sites exist in production, e.g.:pkg/stringutil/stringutil.go:44—if len(normalized) > 0 {pkg/stringutil/sanitize.go:209—if len(result) > 0 && ...(The narrow scoping is plausibly intentional precisely because
> 0is so prevalent — see the tradeoff below.)Recommendation (team to decide scope)
The equivalence is exact, so extending coverage is consistent with the linter's stated goal (prefer direct string comparison). Two viable directions:
len(s) > 0,len(s) >= 1,0 < len(s),1 <= len(s)→s != ""len(s) < 1,len(s) <= 0,1 > len(s),0 >= len(s)→s == ""lenStringArg), and adjustcmpVerb+ the suggested-fix operator accordingly.Docanddoc.goif> 0is deliberately excluded as too noisy — so the asymmetry is an intentional, recorded decision rather than an apparent oversight.This report favors a deliberate choice over the current silent asymmetry where
!= 0is flagged but the identical> 0is not.Validation checklist
len(s) > 0→ diagnostic + fixs != "";len(s) < 1→ fixs == ""; yoda0 < len(s)handled.len(slice) > 0still not flagged (the*types.BasicString guard already covers this).len(s) >= 0(always-true) andlen(s) < 0(always-false) are not mis-fixed.go test ./pkg/linters/lenstringzero/...passes.Effort
Small–medium — single file; the type guard, alias tracking, and yoda handling already exist and are reusable; main work is operator mapping + fixer text + testdata.
Not a duplicate of any open
sergoissue (no existinglenstringzeroissue).