sa4029.go raw

   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  	"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.RawDocumentation{
  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 = types.Unalias(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(types.Unalias(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  }
  88