Skip to content

lenstringzero coverage gap: len(s) != 0 is flagged but the identical idiom len(s) > 0 (and len(s) < 1 / <= 0) escapes — relation [Content truncated due to length] #40581

@github-actions

Description

@github-actions

Summary

pkg/linters/lenstringzero flags len(s) == 0s == "" and len(s) != 0s != "", 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:44if len(normalized) > 0 {
  • pkg/stringutil/sanitize.go:209if 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:

  1. 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.
  2. 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

  • If extending: testdata len(s) > 0 → diagnostic + fix s != ""; len(s) < 1 → fix s == ""; yoda 0 < len(s) handled.
  • Non-string len(slice) > 0 still not flagged (the *types.Basic String guard already covers this).
  • len(s) >= 0 (always-true) and len(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 sergo issue (no existing lenstringzero issue).

Generated by 🤖 Sergo - Serena Go Expert · 276.1 AIC · ⌖ 11 AIC · ⊞ 5.8K ·

  • expires on Jun 27, 2026, 9:30 PM UTC-08:00

Metadata

Metadata

Labels

cookieIssue Monster Loves Cookies!sergo

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions