golang.org/x/tools@v0.21.0/go/analysis/passes/sortslice/analyzer.go (about)

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package sortslice defines an Analyzer that checks for calls
     6  // to sort.Slice that do not use a slice type as first argument.
     7  package sortslice
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"go/ast"
    13  	"go/format"
    14  	"go/types"
    15  
    16  	"golang.org/x/tools/go/analysis"
    17  	"golang.org/x/tools/go/analysis/passes/inspect"
    18  	"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
    19  	"golang.org/x/tools/go/ast/inspector"
    20  	"golang.org/x/tools/go/types/typeutil"
    21  )
    22  
    23  const Doc = `check the argument type of sort.Slice
    24  
    25  sort.Slice requires an argument of a slice type. Check that
    26  the interface{} value passed to sort.Slice is actually a slice.`
    27  
    28  var Analyzer = &analysis.Analyzer{
    29  	Name:     "sortslice",
    30  	Doc:      Doc,
    31  	URL:      "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sortslice",
    32  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    33  	Run:      run,
    34  }
    35  
    36  func run(pass *analysis.Pass) (interface{}, error) {
    37  	if !analysisutil.Imports(pass.Pkg, "sort") {
    38  		return nil, nil // doesn't directly import sort
    39  	}
    40  
    41  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    42  
    43  	nodeFilter := []ast.Node{
    44  		(*ast.CallExpr)(nil),
    45  	}
    46  
    47  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    48  		call := n.(*ast.CallExpr)
    49  		fn, _ := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
    50  		if !analysisutil.IsFunctionNamed(fn, "sort", "Slice", "SliceStable", "SliceIsSorted") {
    51  			return
    52  		}
    53  
    54  		arg := call.Args[0]
    55  		typ := pass.TypesInfo.Types[arg].Type
    56  
    57  		if tuple, ok := typ.(*types.Tuple); ok {
    58  			typ = tuple.At(0).Type() // special case for Slice(f(...))
    59  		}
    60  
    61  		switch typ.Underlying().(type) {
    62  		case *types.Slice, *types.Interface:
    63  			return
    64  		}
    65  
    66  		// Restore typ to the original type, we may unwrap the tuple above,
    67  		// typ might not be the type of arg.
    68  		typ = pass.TypesInfo.Types[arg].Type
    69  
    70  		var fixes []analysis.SuggestedFix
    71  		switch v := typ.Underlying().(type) {
    72  		case *types.Array:
    73  			var buf bytes.Buffer
    74  			format.Node(&buf, pass.Fset, &ast.SliceExpr{
    75  				X:      arg,
    76  				Slice3: false,
    77  				Lbrack: arg.End() + 1,
    78  				Rbrack: arg.End() + 3,
    79  			})
    80  			fixes = append(fixes, analysis.SuggestedFix{
    81  				Message: "Get a slice of the full array",
    82  				TextEdits: []analysis.TextEdit{{
    83  					Pos:     arg.Pos(),
    84  					End:     arg.End(),
    85  					NewText: buf.Bytes(),
    86  				}},
    87  			})
    88  		case *types.Pointer:
    89  			_, ok := v.Elem().Underlying().(*types.Slice)
    90  			if !ok {
    91  				break
    92  			}
    93  			var buf bytes.Buffer
    94  			format.Node(&buf, pass.Fset, &ast.StarExpr{
    95  				X: arg,
    96  			})
    97  			fixes = append(fixes, analysis.SuggestedFix{
    98  				Message: "Dereference the pointer to the slice",
    99  				TextEdits: []analysis.TextEdit{{
   100  					Pos:     arg.Pos(),
   101  					End:     arg.End(),
   102  					NewText: buf.Bytes(),
   103  				}},
   104  			})
   105  		case *types.Signature:
   106  			if v.Params().Len() != 0 || v.Results().Len() != 1 {
   107  				break
   108  			}
   109  			if _, ok := v.Results().At(0).Type().Underlying().(*types.Slice); !ok {
   110  				break
   111  			}
   112  			var buf bytes.Buffer
   113  			format.Node(&buf, pass.Fset, &ast.CallExpr{
   114  				Fun: arg,
   115  			})
   116  			fixes = append(fixes, analysis.SuggestedFix{
   117  				Message: "Call the function",
   118  				TextEdits: []analysis.TextEdit{{
   119  					Pos:     arg.Pos(),
   120  					End:     arg.End(),
   121  					NewText: buf.Bytes(),
   122  				}},
   123  			})
   124  		}
   125  
   126  		pass.Report(analysis.Diagnostic{
   127  			Pos:            call.Pos(),
   128  			End:            call.End(),
   129  			Message:        fmt.Sprintf("%s's argument must be a slice; is called with %s", fn.FullName(), typ.String()),
   130  			SuggestedFixes: fixes,
   131  		})
   132  	})
   133  	return nil, nil
   134  }