src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

s1032.go (3310B)


      1 package s1032
      2 
      3 import (
      4 	"go/ast"
      5 	"go/token"
      6 	"sort"
      7 
      8 	"honnef.co/go/tools/analysis/code"
      9 	"honnef.co/go/tools/analysis/facts/generated"
     10 	"honnef.co/go/tools/analysis/lint"
     11 	"honnef.co/go/tools/analysis/report"
     12 	"honnef.co/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.RawDocumentation{
     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) (any, 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 }