golangci-lint/vendor/github.com/go-critic/checkers/commentedOutCode_checker.go
David López 37e6995b45 lintpack/gocritic: update lintpack & gocritic versions
update lintpack & gocritic versions to support all the new gocritic checks
2018-12-22 15:34:16 +03:00

120 lines
2.9 KiB
Go

package checkers
import (
"go/ast"
"go/token"
"regexp"
"strings"
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
"github.com/go-toolsmith/strparse"
)
func init() {
var info lintpack.CheckerInfo
info.Name = "commentedOutCode"
info.Tags = []string{"diagnostic", "experimental"}
info.Summary = "Detects commented-out code inside function bodies"
info.Before = `
// fmt.Println("Debugging hard")
foo(1, 2)`
info.After = `foo(1, 2)`
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
return astwalk.WalkerForLocalComment(&commentedOutCodeChecker{
ctx: ctx,
notQuiteFuncCall: regexp.MustCompile(`\w+\s+\([^)]*\)\s*$`),
})
})
}
type commentedOutCodeChecker struct {
astwalk.WalkHandler
ctx *lintpack.CheckerContext
notQuiteFuncCall *regexp.Regexp
}
func (c *commentedOutCodeChecker) VisitLocalComment(cg *ast.CommentGroup) {
s := cg.Text() // Collect text once
// We do multiple heuristics to avoid false positives.
// Many things can be improved here.
markers := []string{
"TODO", // TODO comments with code are permitted.
// "http://" is interpreted as a label with comment.
// There are other protocols we might want to include.
"http://",
"https://",
"e.g. ", // Clearly not a "selector expr" (mostly due to extra space)
}
for _, m := range markers {
if strings.Contains(s, m) {
return
}
}
// Some very short comment that can be skipped.
// Usually triggering on these results in false positive.
// Unless there is a very popular call like print/println.
cond := len(s) < len("quite too short") &&
!strings.Contains(s, "print") &&
!strings.Contains(s, "fmt.") &&
!strings.Contains(s, "log.")
if cond {
return
}
// Almost looks like a commented-out function call,
// but there is a whitespace between function name and
// parameters list. Skip these to avoid false positives.
if c.notQuiteFuncCall.MatchString(s) {
return
}
stmt := strparse.Stmt(s)
if stmt == strparse.BadStmt {
return // Most likely not a code
}
if !c.isPermittedStmt(stmt) {
c.warn(cg)
}
}
func (c *commentedOutCodeChecker) isPermittedStmt(stmt ast.Stmt) bool {
switch stmt := stmt.(type) {
case *ast.ExprStmt:
return c.isPermittedExpr(stmt.X)
case *ast.LabeledStmt:
return c.isPermittedStmt(stmt.Stmt)
case *ast.DeclStmt:
decl := stmt.Decl.(*ast.GenDecl)
return decl.Tok == token.TYPE
default:
return false
}
}
func (c *commentedOutCodeChecker) isPermittedExpr(x ast.Expr) bool {
// Permit anything except expressions that can be used
// with complete result discarding.
switch x := x.(type) {
case *ast.CallExpr:
return false
case *ast.UnaryExpr:
// "<-" channel receive is not permitted.
return x.Op != token.ARROW
default:
return true
}
}
func (c *commentedOutCodeChecker) warn(cause ast.Node) {
c.ctx.Warn(cause, "may want to remove commented-out code")
}