diff --git a/README.md b/README.md index 9fb6e36eb..0056ad32b 100644 --- a/README.md +++ b/README.md @@ -398,6 +398,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a | Name | Config | Description | `golint` | Typed | | --------------------- | :----: | :--------------------------------------------------------------- | :------: | :---: | | [`context-keys-type`](./RULES_DESCRIPTIONS.md#context-key-types) | n/a | Disallows the usage of basic types in `context.WithValue`. | yes | yes | +| [`time-equal`](./RULES_DESCRIPTIONS.md#time-equal) | n/a | Suggests to use `time.Time.Equal` instead of `==` and `!=` for equality check time. | no | yes | | [`time-naming`](./RULES_DESCRIPTIONS.md#time-naming) | n/a | Conventions around the naming of time variables. | yes | yes | | [`var-declaration`](./RULES_DESCRIPTIONS.md#var-declaration) | n/a | Reduces redundancies around variable declaration. | yes | yes | | [`unexported-return`](./RULES_DESCRIPTIONS.md#unexported-return) | n/a | Warns when a public return is from unexported type. | yes | yes | diff --git a/RULES_DESCRIPTIONS.md b/RULES_DESCRIPTIONS.md index 2b9f5895f..5a3848b40 100644 --- a/RULES_DESCRIPTIONS.md +++ b/RULES_DESCRIPTIONS.md @@ -56,6 +56,7 @@ List of all available rules. - [struct-tag](#struct-tag) - [string-format](#string-format) - [superfluous-else](#superfluous-else) + - [time-equal](#time-equal) - [time-naming](#time-naming) - [var-naming](#var-naming) - [var-declaration](#var-declaration) @@ -547,6 +548,12 @@ This rule highlights redundant _else-blocks_ that can be eliminated from the cod _Configuration_: N/A +## time-equal + +_Description_: This rule warns when using `==` and `!=` for equality check `time.Time` and suggest to `time.time.Equal` method, for about information follow this [link](https://pkg.go.dev/time#Time) + +_Configuration_: N/A + ## time-naming _Description_: Using unit-specific suffix like "Secs", "Mins", ... when naming variables of type `time.Duration` can be misleading, this rule highlights those cases. diff --git a/config/config.go b/config/config.go index 298c0bdf5..56f85a57f 100644 --- a/config/config.go +++ b/config/config.go @@ -81,6 +81,7 @@ var allRules = append([]lint.Rule{ &rule.NestedStructs{}, &rule.IfReturnRule{}, &rule.UselessBreak{}, + &rule.TimeEqualRule{}, }, defaultRules...) var allFormatters = []lint.Formatter{ diff --git a/rule/time-equal.go b/rule/time-equal.go new file mode 100644 index 000000000..72ecf26fe --- /dev/null +++ b/rule/time-equal.go @@ -0,0 +1,76 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/token" + + "github.com/mgechev/revive/lint" +) + +// TimeEqualRule shows where "==" and "!=" used for equality check time.Time +type TimeEqualRule struct{} + +// Apply applies the rule to given file. +func (*TimeEqualRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := &lintTimeEqual{file, onFailure} + if w.file.Pkg.TypeCheck() != nil { + return nil + } + + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (*TimeEqualRule) Name() string { + return "time-equal" +} + +type lintTimeEqual struct { + file *lint.File + onFailure func(lint.Failure) +} + +func (l *lintTimeEqual) Visit(node ast.Node) ast.Visitor { + expr, ok := node.(*ast.BinaryExpr) + if !ok { + return l + } + + switch expr.Op { + case token.EQL, token.NEQ: + default: + return l + } + + xtyp := l.file.Pkg.TypeOf(expr.X) + ytyp := l.file.Pkg.TypeOf(expr.Y) + + if !isNamedType(xtyp, "time", "Time") || !isNamedType(ytyp, "time", "Time") { + return l + } + + var failure string + switch expr.Op { + case token.EQL: + failure = fmt.Sprintf("use %s.Equal(%s) instead of %q operator", expr.X, expr.Y, expr.Op) + case token.NEQ: + failure = fmt.Sprintf("use !%s.Equal(%s) instead of %q operator", expr.X, expr.Y, expr.Op) + } + + l.onFailure(lint.Failure{ + Category: "time", + Confidence: 1, + Node: node, + Failure: failure, + }) + + return l +} diff --git a/test/time-equal_test.go b/test/time-equal_test.go new file mode 100644 index 000000000..7e54269ed --- /dev/null +++ b/test/time-equal_test.go @@ -0,0 +1,12 @@ +package test + +import ( + "testing" + + "github.com/mgechev/revive/rule" +) + +// TestTimeEqual rule. +func TestTimeEqual(t *testing.T) { + testRule(t, "time-equal", &rule.TimeEqualRule{}) +} diff --git a/testdata/time-equal.go b/testdata/time-equal.go new file mode 100644 index 000000000..be72c4e5d --- /dev/null +++ b/testdata/time-equal.go @@ -0,0 +1,14 @@ +package pkg + +import "time" + +func t() bool { + t := time.Now() + u := t + + if !t.After(u) { + return t == u // MATCH /use t.Equal(u) instead of "==" operator/ + } + + return t != u // MATCH /use !t.Equal(u) instead of "!=" operator/ +}