mirror of
https://github.com/mgechev/revive.git
synced 2025-07-15 01:04:40 +02:00
refactor: moves code related to AST from rule.utils into astutils package (#1380)
Modifications summary: * Moves AST-related functions from rule/utils.go to astutils/ast_utils.go (+ modifies function calls) * Renames some of these AST-related functions * Avoids instantiating a printer config at each call to astutils.GoFmt * Uses astutils.IsIdent and astutils.IsPkgDotName when possible
This commit is contained in:
@ -2,8 +2,12 @@
|
||||
package astutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"regexp"
|
||||
"slices"
|
||||
)
|
||||
|
||||
@ -78,9 +82,80 @@ func getFieldTypeName(typ ast.Expr) string {
|
||||
}
|
||||
}
|
||||
|
||||
// IsStringLiteral returns true if the given expression is a string literal, false otherwise
|
||||
// IsStringLiteral returns true if the given expression is a string literal, false otherwise.
|
||||
func IsStringLiteral(e ast.Expr) bool {
|
||||
sl, ok := e.(*ast.BasicLit)
|
||||
|
||||
return ok && sl.Kind == token.STRING
|
||||
}
|
||||
|
||||
// IsCgoExported returns true if the given function declaration is exported as Cgo function, false otherwise.
|
||||
func IsCgoExported(f *ast.FuncDecl) bool {
|
||||
if f.Recv != nil || f.Doc == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
cgoExport := regexp.MustCompile(fmt.Sprintf("(?m)^//export %s$", regexp.QuoteMeta(f.Name.Name)))
|
||||
for _, c := range f.Doc.List {
|
||||
if cgoExport.MatchString(c.Text) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsIdent returns true if the given expression is the identifier with name ident, false otherwise.
|
||||
func IsIdent(expr ast.Expr, ident string) bool {
|
||||
id, ok := expr.(*ast.Ident)
|
||||
return ok && id.Name == ident
|
||||
}
|
||||
|
||||
// IsPkgDotName returns true if the given expression is a selector expression of the form <pkg>.<name>, false otherwise.
|
||||
func IsPkgDotName(expr ast.Expr, pkg, name string) bool {
|
||||
sel, ok := expr.(*ast.SelectorExpr)
|
||||
return ok && IsIdent(sel.X, pkg) && IsIdent(sel.Sel, name)
|
||||
}
|
||||
|
||||
// PickNodes yields a list of nodes by picking them from a sub-ast with root node n.
|
||||
// Nodes are selected by applying the selector function
|
||||
func PickNodes(n ast.Node, selector func(n ast.Node) bool) []ast.Node {
|
||||
var result []ast.Node
|
||||
|
||||
if n == nil {
|
||||
return result
|
||||
}
|
||||
|
||||
onSelect := func(n ast.Node) {
|
||||
result = append(result, n)
|
||||
}
|
||||
p := picker{selector: selector, onSelect: onSelect}
|
||||
ast.Walk(p, n)
|
||||
return result
|
||||
}
|
||||
|
||||
type picker struct {
|
||||
selector func(n ast.Node) bool
|
||||
onSelect func(n ast.Node)
|
||||
}
|
||||
|
||||
func (p picker) Visit(node ast.Node) ast.Visitor {
|
||||
if p.selector == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if p.selector(node) {
|
||||
p.onSelect(node)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
var gofmtConfig = &printer.Config{Tabwidth: 8}
|
||||
|
||||
// GoFmt returns a string representation of an AST subtree.
|
||||
func GoFmt(x any) string {
|
||||
buf := bytes.Buffer{}
|
||||
fs := token.NewFileSet()
|
||||
gofmtConfig.Fprint(&buf, fs, x)
|
||||
return buf.String()
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -76,9 +77,9 @@ func (w atomic) Visit(node ast.Node) ast.Visitor {
|
||||
broken := false
|
||||
|
||||
if uarg, ok := arg.(*ast.UnaryExpr); ok && uarg.Op == token.AND {
|
||||
broken = gofmt(left) == gofmt(uarg.X)
|
||||
broken = astutils.GoFmt(left) == astutils.GoFmt(uarg.X)
|
||||
} else if star, ok := left.(*ast.StarExpr); ok {
|
||||
broken = gofmt(star.X) == gofmt(arg)
|
||||
broken = astutils.GoFmt(star.X) == astutils.GoFmt(arg)
|
||||
}
|
||||
|
||||
if broken {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -190,7 +191,7 @@ func (w *lintConfusingNames) Visit(n ast.Node) ast.Visitor {
|
||||
// Exclude naming warnings for functions that are exported to C but
|
||||
// not exported in the Go API.
|
||||
// See https://github.com/golang/lint/issues/144.
|
||||
if ast.IsExported(v.Name.Name) || !isCgoExported(v) {
|
||||
if ast.IsExported(v.Name.Name) || !astutils.IsCgoExported(v) {
|
||||
checkMethodName(getStructName(v.Recv), v.Name, w)
|
||||
}
|
||||
case *ast.TypeSpec:
|
||||
|
@ -3,6 +3,7 @@ package rule
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -28,7 +29,7 @@ func (*ConfusingResultsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Fai
|
||||
|
||||
lastType := ""
|
||||
for _, result := range funcDecl.Type.Results.List {
|
||||
resultTypeName := gofmt(result.Type)
|
||||
resultTypeName := astutils.GoFmt(result.Type)
|
||||
|
||||
if resultTypeName == lastType {
|
||||
failures = append(failures, lint.Failure{
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -40,7 +41,7 @@ func (w *lintConstantLogicalExpr) Visit(node ast.Node) ast.Visitor {
|
||||
return w
|
||||
}
|
||||
|
||||
subExpressionsAreNotEqual := gofmt(n.X) != gofmt(n.Y)
|
||||
subExpressionsAreNotEqual := astutils.GoFmt(n.X) != astutils.GoFmt(n.Y)
|
||||
if subExpressionsAreNotEqual {
|
||||
return w // nothing to say
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"go/ast"
|
||||
"strings"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -28,7 +29,7 @@ func (r *ContextAsArgumentRule) Apply(file *lint.File, _ lint.Arguments) []lint.
|
||||
// Flag any that show up after the first.
|
||||
isCtxStillAllowed := true
|
||||
for _, arg := range fnArgs {
|
||||
argIsCtx := isPkgDot(arg.Type, "context", "Context")
|
||||
argIsCtx := astutils.IsPkgDotName(arg.Type, "context", "Context")
|
||||
if argIsCtx && !isCtxStillAllowed {
|
||||
failures = append(failures, lint.Failure{
|
||||
Node: arg,
|
||||
@ -40,7 +41,7 @@ func (r *ContextAsArgumentRule) Apply(file *lint.File, _ lint.Arguments) []lint.
|
||||
break // only flag one
|
||||
}
|
||||
|
||||
typeName := gofmt(arg.Type)
|
||||
typeName := astutils.GoFmt(arg.Type)
|
||||
// a parameter of type context.Context is still allowed if the current arg type is in the allow types LookUpTable
|
||||
_, isCtxStillAllowed = r.allowTypes[typeName]
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -51,15 +52,7 @@ func (w lintContextKeyTypes) Visit(n ast.Node) ast.Visitor {
|
||||
|
||||
func checkContextKeyType(w lintContextKeyTypes, x *ast.CallExpr) {
|
||||
f := w.file
|
||||
sel, ok := x.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
pkg, ok := sel.X.(*ast.Ident)
|
||||
if !ok || pkg.Name != "context" {
|
||||
return
|
||||
}
|
||||
if sel.Sel.Name != "WithValue" {
|
||||
if !astutils.IsPkgDotName(x.Fun, "context", "WithValue") {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -111,7 +112,7 @@ func (w lintFunctionForDataRaces) Visit(node ast.Node) ast.Visitor {
|
||||
return ok
|
||||
}
|
||||
|
||||
ids := pick(funcLit.Body, selectIDs)
|
||||
ids := astutils.PickNodes(funcLit.Body, selectIDs)
|
||||
for _, id := range ids {
|
||||
id := id.(*ast.Ident)
|
||||
_, isRangeID := w.rangeIDs[id.Obj]
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -106,7 +107,7 @@ func (w lintDeferRule) Visit(node ast.Node) ast.Visitor {
|
||||
w.newFailure("return in a defer function has no effect", n, 1.0, lint.FailureCategoryLogic, deferOptionReturn)
|
||||
}
|
||||
case *ast.CallExpr:
|
||||
isCallToRecover := isIdent(n.Fun, "recover")
|
||||
isCallToRecover := astutils.IsIdent(n.Fun, "recover")
|
||||
switch {
|
||||
case !w.inADefer && isCallToRecover:
|
||||
// func fn() { recover() }
|
||||
@ -122,7 +123,7 @@ func (w lintDeferRule) Visit(node ast.Node) ast.Visitor {
|
||||
}
|
||||
return nil // no need to analyze the arguments of the function call
|
||||
case *ast.DeferStmt:
|
||||
if isIdent(n.Call.Fun, "recover") {
|
||||
if astutils.IsIdent(n.Call.Fun, "recover") {
|
||||
// defer recover()
|
||||
//
|
||||
// confidence is not truly 1 because this could be in a correctly-deferred func,
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -101,8 +102,7 @@ func (r *EnforceMapStyleRule) Apply(file *lint.File, _ lint.Arguments) []lint.Fa
|
||||
return true
|
||||
}
|
||||
|
||||
ident, ok := v.Fun.(*ast.Ident)
|
||||
if !ok || ident.Name != "make" {
|
||||
if !astutils.IsIdent(v.Fun, "make") {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -130,8 +131,8 @@ func (r *EnforceRepeatedArgTypeStyleRule) Apply(file *lint.File, _ lint.Argument
|
||||
if fn.Type.Params != nil {
|
||||
var prevType ast.Expr
|
||||
for _, field := range fn.Type.Params.List {
|
||||
prevTypeStr := gofmt(prevType)
|
||||
currentTypeStr := gofmt(field.Type)
|
||||
prevTypeStr := astutils.GoFmt(prevType)
|
||||
currentTypeStr := astutils.GoFmt(field.Type)
|
||||
if currentTypeStr == prevTypeStr {
|
||||
failures = append(failures, lint.Failure{
|
||||
Confidence: 1,
|
||||
@ -163,8 +164,8 @@ func (r *EnforceRepeatedArgTypeStyleRule) Apply(file *lint.File, _ lint.Argument
|
||||
if fn.Type.Results != nil {
|
||||
var prevType ast.Expr
|
||||
for _, field := range fn.Type.Results.List {
|
||||
prevTypeStr := gofmt(prevType)
|
||||
currentTypeStr := gofmt(field.Type)
|
||||
prevTypeStr := astutils.GoFmt(prevType)
|
||||
currentTypeStr := astutils.GoFmt(field.Type)
|
||||
if field.Names != nil && currentTypeStr == prevTypeStr {
|
||||
failures = append(failures, lint.Failure{
|
||||
Confidence: 1,
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -117,8 +118,7 @@ func (r *EnforceSliceStyleRule) Apply(file *lint.File, _ lint.Arguments) []lint.
|
||||
return true
|
||||
}
|
||||
|
||||
ident, ok := v.Fun.(*ast.Ident)
|
||||
if !ok || ident.Name != "make" {
|
||||
if !astutils.IsIdent(v.Fun, "make") {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -56,7 +57,7 @@ func (w lintErrors) Visit(_ ast.Node) ast.Visitor {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") {
|
||||
if !astutils.IsPkgDotName(ce.Fun, "errors", "New") && !astutils.IsPkgDotName(ce.Fun, "fmt", "Errorf") {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package rule
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -21,7 +22,7 @@ func (*ErrorReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure
|
||||
}
|
||||
|
||||
funcResults := funcDecl.Type.Results.List
|
||||
isLastResultError := isIdent(funcResults[len(funcResults)-1].Type, "error")
|
||||
isLastResultError := astutils.IsIdent(funcResults[len(funcResults)-1].Type, "error")
|
||||
if isLastResultError {
|
||||
continue
|
||||
}
|
||||
@ -29,7 +30,7 @@ func (*ErrorReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure
|
||||
// An error return parameter should be the last parameter.
|
||||
// Flag any error parameters found before the last.
|
||||
for _, r := range funcResults[:len(funcResults)-1] {
|
||||
if isIdent(r.Type, "error") {
|
||||
if astutils.IsIdent(r.Type, "error") {
|
||||
failures = append(failures, lint.Failure{
|
||||
Category: lint.FailureCategoryStyle,
|
||||
Confidence: 0.9,
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -47,7 +48,7 @@ func (w lintErrorf) Visit(n ast.Node) ast.Visitor {
|
||||
if !ok || len(ce.Args) != 1 {
|
||||
return w
|
||||
}
|
||||
isErrorsNew := isPkgDot(ce.Fun, "errors", "New")
|
||||
isErrorsNew := astutils.IsPkgDotName(ce.Fun, "errors", "New")
|
||||
var isTestingError bool
|
||||
se, ok := ce.Fun.(*ast.SelectorExpr)
|
||||
if ok && se.Sel.Name == "Error" {
|
||||
@ -60,7 +61,7 @@ func (w lintErrorf) Visit(n ast.Node) ast.Visitor {
|
||||
}
|
||||
arg := ce.Args[0]
|
||||
ce, ok = arg.(*ast.CallExpr)
|
||||
if !ok || !isPkgDot(ce.Fun, "fmt", "Sprintf") {
|
||||
if !ok || !astutils.IsPkgDotName(ce.Fun, "fmt", "Sprintf") {
|
||||
return w
|
||||
}
|
||||
errorfPrefix := "fmt"
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -26,7 +27,7 @@ func (*FlagParamRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure {
|
||||
|
||||
boolParams := map[string]struct{}{}
|
||||
for _, param := range fd.Type.Params.List {
|
||||
if !isIdent(param.Type, "bool") {
|
||||
if !astutils.IsIdent(param.Type, "bool") {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -77,7 +78,7 @@ func (w conditionVisitor) Visit(node ast.Node) ast.Visitor {
|
||||
return w.idents[ident.Name] == struct{}{}
|
||||
}
|
||||
|
||||
uses := pick(ifStmt.Cond, findUsesOfIdents)
|
||||
uses := astutils.PickNodes(ifStmt.Cond, findUsesOfIdents)
|
||||
|
||||
if len(uses) < 1 {
|
||||
return w
|
||||
|
@ -3,6 +3,7 @@ package rule
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -64,12 +65,12 @@ func (*lintIdenticalBranches) identicalBranches(branches []*ast.BlockStmt) bool
|
||||
return false // only one branch to compare thus we return
|
||||
}
|
||||
|
||||
referenceBranch := gofmt(branches[0])
|
||||
referenceBranch := astutils.GoFmt(branches[0])
|
||||
referenceBranchSize := len(branches[0].List)
|
||||
for i := 1; i < len(branches); i++ {
|
||||
currentBranch := branches[i]
|
||||
currentBranchSize := len(currentBranch.List)
|
||||
if currentBranchSize != referenceBranchSize || gofmt(currentBranch) != referenceBranch {
|
||||
if currentBranchSize != referenceBranchSize || astutils.GoFmt(currentBranch) != referenceBranch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -116,7 +117,7 @@ func (r *ModifiesValRecRule) findReturnReceiverStatements(receiverName string, t
|
||||
return false
|
||||
}
|
||||
|
||||
return pick(target, finder)
|
||||
return astutils.PickNodes(target, finder)
|
||||
}
|
||||
|
||||
func (r *ModifiesValRecRule) mustSkip(receiver *ast.Field, pkg *lint.Package) bool {
|
||||
@ -179,5 +180,5 @@ func (r *ModifiesValRecRule) getReceiverModifications(receiverName string, funcB
|
||||
return false
|
||||
}
|
||||
|
||||
return pick(funcBody, receiverModificationFinder)
|
||||
return astutils.PickNodes(funcBody, receiverModificationFinder)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -63,20 +64,20 @@ func (w lintOptimizeOperandsOrderExpr) Visit(node ast.Node) ast.Visitor {
|
||||
}
|
||||
|
||||
// check if the left sub-expression contains a function call
|
||||
nodes := pick(binExpr.X, isCaller)
|
||||
nodes := astutils.PickNodes(binExpr.X, isCaller)
|
||||
if len(nodes) < 1 {
|
||||
return w
|
||||
}
|
||||
|
||||
// check if the right sub-expression does not contain a function call
|
||||
nodes = pick(binExpr.Y, isCaller)
|
||||
nodes = astutils.PickNodes(binExpr.Y, isCaller)
|
||||
if len(nodes) > 0 {
|
||||
return w
|
||||
}
|
||||
|
||||
newExpr := ast.BinaryExpr{X: binExpr.Y, Y: binExpr.X, Op: binExpr.Op}
|
||||
w.onFailure(lint.Failure{
|
||||
Failure: fmt.Sprintf("for better performance '%v' might be rewritten as '%v'", gofmt(binExpr), gofmt(&newExpr)),
|
||||
Failure: fmt.Sprintf("for better performance '%v' might be rewritten as '%v'", astutils.GoFmt(binExpr), astutils.GoFmt(&newExpr)),
|
||||
Node: node,
|
||||
Category: lint.FailureCategoryOptimization,
|
||||
Confidence: 0.3,
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"go/ast"
|
||||
"strings"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -43,7 +44,7 @@ func (w *lintRanges) Visit(node ast.Node) ast.Visitor {
|
||||
// for x = range m { ... }
|
||||
return w // single var form
|
||||
}
|
||||
if !isIdent(rs.Value, "_") {
|
||||
if !astutils.IsIdent(rs.Value, "_") {
|
||||
// for ?, y = range m { ... }
|
||||
return w
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/structtag"
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -390,7 +391,7 @@ func checkCompoundPropertiesOption(key, value string, fieldType ast.Expr, seenOp
|
||||
return msgTypeMismatch, false
|
||||
}
|
||||
case "layout":
|
||||
if gofmt(fieldType) != "time.Time" {
|
||||
if astutils.GoFmt(fieldType) != "time.Time" {
|
||||
return "layout option is only applicable to fields of type time.Time", false
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
"github.com/mgechev/revive/logging"
|
||||
)
|
||||
@ -61,7 +62,7 @@ func (w lintTimeDate) Visit(n ast.Node) ast.Visitor {
|
||||
if !ok || len(ce.Args) != timeDateArity {
|
||||
return w
|
||||
}
|
||||
if !isPkgDot(ce.Fun, "time", "Date") {
|
||||
if !astutils.IsPkgDotName(ce.Fun, "time", "Date") {
|
||||
return w
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -66,7 +67,7 @@ func (l *lintTimeEqual) Visit(node ast.Node) ast.Visitor {
|
||||
Category: lint.FailureCategoryTime,
|
||||
Confidence: 1,
|
||||
Node: node,
|
||||
Failure: fmt.Sprintf("use %s%s.Equal(%s) instead of %q operator", negateStr, gofmt(expr.X), gofmt(expr.Y), expr.Op),
|
||||
Failure: fmt.Sprintf("use %s%s.Equal(%s) instead of %q operator", negateStr, astutils.GoFmt(expr.X), astutils.GoFmt(expr.Y), expr.Op),
|
||||
})
|
||||
|
||||
return l
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -70,12 +71,7 @@ type lintUncheckedTypeAssertion struct {
|
||||
}
|
||||
|
||||
func isIgnored(e ast.Expr) bool {
|
||||
ident, ok := e.(*ast.Ident)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return ident.Name == "_"
|
||||
return astutils.IsIdent(e, "_")
|
||||
}
|
||||
|
||||
func isTypeSwitch(e *ast.TypeAssertExpr) bool {
|
||||
@ -177,7 +173,7 @@ func (w *lintUncheckedTypeAssertion) Visit(node ast.Node) ast.Visitor {
|
||||
}
|
||||
|
||||
func (w *lintUncheckedTypeAssertion) addFailure(n *ast.TypeAssertExpr, why string) {
|
||||
s := fmt.Sprintf("type cast result is unchecked in %v - %s", gofmt(n), why)
|
||||
s := fmt.Sprintf("type cast result is unchecked in %v - %s", astutils.GoFmt(n), why)
|
||||
w.onFailure(lint.Failure{
|
||||
Category: lint.FailureCategoryBadPractice,
|
||||
Confidence: 1,
|
||||
|
@ -3,6 +3,7 @@ package rule
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -174,7 +175,7 @@ func (*lintUnconditionalRecursionRule) hasControlExit(node ast.Node) bool {
|
||||
case *ast.ReturnStmt:
|
||||
return true
|
||||
case *ast.CallExpr:
|
||||
if isIdent(n.Fun, "panic") {
|
||||
if astutils.IsIdent(n.Fun, "panic") {
|
||||
return true
|
||||
}
|
||||
se, ok := n.Fun.(*ast.SelectorExpr)
|
||||
@ -197,5 +198,5 @@ func (*lintUnconditionalRecursionRule) hasControlExit(node ast.Node) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
return len(pick(node, isExit)) != 0
|
||||
return len(astutils.PickNodes(node, isExit)) != 0
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -122,7 +123,7 @@ func (w *lintUnhandledErrors) addFailure(n *ast.CallExpr) {
|
||||
func (w *lintUnhandledErrors) funcName(call *ast.CallExpr) string {
|
||||
fn, ok := w.getFunc(call)
|
||||
if !ok {
|
||||
return gofmt(call.Fun)
|
||||
return astutils.GoFmt(call.Fun)
|
||||
}
|
||||
|
||||
name := fn.FullName()
|
||||
|
@ -94,7 +94,7 @@ func (w lintUnnecessaryFormat) Visit(n ast.Node) ast.Visitor {
|
||||
return w
|
||||
}
|
||||
|
||||
funcName := gofmt(ce.Fun)
|
||||
funcName := astutils.GoFmt(ce.Fun)
|
||||
spec, ok := formattingFuncs[funcName]
|
||||
if !ok {
|
||||
return w
|
||||
@ -110,7 +110,7 @@ func (w lintUnnecessaryFormat) Visit(n ast.Node) ast.Visitor {
|
||||
return w
|
||||
}
|
||||
|
||||
format := gofmt(arg)
|
||||
format := astutils.GoFmt(arg)
|
||||
|
||||
if strings.Contains(format, `%`) {
|
||||
return w
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"go/ast"
|
||||
"regexp"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -122,7 +123,7 @@ func (w lintUnusedParamRule) Visit(node ast.Node) ast.Visitor {
|
||||
|
||||
return false
|
||||
}
|
||||
_ = pick(funcBody, fselect)
|
||||
_ = astutils.PickNodes(funcBody, fselect)
|
||||
|
||||
for _, p := range funcType.Params.List {
|
||||
for _, n := range p.Names {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"go/ast"
|
||||
"regexp"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -82,7 +83,7 @@ func (r *UnusedReceiverRule) Apply(file *lint.File, _ lint.Arguments) []lint.Fai
|
||||
|
||||
return isAnID && ident.Obj == recID.Obj
|
||||
}
|
||||
receiverUses := pick(funcDecl.Body, selectReceiverUses)
|
||||
receiverUses := astutils.PickNodes(funcDecl.Body, selectReceiverUses)
|
||||
|
||||
if len(receiverUses) > 0 {
|
||||
continue // the receiver is referenced in the func body
|
||||
|
@ -3,6 +3,7 @@ package rule
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -39,7 +40,7 @@ func (w lintFmtErrorf) Visit(n ast.Node) ast.Visitor {
|
||||
return w // not a function call
|
||||
}
|
||||
|
||||
isFmtErrorf := isPkgDot(funcCall.Fun, "fmt", "Errorf")
|
||||
isFmtErrorf := astutils.IsPkgDotName(funcCall.Fun, "fmt", "Errorf")
|
||||
if !isFmtErrorf {
|
||||
return w // not a call to fmt.Errorf
|
||||
}
|
||||
|
@ -1,10 +1,7 @@
|
||||
package rule
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"regexp"
|
||||
"strings"
|
||||
@ -26,31 +23,6 @@ var exitFunctions = map[string]map[string]bool{
|
||||
},
|
||||
}
|
||||
|
||||
func isCgoExported(f *ast.FuncDecl) bool {
|
||||
if f.Recv != nil || f.Doc == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
cgoExport := regexp.MustCompile(fmt.Sprintf("(?m)^//export %s$", regexp.QuoteMeta(f.Name.Name)))
|
||||
for _, c := range f.Doc.List {
|
||||
if cgoExport.MatchString(c.Text) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isIdent(expr ast.Expr, ident string) bool {
|
||||
id, ok := expr.(*ast.Ident)
|
||||
return ok && id.Name == ident
|
||||
}
|
||||
|
||||
// isPkgDot checks if the expression is <pkg>.<name>
|
||||
func isPkgDot(expr ast.Expr, pkg, name string) bool {
|
||||
sel, ok := expr.(*ast.SelectorExpr)
|
||||
return ok && isIdent(sel.X, pkg) && isIdent(sel.Sel, name)
|
||||
}
|
||||
|
||||
func srcLine(src []byte, p token.Position) string {
|
||||
// Run to end of line in both directions if not at line start/end.
|
||||
lo, hi := p.Offset, p.Offset+1
|
||||
@ -63,48 +35,6 @@ func srcLine(src []byte, p token.Position) string {
|
||||
return string(src[lo:hi])
|
||||
}
|
||||
|
||||
// pick yields a list of nodes by picking them from a sub-ast with root node n.
|
||||
// Nodes are selected by applying the fselect function
|
||||
func pick(n ast.Node, fselect func(n ast.Node) bool) []ast.Node {
|
||||
var result []ast.Node
|
||||
|
||||
if n == nil {
|
||||
return result
|
||||
}
|
||||
|
||||
onSelect := func(n ast.Node) {
|
||||
result = append(result, n)
|
||||
}
|
||||
p := picker{fselect: fselect, onSelect: onSelect}
|
||||
ast.Walk(p, n)
|
||||
return result
|
||||
}
|
||||
|
||||
type picker struct {
|
||||
fselect func(n ast.Node) bool
|
||||
onSelect func(n ast.Node)
|
||||
}
|
||||
|
||||
func (p picker) Visit(node ast.Node) ast.Visitor {
|
||||
if p.fselect == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if p.fselect(node) {
|
||||
p.onSelect(node)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// gofmt returns a string representation of an AST subtree.
|
||||
func gofmt(x any) string {
|
||||
buf := bytes.Buffer{}
|
||||
fs := token.NewFileSet()
|
||||
printer.Fprint(&buf, fs, x)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// checkNumberOfArguments fails if the given number of arguments is not, at least, the expected one
|
||||
func checkNumberOfArguments(expected int, args lint.Arguments, ruleName string) error {
|
||||
if len(args) < expected {
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -79,14 +80,14 @@ func (w *lintVarDeclarations) Visit(node ast.Node) ast.Visitor {
|
||||
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], "_") {
|
||||
if astutils.IsIdent(v.Names[0], "_") {
|
||||
return nil
|
||||
}
|
||||
// If the RHS is a isZero value, suggest dropping it.
|
||||
isZero := false
|
||||
if lit, ok := rhs.(*ast.BasicLit); ok {
|
||||
isZero = isZeroValue(lit.Value, v.Type)
|
||||
} else if isIdent(rhs, "nil") {
|
||||
} else if astutils.IsIdent(rhs, "nil") {
|
||||
isZero = true
|
||||
}
|
||||
if isZero {
|
||||
@ -122,7 +123,7 @@ func (w *lintVarDeclarations) Visit(node ast.Node) ast.Visitor {
|
||||
return nil
|
||||
}
|
||||
// If the RHS is an untyped const, only warn if the LHS type is its default type.
|
||||
if defType, ok := w.file.IsUntypedConst(rhs); ok && !isIdent(v.Type, defType) {
|
||||
if defType, ok := w.file.IsUntypedConst(rhs); ok && !astutils.IsIdent(v.Type, defType) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -267,7 +268,7 @@ func (w *lintNames) Visit(n ast.Node) ast.Visitor {
|
||||
// Exclude naming warnings for functions that are exported to C but
|
||||
// not exported in the Go API.
|
||||
// See https://github.com/golang/lint/issues/144.
|
||||
if ast.IsExported(v.Name.Name) || !isCgoExported(v) {
|
||||
if ast.IsExported(v.Name.Name) || !astutils.IsCgoExported(v) {
|
||||
w.check(v.Name, thing)
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package rule
|
||||
import (
|
||||
"go/ast"
|
||||
|
||||
"github.com/mgechev/revive/internal/astutils"
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
@ -40,7 +41,7 @@ func (w lintWaitGroupByValueRule) Visit(node ast.Node) ast.Visitor {
|
||||
|
||||
// Check all function parameters
|
||||
for _, field := range fd.Type.Params.List {
|
||||
if !w.isWaitGroup(field.Type) {
|
||||
if !astutils.IsPkgDotName(field.Type, "sync", "WaitGroup") {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -53,14 +54,3 @@ func (w lintWaitGroupByValueRule) Visit(node ast.Node) ast.Visitor {
|
||||
|
||||
return nil // skip visiting function body
|
||||
}
|
||||
|
||||
func (lintWaitGroupByValueRule) isWaitGroup(ft ast.Expr) bool {
|
||||
se, ok := ft.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
x, _ := se.X.(*ast.Ident)
|
||||
sel := se.Sel.Name
|
||||
return x.Name == "sync" && sel == "WaitGroup"
|
||||
}
|
||||
|
8
testdata/unnecessary_stmt.go
vendored
8
testdata/unnecessary_stmt.go
vendored
@ -1,5 +1,11 @@
|
||||
package fixtures
|
||||
|
||||
import (
|
||||
ast "go/ast"
|
||||
|
||||
"github.com/mgechev/revive/lint"
|
||||
)
|
||||
|
||||
func foo(a, b, c, d int) {
|
||||
switch n := node.(type) { // MATCH /switch with only one case can be replaced by an if-then/
|
||||
case *ast.SwitchStmt:
|
||||
@ -7,7 +13,7 @@ func foo(a, b, c, d int) {
|
||||
_, ok := n.(*ast.CaseClause)
|
||||
return ok
|
||||
}
|
||||
cases := pick(n.Body, caseSelector, nil)
|
||||
cases := astutils.PickNodes(n.Body, caseSelector, nil)
|
||||
if len(cases) == 1 {
|
||||
cs, ok := cases[0].(*ast.CaseClause)
|
||||
if ok && len(cs.List) == 1 {
|
||||
|
Reference in New Issue
Block a user