1
0
mirror of https://github.com/mgechev/revive.git synced 2024-11-28 08:49:11 +02:00
revive/rule/var-declarations.go

121 lines
3.1 KiB
Go
Raw Normal View History

2018-01-22 04:04:41 +02:00
package rule
2017-11-27 06:03:12 +02:00
import (
"fmt"
"go/ast"
"go/token"
2018-01-22 04:41:38 +02:00
"go/types"
2017-11-27 06:03:12 +02:00
2018-01-25 01:44:03 +02:00
"github.com/mgechev/revive/lint"
2017-11-27 06:03:12 +02:00
)
// VarDeclarationsRule lints given else constructs.
type VarDeclarationsRule struct{}
// Apply applies the rule to given file.
func (r *VarDeclarationsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
2018-01-25 01:44:03 +02:00
var failures []lint.Failure
2017-11-27 06:03:12 +02:00
2018-01-22 04:48:51 +02:00
fileAst := file.AST
2017-11-27 06:03:12 +02:00
walker := &lintVarDeclarations{
file: file,
fileAst: fileAst,
2018-01-25 01:44:03 +02:00
onFailure: func(failure lint.Failure) {
2017-11-27 06:03:12 +02:00
failures = append(failures, failure)
},
}
file.Pkg.TypeCheck()
2017-11-27 06:03:12 +02:00
ast.Walk(walker, fileAst)
return failures
}
// Name returns the rule name.
func (r *VarDeclarationsRule) Name() string {
2018-01-28 03:01:18 +02:00
return "var-declaration"
2017-11-27 06:03:12 +02:00
}
type lintVarDeclarations struct {
fileAst *ast.File
2018-01-25 01:44:03 +02:00
file *lint.File
2017-11-27 06:03:12 +02:00
lastGen *ast.GenDecl
2018-01-25 01:44:03 +02:00
onFailure func(lint.Failure)
2017-11-27 06:03:12 +02:00
}
func (w *lintVarDeclarations) Visit(node ast.Node) ast.Visitor {
switch v := node.(type) {
case *ast.GenDecl:
if v.Tok != token.CONST && v.Tok != token.VAR {
return nil
}
w.lastGen = v
return w
case *ast.ValueSpec:
if w.lastGen.Tok == token.CONST {
return nil
}
if len(v.Names) > 1 || v.Type == nil || len(v.Values) == 0 {
return nil
}
rhs := v.Values[0]
// An underscore var appears in a common idiom for compile-time interface satisfaction,
// as in "var _ Interface = (*Concrete)(nil)".
if isIdent(v.Names[0], "_") {
return nil
}
// If the RHS is a zero value, suggest dropping it.
zero := false
if lit, ok := rhs.(*ast.BasicLit); ok {
zero = zeroLiteral[lit.Value]
} else if isIdent(rhs, "nil") {
zero = true
}
if zero {
2018-01-25 01:44:03 +02:00
w.onFailure(lint.Failure{
2017-11-27 06:03:12 +02:00
Confidence: 0.9,
Node: rhs,
2018-01-25 20:35:27 +02:00
Category: "zero-value",
2018-01-22 04:41:38 +02:00
Failure: fmt.Sprintf("should drop = %s from declaration of var %s; it is the zero value", w.file.Render(rhs), v.Names[0]),
2017-11-27 06:03:12 +02:00
})
return nil
}
2018-01-22 04:41:38 +02:00
lhsTyp := w.file.Pkg.TypeOf(v.Type)
rhsTyp := w.file.Pkg.TypeOf(rhs)
if !validType(lhsTyp) || !validType(rhsTyp) {
// Type checking failed (often due to missing imports).
return nil
}
if !types.Identical(lhsTyp, rhsTyp) {
// Assignment to a different type is not redundant.
return nil
}
// The next three conditions are for suppressing the warning in situations
// where we were unable to typecheck.
// If the LHS type is an interface, don't warn, since it is probably a
// concrete type on the RHS. Note that our feeble lexical check here
// will only pick up interface{} and other literal interface types;
// that covers most of the cases we care to exclude right now.
if _, ok := v.Type.(*ast.InterfaceType); ok {
return nil
}
// If the RHS is an untyped const, only warn if the LHS type is its default type.
if defType, ok := w.file.IsUntypedConst(rhs); ok && !isIdent(v.Type, defType) {
return nil
}
2017-11-27 06:03:12 +02:00
2018-01-25 01:44:03 +02:00
w.onFailure(lint.Failure{
2018-01-22 04:41:38 +02:00
Category: "type-inference",
Confidence: 0.8,
Node: v.Type,
Failure: fmt.Sprintf("should omit type %s from declaration of var %s; it will be inferred from the right-hand side", w.file.Render(v.Type), v.Names[0]),
})
return nil
2017-11-27 06:03:12 +02:00
}
2018-01-22 04:41:38 +02:00
return w
2017-11-27 06:03:12 +02:00
}