diff --git a/defaultrule/utils.go b/defaultrule/utils.go index 324f617..d9ee654 100644 --- a/defaultrule/utils.go +++ b/defaultrule/utils.go @@ -3,6 +3,7 @@ package defaultrule import ( "fmt" "go/ast" + "go/types" "regexp" "strings" @@ -101,3 +102,29 @@ var commonInitialisms = map[string]bool{ } var allCapsRE = regexp.MustCompile(`^[A-Z0-9_]+$`) + +func isIdent(expr ast.Expr, ident string) bool { + id, ok := expr.(*ast.Ident) + return ok && id.Name == ident +} + +var zeroLiteral = map[string]bool{ + "false": true, // bool + // runes + `'\x00'`: true, + `'\000'`: true, + // strings + `""`: true, + "``": true, + // numerics + "0": true, + "0.": true, + "0.0": true, + "0i": true, +} + +func validType(T types.Type) bool { + return T != nil && + T != types.Typ[types.Invalid] && + !strings.Contains(T.String(), "invalid type") // good but not foolproof +} diff --git a/defaultrule/var-declarations.go b/defaultrule/var-declarations.go new file mode 100644 index 0000000..6217ddc --- /dev/null +++ b/defaultrule/var-declarations.go @@ -0,0 +1,93 @@ +package defaultrule + +import ( + "bytes" + "fmt" + "go/ast" + "go/printer" + "go/token" + + "github.com/mgechev/revive/file" + "github.com/mgechev/revive/rule" +) + +// VarDeclarationsRule lints given else constructs. +type VarDeclarationsRule struct{} + +// Apply applies the rule to given file. +func (r *VarDeclarationsRule) Apply(file *file.File, arguments rule.Arguments) []rule.Failure { + var failures []rule.Failure + + fileAst := file.GetAST() + walker := &lintVarDeclarations{ + file: file, + fileAst: fileAst, + onFailure: func(failure rule.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *VarDeclarationsRule) Name() string { + return "blank-imports" +} + +type lintVarDeclarations struct { + fileAst *ast.File + file *file.File + lastGen *ast.GenDecl + onFailure func(rule.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(rule.Failure{ + Confidence: 0.9, + Node: rhs, + Failure: fmt.Sprintf("should drop = %s from declaration of var %s; it is the zero value", render(rhs), v.Names[0]), + }) + return nil + } + } + return w +} + +func render(x interface{}) string { + var buf bytes.Buffer + if err := printer.Fprint(&buf, token.NewFileSet(), x); err != nil { + panic(err) + } + return buf.String() +}