diff --git a/pkg/linters/tolowerequalfold/testdata/src/tolowerequalfold/tolowerequalfold.go b/pkg/linters/tolowerequalfold/testdata/src/tolowerequalfold/tolowerequalfold.go index c17e4c69d95..72e12101edc 100644 --- a/pkg/linters/tolowerequalfold/testdata/src/tolowerequalfold/tolowerequalfold.go +++ b/pkg/linters/tolowerequalfold/testdata/src/tolowerequalfold/tolowerequalfold.go @@ -7,10 +7,10 @@ func flaggedExamples() { name := "Alice" // should use strings.EqualFold(name, "alice") - _ = strings.ToLower(name) == "alice" // want `use strings\.EqualFold` - _ = strings.ToUpper(name) == "ALICE" // want `use strings\.EqualFold` - _ = "alice" == strings.ToLower(name) // want `use strings\.EqualFold` - _ = strings.ToLower(name) != "alice" // want `use strings\.EqualFold` + _ = strings.ToLower(name) == "alice" // want `use strings\.EqualFold` + _ = strings.ToUpper(name) == "ALICE" // want `use strings\.EqualFold` + _ = "alice" == strings.ToLower(name) // want `use strings\.EqualFold` + _ = strings.ToLower(name) != "alice" // want `use strings\.EqualFold` _ = strings.ToLower(name) == strings.ToLower("alice") // want `use strings\.EqualFold` } @@ -23,4 +23,6 @@ func okExamples() { // Regular case-sensitive comparison — no diagnostic _ = name == "Alice" _ = strings.ToLower(name) // used standalone, not in a comparison + _ = strings.ToLower(name) == name + _ = strings.ToLower(name) != name } diff --git a/pkg/linters/tolowerequalfold/tolowerequalfold.go b/pkg/linters/tolowerequalfold/tolowerequalfold.go index 9c98971abcb..92a14cc753c 100644 --- a/pkg/linters/tolowerequalfold/tolowerequalfold.go +++ b/pkg/linters/tolowerequalfold/tolowerequalfold.go @@ -47,10 +47,14 @@ func run(pass *analysis.Pass) (any, error) { return } - lowerLeft := isCaseConvCall(expr.X) - lowerRight := isCaseConvCall(expr.Y) + if arg, ok := caseConvArg(expr.X); ok && sameOperand(pass, arg, expr.Y) { + return + } + if arg, ok := caseConvArg(expr.Y); ok && sameOperand(pass, expr.X, arg) { + return + } - if lowerLeft || lowerRight { + if isCaseConvCall(expr.X) || isCaseConvCall(expr.Y) { pass.ReportRangef(expr, "use strings.EqualFold for case-insensitive comparison instead of strings.ToLower/ToUpper with ==") } @@ -61,18 +65,44 @@ func run(pass *analysis.Pass) (any, error) { // isCaseConvCall reports whether node is a call to strings.ToLower or strings.ToUpper. func isCaseConvCall(n ast.Node) bool { + _, ok := caseConvArg(n) + return ok +} + +// caseConvArg returns the argument when n is strings.ToLower/ToUpper(). +func caseConvArg(n ast.Node) (ast.Expr, bool) { call, ok := n.(*ast.CallExpr) if !ok { - return false + return nil, false + } + if len(call.Args) != 1 { + return nil, false } sel, ok := call.Fun.(*ast.SelectorExpr) if !ok { - return false + return nil, false } ident, ok := sel.X.(*ast.Ident) if !ok { + return nil, false + } + if ident.Name != "strings" { + return nil, false + } + if sel.Sel.Name != "ToLower" && sel.Sel.Name != "ToUpper" { + return nil, false + } + return call.Args[0], true +} + +func sameOperand(pass *analysis.Pass, left ast.Expr, right ast.Expr) bool { + leftIdent, leftOK := left.(*ast.Ident) + rightIdent, rightOK := right.(*ast.Ident) + if !leftOK || !rightOK { return false } - return ident.Name == "strings" && - (sel.Sel.Name == "ToLower" || sel.Sel.Name == "ToUpper") + + leftObj := pass.TypesInfo.ObjectOf(leftIdent) + rightObj := pass.TypesInfo.ObjectOf(rightIdent) + return leftObj != nil && rightObj != nil && leftObj == rightObj }