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

215 lines
4.5 KiB
Go

package checkers
import (
"go/ast"
"go/token"
"go/types"
"strings"
"github.com/go-lintpack/lintpack"
"github.com/go-lintpack/lintpack/astwalk"
"github.com/go-toolsmith/astcast"
)
func init() {
var info lintpack.CheckerInfo
info.Name = "wrapperFunc"
info.Tags = []string{"style", "experimental"}
info.Summary = "Detects function calls that can be replaced with convenience wrappers"
info.Before = `wg.Add(-1)`
info.After = `wg.Done()`
collection.AddChecker(&info, func(ctx *lintpack.CheckerContext) lintpack.FileWalker {
type arg struct {
index int
value string
}
type pattern struct {
pkg string
typ string // Only for typ patterns
args []arg
suggestion string
}
type matcher struct {
pkgPatterns []pattern
typPatterns []pattern
}
typPatterns := map[string][]arg{
"sync.WaitGroup.Add => WaitGroup.Done": {
{0, "-1"},
},
}
pkgPatterns := map[string][]arg{
"http.HandlerFunc => http.NotFoundHandler": {
{0, "http.NotFound"},
},
"strings.Replace => strings.ReplaceAll": {
{3, "-1"},
},
"strings.TrimFunc => strings.TrimSpace": {
{1, "unicode.IsSpace"},
},
"strings.Map => strings.ToTitle": {
{0, "unicode.ToTitle"},
},
"bytes.Replace => bytes.ReplaceAll": {
{3, "-1"},
},
"bytes.TrimFunc => bytes.TrimSpace": {
{1, "unicode.IsSpace"},
},
"bytes.Map => bytes.ToUpper": {
{0, "unicode.ToUpper"},
},
"bytes.Map => bytes.ToLower": {
{0, "unicode.ToLower"},
},
"bytes.Map => bytes.ToTitle": {
{0, "unicode.ToTitle"},
},
}
matchers := make(map[string]*matcher)
type templateKey struct {
from string
to string
}
decodeKey := func(key string) templateKey {
parts := strings.Split(key, " => ")
return templateKey{from: parts[0], to: parts[1]}
}
// Expand pkg patterns.
for key, args := range pkgPatterns {
key := decodeKey(key)
parts := strings.Split(key.from, ".")
fn := parts[1]
m := matchers[fn]
if m == nil {
m = &matcher{}
matchers[fn] = m
}
m.pkgPatterns = append(m.pkgPatterns, pattern{
pkg: parts[0],
args: args,
suggestion: key.to,
})
}
// Expand typ patterns.
for key, args := range typPatterns {
key := decodeKey(key)
parts := strings.Split(key.from, ".")
fn := parts[2]
m := matchers[fn]
if m == nil {
m = &matcher{}
matchers[fn] = m
}
m.typPatterns = append(m.typPatterns, pattern{
pkg: parts[0],
typ: parts[1],
args: args,
suggestion: key.to,
})
}
var valueOf func(x ast.Expr) string
valueOf = func(x ast.Expr) string {
switch x := x.(type) {
case *ast.Ident:
return x.Name
case *ast.SelectorExpr:
id, ok := x.X.(*ast.Ident)
if ok {
return id.Name + "." + x.Sel.Name
}
case *ast.BasicLit:
return x.Value
case *ast.UnaryExpr:
switch x.Op {
case token.SUB:
return "-" + valueOf(x.X)
case token.ADD:
return valueOf(x.X)
}
}
return ""
}
findSuggestion := func(call *ast.CallExpr, pkg, typ string, patterns []pattern) string {
for _, pat := range patterns {
if pat.pkg != pkg || pat.typ != typ {
continue
}
for _, arg := range pat.args {
if arg.value == valueOf(call.Args[arg.index]) {
return pat.suggestion
}
}
}
return ""
}
c := &wrapperFuncChecker{ctx: ctx}
c.findSuggestion = func(call *ast.CallExpr) string {
sel := astcast.ToSelectorExpr(call.Fun).Sel
if sel == nil {
return ""
}
x := astcast.ToSelectorExpr(call.Fun).X
m := matchers[sel.Name]
if m == nil {
return ""
}
if x, ok := x.(*ast.Ident); ok {
obj, ok := c.ctx.TypesInfo.ObjectOf(x).(*types.PkgName)
if ok {
return findSuggestion(call, obj.Name(), "", m.pkgPatterns)
}
}
typ := c.ctx.TypesInfo.TypeOf(x)
tn, ok := typ.(*types.Named)
if !ok {
return ""
}
return findSuggestion(
call,
tn.Obj().Pkg().Name(),
tn.Obj().Name(),
m.typPatterns)
}
return astwalk.WalkerForExpr(c)
})
}
type wrapperFuncChecker struct {
astwalk.WalkHandler
ctx *lintpack.CheckerContext
findSuggestion func(*ast.CallExpr) string
}
func (c *wrapperFuncChecker) VisitExpr(expr ast.Expr) {
call := astcast.ToCallExpr(expr)
if len(call.Args) == 0 {
return
}
if suggest := c.findSuggestion(call); suggest != "" {
c.warn(call, suggest)
}
}
func (c *wrapperFuncChecker) warn(cause ast.Node, suggest string) {
c.ctx.Warn(cause, "use %s method in `%s`", suggest, cause)
}