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