st1011.go raw

   1  package st1011
   2  
   3  import (
   4  	"fmt"
   5  	"go/ast"
   6  	"go/token"
   7  	"strings"
   8  
   9  	"honnef.co/go/tools/analysis/code"
  10  	"honnef.co/go/tools/analysis/lint"
  11  	"honnef.co/go/tools/analysis/report"
  12  	"honnef.co/go/tools/go/types/typeutil"
  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:     "ST1011",
  21  		Run:      run,
  22  		Requires: []*analysis.Analyzer{inspect.Analyzer},
  23  	},
  24  	Doc: &lint.RawDocumentation{
  25  		Title: `Poorly chosen name for variable of type \'time.Duration\'`,
  26  		Text: `\'time.Duration\' values represent an amount of time, which is represented
  27  as a count of nanoseconds. An expression like \'5 * time.Microsecond\'
  28  yields the value \'5000\'. It is therefore not appropriate to suffix a
  29  variable of type \'time.Duration\' with any time unit, such as \'Msec\' or
  30  \'Milli\'.`,
  31  		Since:   `2019.1`,
  32  		MergeIf: lint.MergeIfAny,
  33  	},
  34  })
  35  
  36  var Analyzer = SCAnalyzer.Analyzer
  37  
  38  func run(pass *analysis.Pass) (interface{}, error) {
  39  	suffixes := []string{
  40  		"Sec", "Secs", "Seconds",
  41  		"Msec", "Msecs",
  42  		"Milli", "Millis", "Milliseconds",
  43  		"Usec", "Usecs", "Microseconds",
  44  		"MS", "Ms",
  45  	}
  46  	fn := func(names []*ast.Ident) {
  47  		for _, name := range names {
  48  			if _, ok := pass.TypesInfo.Defs[name]; !ok {
  49  				continue
  50  			}
  51  			T := pass.TypesInfo.TypeOf(name)
  52  			if !typeutil.IsTypeWithName(T, "time.Duration") && !typeutil.IsPointerToTypeWithName(T, "time.Duration") {
  53  				continue
  54  			}
  55  			for _, suffix := range suffixes {
  56  				if strings.HasSuffix(name.Name, suffix) {
  57  					report.Report(pass, name, fmt.Sprintf("var %s is of type %v; don't use unit-specific suffix %q", name.Name, T, suffix))
  58  					break
  59  				}
  60  			}
  61  		}
  62  	}
  63  
  64  	fn2 := func(node ast.Node) {
  65  		switch node := node.(type) {
  66  		case *ast.ValueSpec:
  67  			fn(node.Names)
  68  		case *ast.FieldList:
  69  			for _, field := range node.List {
  70  				fn(field.Names)
  71  			}
  72  		case *ast.AssignStmt:
  73  			if node.Tok != token.DEFINE {
  74  				break
  75  			}
  76  			var names []*ast.Ident
  77  			for _, lhs := range node.Lhs {
  78  				if lhs, ok := lhs.(*ast.Ident); ok {
  79  					names = append(names, lhs)
  80  				}
  81  			}
  82  			fn(names)
  83  		}
  84  	}
  85  
  86  	code.Preorder(pass, fn2, (*ast.ValueSpec)(nil), (*ast.FieldList)(nil), (*ast.AssignStmt)(nil))
  87  	return nil, nil
  88  }
  89