github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/simple/s1032/s1032.go (about) 1 package s1032 2 3 import ( 4 "go/ast" 5 "go/token" 6 "sort" 7 8 "github.com/amarpal/go-tools/analysis/code" 9 "github.com/amarpal/go-tools/analysis/facts/generated" 10 "github.com/amarpal/go-tools/analysis/lint" 11 "github.com/amarpal/go-tools/analysis/report" 12 "github.com/amarpal/go-tools/knowledge" 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: "S1032", 21 Run: run, 22 Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, 23 }, 24 Doc: &lint.Documentation{ 25 Title: `Use \'sort.Ints(x)\', \'sort.Float64s(x)\', and \'sort.Strings(x)\'`, 26 Text: `The \'sort.Ints\', \'sort.Float64s\' and \'sort.Strings\' functions are easier to 27 read than \'sort.Sort(sort.IntSlice(x))\', \'sort.Sort(sort.Float64Slice(x))\' 28 and \'sort.Sort(sort.StringSlice(x))\'.`, 29 Before: `sort.Sort(sort.StringSlice(x))`, 30 After: `sort.Strings(x)`, 31 Since: "2019.1", 32 MergeIf: lint.MergeIfAny, 33 }, 34 }) 35 36 var Analyzer = SCAnalyzer.Analyzer 37 38 func isPermissibleSort(pass *analysis.Pass, node ast.Node) bool { 39 call := node.(*ast.CallExpr) 40 typeconv, ok := call.Args[0].(*ast.CallExpr) 41 if !ok { 42 return true 43 } 44 45 sel, ok := typeconv.Fun.(*ast.SelectorExpr) 46 if !ok { 47 return true 48 } 49 name := code.SelectorName(pass, sel) 50 switch name { 51 case "sort.IntSlice", "sort.Float64Slice", "sort.StringSlice": 52 default: 53 return true 54 } 55 56 return false 57 } 58 59 func run(pass *analysis.Pass) (interface{}, error) { 60 type Error struct { 61 node ast.Node 62 msg string 63 } 64 var allErrors []Error 65 fn := func(node ast.Node) { 66 var body *ast.BlockStmt 67 switch node := node.(type) { 68 case *ast.FuncLit: 69 body = node.Body 70 case *ast.FuncDecl: 71 body = node.Body 72 default: 73 lint.ExhaustiveTypeSwitch(node) 74 } 75 if body == nil { 76 return 77 } 78 79 var errors []Error 80 permissible := false 81 fnSorts := func(node ast.Node) bool { 82 if permissible { 83 return false 84 } 85 if !code.IsCallTo(pass, node, "sort.Sort") { 86 return true 87 } 88 if isPermissibleSort(pass, node) { 89 permissible = true 90 return false 91 } 92 call := node.(*ast.CallExpr) 93 // isPermissibleSort guarantees that this type assertion will succeed 94 typeconv := call.Args[knowledge.Arg("sort.Sort.data")].(*ast.CallExpr) 95 sel := typeconv.Fun.(*ast.SelectorExpr) 96 name := code.SelectorName(pass, sel) 97 98 switch name { 99 case "sort.IntSlice": 100 errors = append(errors, Error{node, "should use sort.Ints(...) instead of sort.Sort(sort.IntSlice(...))"}) 101 case "sort.Float64Slice": 102 errors = append(errors, Error{node, "should use sort.Float64s(...) instead of sort.Sort(sort.Float64Slice(...))"}) 103 case "sort.StringSlice": 104 errors = append(errors, Error{node, "should use sort.Strings(...) instead of sort.Sort(sort.StringSlice(...))"}) 105 } 106 return true 107 } 108 ast.Inspect(body, fnSorts) 109 110 if permissible { 111 return 112 } 113 allErrors = append(allErrors, errors...) 114 } 115 code.Preorder(pass, fn, (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)) 116 sort.Slice(allErrors, func(i, j int) bool { 117 return allErrors[i].node.Pos() < allErrors[j].node.Pos() 118 }) 119 var prev token.Pos 120 for _, err := range allErrors { 121 if err.node.Pos() == prev { 122 continue 123 } 124 prev = err.node.Pos() 125 report.Report(pass, err.node, err.msg, report.FilterGenerated()) 126 } 127 return nil, nil 128 }