github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/sa4029/sa4029.go (about)

     1  package sa4029
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/types"
     7  
     8  	"github.com/amarpal/go-tools/analysis/code"
     9  	"github.com/amarpal/go-tools/analysis/edit"
    10  	"github.com/amarpal/go-tools/analysis/lint"
    11  	"github.com/amarpal/go-tools/analysis/report"
    12  	"github.com/amarpal/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.Documentation{
    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 = 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(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  }