package rule import ( "fmt" "go/ast" "go/token" "go/types" "github.com/mgechev/revive/lint" ) // VarDeclarationsRule lints given else constructs. type VarDeclarationsRule struct{} // Apply applies the rule to given file. func (r *VarDeclarationsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { var failures []lint.Failure fileAst := file.AST walker := &lintVarDeclarations{ file: file, fileAst: fileAst, onFailure: func(failure lint.Failure) { failures = append(failures, failure) }, } file.Pkg.TypeCheck() ast.Walk(walker, fileAst) return failures } // Name returns the rule name. func (r *VarDeclarationsRule) Name() string { return "var-declaration" } type lintVarDeclarations struct { fileAst *ast.File file *lint.File lastGen *ast.GenDecl onFailure func(lint.Failure) } 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 { w.onFailure(lint.Failure{ Confidence: 0.9, Node: rhs, Category: "zero-value", Failure: fmt.Sprintf("should drop = %s from declaration of var %s; it is the zero value", w.file.Render(rhs), v.Names[0]), }) return nil } 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 } w.onFailure(lint.Failure{ 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 } return w }