summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/mgechev/revive/rule/time-naming.go
blob: a93f4b5ae00f18f01905b7886d0fd068f589d7d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package rule

import (
	"fmt"
	"go/ast"
	"go/types"
	"strings"

	"github.com/mgechev/revive/lint"
)

// TimeNamingRule lints given else constructs.
type TimeNamingRule struct{}

// Apply applies the rule to given file.
func (r *TimeNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
	var failures []lint.Failure

	onFailure := func(failure lint.Failure) {
		failures = append(failures, failure)
	}

	w := &lintTimeNames{file, onFailure}

	file.Pkg.TypeCheck()
	ast.Walk(w, file.AST)
	return failures
}

// Name returns the rule name.
func (r *TimeNamingRule) Name() string {
	return "time-naming"
}

type lintTimeNames struct {
	file      *lint.File
	onFailure func(lint.Failure)
}

func (w *lintTimeNames) Visit(node ast.Node) ast.Visitor {
	v, ok := node.(*ast.ValueSpec)
	if !ok {
		return w
	}
	for _, name := range v.Names {
		origTyp := w.file.Pkg.TypeOf(name)
		// Look for time.Duration or *time.Duration;
		// the latter is common when using flag.Duration.
		typ := origTyp
		if pt, ok := typ.(*types.Pointer); ok {
			typ = pt.Elem()
		}
		if !isNamedType(typ, "time", "Duration") {
			continue
		}
		suffix := ""
		for _, suf := range timeSuffixes {
			if strings.HasSuffix(name.Name, suf) {
				suffix = suf
				break
			}
		}
		if suffix == "" {
			continue
		}
		w.onFailure(lint.Failure{
			Category:   "time",
			Confidence: 0.9,
			Node:       v,
			Failure:    fmt.Sprintf("var %s is of type %v; don't use unit-specific suffix %q", name.Name, origTyp, suffix),
		})
	}
	return w
}

// timeSuffixes is a list of name suffixes that imply a time unit.
// This is not an exhaustive list.
var timeSuffixes = []string{
	"Sec", "Secs", "Seconds",
	"Msec", "Msecs",
	"Milli", "Millis", "Milliseconds",
	"Usec", "Usecs", "Microseconds",
	"MS", "Ms",
}

func isNamedType(typ types.Type, importPath, name string) bool {
	n, ok := typ.(*types.Named)
	if !ok {
		return false
	}
	tn := n.Obj()
	return tn != nil && tn.Pkg() != nil && tn.Pkg().Path() == importPath && tn.Name() == name
}