github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/stylecheck/st1011/st1011.go (about) 1 package st1011 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/token" 7 "strings" 8 9 "github.com/amarpal/go-tools/analysis/code" 10 "github.com/amarpal/go-tools/analysis/lint" 11 "github.com/amarpal/go-tools/analysis/report" 12 "github.com/amarpal/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.Documentation{ 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.IsType(T, "time.Duration") && !typeutil.IsType(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 }