package s1032 import ( "go/ast" "go/token" "sort" "honnef.co/go/tools/analysis/code" "honnef.co/go/tools/analysis/facts/generated" "honnef.co/go/tools/analysis/lint" "honnef.co/go/tools/analysis/report" "honnef.co/go/tools/knowledge" "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" ) var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ Analyzer: &analysis.Analyzer{ Name: "S1032", Run: run, Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, }, Doc: &lint.RawDocumentation{ Title: `Use \'sort.Ints(x)\', \'sort.Float64s(x)\', and \'sort.Strings(x)\'`, Text: `The \'sort.Ints\', \'sort.Float64s\' and \'sort.Strings\' functions are easier to read than \'sort.Sort(sort.IntSlice(x))\', \'sort.Sort(sort.Float64Slice(x))\' and \'sort.Sort(sort.StringSlice(x))\'.`, Before: `sort.Sort(sort.StringSlice(x))`, After: `sort.Strings(x)`, Since: "2019.1", MergeIf: lint.MergeIfAny, }, }) var Analyzer = SCAnalyzer.Analyzer func isPermissibleSort(pass *analysis.Pass, node ast.Node) bool { call := node.(*ast.CallExpr) typeconv, ok := call.Args[0].(*ast.CallExpr) if !ok { return true } sel, ok := typeconv.Fun.(*ast.SelectorExpr) if !ok { return true } name := code.SelectorName(pass, sel) switch name { case "sort.IntSlice", "sort.Float64Slice", "sort.StringSlice": default: return true } return false } func run(pass *analysis.Pass) (interface{}, error) { type Error struct { node ast.Node msg string } var allErrors []Error fn := func(node ast.Node) { var body *ast.BlockStmt switch node := node.(type) { case *ast.FuncLit: body = node.Body case *ast.FuncDecl: body = node.Body default: lint.ExhaustiveTypeSwitch(node) } if body == nil { return } var errors []Error permissible := false fnSorts := func(node ast.Node) bool { if permissible { return false } if !code.IsCallTo(pass, node, "sort.Sort") { return true } if isPermissibleSort(pass, node) { permissible = true return false } call := node.(*ast.CallExpr) // isPermissibleSort guarantees that this type assertion will succeed typeconv := call.Args[knowledge.Arg("sort.Sort.data")].(*ast.CallExpr) sel := typeconv.Fun.(*ast.SelectorExpr) name := code.SelectorName(pass, sel) switch name { case "sort.IntSlice": errors = append(errors, Error{node, "should use sort.Ints(...) instead of sort.Sort(sort.IntSlice(...))"}) case "sort.Float64Slice": errors = append(errors, Error{node, "should use sort.Float64s(...) instead of sort.Sort(sort.Float64Slice(...))"}) case "sort.StringSlice": errors = append(errors, Error{node, "should use sort.Strings(...) instead of sort.Sort(sort.StringSlice(...))"}) } return true } ast.Inspect(body, fnSorts) if permissible { return } allErrors = append(allErrors, errors...) } code.Preorder(pass, fn, (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)) sort.Slice(allErrors, func(i, j int) bool { return allErrors[i].node.Pos() < allErrors[j].node.Pos() }) var prev token.Pos for _, err := range allErrors { if err.node.Pos() == prev { continue } prev = err.node.Pos() report.Report(pass, err.node, err.msg, report.FilterGenerated()) } return nil, nil }