sa9004.go raw

   1  package sa9004
   2  
   3  import (
   4  	"go/ast"
   5  	"go/token"
   6  	"go/types"
   7  
   8  	"honnef.co/go/tools/analysis/code"
   9  	"honnef.co/go/tools/analysis/edit"
  10  	"honnef.co/go/tools/analysis/lint"
  11  	"honnef.co/go/tools/analysis/report"
  12  	"honnef.co/go/tools/go/ast/astutil"
  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:     "SA9004",
  21  		Run:      run,
  22  		Requires: []*analysis.Analyzer{inspect.Analyzer},
  23  	},
  24  	Doc: &lint.RawDocumentation{
  25  		Title: `Only the first constant has an explicit type`,
  26  
  27  		Text: `In a constant declaration such as the following:
  28  
  29      const (
  30          First byte = 1
  31          Second     = 2
  32      )
  33  
  34  the constant Second does not have the same type as the constant First.
  35  This construct shouldn't be confused with
  36  
  37      const (
  38          First byte = iota
  39          Second
  40      )
  41  
  42  where \'First\' and \'Second\' do indeed have the same type. The type is only
  43  passed on when no explicit value is assigned to the constant.
  44  
  45  When declaring enumerations with explicit values it is therefore
  46  important not to write
  47  
  48      const (
  49            EnumFirst EnumType = 1
  50            EnumSecond         = 2
  51            EnumThird          = 3
  52      )
  53  
  54  This discrepancy in types can cause various confusing behaviors and
  55  bugs.
  56  
  57  
  58  Wrong type in variable declarations
  59  
  60  The most obvious issue with such incorrect enumerations expresses
  61  itself as a compile error:
  62  
  63      package pkg
  64  
  65      const (
  66          EnumFirst  uint8 = 1
  67          EnumSecond       = 2
  68      )
  69  
  70      func fn(useFirst bool) {
  71          x := EnumSecond
  72          if useFirst {
  73              x = EnumFirst
  74          }
  75      }
  76  
  77  fails to compile with
  78  
  79      ./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment
  80  
  81  
  82  Losing method sets
  83  
  84  A more subtle issue occurs with types that have methods and optional
  85  interfaces. Consider the following:
  86  
  87      package main
  88  
  89      import "fmt"
  90  
  91      type Enum int
  92  
  93      func (e Enum) String() string {
  94          return "an enum"
  95      }
  96  
  97      const (
  98          EnumFirst  Enum = 1
  99          EnumSecond      = 2
 100      )
 101  
 102      func main() {
 103          fmt.Println(EnumFirst)
 104          fmt.Println(EnumSecond)
 105      }
 106  
 107  This code will output
 108  
 109      an enum
 110      2
 111  
 112  as \'EnumSecond\' has no explicit type, and thus defaults to \'int\'.`,
 113  		Since:    "2019.1",
 114  		Severity: lint.SeverityWarning,
 115  		MergeIf:  lint.MergeIfAny,
 116  	},
 117  })
 118  
 119  var Analyzer = SCAnalyzer.Analyzer
 120  
 121  func run(pass *analysis.Pass) (interface{}, error) {
 122  	fn := func(node ast.Node) {
 123  		decl := node.(*ast.GenDecl)
 124  		if !decl.Lparen.IsValid() {
 125  			return
 126  		}
 127  		if decl.Tok != token.CONST {
 128  			return
 129  		}
 130  
 131  		groups := astutil.GroupSpecs(pass.Fset, decl.Specs)
 132  	groupLoop:
 133  		for _, group := range groups {
 134  			if len(group) < 2 {
 135  				continue
 136  			}
 137  			if group[0].(*ast.ValueSpec).Type == nil {
 138  				// first constant doesn't have a type
 139  				continue groupLoop
 140  			}
 141  
 142  			firstType := pass.TypesInfo.TypeOf(group[0].(*ast.ValueSpec).Values[0])
 143  			for i, spec := range group {
 144  				spec := spec.(*ast.ValueSpec)
 145  				if i > 0 && spec.Type != nil {
 146  					continue groupLoop
 147  				}
 148  				if len(spec.Names) != 1 || len(spec.Values) != 1 {
 149  					continue groupLoop
 150  				}
 151  
 152  				if !types.ConvertibleTo(pass.TypesInfo.TypeOf(spec.Values[0]), firstType) {
 153  					continue groupLoop
 154  				}
 155  
 156  				switch v := spec.Values[0].(type) {
 157  				case *ast.BasicLit:
 158  				case *ast.UnaryExpr:
 159  					if _, ok := v.X.(*ast.BasicLit); !ok {
 160  						continue groupLoop
 161  					}
 162  				default:
 163  					// if it's not a literal it might be typed, such as
 164  					// time.Microsecond = 1000 * Nanosecond
 165  					continue groupLoop
 166  				}
 167  			}
 168  			var edits []analysis.TextEdit
 169  			typ := group[0].(*ast.ValueSpec).Type
 170  			for _, spec := range group[1:] {
 171  				nspec := *spec.(*ast.ValueSpec)
 172  				nspec.Type = typ
 173  				// The position of `spec` node excludes comments (if any).
 174  				// However, on generating the source back from the node, the comments are included. Setting `Comment` to nil ensures deduplication of comments.
 175  				nspec.Comment = nil
 176  				edits = append(edits, edit.ReplaceWithNode(pass.Fset, spec, &nspec))
 177  			}
 178  			report.Report(pass, group[0], "only the first constant in this group has an explicit type", report.Fixes(edit.Fix("add type to all constants in group", edits...)))
 179  		}
 180  	}
 181  	code.Preorder(pass, fn, (*ast.GenDecl)(nil))
 182  	return nil, nil
 183  }
 184