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