120 lines
2.9 KiB
Go
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")
|
|
}
|