Skip to content

feat: add ExcludeMethod options with default #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Nov 18, 2024
4 changes: 3 additions & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@ jobs:
go-version-file: go.mod
- uses: golangci/golangci-lint-action@v6
with:
version: v1.61
version: v1.62
- run: make build
- run: go vet -vettool=./recvcheck ./...
10 changes: 10 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
linters:
enable:
- recvcheck

output:
show-stats: true
sort-results: true
sort-order:
- linter
- file
61 changes: 55 additions & 6 deletions analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,50 @@ import (
"golang.org/x/tools/go/ast/inspector"
)

var Analyzer = &analysis.Analyzer{
Name: "recvcheck",
Doc: "checks for receiver type consistency",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
// 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": {},
}

if s.DisableBuiltin {
excludedMethods = map[string]struct{}{}
}

a := &analyzer{excludedMethods: excludedMethods}

return &analysis.Analyzer{
Name: "recvcheck",
Doc: "checks for receiver type consistency",
Run: a.run,
Requires: []*analysis.Analyzer{inspect.Analyzer},
}
}

// Settings is the configuration for the analyzer.
type Settings struct {
// DisableBuiltin if true, disables the built-in method excludes.
// Built-in excluded methods:
// - "MarshalText"
// - "MarshalJSON"
// - "MarshalYAML"
// - "MarshalXML"
// - "MarshalBinary"
// - "GobEncode"
DisableBuiltin bool
}

func run(pass *analysis.Pass) (any, error) {
type analyzer struct {
excludedMethods map[string]struct{}
}

func (r *analyzer) run(pass *analysis.Pass) (any, error) {
inspector := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)

structs := map[string]*structType{}
Expand All @@ -25,6 +61,10 @@ func run(pass *analysis.Pass) (any, error) {
return
}

if r.isExcluded(funcDecl) {
return
}

var recv *ast.Ident
var isStar bool
switch recvType := funcDecl.Recv.List[0].Type.(type) {
Expand Down Expand Up @@ -61,6 +101,15 @@ func run(pass *analysis.Pass) (any, error) {
return nil, nil
}

func (r *analyzer) isExcluded(f *ast.FuncDecl) bool {
if f.Name == nil || f.Name.Name == "" {
return true
}

_, found := r.excludedMethods[f.Name.Name]
return found
}

type structType struct {
starUsed bool
typeUsed bool
Expand Down
26 changes: 25 additions & 1 deletion analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,29 @@ import (
)

func TestAnalyzer(t *testing.T) {
analysistest.Run(t, analysistest.TestData(), recvcheck.Analyzer, "test")
testCases := []struct {
desc string
settings recvcheck.Settings
}{
{
desc: "basic",
settings: recvcheck.Settings{},
},
{
desc: "excluded",
settings: recvcheck.Settings{},
},
{
desc: "disablebuiltin",
settings: recvcheck.Settings{DisableBuiltin: true},
},
}

for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
a := recvcheck.NewAnalyzer(test.settings)

analysistest.Run(t, analysistest.TestData(), a, test.desc)
})
}
}
13 changes: 12 additions & 1 deletion cmd/recvcheck/main.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
package main

import (
"flag"

"github.com/raeperd/recvcheck"
"golang.org/x/tools/go/analysis/singlechecker"
)

func main() {
singlechecker.Main(recvcheck.Analyzer)
var noBuiltinExcludeMethod bool

flag.BoolVar(&noBuiltinExcludeMethod, "no-builtin-exclude-method", false,
`disables the default exclude methods such as "MarshalJSON"`)

setting := recvcheck.Settings{
DisableBuiltin: noBuiltinExcludeMethod,
}

singlechecker.Main(recvcheck.NewAnalyzer(setting))
}
2 changes: 1 addition & 1 deletion testdata/src/test/rpc.go → testdata/src/basic/rpc.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package test
package basic

import (
"time"
Expand Down
11 changes: 11 additions & 0 deletions testdata/src/disablebuiltin/binary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package disablebuiltin

type Binary struct{} // want `the methods of "Binary" use pointer receiver and non-pointer receiver.`

func (b Binary) MarshalBinary() ([]byte, error) {
panic("not implemented")
}

func (b *Binary) UnmarshalBinary(data []byte) error {
panic("not implemented")
}
11 changes: 11 additions & 0 deletions testdata/src/disablebuiltin/gob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package disablebuiltin

type Gob struct{} // want `the methods of "Gob" use pointer receiver and non-pointer receiver.`

func (g Gob) GobEncode() ([]byte, error) {
panic("not implemented")
}

func (g *Gob) GobDecode(data []byte) error {
panic("not implemented")
}
11 changes: 11 additions & 0 deletions testdata/src/disablebuiltin/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package disablebuiltin

type JSON struct{} // want `the methods of "JSON" use pointer receiver and non-pointer receiver.`

func (j JSON) MarshalJSON() ([]byte, error) {
panic("not implemented")
}

func (j *JSON) UnmarshalJSON(b []byte) error {
panic("not implemented")
}
11 changes: 11 additions & 0 deletions testdata/src/disablebuiltin/text.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package disablebuiltin

type Text struct{} // want `the methods of "Text" use pointer receiver and non-pointer receiver.`

func (t Text) MarshalText() ([]byte, error) {
panic("not implemented")
}

func (t *Text) UnmarshalText(b []byte) error {
panic("not implemented")
}
13 changes: 13 additions & 0 deletions testdata/src/disablebuiltin/xml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package disablebuiltin

import "encoding/xml"

type XML struct{} // want `the methods of "XML" use pointer receiver and non-pointer receiver.`

func (x XML) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
panic("not implemented")
}

func (x *XML) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
panic("not implemented")
}
13 changes: 13 additions & 0 deletions testdata/src/disablebuiltin/yaml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package disablebuiltin

type Node struct{}

type YAML struct{} // want `the methods of "YAML" use pointer receiver and non-pointer receiver.`

func (j YAML) MarshalYAML() (any, error) {
panic("not implemented")
}

func (j *YAML) UnmarshalYAML(value *Node) error {
panic("not implemented")
}
11 changes: 11 additions & 0 deletions testdata/src/excluded/binary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package excluded

type Binary struct{}

func (b Binary) MarshalBinary() ([]byte, error) {
panic("not implemented")
}

func (b *Binary) UnmarshalBinary(data []byte) error {
panic("not implemented")
}
11 changes: 11 additions & 0 deletions testdata/src/excluded/gob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package excluded

type Gob struct{}

func (g Gob) GobEncode() ([]byte, error) {
panic("not implemented")
}

func (g *Gob) GobDecode(data []byte) error {
panic("not implemented")
}
11 changes: 11 additions & 0 deletions testdata/src/excluded/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package excluded

type JSON struct{}

func (j JSON) MarshalJSON() ([]byte, error) {
panic("not implemented")
}

func (j *JSON) UnmarshalJSON(b []byte) error {
panic("not implemented")
}
11 changes: 11 additions & 0 deletions testdata/src/excluded/text.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package excluded

type Text struct{}

func (t Text) MarshalText() ([]byte, error) {
panic("not implemented")
}

func (t *Text) UnmarshalText(b []byte) error {
panic("not implemented")
}
13 changes: 13 additions & 0 deletions testdata/src/excluded/xml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package excluded

import "encoding/xml"

type XML struct{}

func (x XML) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
panic("not implemented")
}

func (x *XML) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
panic("not implemented")
}
13 changes: 13 additions & 0 deletions testdata/src/excluded/yaml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package excluded

type Node struct{}

type YAML struct{}

func (j YAML) MarshalYAML() (any, error) {
panic("not implemented")
}

func (j *YAML) UnmarshalYAML(value *Node) error {
panic("not implemented")
}