diff options
Diffstat (limited to 'vendor/github.com/mgechev/revive')
10 files changed, 639 insertions, 52 deletions
diff --git a/vendor/github.com/mgechev/revive/formatter/friendly.go b/vendor/github.com/mgechev/revive/formatter/friendly.go index a543eebe00..d0a3099f8f 100644 --- a/vendor/github.com/mgechev/revive/formatter/friendly.go +++ b/vendor/github.com/mgechev/revive/formatter/friendly.go @@ -10,11 +10,6 @@ import ( "github.com/olekukonko/tablewriter" ) -var ( - errorEmoji = color.RedString("✘") - warningEmoji = color.YellowString("⚠") -) - var newLines = map[rune]bool{ 0x000A: true, 0x000B: true, @@ -25,6 +20,14 @@ var newLines = map[rune]bool{ 0x2029: true, } +func getErrorEmoji() string { + return color.RedString("✘") +} + +func getWarningEmoji() string { + return color.YellowString("⚠") +} + // Friendly is an implementation of the Formatter interface // which formats the errors to JSON. type Friendly struct { @@ -68,9 +71,9 @@ func (f *Friendly) printFriendlyFailure(failure lint.Failure, severity lint.Seve } func (f *Friendly) printHeaderRow(failure lint.Failure, severity lint.Severity) { - emoji := warningEmoji + emoji := getWarningEmoji() if severity == lint.SeverityError { - emoji = errorEmoji + emoji = getErrorEmoji() } fmt.Print(f.table([][]string{{emoji, "https://revive.run/r#" + failure.RuleName, color.GreenString(failure.Failure)}})) } @@ -85,9 +88,9 @@ type statEntry struct { } func (f *Friendly) printSummary(errors, warnings int) { - emoji := warningEmoji + emoji := getWarningEmoji() if errors > 0 { - emoji = errorEmoji + emoji = getErrorEmoji() } problemsLabel := "problems" if errors+warnings == 1 { diff --git a/vendor/github.com/mgechev/revive/rule/cognitive-complexity.go b/vendor/github.com/mgechev/revive/rule/cognitive-complexity.go index 711aa22897..ccd36bd09f 100644 --- a/vendor/github.com/mgechev/revive/rule/cognitive-complexity.go +++ b/vendor/github.com/mgechev/revive/rule/cognitive-complexity.go @@ -52,7 +52,7 @@ type cognitiveComplexityLinter struct { func (w cognitiveComplexityLinter) lint() { f := w.file for _, decl := range f.AST.Decls { - if fn, ok := decl.(*ast.FuncDecl); ok { + if fn, ok := decl.(*ast.FuncDecl); ok && fn.Body != nil { v := cognitiveComplexityVisitor{} c := v.subTreeComplexity(fn.Body) if c > w.maxComplexity { @@ -109,7 +109,7 @@ func (v *cognitiveComplexityVisitor) Visit(n ast.Node) ast.Visitor { return nil // skip visiting binexp sub-tree (already visited by binExpComplexity) case *ast.BranchStmt: if n.Label != nil { - v.complexity += 1 + v.complexity++ } } // TODO handle (at least) direct recursion diff --git a/vendor/github.com/mgechev/revive/rule/defer.go b/vendor/github.com/mgechev/revive/rule/defer.go new file mode 100644 index 0000000000..2ec7ef47c2 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/defer.go @@ -0,0 +1,137 @@ +package rule + +import ( + "fmt" + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// DeferRule lints unused params in functions. +type DeferRule struct{} + +// Apply applies the rule to given file. +func (r *DeferRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + allow := r.allowFromArgs(arguments) + + var failures []lint.Failure + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintDeferRule{onFailure: onFailure, allow: allow} + + ast.Walk(w, file.AST) + + return failures +} + +// Name returns the rule name. +func (r *DeferRule) Name() string { + return "defer" +} + +func (r *DeferRule) allowFromArgs(args lint.Arguments) map[string]bool { + if len(args) < 1 { + allow := map[string]bool{ + "loop": true, + "call-chain": true, + "method-call": true, + "return": true, + "recover": true, + } + + return allow + } + + aa, ok := args[0].([]interface{}) + if !ok { + panic(fmt.Sprintf("Invalid argument '%v' for 'defer' rule. Expecting []string, got %T", args[0], args[0])) + } + + allow := make(map[string]bool, len(aa)) + for _, subcase := range aa { + sc, ok := subcase.(string) + if !ok { + panic(fmt.Sprintf("Invalid argument '%v' for 'defer' rule. Expecting string, got %T", subcase, subcase)) + } + allow[sc] = true + } + + return allow +} + +type lintDeferRule struct { + onFailure func(lint.Failure) + inALoop bool + inADefer bool + inAFuncLit bool + allow map[string]bool +} + +func (w lintDeferRule) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.ForStmt: + w.visitSubtree(n.Body, w.inADefer, true, w.inAFuncLit) + return nil + case *ast.RangeStmt: + w.visitSubtree(n.Body, w.inADefer, true, w.inAFuncLit) + return nil + case *ast.FuncLit: + w.visitSubtree(n.Body, w.inADefer, false, true) + return nil + case *ast.ReturnStmt: + if len(n.Results) != 0 && w.inADefer && w.inAFuncLit { + w.newFailure("return in a defer function has no effect", n, 1.0, "logic", "return") + } + case *ast.CallExpr: + if isIdent(n.Fun, "recover") && !w.inADefer { + // confidence is not 1 because recover can be in a function that is deferred elsewhere + w.newFailure("recover must be called inside a deferred function", n, 0.8, "logic", "recover") + } + case *ast.DeferStmt: + w.visitSubtree(n.Call.Fun, true, false, false) + + if w.inALoop { + w.newFailure("prefer not to defer inside loops", n, 1.0, "bad practice", "loop") + } + + switch fn := n.Call.Fun.(type) { + case *ast.CallExpr: + w.newFailure("prefer not to defer chains of function calls", fn, 1.0, "bad practice", "call-chain") + case *ast.SelectorExpr: + if id, ok := fn.X.(*ast.Ident); ok { + isMethodCall := id != nil && id.Obj != nil && id.Obj.Kind == ast.Typ + if isMethodCall { + w.newFailure("be careful when deferring calls to methods without pointer receiver", fn, 0.8, "bad practice", "method-call") + } + } + } + return nil + } + + return w +} + +func (w lintDeferRule) visitSubtree(n ast.Node, inADefer, inALoop, inAFuncLit bool) { + nw := &lintDeferRule{ + onFailure: w.onFailure, + inADefer: inADefer, + inALoop: inALoop, + inAFuncLit: inAFuncLit, + allow: w.allow} + ast.Walk(nw, n) +} + +func (w lintDeferRule) newFailure(msg string, node ast.Node, confidence float64, cat string, subcase string) { + if !w.allow[subcase] { + return + } + + w.onFailure(lint.Failure{ + Confidence: confidence, + Node: node, + Category: cat, + Failure: msg, + }) +} diff --git a/vendor/github.com/mgechev/revive/rule/early-return.go b/vendor/github.com/mgechev/revive/rule/early-return.go new file mode 100644 index 0000000000..ffb568a867 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/early-return.go @@ -0,0 +1,78 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// EarlyReturnRule lints given else constructs. +type EarlyReturnRule struct{} + +// Apply applies the rule to given file. +func (r *EarlyReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintEarlyReturnRule{onFailure: onFailure} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *EarlyReturnRule) Name() string { + return "early-return" +} + +type lintEarlyReturnRule struct { + onFailure func(lint.Failure) +} + +func (w lintEarlyReturnRule) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.IfStmt: + if n.Else == nil { + // no else branch + return w + } + + elseBlock, ok := n.Else.(*ast.BlockStmt) + if !ok { + // is if-else-if + return w + } + + lenElseBlock := len(elseBlock.List) + if lenElseBlock < 1 { + // empty else block, continue (there is another rule that warns on empty blocks) + return w + } + + lenThenBlock := len(n.Body.List) + if lenThenBlock < 1 { + // then block is empty thus the stmt can be simplified + w.onFailure(lint.Failure{ + Confidence: 1, + Node: n, + Failure: "if c { } else {... return} can be simplified to if !c { ... return }", + }) + + return w + } + + _, lastThenStmtIsReturn := n.Body.List[lenThenBlock-1].(*ast.ReturnStmt) + _, lastElseStmtIsReturn := elseBlock.List[lenElseBlock-1].(*ast.ReturnStmt) + if lastElseStmtIsReturn && !lastThenStmtIsReturn { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: n, + Failure: "if c {...} else {... return } can be simplified to if !c { ... return } ...", + }) + } + } + + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/empty-block.go b/vendor/github.com/mgechev/revive/rule/empty-block.go index 7861394b32..fbec4d93c5 100644 --- a/vendor/github.com/mgechev/revive/rule/empty-block.go +++ b/vendor/github.com/mgechev/revive/rule/empty-block.go @@ -17,7 +17,7 @@ func (r *EmptyBlockRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure failures = append(failures, failure) } - w := lintEmptyBlock{make([]*ast.BlockStmt, 0), onFailure} + w := lintEmptyBlock{make(map[*ast.BlockStmt]bool, 0), onFailure} ast.Walk(w, file.AST) return failures } @@ -28,49 +28,38 @@ func (r *EmptyBlockRule) Name() string { } type lintEmptyBlock struct { - ignore []*ast.BlockStmt + ignore map[*ast.BlockStmt]bool onFailure func(lint.Failure) } func (w lintEmptyBlock) Visit(node ast.Node) ast.Visitor { - fd, ok := node.(*ast.FuncDecl) - if ok { - w.ignore = append(w.ignore, fd.Body) + switch n := node.(type) { + case *ast.FuncDecl: + w.ignore[n.Body] = true return w - } - - fl, ok := node.(*ast.FuncLit) - if ok { - w.ignore = append(w.ignore, fl.Body) - return w - } - - block, ok := node.(*ast.BlockStmt) - if !ok { - return w - } - - if mustIgnore(block, w.ignore) { + case *ast.FuncLit: + w.ignore[n.Body] = true return w - } - - if len(block.List) == 0 { - w.onFailure(lint.Failure{ - Confidence: 1, - Node: block, - Category: "logic", - Failure: "this block is empty, you can remove it", - }) + case *ast.RangeStmt: + if len(n.Body.List) == 0 { + w.onFailure(lint.Failure{ + Confidence: 0.9, + Node: n, + Category: "logic", + Failure: "this block is empty, you can remove it", + }) + return nil // skip visiting the range subtree (it will produce a duplicated failure) + } + case *ast.BlockStmt: + if !w.ignore[n] && len(n.List) == 0 { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: n, + Category: "logic", + Failure: "this block is empty, you can remove it", + }) + } } return w } - -func mustIgnore(block *ast.BlockStmt, blackList []*ast.BlockStmt) bool { - for _, b := range blackList { - if b == block { - return true - } - } - return false -} diff --git a/vendor/github.com/mgechev/revive/rule/identical-branches.go b/vendor/github.com/mgechev/revive/rule/identical-branches.go new file mode 100644 index 0000000000..094a79147b --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/identical-branches.go @@ -0,0 +1,82 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// IdenticalBranchesRule warns on constant logical expressions. +type IdenticalBranchesRule struct{} + +// Apply applies the rule to given file. +func (r *IdenticalBranchesRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + astFile := file.AST + w := &lintIdenticalBranches{astFile, onFailure} + ast.Walk(w, astFile) + return failures +} + +// Name returns the rule name. +func (r *IdenticalBranchesRule) Name() string { + return "identical-branches" +} + +type lintIdenticalBranches struct { + file *ast.File + onFailure func(lint.Failure) +} + +func (w *lintIdenticalBranches) Visit(node ast.Node) ast.Visitor { + n, ok := node.(*ast.IfStmt) + if !ok { + return w + } + + if n.Else == nil { + return w + } + branches := []*ast.BlockStmt{n.Body} + + elseBranch, ok := n.Else.(*ast.BlockStmt) + if !ok { // if-else-if construction + return w + } + branches = append(branches, elseBranch) + + if w.identicalBranches(branches) { + w.newFailure(n, "both branches of the if are identical") + } + + return w +} + +func (w *lintIdenticalBranches) identicalBranches(branches []*ast.BlockStmt) bool { + if len(branches) < 2 { + return false + } + + ref := gofmt(branches[0]) + for i := 1; i < len(branches); i++ { + if gofmt(branches[i]) != ref { + return false + } + } + + return true +} + +func (w lintIdenticalBranches) newFailure(node ast.Node, msg string) { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: node, + Category: "logic", + Failure: msg, + }) +} diff --git a/vendor/github.com/mgechev/revive/rule/unconditional-recursion.go b/vendor/github.com/mgechev/revive/rule/unconditional-recursion.go new file mode 100644 index 0000000000..c06626b5ac --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/unconditional-recursion.go @@ -0,0 +1,183 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// UnconditionalRecursionRule lints given else constructs. +type UnconditionalRecursionRule struct{} + +// Apply applies the rule to given file. +func (r *UnconditionalRecursionRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintUnconditionalRecursionRule{onFailure: onFailure} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *UnconditionalRecursionRule) Name() string { + return "unconditional-recursion" +} + +type funcDesc struct { + reciverID *ast.Ident + id *ast.Ident +} + +func (fd *funcDesc) equal(other *funcDesc) bool { + receiversAreEqual := (fd.reciverID == nil && other.reciverID == nil) || fd.reciverID != nil && other.reciverID != nil && fd.reciverID.Name == other.reciverID.Name + idsAreEqual := (fd.id == nil && other.id == nil) || fd.id.Name == other.id.Name + + return receiversAreEqual && idsAreEqual +} + +type funcStatus struct { + funcDesc *funcDesc + seenConditionalExit bool +} + +type lintUnconditionalRecursionRule struct { + onFailure func(lint.Failure) + currentFunc *funcStatus +} + +// Visit will traverse the file AST. +// The rule is based in the following algorithm: inside each function body we search for calls to the function itself. +// We do not search inside conditional control structures (if, for, switch, ...) because any recursive call inside them is conditioned +// We do search inside conditional control structures are statements that will take the control out of the function (return, exit, panic) +// If we find conditional control exits, it means the function is NOT unconditionally-recursive +// If we find a recursive call before finding any conditional exit, a failure is generated +// In resume: if we found a recursive call control-dependant from the entry point of the function then we raise a failure. +func (w lintUnconditionalRecursionRule) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.FuncDecl: + var rec *ast.Ident + switch { + case n.Recv == nil || n.Recv.NumFields() < 1 || len(n.Recv.List[0].Names) < 1: + rec = nil + default: + rec = n.Recv.List[0].Names[0] + } + + w.currentFunc = &funcStatus{&funcDesc{rec, n.Name}, false} + case *ast.CallExpr: + var funcID *ast.Ident + var selector *ast.Ident + switch c := n.Fun.(type) { + case *ast.Ident: + selector = nil + funcID = c + case *ast.SelectorExpr: + var ok bool + selector, ok = c.X.(*ast.Ident) + if !ok { // a.b....Foo() + return nil + } + funcID = c.Sel + default: + return w + } + + if w.currentFunc != nil && // not in a func body + !w.currentFunc.seenConditionalExit && // there is a conditional exit in the function + w.currentFunc.funcDesc.equal(&funcDesc{selector, funcID}) { + w.onFailure(lint.Failure{ + Category: "logic", + Confidence: 1, + Node: n, + Failure: "unconditional recursive call", + }) + } + case *ast.IfStmt: + w.updateFuncStatus(n.Body) + w.updateFuncStatus(n.Else) + return nil + case *ast.SelectStmt: + w.updateFuncStatus(n.Body) + return nil + case *ast.RangeStmt: + w.updateFuncStatus(n.Body) + return nil + case *ast.TypeSwitchStmt: + w.updateFuncStatus(n.Body) + return nil + case *ast.SwitchStmt: + w.updateFuncStatus(n.Body) + return nil + case *ast.GoStmt: + for _, a := range n.Call.Args { + ast.Walk(w, a) // check if arguments have a recursive call + } + return nil // recursive async call is not an issue + case *ast.ForStmt: + if n.Cond != nil { + return nil + } + // unconditional loop + return w + } + + return w +} + +func (w *lintUnconditionalRecursionRule) updateFuncStatus(node ast.Node) { + if node == nil || w.currentFunc == nil || w.currentFunc.seenConditionalExit { + return + } + + w.currentFunc.seenConditionalExit = w.hasControlExit(node) +} + +var exitFunctions = map[string]map[string]bool{ + "os": map[string]bool{"Exit": true}, + "syscall": map[string]bool{"Exit": true}, + "log": map[string]bool{ + "Fatal": true, + "Fatalf": true, + "Fatalln": true, + "Panic": true, + "Panicf": true, + "Panicln": true, + }, +} + +func (w *lintUnconditionalRecursionRule) hasControlExit(node ast.Node) bool { + // isExit returns true if the given node makes control exit the function + isExit := func(node ast.Node) bool { + switch n := node.(type) { + case *ast.ReturnStmt: + return true + case *ast.CallExpr: + if isIdent(n.Fun, "panic") { + return true + } + se, ok := n.Fun.(*ast.SelectorExpr) + if !ok { + return false + } + + id, ok := se.X.(*ast.Ident) + if !ok { + return false + } + + fn := se.Sel.Name + pkg := id.Name + if exitFunctions[pkg] != nil && exitFunctions[pkg][fn] { // it's a call to an exit function + return true + } + } + + return false + } + + return len(pick(node, isExit, nil)) != 0 +} diff --git a/vendor/github.com/mgechev/revive/rule/unexported-naming.go b/vendor/github.com/mgechev/revive/rule/unexported-naming.go new file mode 100644 index 0000000000..96cec3e46d --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/unexported-naming.go @@ -0,0 +1,115 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/token" + + "github.com/mgechev/revive/lint" +) + +// UnexportedNamingRule lints wrongly named unexported symbols. +type UnexportedNamingRule struct{} + +// Apply applies the rule to given file. +func (r *UnexportedNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + ba := &unexportablenamingLinter{onFailure} + ast.Walk(ba, file.AST) + + return failures +} + +// Name returns the rule name. +func (r *UnexportedNamingRule) Name() string { + return "unexported-naming" +} + +type unexportablenamingLinter struct { + onFailure func(lint.Failure) +} + +func (unl unexportablenamingLinter) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.FuncDecl: + unl.lintFunction(n.Type, n.Body) + return nil + case *ast.FuncLit: + unl.lintFunction(n.Type, n.Body) + + return nil + case *ast.AssignStmt: + if n.Tok != token.DEFINE { + return nil + } + + ids := []*ast.Ident{} + for _, e := range n.Lhs { + id, ok := e.(*ast.Ident) + if !ok { + continue + } + ids = append(ids, id) + } + + unl.lintIDs(ids) + + case *ast.DeclStmt: + gd, ok := n.Decl.(*ast.GenDecl) + if !ok { + return nil + } + + if len(gd.Specs) < 1 { + return nil + } + + vs, ok := gd.Specs[0].(*ast.ValueSpec) + if !ok { + return nil + } + + unl.lintIDs(vs.Names) + } + + return unl +} + +func (unl unexportablenamingLinter) lintFunction(ft *ast.FuncType, body *ast.BlockStmt) { + unl.lintFields(ft.Params) + unl.lintFields(ft.Results) + + if body != nil { + ast.Walk(unl, body) + } +} + +func (unl unexportablenamingLinter) lintFields(fields *ast.FieldList) { + if fields == nil { + return + } + + ids := []*ast.Ident{} + for _, field := range fields.List { + ids = append(ids, field.Names...) + } + + unl.lintIDs(ids) +} + +func (unl unexportablenamingLinter) lintIDs(ids []*ast.Ident) { + for _, id := range ids { + if id.IsExported() { + unl.onFailure(lint.Failure{ + Node: id, + Confidence: 1, + Category: "naming", + Failure: fmt.Sprintf("the symbol %s is local, its name should start with a lowercase letter", id.String()), + }) + } + } +} diff --git a/vendor/github.com/mgechev/revive/rule/unused-receiver.go b/vendor/github.com/mgechev/revive/rule/unused-receiver.go index 43eaf83a49..2289a517e5 100644 --- a/vendor/github.com/mgechev/revive/rule/unused-receiver.go +++ b/vendor/github.com/mgechev/revive/rule/unused-receiver.go @@ -11,7 +11,7 @@ import ( type UnusedReceiverRule struct{} // Apply applies the rule to given file. -func (_ *UnusedReceiverRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { +func (*UnusedReceiverRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { var failures []lint.Failure onFailure := func(failure lint.Failure) { @@ -26,7 +26,7 @@ func (_ *UnusedReceiverRule) Apply(file *lint.File, _ lint.Arguments) []lint.Fai } // Name returns the rule name. -func (_ *UnusedReceiverRule) Name() string { +func (*UnusedReceiverRule) Name() string { return "unused-receiver" } diff --git a/vendor/github.com/mgechev/revive/rule/utils.go b/vendor/github.com/mgechev/revive/rule/utils.go index 6ba542b716..38677c839d 100644 --- a/vendor/github.com/mgechev/revive/rule/utils.go +++ b/vendor/github.com/mgechev/revive/rule/utils.go @@ -182,8 +182,8 @@ func isExprABooleanLit(n ast.Node) (lexeme string, ok bool) { return oper.Name, (oper.Name == trueName || oper.Name == falseName) } -// gofmt returns a string representation of the expression. -func gofmt(x ast.Expr) string { +// gofmt returns a string representation of an AST subtree. +func gofmt(x interface{}) string { buf := bytes.Buffer{} fs := token.NewFileSet() printer.Fprint(&buf, fs, x) |