From 3dc66a98f30184e644656c382b0d634542441e0c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 04:21:57 +0000 Subject: [PATCH 1/2] Initial plan From 56d68bad3a06259309d98902250f90caae882652 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 22 Aug 2026 04:28:56 +0000 Subject: [PATCH 2/2] lenstringzero: interpolate real expression text in diagnostic message Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/linters/lenstringzero/lenstringzero.go | 41 +++++++++++++------ .../src/lenstringzero/lenstringzero.go | 21 +++++++--- .../src/lenstringzero/lenstringzero.go.golden | 21 +++++++--- 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/pkg/linters/lenstringzero/lenstringzero.go b/pkg/linters/lenstringzero/lenstringzero.go index 3f9ffe659c1..963254f022d 100644 --- a/pkg/linters/lenstringzero/lenstringzero.go +++ b/pkg/linters/lenstringzero/lenstringzero.go @@ -51,7 +51,7 @@ func analyzeLenStringExpr(pass *analysis.Pass, n ast.Node, generatedFiles filech if nolint.HasDirectiveForLinter(pos, noLintIndex, "lenstringzero") { return } - lenArg, isDirect, normalOp, lit, matched := matchLenLiteralExpr(pass, expr, lenStringAliases) + lenArg, lenNode, isDirect, normalOp, lit, matched := matchLenLiteralExpr(pass, expr, lenStringAliases) if !matched { return } @@ -67,6 +67,8 @@ func analyzeLenStringExpr(pass *analysis.Pass, n ast.Node, generatedFiles filech if !ok || basic.Kind() != types.String { return } + argText := exprTextOr(pass, lenArg, "s") + lenText := exprTextOr(pass, lenNode, "len(s)") var fixes []analysis.SuggestedFix if isDirect { fixes = buildLenStringFix(pass, expr, lenArg, fixOp) @@ -74,38 +76,39 @@ func analyzeLenStringExpr(pass *analysis.Pass, n ast.Node, generatedFiles filech pass.Report(analysis.Diagnostic{ Pos: expr.Pos(), End: expr.End(), - Message: fmt.Sprintf(`use s %s "" to check for %s string instead of len(s) %s %d`, fixOp, cmpVerb, normalOp, lit), + Message: fmt.Sprintf(`use %s %s "" to check for %s string instead of %s %s %d`, argText, fixOp, cmpVerb, lenText, normalOp, lit), SuggestedFixes: fixes, }) } // matchLenLiteralExpr tries to match len(s)/alias OP literal or literal OP len(s)/alias. -// Returns (lenArg, isDirect, normalOp, lit, matched) where: +// Returns (lenArg, lenNode, isDirect, normalOp, lit, matched) where: // - lenArg is the string expression passed to len() +// - lenNode is the expression being compared (the len() call or the alias identifier) // - isDirect indicates a direct len() call (true) vs a stored alias (false) // - normalOp is the operator normalized so that len is on the left side // - lit is the integer literal value (0 or 1) // - matched indicates whether a valid pattern was found -func matchLenLiteralExpr(pass *analysis.Pass, expr *ast.BinaryExpr, aliases map[types.Object]ast.Expr) (lenArg ast.Expr, isDirect bool, normalOp token.Token, lit int, ok bool) { +func matchLenLiteralExpr(pass *analysis.Pass, expr *ast.BinaryExpr, aliases map[types.Object]ast.Expr) (lenArg ast.Expr, lenNode ast.Expr, isDirect bool, normalOp token.Token, lit int, ok bool) { op := expr.Op // Normal order: len/alias on the left, literal on the right. if isLenCall(expr.X) { if isIntZero(expr.Y) { - return lenCallArg(expr.X), true, op, 0, true + return lenCallArg(expr.X), expr.X, true, op, 0, true } if isIntOne(expr.Y) { - return lenCallArg(expr.X), true, op, 1, true + return lenCallArg(expr.X), expr.X, true, op, 1, true } } if isIntZero(expr.Y) { if arg, ok2 := lenAliasArg(pass, expr.X, aliases); ok2 { - return arg, false, op, 0, true + return arg, expr.X, false, op, 0, true } } if isIntOne(expr.Y) { if arg, ok2 := lenAliasArg(pass, expr.X, aliases); ok2 { - return arg, false, op, 1, true + return arg, expr.X, false, op, 1, true } } @@ -113,24 +116,24 @@ func matchLenLiteralExpr(pass *analysis.Pass, expr *ast.BinaryExpr, aliases map[ // Flip the operator so the normalized form has len on the left. if isLenCall(expr.Y) { if isIntZero(expr.X) { - return lenCallArg(expr.Y), true, astutil.FlipComparisonOp(op), 0, true + return lenCallArg(expr.Y), expr.Y, true, astutil.FlipComparisonOp(op), 0, true } if isIntOne(expr.X) { - return lenCallArg(expr.Y), true, astutil.FlipComparisonOp(op), 1, true + return lenCallArg(expr.Y), expr.Y, true, astutil.FlipComparisonOp(op), 1, true } } if isIntZero(expr.X) { if arg, ok2 := lenAliasArg(pass, expr.Y, aliases); ok2 { - return arg, false, astutil.FlipComparisonOp(op), 0, true + return arg, expr.Y, false, astutil.FlipComparisonOp(op), 0, true } } if isIntOne(expr.X) { if arg, ok2 := lenAliasArg(pass, expr.Y, aliases); ok2 { - return arg, false, astutil.FlipComparisonOp(op), 1, true + return arg, expr.Y, false, astutil.FlipComparisonOp(op), 1, true } } - return nil, false, 0, 0, false + return nil, nil, false, 0, 0, false } // resolveFixOp returns the fix operator and comparison verb for a normalized @@ -195,6 +198,18 @@ func buildLenStringFix(pass *analysis.Pass, expr *ast.BinaryExpr, lenArg ast.Exp }} } +// exprTextOr renders node's source text, falling back to fallback when the +// node cannot be printed. +func exprTextOr(pass *analysis.Pass, node ast.Expr, fallback string) string { + if node == nil { + return fallback + } + if text := astutil.NodeText(pass.Fset, node); text != "" { + return text + } + return fallback +} + func isLenCall(expr ast.Expr) bool { call, ok := expr.(*ast.CallExpr) if !ok || len(call.Args) != 1 { diff --git a/pkg/linters/lenstringzero/testdata/src/lenstringzero/lenstringzero.go b/pkg/linters/lenstringzero/testdata/src/lenstringzero/lenstringzero.go index b254a00ee11..e84af44af04 100644 --- a/pkg/linters/lenstringzero/testdata/src/lenstringzero/lenstringzero.go +++ b/pkg/linters/lenstringzero/testdata/src/lenstringzero/lenstringzero.go @@ -85,32 +85,32 @@ func lenNotComparedToZero(s string) bool { func aliasEmpty(s string) bool { n := len(s) - return n == 0 // want `use s == "" to check for empty string instead of len\(s\) == 0` + return n == 0 // want `use s == "" to check for empty string instead of n == 0` } func aliasNotEmpty(s string) bool { n := len(s) - return n != 0 // want `use s != "" to check for non-empty string instead of len\(s\) != 0` + return n != 0 // want `use s != "" to check for non-empty string instead of n != 0` } func aliasGreaterThanZero(s string) bool { n := len(s) - return n > 0 // want `use s != "" to check for non-empty string instead of len\(s\) > 0` + return n > 0 // want `use s != "" to check for non-empty string instead of n > 0` } func aliasGreaterOrEqualOne(s string) bool { n := len(s) - return n >= 1 // want `use s != "" to check for non-empty string instead of len\(s\) >= 1` + return n >= 1 // want `use s != "" to check for non-empty string instead of n >= 1` } func aliasLessThanOne(s string) bool { n := len(s) - return n < 1 // want `use s == "" to check for empty string instead of len\(s\) < 1` + return n < 1 // want `use s == "" to check for empty string instead of n < 1` } func aliasLessOrEqualZero(s string) bool { n := len(s) - return n <= 0 // want `use s == "" to check for empty string instead of len\(s\) <= 0` + return n <= 0 // want `use s == "" to check for empty string instead of n <= 0` } func aliasReassignedNotFlagged(s string) bool { @@ -135,6 +135,15 @@ func arrayAliasNotFlagged(s [1]byte) bool { return n == 0 } +func namedVariableEmpty(username string) bool { + return len(username) == 0 // want `use username == "" to check for empty string instead of len\(username\) == 0` +} + +func namedVariableAliasEmpty(username string) bool { + usernameLen := len(username) + return usernameLen == 0 // want `use username == "" to check for empty string instead of usernameLen == 0` +} + func suppressedEmpty(s string) bool { return len(s) == 0 //nolint:lenstringzero } diff --git a/pkg/linters/lenstringzero/testdata/src/lenstringzero/lenstringzero.go.golden b/pkg/linters/lenstringzero/testdata/src/lenstringzero/lenstringzero.go.golden index 4027ded0d3f..172f99871f1 100644 --- a/pkg/linters/lenstringzero/testdata/src/lenstringzero/lenstringzero.go.golden +++ b/pkg/linters/lenstringzero/testdata/src/lenstringzero/lenstringzero.go.golden @@ -85,32 +85,32 @@ func lenNotComparedToZero(s string) bool { func aliasEmpty(s string) bool { n := len(s) - return n == 0 // want `use s == "" to check for empty string instead of len\(s\) == 0` + return n == 0 // want `use s == "" to check for empty string instead of n == 0` } func aliasNotEmpty(s string) bool { n := len(s) - return n != 0 // want `use s != "" to check for non-empty string instead of len\(s\) != 0` + return n != 0 // want `use s != "" to check for non-empty string instead of n != 0` } func aliasGreaterThanZero(s string) bool { n := len(s) - return n > 0 // want `use s != "" to check for non-empty string instead of len\(s\) > 0` + return n > 0 // want `use s != "" to check for non-empty string instead of n > 0` } func aliasGreaterOrEqualOne(s string) bool { n := len(s) - return n >= 1 // want `use s != "" to check for non-empty string instead of len\(s\) >= 1` + return n >= 1 // want `use s != "" to check for non-empty string instead of n >= 1` } func aliasLessThanOne(s string) bool { n := len(s) - return n < 1 // want `use s == "" to check for empty string instead of len\(s\) < 1` + return n < 1 // want `use s == "" to check for empty string instead of n < 1` } func aliasLessOrEqualZero(s string) bool { n := len(s) - return n <= 0 // want `use s == "" to check for empty string instead of len\(s\) <= 0` + return n <= 0 // want `use s == "" to check for empty string instead of n <= 0` } func aliasReassignedNotFlagged(s string) bool { @@ -135,6 +135,15 @@ func arrayAliasNotFlagged(s [1]byte) bool { return n == 0 } +func namedVariableEmpty(username string) bool { + return username == "" // want `use username == "" to check for empty string instead of len\(username\) == 0` +} + +func namedVariableAliasEmpty(username string) bool { + usernameLen := len(username) + return usernameLen == 0 // want `use username == "" to check for empty string instead of usernameLen == 0` +} + func suppressedEmpty(s string) bool { return len(s) == 0 //nolint:lenstringzero }