diff --git a/analyzer.go b/analyzer.go index 96bf637..787d920 100644 --- a/analyzer.go +++ b/analyzer.go @@ -10,21 +10,26 @@ import ( // NewAnalyzer returns a new analyzer to check for receiver type consistency. func NewAnalyzer(s Settings) *analysis.Analyzer { - // Default excludes for Marshal/Encode methods https://github.com/raeperd/recvcheck/issues/7 - excludedMethods := map[string]struct{}{ - "MarshalText": {}, - "MarshalJSON": {}, - "MarshalYAML": {}, - "MarshalXML": {}, - "MarshalBinary": {}, - "GobEncode": {}, + a := &analyzer{ + // Default excludes for Marshal/Encode methods https://github.com/raeperd/recvcheck/issues/7 + excludedMethods: map[string]struct{}{ + "MarshalText": {}, + "MarshalJSON": {}, + "MarshalYAML": {}, + "MarshalXML": {}, + "MarshalBinary": {}, + "GobEncode": {}, + }, + excludedTuple: map[string]struct{}{}, } if s.DisableBuiltin { - excludedMethods = map[string]struct{}{} + a.excludedMethods = map[string]struct{}{} } - a := &analyzer{excludedMethods: excludedMethods} + for _, exclusion := range s.Exclusions { + a.excludedTuple[exclusion] = struct{}{} + } return &analysis.Analyzer{ Name: "recvcheck", @@ -45,10 +50,15 @@ type Settings struct { // - "MarshalBinary" // - "GobEncode" DisableBuiltin bool + + // Exclusions format is `struct_name.method_name` (ex: `Foo.MethodName`). + // A wildcard `*` can use as a struct name (ex: `*.MethodName`). + Exclusions []string } type analyzer struct { excludedMethods map[string]struct{} + excludedTuple map[string]struct{} } func (r *analyzer) run(pass *analysis.Pass) (any, error) { @@ -61,21 +71,16 @@ func (r *analyzer) run(pass *analysis.Pass) (any, error) { return } - if r.isExcluded(funcDecl) { + if r.isBuildInExcluded(funcDecl) { + return + } + + recv, isStar := recvTypeIdent(funcDecl.Recv.List[0].Type) + if recv == nil { return } - var recv *ast.Ident - var isStar bool - switch recvType := funcDecl.Recv.List[0].Type.(type) { - case *ast.StarExpr: - isStar = true - if recv, ok = recvType.X.(*ast.Ident); !ok { - return - } - case *ast.Ident: - recv = recvType - default: + if r.isUserExcluded(recv, funcDecl) { return } @@ -101,7 +106,22 @@ func (r *analyzer) run(pass *analysis.Pass) (any, error) { return nil, nil } -func (r *analyzer) isExcluded(f *ast.FuncDecl) bool { +func (r *analyzer) isUserExcluded(recv *ast.Ident, f *ast.FuncDecl) bool { + if f.Name == nil || f.Name.Name == "" { + return true + } + + _, found := r.excludedTuple[recv.Name+"."+f.Name.Name] + if found { + return true + } + + _, found = r.excludedTuple["*."+f.Name.Name] + + return found +} + +func (r *analyzer) isBuildInExcluded(f *ast.FuncDecl) bool { if f.Name == nil || f.Name.Name == "" { return true } @@ -114,3 +134,17 @@ type structType struct { starUsed bool typeUsed bool } + +func recvTypeIdent(r ast.Expr) (*ast.Ident, bool) { + switch n := r.(type) { + case *ast.StarExpr: + if i, ok := n.X.(*ast.Ident); ok { + return i, true + } + + case *ast.Ident: + return n, false + } + + return nil, false +}