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  }