github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/var-declarations.go (about)

     1  package rule
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/token"
     7  	"go/types"
     8  
     9  	"github.com/mgechev/revive/lint"
    10  )
    11  
    12  // VarDeclarationsRule lints given else constructs.
    13  type VarDeclarationsRule struct{}
    14  
    15  // Apply applies the rule to given file.
    16  func (r *VarDeclarationsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
    17  	var failures []lint.Failure
    18  
    19  	fileAst := file.AST
    20  	walker := &lintVarDeclarations{
    21  		file:    file,
    22  		fileAst: fileAst,
    23  		onFailure: func(failure lint.Failure) {
    24  			failures = append(failures, failure)
    25  		},
    26  	}
    27  
    28  	file.Pkg.TypeCheck()
    29  	ast.Walk(walker, fileAst)
    30  
    31  	return failures
    32  }
    33  
    34  // Name returns the rule name.
    35  func (r *VarDeclarationsRule) Name() string {
    36  	return "var-declaration"
    37  }
    38  
    39  type lintVarDeclarations struct {
    40  	fileAst   *ast.File
    41  	file      *lint.File
    42  	lastGen   *ast.GenDecl
    43  	onFailure func(lint.Failure)
    44  }
    45  
    46  func (w *lintVarDeclarations) Visit(node ast.Node) ast.Visitor {
    47  	switch v := node.(type) {
    48  	case *ast.GenDecl:
    49  		if v.Tok != token.CONST && v.Tok != token.VAR {
    50  			return nil
    51  		}
    52  		w.lastGen = v
    53  		return w
    54  	case *ast.ValueSpec:
    55  		if w.lastGen.Tok == token.CONST {
    56  			return nil
    57  		}
    58  		if len(v.Names) > 1 || v.Type == nil || len(v.Values) == 0 {
    59  			return nil
    60  		}
    61  		rhs := v.Values[0]
    62  		// An underscore var appears in a common idiom for compile-time interface satisfaction,
    63  		// as in "var _ Interface = (*Concrete)(nil)".
    64  		if isIdent(v.Names[0], "_") {
    65  			return nil
    66  		}
    67  		// If the RHS is a zero value, suggest dropping it.
    68  		zero := false
    69  		if lit, ok := rhs.(*ast.BasicLit); ok {
    70  			zero = zeroLiteral[lit.Value]
    71  		} else if isIdent(rhs, "nil") {
    72  			zero = true
    73  		}
    74  		if zero {
    75  			w.onFailure(lint.Failure{
    76  				Confidence: 0.9,
    77  				Node:       rhs,
    78  				Category:   "zero-value",
    79  				Failure:    fmt.Sprintf("should drop = %s from declaration of var %s; it is the zero value", w.file.Render(rhs), v.Names[0]),
    80  			})
    81  			return nil
    82  		}
    83  		lhsTyp := w.file.Pkg.TypeOf(v.Type)
    84  		rhsTyp := w.file.Pkg.TypeOf(rhs)
    85  
    86  		if !validType(lhsTyp) || !validType(rhsTyp) {
    87  			// Type checking failed (often due to missing imports).
    88  			return nil
    89  		}
    90  
    91  		if !types.Identical(lhsTyp, rhsTyp) {
    92  			// Assignment to a different type is not redundant.
    93  			return nil
    94  		}
    95  
    96  		// The next three conditions are for suppressing the warning in situations
    97  		// where we were unable to typecheck.
    98  
    99  		// If the LHS type is an interface, don't warn, since it is probably a
   100  		// concrete type on the RHS. Note that our feeble lexical check here
   101  		// will only pick up interface{} and other literal interface types;
   102  		// that covers most of the cases we care to exclude right now.
   103  		if _, ok := v.Type.(*ast.InterfaceType); ok {
   104  			return nil
   105  		}
   106  		// If the RHS is an untyped const, only warn if the LHS type is its default type.
   107  		if defType, ok := w.file.IsUntypedConst(rhs); ok && !isIdent(v.Type, defType) {
   108  			return nil
   109  		}
   110  
   111  		w.onFailure(lint.Failure{
   112  			Category:   "type-inference",
   113  			Confidence: 0.8,
   114  			Node:       v.Type,
   115  			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]),
   116  		})
   117  		return nil
   118  	}
   119  	return w
   120  }