...

Source file src/golang.org/x/tools/go/analysis/passes/sortslice/analyzer.go

Documentation: golang.org/x/tools/go/analysis/passes/sortslice

     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/ast/inspector"
    19  	"golang.org/x/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  
    56  		if tuple, ok := typ.(*types.Tuple); ok {
    57  			typ = tuple.At(0).Type() // special case for Slice(f(...))
    58  		}
    59  
    60  		switch typ.Underlying().(type) {
    61  		case *types.Slice, *types.Interface:
    62  			return
    63  		}
    64  
    65  		// Restore typ to the original type, we may unwrap the tuple above,
    66  		// typ might not be the type of arg.
    67  		typ = pass.TypesInfo.Types[arg].Type
    68  
    69  		var fixes []analysis.SuggestedFix
    70  		switch v := typ.Underlying().(type) {
    71  		case *types.Array:
    72  			var buf bytes.Buffer
    73  			format.Node(&buf, pass.Fset, &ast.SliceExpr{
    74  				X:      arg,
    75  				Slice3: false,
    76  				Lbrack: arg.End() + 1,
    77  				Rbrack: arg.End() + 3,
    78  			})
    79  			fixes = append(fixes, analysis.SuggestedFix{
    80  				Message: "Get a slice of the full array",
    81  				TextEdits: []analysis.TextEdit{{
    82  					Pos:     arg.Pos(),
    83  					End:     arg.End(),
    84  					NewText: buf.Bytes(),
    85  				}},
    86  			})
    87  		case *types.Pointer:
    88  			_, ok := v.Elem().Underlying().(*types.Slice)
    89  			if !ok {
    90  				break
    91  			}
    92  			var buf bytes.Buffer
    93  			format.Node(&buf, pass.Fset, &ast.StarExpr{
    94  				X: arg,
    95  			})
    96  			fixes = append(fixes, analysis.SuggestedFix{
    97  				Message: "Dereference the pointer to the slice",
    98  				TextEdits: []analysis.TextEdit{{
    99  					Pos:     arg.Pos(),
   100  					End:     arg.End(),
   101  					NewText: buf.Bytes(),
   102  				}},
   103  			})
   104  		case *types.Signature:
   105  			if v.Params().Len() != 0 || v.Results().Len() != 1 {
   106  				break
   107  			}
   108  			if _, ok := v.Results().At(0).Type().Underlying().(*types.Slice); !ok {
   109  				break
   110  			}
   111  			var buf bytes.Buffer
   112  			format.Node(&buf, pass.Fset, &ast.CallExpr{
   113  				Fun: arg,
   114  			})
   115  			fixes = append(fixes, analysis.SuggestedFix{
   116  				Message: "Call the function",
   117  				TextEdits: []analysis.TextEdit{{
   118  					Pos:     arg.Pos(),
   119  					End:     arg.End(),
   120  					NewText: buf.Bytes(),
   121  				}},
   122  			})
   123  		}
   124  
   125  		pass.Report(analysis.Diagnostic{
   126  			Pos:            call.Pos(),
   127  			End:            call.End(),
   128  			Message:        fmt.Sprintf("%s's argument must be a slice; is called with %s", fnName, typ.String()),
   129  			SuggestedFixes: fixes,
   130  		})
   131  	})
   132  	return nil, nil
   133  }
   134  

View as plain text