github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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  	"github.com/powerman/golang-tools/go/analysis"
    17  	"github.com/powerman/golang-tools/go/analysis/passes/inspect"
    18  	"github.com/powerman/golang-tools/go/ast/inspector"
    19  	"github.com/powerman/golang-tools/go/types/typeutil"
    20  )
    21  
    22  const Doc = `check the argument type of sort.Slice
    23  
    24  sort.Slice requires an argument of a slice type. Check that
    25  the interface{} value passed to sort.Slice is actually a slice.`
    26  
    27  var Analyzer = &analysis.Analyzer{
    28  	Name:     "sortslice",
    29  	Doc:      Doc,
    30  	Requires: []*analysis.Analyzer{inspect.Analyzer},
    31  	Run:      run,
    32  }
    33  
    34  func run(pass *analysis.Pass) (interface{}, error) {
    35  	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
    36  
    37  	nodeFilter := []ast.Node{
    38  		(*ast.CallExpr)(nil),
    39  	}
    40  
    41  	inspect.Preorder(nodeFilter, func(n ast.Node) {
    42  		call := n.(*ast.CallExpr)
    43  		fn, _ := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
    44  		if fn == nil {
    45  			return
    46  		}
    47  
    48  		fnName := fn.FullName()
    49  		if fnName != "sort.Slice" && fnName != "sort.SliceStable" && fnName != "sort.SliceIsSorted" {
    50  			return
    51  		}
    52  
    53  		arg := call.Args[0]
    54  		typ := pass.TypesInfo.Types[arg].Type
    55  		switch typ.Underlying().(type) {
    56  		case *types.Slice, *types.Interface:
    57  			return
    58  		}
    59  
    60  		var fixes []analysis.SuggestedFix
    61  		switch v := typ.Underlying().(type) {
    62  		case *types.Array:
    63  			var buf bytes.Buffer
    64  			format.Node(&buf, pass.Fset, &ast.SliceExpr{
    65  				X:      arg,
    66  				Slice3: false,
    67  				Lbrack: arg.End() + 1,
    68  				Rbrack: arg.End() + 3,
    69  			})
    70  			fixes = append(fixes, analysis.SuggestedFix{
    71  				Message: "Get a slice of the full array",
    72  				TextEdits: []analysis.TextEdit{{
    73  					Pos:     arg.Pos(),
    74  					End:     arg.End(),
    75  					NewText: buf.Bytes(),
    76  				}},
    77  			})
    78  		case *types.Pointer:
    79  			_, ok := v.Elem().Underlying().(*types.Slice)
    80  			if !ok {
    81  				break
    82  			}
    83  			var buf bytes.Buffer
    84  			format.Node(&buf, pass.Fset, &ast.StarExpr{
    85  				X: arg,
    86  			})
    87  			fixes = append(fixes, analysis.SuggestedFix{
    88  				Message: "Dereference the pointer to the slice",
    89  				TextEdits: []analysis.TextEdit{{
    90  					Pos:     arg.Pos(),
    91  					End:     arg.End(),
    92  					NewText: buf.Bytes(),
    93  				}},
    94  			})
    95  		case *types.Signature:
    96  			if v.Params().Len() != 0 || v.Results().Len() != 1 {
    97  				break
    98  			}
    99  			if _, ok := v.Results().At(0).Type().Underlying().(*types.Slice); !ok {
   100  				break
   101  			}
   102  			var buf bytes.Buffer
   103  			format.Node(&buf, pass.Fset, &ast.CallExpr{
   104  				Fun: arg,
   105  			})
   106  			fixes = append(fixes, analysis.SuggestedFix{
   107  				Message: "Call the function",
   108  				TextEdits: []analysis.TextEdit{{
   109  					Pos:     arg.Pos(),
   110  					End:     arg.End(),
   111  					NewText: buf.Bytes(),
   112  				}},
   113  			})
   114  		}
   115  
   116  		pass.Report(analysis.Diagnostic{
   117  			Pos:            call.Pos(),
   118  			End:            call.End(),
   119  			Message:        fmt.Sprintf("%s's argument must be a slice; is called with %s", fnName, typ.String()),
   120  			SuggestedFixes: fixes,
   121  		})
   122  	})
   123  	return nil, nil
   124  }