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  }