lint.go raw

   1  package sharedcheck
   2  
   3  import (
   4  	"fmt"
   5  	"go/ast"
   6  	"go/token"
   7  	"go/types"
   8  
   9  	"honnef.co/go/tools/analysis/code"
  10  	"honnef.co/go/tools/analysis/edit"
  11  	"honnef.co/go/tools/analysis/facts/generated"
  12  	"honnef.co/go/tools/analysis/facts/tokenfile"
  13  	"honnef.co/go/tools/analysis/report"
  14  	"honnef.co/go/tools/go/ast/astutil"
  15  	"honnef.co/go/tools/go/ir"
  16  	"honnef.co/go/tools/go/ir/irutil"
  17  	"honnef.co/go/tools/go/types/typeutil"
  18  	"honnef.co/go/tools/internal/passes/buildir"
  19  
  20  	"golang.org/x/tools/go/analysis"
  21  	"golang.org/x/tools/go/analysis/passes/inspect"
  22  )
  23  
  24  func CheckRangeStringRunes(pass *analysis.Pass) (interface{}, error) {
  25  	for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
  26  		cb := func(node ast.Node) bool {
  27  			rng, ok := node.(*ast.RangeStmt)
  28  			if !ok || !astutil.IsBlank(rng.Key) {
  29  				return true
  30  			}
  31  
  32  			v, _ := fn.ValueForExpr(rng.X)
  33  
  34  			// Check that we're converting from string to []rune
  35  			val, _ := v.(*ir.Convert)
  36  			if val == nil {
  37  				return true
  38  			}
  39  			Tsrc, ok := typeutil.CoreType(val.X.Type()).(*types.Basic)
  40  			if !ok || Tsrc.Kind() != types.String {
  41  				return true
  42  			}
  43  			Tdst, ok := typeutil.CoreType(val.Type()).(*types.Slice)
  44  			if !ok {
  45  				return true
  46  			}
  47  			TdstElem, ok := types.Unalias(Tdst.Elem()).(*types.Basic)
  48  			if !ok || TdstElem.Kind() != types.Int32 {
  49  				return true
  50  			}
  51  
  52  			// Check that the result of the conversion is only used to
  53  			// range over
  54  			refs := val.Referrers()
  55  			if refs == nil {
  56  				return true
  57  			}
  58  
  59  			// Expect two refs: one for obtaining the length of the slice,
  60  			// one for accessing the elements
  61  			if len(irutil.FilterDebug(*refs)) != 2 {
  62  				// TODO(dh): right now, we check that only one place
  63  				// refers to our slice. This will miss cases such as
  64  				// ranging over the slice twice. Ideally, we'd ensure that
  65  				// the slice is only used for ranging over (without
  66  				// accessing the key), but that is harder to do because in
  67  				// IR form, ranging over a slice looks like an ordinary
  68  				// loop with index increments and slice accesses. We'd
  69  				// have to look at the associated AST node to check that
  70  				// it's a range statement.
  71  				return true
  72  			}
  73  
  74  			pass.Reportf(rng.Pos(), "should range over string, not []rune(string)")
  75  
  76  			return true
  77  		}
  78  		if source := fn.Source(); source != nil {
  79  			ast.Inspect(source, cb)
  80  		}
  81  	}
  82  	return nil, nil
  83  }
  84  
  85  // RedundantTypeInDeclarationChecker returns a checker that flags variable declarations with redundantly specified types.
  86  // That is, it flags 'var v T = e' where e's type is identical to T and 'var v = e' (or 'v := e') would have the same effect.
  87  //
  88  // It does not flag variables under the following conditions, to reduce the number of false positives:
  89  // - global variables – these often specify types to aid godoc
  90  // - files that use cgo – cgo code generation and pointer checking emits redundant types
  91  //
  92  // It does not flag variables under the following conditions, unless flagHelpfulTypes is true, to reduce the number of noisy positives:
  93  // - packages that import syscall or unsafe – these sometimes use this form of assignment to make sure types are as expected
  94  // - variables named the blank identifier – a pattern used to confirm the types of variables
  95  // - untyped expressions on the rhs – the explicitness might aid readability
  96  func RedundantTypeInDeclarationChecker(verb string, flagHelpfulTypes bool) *analysis.Analyzer {
  97  	fn := func(pass *analysis.Pass) (interface{}, error) {
  98  		eval := func(expr ast.Expr) (types.TypeAndValue, error) {
  99  			info := &types.Info{
 100  				Types: map[ast.Expr]types.TypeAndValue{},
 101  			}
 102  			err := types.CheckExpr(pass.Fset, pass.Pkg, expr.Pos(), expr, info)
 103  			return info.Types[expr], err
 104  		}
 105  
 106  		if !flagHelpfulTypes {
 107  			// Don't look at code in low-level packages
 108  			for _, imp := range pass.Pkg.Imports() {
 109  				if imp.Path() == "syscall" || imp.Path() == "unsafe" {
 110  					return nil, nil
 111  				}
 112  			}
 113  		}
 114  
 115  		fn := func(node ast.Node) {
 116  			decl := node.(*ast.GenDecl)
 117  			if decl.Tok != token.VAR {
 118  				return
 119  			}
 120  
 121  			gen, _ := code.Generator(pass, decl.Pos())
 122  			if gen == generated.Cgo {
 123  				// TODO(dh): remove this exception once we can use UsesCgo
 124  				return
 125  			}
 126  
 127  			// Delay looking up parent AST nodes until we have to
 128  			checkedDecl := false
 129  
 130  		specLoop:
 131  			for _, spec := range decl.Specs {
 132  				spec := spec.(*ast.ValueSpec)
 133  				if spec.Type == nil {
 134  					continue
 135  				}
 136  				if len(spec.Names) != len(spec.Values) {
 137  					continue
 138  				}
 139  				Tlhs := pass.TypesInfo.TypeOf(spec.Type)
 140  				for i, v := range spec.Values {
 141  					if !flagHelpfulTypes && spec.Names[i].Name == "_" {
 142  						continue specLoop
 143  					}
 144  					Trhs := pass.TypesInfo.TypeOf(v)
 145  					if !types.Identical(Tlhs, Trhs) {
 146  						continue specLoop
 147  					}
 148  
 149  					// Some expressions are untyped and get converted to the lhs type implicitly.
 150  					// This applies to untyped constants, shift operations with an untyped lhs, and possibly others.
 151  					//
 152  					// Check if the type is truly redundant, i.e. if the type on the lhs doesn't match the default type of the untyped constant.
 153  					tv, err := eval(v)
 154  					if err != nil {
 155  						panic(err)
 156  					}
 157  					if b, ok := types.Unalias(tv.Type).(*types.Basic); ok && (b.Info()&types.IsUntyped) != 0 {
 158  						if Tlhs != types.Default(b) {
 159  							// The rhs is untyped and its default type differs from the explicit type on the lhs
 160  							continue specLoop
 161  						}
 162  						switch v := v.(type) {
 163  						case *ast.Ident:
 164  							// Only flag named constant rhs if it's a predeclared identifier.
 165  							// Don't flag other named constants, as the explicit type may aid readability.
 166  							if pass.TypesInfo.ObjectOf(v).Pkg() != nil && !flagHelpfulTypes {
 167  								continue specLoop
 168  							}
 169  						case *ast.BasicLit:
 170  							// Do flag basic literals
 171  						default:
 172  							// Don't flag untyped rhs expressions unless flagHelpfulTypes is set
 173  							if !flagHelpfulTypes {
 174  								continue specLoop
 175  							}
 176  						}
 177  					}
 178  				}
 179  
 180  				if !checkedDecl {
 181  					// Don't flag global variables. These often have explicit types for godoc's sake.
 182  					path, _ := astutil.PathEnclosingInterval(code.File(pass, decl), decl.Pos(), decl.Pos())
 183  				pathLoop:
 184  					for _, el := range path {
 185  						switch el.(type) {
 186  						case *ast.FuncDecl, *ast.FuncLit:
 187  							checkedDecl = true
 188  							break pathLoop
 189  						}
 190  					}
 191  					if !checkedDecl {
 192  						// decl is not inside a function
 193  						break specLoop
 194  					}
 195  				}
 196  
 197  				report.Report(pass, spec.Type, fmt.Sprintf("%s omit type %s from declaration; it will be inferred from the right-hand side", verb, report.Render(pass, spec.Type)), report.FilterGenerated(),
 198  					report.Fixes(edit.Fix("Remove redundant type", edit.Delete(spec.Type))))
 199  			}
 200  		}
 201  		code.Preorder(pass, fn, (*ast.GenDecl)(nil))
 202  		return nil, nil
 203  	}
 204  
 205  	return &analysis.Analyzer{
 206  		Run:      fn,
 207  		Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer, tokenfile.Analyzer},
 208  	}
 209  }
 210