sa4029.go (2430B)
1 package sa4029 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/types" 7 8 "honnef.co/go/tools/analysis/code" 9 "honnef.co/go/tools/analysis/edit" 10 "honnef.co/go/tools/analysis/lint" 11 "honnef.co/go/tools/analysis/report" 12 "honnef.co/go/tools/pattern" 13 14 "golang.org/x/tools/go/analysis" 15 ) 16 17 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 18 Analyzer: &analysis.Analyzer{ 19 Name: "SA4029", 20 Run: run, 21 Requires: code.RequiredAnalyzers, 22 }, 23 Doc: &lint.RawDocumentation{ 24 Title: "Ineffective attempt at sorting slice", 25 Text: ` 26 \'sort.Float64Slice\', \'sort.IntSlice\', and \'sort.StringSlice\' are 27 types, not functions. Doing \'x = sort.StringSlice(x)\' does nothing, 28 especially not sort any values. The correct usage is 29 \'sort.Sort(sort.StringSlice(x))\' or \'sort.StringSlice(x).Sort()\', 30 but there are more convenient helpers, namely \'sort.Float64s\', 31 \'sort.Ints\', and \'sort.Strings\'. 32 `, 33 Since: "2022.1", 34 Severity: lint.SeverityWarning, 35 MergeIf: lint.MergeIfAny, 36 }, 37 }) 38 39 var Analyzer = SCAnalyzer.Analyzer 40 41 var ineffectiveSortQ = pattern.MustParse(`(AssignStmt target@(Ident _) "=" (CallExpr typ@(Symbol (Or "sort.Float64Slice" "sort.IntSlice" "sort.StringSlice")) [target]))`) 42 43 func run(pass *analysis.Pass) (any, error) { 44 for node, m := range code.Matches(pass, ineffectiveSortQ) { 45 _, ok := types.Unalias(pass.TypesInfo.TypeOf(m.State["target"].(ast.Expr))).(*types.Slice) 46 if !ok { 47 // Avoid flagging 'x = sort.StringSlice(x)' where TypeOf(x) == sort.StringSlice 48 continue 49 } 50 51 var alternative string 52 typeName := types.TypeString(types.Unalias(m.State["typ"].(*types.TypeName).Type()), nil) 53 switch typeName { 54 case "sort.Float64Slice": 55 alternative = "Float64s" 56 case "sort.IntSlice": 57 alternative = "Ints" 58 case "sort.StringSlice": 59 alternative = "Strings" 60 default: 61 panic(fmt.Sprintf("unreachable: %q", typeName)) 62 } 63 64 r := &ast.CallExpr{ 65 Fun: &ast.SelectorExpr{ 66 X: &ast.Ident{Name: "sort"}, 67 Sel: &ast.Ident{Name: alternative}, 68 }, 69 Args: []ast.Expr{m.State["target"].(ast.Expr)}, 70 } 71 72 report.Report(pass, node, 73 fmt.Sprintf("%s is a type, not a function, and %s doesn't sort your values; consider using sort.%s instead", 74 typeName, 75 report.Render(pass, node.(*ast.AssignStmt).Rhs[0]), 76 alternative), 77 report.Fixes(edit.Fix(fmt.Sprintf("Replace with call to sort.%s", alternative), edit.ReplaceWithNode(pass.Fset, node, r)))) 78 } 79 return nil, nil 80 }