From efe0c483ac81cbb6738c20c5a69fcf2814b37d55 Mon Sep 17 00:00:00 2001 From: Tom <73077675+tmzane@users.noreply.github.com> Date: Mon, 9 Oct 2023 17:53:37 +0300 Subject: [PATCH 1/2] refactor: report only the function call + merge tests --- builtins.go | 88 ++------ cmd/musttag/main.go | 3 +- musttag.go | 151 +++++-------- musttag_test.go | 42 +--- testdata/src/builtins/builtins.go | 228 -------------------- testdata/src/builtins/go.mod | 13 -- testdata/src/custom/custom.go | 9 +- testdata/src/go.work | 3 +- testdata/src/{builtins => tests}/.gitignore | 0 testdata/src/tests/builtins.go | 163 ++++++++++++++ testdata/src/tests/go.mod | 10 + testdata/src/{builtins => tests}/go.sum | 0 testdata/src/tests/tests.go | 80 +++---- 13 files changed, 287 insertions(+), 503 deletions(-) delete mode 100644 testdata/src/builtins/builtins.go delete mode 100644 testdata/src/builtins/go.mod rename testdata/src/{builtins => tests}/.gitignore (100%) create mode 100644 testdata/src/tests/builtins.go rename testdata/src/{builtins => tests}/go.sum (100%) diff --git a/builtins.go b/builtins.go index db86317..c5e0b52 100644 --- a/builtins.go +++ b/builtins.go @@ -4,141 +4,97 @@ package musttag var builtins = []Func{ // https://pkg.go.dev/encoding/json { - Name: "encoding/json.Marshal", - Tag: "json", - ArgPos: 0, + Name: "encoding/json.Marshal", Tag: "json", ArgPos: 0, ifaceWhitelist: []string{"encoding/json.Marshaler", "encoding.TextMarshaler"}, }, { - Name: "encoding/json.MarshalIndent", - Tag: "json", - ArgPos: 0, + Name: "encoding/json.MarshalIndent", Tag: "json", ArgPos: 0, ifaceWhitelist: []string{"encoding/json.Marshaler", "encoding.TextMarshaler"}, }, { - Name: "encoding/json.Unmarshal", - Tag: "json", - ArgPos: 1, + Name: "encoding/json.Unmarshal", Tag: "json", ArgPos: 1, ifaceWhitelist: []string{"encoding/json.Unmarshaler", "encoding.TextUnmarshaler"}, }, { - Name: "(*encoding/json.Encoder).Encode", - Tag: "json", - ArgPos: 0, + Name: "(*encoding/json.Encoder).Encode", Tag: "json", ArgPos: 0, ifaceWhitelist: []string{"encoding/json.Marshaler", "encoding.TextMarshaler"}, }, { - Name: "(*encoding/json.Decoder).Decode", - Tag: "json", - ArgPos: 0, + Name: "(*encoding/json.Decoder).Decode", Tag: "json", ArgPos: 0, ifaceWhitelist: []string{"encoding/json.Unmarshaler", "encoding.TextUnmarshaler"}, }, // https://pkg.go.dev/encoding/xml { - Name: "encoding/xml.Marshal", - Tag: "xml", - ArgPos: 0, + Name: "encoding/xml.Marshal", Tag: "xml", ArgPos: 0, ifaceWhitelist: []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"}, }, { - Name: "encoding/xml.MarshalIndent", - Tag: "xml", - ArgPos: 0, + Name: "encoding/xml.MarshalIndent", Tag: "xml", ArgPos: 0, ifaceWhitelist: []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"}, }, { - Name: "encoding/xml.Unmarshal", - Tag: "xml", - ArgPos: 1, + Name: "encoding/xml.Unmarshal", Tag: "xml", ArgPos: 1, ifaceWhitelist: []string{"encoding/xml.Unmarshaler", "encoding.TextUnmarshaler"}, }, { - Name: "(*encoding/xml.Encoder).Encode", - Tag: "xml", - ArgPos: 0, + Name: "(*encoding/xml.Encoder).Encode", Tag: "xml", ArgPos: 0, ifaceWhitelist: []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"}, }, { - Name: "(*encoding/xml.Decoder).Decode", - Tag: "xml", - ArgPos: 0, + Name: "(*encoding/xml.Decoder).Decode", Tag: "xml", ArgPos: 0, ifaceWhitelist: []string{"encoding/xml.Unmarshaler", "encoding.TextUnmarshaler"}, }, { - Name: "(*encoding/xml.Encoder).EncodeElement", - Tag: "xml", - ArgPos: 0, + Name: "(*encoding/xml.Encoder).EncodeElement", Tag: "xml", ArgPos: 0, ifaceWhitelist: []string{"encoding/xml.Marshaler", "encoding.TextMarshaler"}, }, { - Name: "(*encoding/xml.Decoder).DecodeElement", - Tag: "xml", - ArgPos: 0, + Name: "(*encoding/xml.Decoder).DecodeElement", Tag: "xml", ArgPos: 0, ifaceWhitelist: []string{"encoding/xml.Unmarshaler", "encoding.TextUnmarshaler"}, }, // https://github.com/go-yaml/yaml { - Name: "gopkg.in/yaml.v3.Marshal", - Tag: "yaml", - ArgPos: 0, + Name: "gopkg.in/yaml.v3.Marshal", Tag: "yaml", ArgPos: 0, ifaceWhitelist: []string{"gopkg.in/yaml.v3.Marshaler"}, }, { - Name: "gopkg.in/yaml.v3.Unmarshal", - Tag: "yaml", - ArgPos: 1, + Name: "gopkg.in/yaml.v3.Unmarshal", Tag: "yaml", ArgPos: 1, ifaceWhitelist: []string{"gopkg.in/yaml.v3.Unmarshaler"}, }, { - Name: "(*gopkg.in/yaml.v3.Encoder).Encode", - Tag: "yaml", - ArgPos: 0, + Name: "(*gopkg.in/yaml.v3.Encoder).Encode", Tag: "yaml", ArgPos: 0, ifaceWhitelist: []string{"gopkg.in/yaml.v3.Marshaler"}, }, { - Name: "(*gopkg.in/yaml.v3.Decoder).Decode", - Tag: "yaml", - ArgPos: 0, + Name: "(*gopkg.in/yaml.v3.Decoder).Decode", Tag: "yaml", ArgPos: 0, ifaceWhitelist: []string{"gopkg.in/yaml.v3.Unmarshaler"}, }, // https://github.com/BurntSushi/toml { - Name: "github.com/BurntSushi/toml.Unmarshal", - Tag: "toml", - ArgPos: 1, + Name: "github.com/BurntSushi/toml.Unmarshal", Tag: "toml", ArgPos: 1, ifaceWhitelist: []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"}, }, { - Name: "github.com/BurntSushi/toml.Decode", - Tag: "toml", - ArgPos: 1, + Name: "github.com/BurntSushi/toml.Decode", Tag: "toml", ArgPos: 1, ifaceWhitelist: []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"}, }, { - Name: "github.com/BurntSushi/toml.DecodeFS", - Tag: "toml", - ArgPos: 2, + Name: "github.com/BurntSushi/toml.DecodeFS", Tag: "toml", ArgPos: 2, ifaceWhitelist: []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"}, }, { - Name: "github.com/BurntSushi/toml.DecodeFile", - Tag: "toml", - ArgPos: 1, + Name: "github.com/BurntSushi/toml.DecodeFile", Tag: "toml", ArgPos: 1, ifaceWhitelist: []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"}, }, { - Name: "(*github.com/BurntSushi/toml.Encoder).Encode", - Tag: "toml", - ArgPos: 0, + Name: "(*github.com/BurntSushi/toml.Encoder).Encode", Tag: "toml", ArgPos: 0, ifaceWhitelist: []string{"encoding.TextMarshaler"}, }, { - Name: "(*github.com/BurntSushi/toml.Decoder).Decode", - Tag: "toml", - ArgPos: 0, + Name: "(*github.com/BurntSushi/toml.Decoder).Decode", Tag: "toml", ArgPos: 0, ifaceWhitelist: []string{"github.com/BurntSushi/toml.Unmarshaler", "encoding.TextUnmarshaler"}, }, diff --git a/cmd/musttag/main.go b/cmd/musttag/main.go index 5233024..d742c4b 100644 --- a/cmd/musttag/main.go +++ b/cmd/musttag/main.go @@ -20,9 +20,8 @@ func main() { type versionFlag struct{} -func (versionFlag) IsBoolFlag() bool { return true } -func (versionFlag) Get() interface{} { return nil } func (versionFlag) String() string { return "" } +func (versionFlag) IsBoolFlag() bool { return true } func (versionFlag) Set(string) error { fmt.Printf("musttag version %s %s/%s\n", version, runtime.GOOS, runtime.GOARCH) os.Exit(0) diff --git a/musttag.go b/musttag.go index 248d11c..bb44a13 100644 --- a/musttag.go +++ b/musttag.go @@ -5,9 +5,7 @@ import ( "flag" "fmt" "go/ast" - "go/token" "go/types" - "path" "reflect" "regexp" "strconv" @@ -19,25 +17,19 @@ import ( "golang.org/x/tools/go/types/typeutil" ) -// Func describes a function call to look for, e.g. json.Marshal. +// Func describes a function call to look for, e.g. [json.Marshal]. type Func struct { - Name string // Name is the full name of the function, including the package. - Tag string // Tag is the struct tag whose presence should be ensured. - ArgPos int // ArgPos is the position of the argument to check. + Name string // The full name of the function, including the package. + Tag string // The struct tag whose presence should be ensured. + ArgPos int // The position of the argument to check. // a list of interface names (including the package); // if at least one is implemented by the argument, no check is performed. ifaceWhitelist []string } -func (fn Func) shortName() string { - name := strings.NewReplacer("*", "", "(", "", ")", "").Replace(fn.Name) - return path.Base(name) -} - // New creates a new musttag analyzer. -// To report a custom function provide its description via Func, -// it will be added to the builtin ones. +// To report a custom function, provide its description as [Func]. func New(funcs ...Func) *analysis.Analyzer { var flagFuncs []Func return &analysis.Analyzer{ @@ -47,32 +39,30 @@ func New(funcs ...Func) *analysis.Analyzer { Requires: []*analysis.Analyzer{inspect.Analyzer}, Run: func(pass *analysis.Pass) (any, error) { l := len(builtins) + len(funcs) + len(flagFuncs) - f := make(map[string]Func, l) + allFuncs := make(map[string]Func, l) - toMap := func(slice []Func) { + merge := func(slice []Func) { for _, fn := range slice { - f[fn.Name] = fn + allFuncs[fn.Name] = fn } } - toMap(builtins) - toMap(funcs) - toMap(flagFuncs) + merge(builtins) + merge(funcs) + merge(flagFuncs) mainModule, err := getMainModule() if err != nil { return nil, err } - return run(pass, mainModule, f) + return run(pass, mainModule, allFuncs) }, } } -// flags creates a flag set for the analyzer. -// The funcs slice will be filled with custom functions passed via CLI flags. func flags(funcs *[]Func) flag.FlagSet { fs := flag.NewFlagSet("musttag", flag.ContinueOnError) - fs.Func("fn", "report custom function (name:tag:argpos)", func(s string) error { + fs.Func("fn", "report a custom function (name:tag:arg-pos)", func(s string) error { parts := strings.Split(s, ":") if len(parts) != 3 || parts[0] == "" || parts[1] == "" { return strconv.ErrSyntax @@ -91,27 +81,18 @@ func flags(funcs *[]Func) flag.FlagSet { return *fs } -// for tests only. -var report = func(pass *analysis.Pass, st *structType, fn Func, fnPos token.Position) { - const format = "`%s` should be annotated with the `%s` tag as it is passed to `%s` at %s" - pass.Reportf(st.Pos, format, st.Name, fn.Tag, fn.shortName(), fnPos) -} - -var trimVendor = regexp.MustCompile(`([^*/(]+/vendor/)`) +var trimVendor = regexp.MustCompile("([^*/(]+/vendor/)") -// run starts the analysis. -func run(pass *analysis.Pass, mainModule string, funcs map[string]Func) (any, error) { - var err error - - walk := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) +func run(pass *analysis.Pass, mainModule string, funcs map[string]Func) (_ any, err error) { + visit := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) filter := []ast.Node{(*ast.CallExpr)(nil)} - walk.Preorder(filter, func(n ast.Node) { + visit.Preorder(filter, func(node ast.Node) { if err != nil { return // there is already an error. } - call, ok := n.(*ast.CallExpr) + call, ok := node.(*ast.CallExpr) if !ok { return // not a function call. } @@ -124,7 +105,7 @@ func run(pass *analysis.Pass, mainModule string, funcs map[string]Func) (any, er name := trimVendor.ReplaceAllString(callee.FullName(), "") fn, ok := funcs[name] if !ok { - return // the function is not supported. + return // unsupported function. } if len(call.Args) <= fn.ArgPos { @@ -133,19 +114,8 @@ func run(pass *analysis.Pass, mainModule string, funcs map[string]Func) (any, er } arg := call.Args[fn.ArgPos] - if unary, ok := arg.(*ast.UnaryExpr); ok { - arg = unary.X // e.g. json.Marshal(&foo) - } - - initialPos := token.NoPos - switch arg := arg.(type) { - case *ast.Ident: // e.g. json.Marshal(foo) - if arg.Obj == nil { - return // e.g. json.Marshal(nil) - } - initialPos = arg.Obj.Pos() - case *ast.CompositeLit: // e.g. json.Marshal(struct{}{}) - initialPos = arg.Pos() + if ident, ok := arg.(*ast.Ident); ok && ident.Obj == nil { + return // e.g. json.Marshal(nil) } typ := pass.TypesInfo.TypeOf(arg) @@ -154,7 +124,7 @@ func run(pass *analysis.Pass, mainModule string, funcs map[string]Func) (any, er } if implementsInterface(typ, fn.ifaceWhitelist, pass.Pkg.Imports()) { - return // the type implements a Marshaler interface, nothing to check; see issue #64. + return // the type implements a Marshaler interface; see issue #64. } checker := checker{ @@ -162,112 +132,88 @@ func run(pass *analysis.Pass, mainModule string, funcs map[string]Func) (any, er seenTypes: make(map[string]struct{}), } - st, ok := checker.parseStructType(typ, initialPos) + styp, ok := checker.parseStruct(typ) if !ok { - return // not a struct argument. + return // not a struct. } - result, ok := checker.checkStructType(st, fn.Tag) - if ok { + if valid := checker.checkStruct(styp, fn.Tag); valid { return // nothing to report. } - p := pass.Fset.Position(call.Pos()) - report(pass, result, fn, p) + pass.Reportf(call.Pos(), "the given struct should be annotated with the `%s` tag", fn.Tag) }) return nil, err } -// structType is an extension for types.Struct. -// The content of the fields depends on whether the type is named or not. -type structType struct { - *types.Struct - Name string // for types.Named: the type's name; for anonymous: a placeholder string. - Pos token.Pos // for types.Named: the type's position; for anonymous: the corresponding identifier's position. -} - -// checker parses and checks struct types. type checker struct { mainModule string - seenTypes map[string]struct{} // prevent panic on recursive types; see issue #16. + seenTypes map[string]struct{} } -// parseStructType parses the given types.Type, returning the underlying struct type. -func (c *checker) parseStructType(t types.Type, pos token.Pos) (*structType, bool) { +func (c *checker) parseStruct(typ types.Type) (*types.Struct, bool) { for { // unwrap pointers (if any) first. - ptr, ok := t.(*types.Pointer) + ptr, ok := typ.(*types.Pointer) if !ok { break } - t = ptr.Elem() + typ = ptr.Elem() } - switch t := t.(type) { + switch typ := typ.(type) { case *types.Named: // a struct of the named type. - pkg := t.Obj().Pkg() // may be nil; see issue #38. + pkg := typ.Obj().Pkg() if pkg == nil { return nil, false } - if !strings.HasPrefix(pkg.Path(), c.mainModule) { return nil, false } - - s, ok := t.Underlying().(*types.Struct) + styp, ok := typ.Underlying().(*types.Struct) if !ok { return nil, false } - - return &structType{ - Struct: s, - Pos: t.Obj().Pos(), - Name: t.Obj().Name(), - }, true + return styp, true case *types.Struct: // an anonymous struct. - return &structType{ - Struct: t, - Pos: pos, - Name: "anonymous struct", - }, true - } + return typ, true - return nil, false + default: + return nil, false + } } -// checkStructType recursively checks whether the given struct type is annotated with the tag. -// The result is the type of the first nested struct which fields are not properly annotated. -func (c *checker) checkStructType(st *structType, tag string) (*structType, bool) { - c.seenTypes[st.String()] = struct{}{} +func (c *checker) checkStruct(styp *types.Struct, tag string) (valid bool) { + c.seenTypes[styp.String()] = struct{}{} - for i := 0; i < st.NumFields(); i++ { - field := st.Field(i) + for i := 0; i < styp.NumFields(); i++ { + field := styp.Field(i) if !field.Exported() { continue } - if _, ok := reflect.StructTag(st.Tag(i)).Lookup(tag); !ok { + if _, ok := reflect.StructTag(styp.Tag(i)).Lookup(tag); !ok { // tag is not required for embedded types; see issue #12. if !field.Embedded() { - return st, false + return false } } - nested, ok := c.parseStructType(field.Type(), st.Pos) // TODO: or field.Pos()? + nested, ok := c.parseStruct(field.Type()) if !ok { continue } if _, ok := c.seenTypes[nested.String()]; ok { continue } - if result, ok := c.checkStructType(nested, tag); !ok { - return result, false + if valid := c.checkStruct(nested, tag); !valid { + return false } } - return nil, true + return true } func implementsInterface(typ types.Type, ifaces []string, imports []*types.Package) bool { @@ -279,6 +225,7 @@ func implementsInterface(typ types.Type, ifaces []string, imports []*types.Packa } } // slow path: check indirect imports (e.g. looking for "encoding.TextMarshaler"). + // TODO: only check indirect imports from the package (e.g. "encoding/json") of the analyzed function (e.g. "encoding/json.Marshal"). for _, direct := range imports { for _, indirect := range direct.Imports() { if pkgName == trimVendor.ReplaceAllString(indirect.Path(), "") { diff --git a/musttag_test.go b/musttag_test.go index 29fd740..f41d1a4 100644 --- a/musttag_test.go +++ b/musttag_test.go @@ -1,7 +1,6 @@ package musttag import ( - "go/token" "io" "os" "os/exec" @@ -10,45 +9,27 @@ import ( "go-simpler.org/assert" . "go-simpler.org/assert/EF" - "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/analysistest" ) func TestAnalyzer(t *testing.T) { testdata := analysistest.TestData() + setupModules(t, testdata) t.Run("tests", func(t *testing.T) { - r := report - defer func() { report = r }() - - report = func(pass *analysis.Pass, st *structType, fn Func, fnPos token.Position) { - pass.Reportf(st.Pos, fn.shortName()) - } - - setupTestData(t, testdata, "tests") - - analyzer := New() - analysistest.Run(t, testdata, analyzer, "tests") - }) - - t.Run("builtins", func(t *testing.T) { - setupTestData(t, testdata, "builtins") - analyzer := New( Func{Name: "example.com/custom.Marshal", Tag: "custom", ArgPos: 0}, Func{Name: "example.com/custom.Unmarshal", Tag: "custom", ArgPos: 1}, ) - analysistest.Run(t, testdata, analyzer, "builtins") + analysistest.Run(t, testdata, analyzer, "tests") }) t.Run("bad Func.ArgPos", func(t *testing.T) { - setupTestData(t, testdata, "tests") - analyzer := New( Func{Name: "encoding/json.Marshal", Tag: "json", ArgPos: 10}, ) err := analysistest.Run(nopT{}, testdata, analyzer, "tests")[0].Err - assert.Equal[E](t, err.Error(), `Func.ArgPos cannot be 10: encoding/json.Marshal accepts only 1 argument(s)`) + assert.Equal[E](t, err.Error(), "Func.ArgPos cannot be 10: encoding/json.Marshal accepts only 1 argument(s)") }) } @@ -67,7 +48,7 @@ func TestFlags(t *testing.T) { assert.Equal[E](t, err.Error(), `invalid value "test.Test" for flag -fn: invalid syntax`) }) - t.Run("non-number argpos", func(t *testing.T) { + t.Run("non-number argument position", func(t *testing.T) { err := analyzer.Flags.Parse([]string{"-fn=test.Test:test:-"}) assert.Equal[E](t, err.Error(), `invalid value "test.Test:test:-" for flag -fn: strconv.Atoi: parsing "-": invalid syntax`) }) @@ -79,17 +60,12 @@ func (nopT) Errorf(string, ...any) {} // NOTE: analysistest does not yet support modules; // see https://github.com/golang/go/issues/37054 for details. -func setupTestData(t *testing.T, testDataDir, dir string) { +func setupModules(t *testing.T, testdata string) { t.Helper() - err := os.Chdir(filepath.Join(testDataDir, "src", dir)) - if err != nil { - t.Fatal(err) - } + err := os.Chdir(filepath.Join(testdata, "src", "tests")) + assert.NoErr[F](t, err) - output, err := exec.Command("go", "mod", "vendor").CombinedOutput() - if err != nil { - t.Log(string(output)) - t.Fatal(err) - } + err = exec.Command("go", "mod", "vendor").Run() + assert.NoErr[F](t, err) } diff --git a/testdata/src/builtins/builtins.go b/testdata/src/builtins/builtins.go deleted file mode 100644 index 60e3c7a..0000000 --- a/testdata/src/builtins/builtins.go +++ /dev/null @@ -1,228 +0,0 @@ -package builtins - -import ( - "encoding/json" - "encoding/xml" - - "example.com/custom" - "github.com/BurntSushi/toml" - "github.com/jmoiron/sqlx" - "github.com/mitchellh/mapstructure" - "gopkg.in/yaml.v3" -) - -type User struct { /* want - "`User` should be annotated with the `json` tag as it is passed to `json.Marshal` at" - "`User` should be annotated with the `json` tag as it is passed to `json.MarshalIndent` at" - "`User` should be annotated with the `json` tag as it is passed to `json.Unmarshal` at" - "`User` should be annotated with the `json` tag as it is passed to `json.Encoder.Encode` at" - "`User` should be annotated with the `json` tag as it is passed to `json.Decoder.Decode` at" - - "`User` should be annotated with the `xml` tag as it is passed to `xml.Marshal` at" - "`User` should be annotated with the `xml` tag as it is passed to `xml.MarshalIndent` at" - "`User` should be annotated with the `xml` tag as it is passed to `xml.Unmarshal` at" - "`User` should be annotated with the `xml` tag as it is passed to `xml.Encoder.Encode` at" - "`User` should be annotated with the `xml` tag as it is passed to `xml.Decoder.Decode` at" - "`User` should be annotated with the `xml` tag as it is passed to `xml.Encoder.EncodeElement` at" - "`User` should be annotated with the `xml` tag as it is passed to `xml.Decoder.DecodeElement` at" - - "`User` should be annotated with the `yaml` tag as it is passed to `yaml.v3.Marshal` at" - "`User` should be annotated with the `yaml` tag as it is passed to `yaml.v3.Unmarshal` at" - "`User` should be annotated with the `yaml` tag as it is passed to `yaml.v3.Encoder.Encode` at" - "`User` should be annotated with the `yaml` tag as it is passed to `yaml.v3.Decoder.Decode` at" - - "`User` should be annotated with the `toml` tag as it is passed to `toml.Unmarshal` at" - "`User` should be annotated with the `toml` tag as it is passed to `toml.Decode` at" - "`User` should be annotated with the `toml` tag as it is passed to `toml.DecodeFS` at" - "`User` should be annotated with the `toml` tag as it is passed to `toml.DecodeFile` at" - "`User` should be annotated with the `toml` tag as it is passed to `toml.Encoder.Encode` at" - "`User` should be annotated with the `toml` tag as it is passed to `toml.Decoder.Decode` at" - - "`User` should be annotated with the `mapstructure` tag as it is passed to `mapstructure.Decode` at" - "`User` should be annotated with the `mapstructure` tag as it is passed to `mapstructure.DecodeMetadata` at" - "`User` should be annotated with the `mapstructure` tag as it is passed to `mapstructure.WeakDecode` at" - "`User` should be annotated with the `mapstructure` tag as it is passed to `mapstructure.WeakDecodeMetadata` at" - - "`User` should be annotated with the `db` tag as it is passed to `sqlx.Get` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.GetContext` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.Select` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.SelectContext` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.StructScan` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.Conn.GetContext` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.Conn.SelectContext` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.DB.Get` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.DB.GetContext` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.DB.Select` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.DB.SelectContext` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.NamedStmt.Get` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.NamedStmt.GetContext` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.NamedStmt.Select` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.NamedStmt.SelectContext` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.Row.StructScan` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.Rows.StructScan` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.Stmt.Get` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.Stmt.GetContext` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.Stmt.Select` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.Stmt.SelectContext` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.Tx.Get` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.Tx.GetContext` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.Tx.Select` at" - "`User` should be annotated with the `db` tag as it is passed to `sqlx.Tx.SelectContext` at" - - "`User` should be annotated with the `custom` tag as it is passed to `custom.Marshal` at" - "`User` should be annotated with the `custom` tag as it is passed to `custom.Unmarshal` at" - */ - Name string - Email string `json:"email" xml:"email" yaml:"email" toml:"email" mapstructure:"email" db:"email" custom:"email"` -} - -// TODO: Unmarshaler should be implemented using pointer semantics. - -type TextMarshaler struct{ NoTag string } - -func (TextMarshaler) MarshalText() ([]byte, error) { return nil, nil } -func (TextMarshaler) UnmarshalText([]byte) error { return nil } - -type Marshaler struct{ NoTag string } - -func (Marshaler) MarshalJSON() ([]byte, error) { return nil, nil } -func (Marshaler) UnmarshalJSON([]byte) error { return nil } -func (Marshaler) MarshalXML(e *xml.Encoder, start xml.StartElement) error { return nil } -func (Marshaler) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { return nil } -func (Marshaler) MarshalYAML() (any, error) { return nil, nil } -func (Marshaler) UnmarshalYAML(*yaml.Node) error { return nil } -func (Marshaler) UnmarshalTOML(any) error { return nil } - -func testJSON() { - var user User - json.Marshal(user) - json.MarshalIndent(user, "", "") - json.Unmarshal(nil, &user) - json.NewEncoder(nil).Encode(user) - json.NewDecoder(nil).Decode(&user) - - var m Marshaler - json.Marshal(m) - json.MarshalIndent(m, "", "") - json.Unmarshal(nil, &m) - json.NewEncoder(nil).Encode(m) - json.NewDecoder(nil).Decode(&m) - - var tm TextMarshaler - json.Marshal(tm) - json.MarshalIndent(tm, "", "") - json.Unmarshal(nil, &tm) - json.NewEncoder(nil).Encode(tm) - json.NewDecoder(nil).Decode(&tm) -} - -func testXML() { - var user User - xml.Marshal(user) - xml.MarshalIndent(user, "", "") - xml.Unmarshal(nil, &user) - xml.NewEncoder(nil).Encode(user) - xml.NewDecoder(nil).Decode(&user) - xml.NewEncoder(nil).EncodeElement(user, xml.StartElement{}) - xml.NewDecoder(nil).DecodeElement(&user, &xml.StartElement{}) - - var m Marshaler - xml.Marshal(m) - xml.MarshalIndent(m, "", "") - xml.Unmarshal(nil, &m) - xml.NewEncoder(nil).Encode(m) - xml.NewDecoder(nil).Decode(&m) - xml.NewEncoder(nil).EncodeElement(m, xml.StartElement{}) - xml.NewDecoder(nil).DecodeElement(&m, &xml.StartElement{}) - - var tm TextMarshaler - xml.Marshal(tm) - xml.MarshalIndent(tm, "", "") - xml.Unmarshal(nil, &tm) - xml.NewEncoder(nil).Encode(tm) - xml.NewDecoder(nil).Decode(&tm) - xml.NewEncoder(nil).EncodeElement(tm, xml.StartElement{}) - xml.NewDecoder(nil).DecodeElement(&tm, &xml.StartElement{}) -} - -func testYAML() { - var user User - yaml.Marshal(user) - yaml.Unmarshal(nil, &user) - yaml.NewEncoder(nil).Encode(user) - yaml.NewDecoder(nil).Decode(&user) - - var m Marshaler - yaml.Marshal(m) - yaml.Unmarshal(nil, &m) - yaml.NewEncoder(nil).Encode(m) - yaml.NewDecoder(nil).Decode(&m) -} - -func testTOML() { - var user User - toml.Unmarshal(nil, &user) - toml.Decode("", &user) - toml.DecodeFS(nil, "", &user) - toml.DecodeFile("", &user) - toml.NewEncoder(nil).Encode(user) - toml.NewDecoder(nil).Decode(&user) - - var m Marshaler - toml.Unmarshal(nil, &m) - toml.Decode("", &m) - toml.DecodeFS(nil, "", &m) - toml.DecodeFile("", &m) - toml.NewDecoder(nil).Decode(&m) - - var tm TextMarshaler - toml.Unmarshal(nil, &tm) - toml.Decode("", &tm) - toml.DecodeFS(nil, "", &tm) - toml.DecodeFile("", &tm) - toml.NewEncoder(nil).Encode(tm) - toml.NewDecoder(nil).Decode(&tm) -} - -func testMapstructure() { - var user User - mapstructure.Decode(nil, &user) - mapstructure.DecodeMetadata(nil, &user, nil) - mapstructure.WeakDecode(nil, &user) - mapstructure.WeakDecodeMetadata(nil, &user, nil) -} - -func testSQLX() { - var user User - sqlx.Get(nil, &user, "") - sqlx.GetContext(nil, nil, &user, "") - sqlx.Select(nil, &user, "") - sqlx.SelectContext(nil, nil, &user, "") - sqlx.StructScan(nil, &user) - new(sqlx.Conn).GetContext(nil, &user, "") - new(sqlx.Conn).SelectContext(nil, &user, "") - new(sqlx.DB).Get(&user, "") - new(sqlx.DB).GetContext(nil, &user, "") - new(sqlx.DB).Select(&user, "") - new(sqlx.DB).SelectContext(nil, &user, "") - new(sqlx.NamedStmt).Get(&user, nil) - new(sqlx.NamedStmt).GetContext(nil, &user, nil) - new(sqlx.NamedStmt).Select(&user, nil) - new(sqlx.NamedStmt).SelectContext(nil, &user, nil) - new(sqlx.Row).StructScan(&user) - new(sqlx.Rows).StructScan(&user) - new(sqlx.Stmt).Get(&user) - new(sqlx.Stmt).GetContext(nil, &user) - new(sqlx.Stmt).Select(&user) - new(sqlx.Stmt).SelectContext(nil, &user) - new(sqlx.Tx).Get(&user, "") - new(sqlx.Tx).GetContext(nil, &user, "") - new(sqlx.Tx).Select(&user, "") - new(sqlx.Tx).SelectContext(nil, &user, "") -} - -func testCustom() { - var user User - custom.Marshal(user) - custom.Unmarshal(nil, &user) -} diff --git a/testdata/src/builtins/go.mod b/testdata/src/builtins/go.mod deleted file mode 100644 index 854704e..0000000 --- a/testdata/src/builtins/go.mod +++ /dev/null @@ -1,13 +0,0 @@ -module builtins - -go 1.19 - -require ( - example.com/custom v0.0.1 - github.com/BurntSushi/toml v1.3.2 - github.com/jmoiron/sqlx v1.3.5 - github.com/mitchellh/mapstructure v1.5.0 - gopkg.in/yaml.v3 v3.0.1 -) - -replace example.com/custom => ../custom diff --git a/testdata/src/custom/custom.go b/testdata/src/custom/custom.go index d6f445f..8dbabc6 100644 --- a/testdata/src/custom/custom.go +++ b/testdata/src/custom/custom.go @@ -1,9 +1,4 @@ package custom -func Marshal(v any) ([]byte, error) { - return nil, nil -} - -func Unmarshal(data []byte, v any) error { - return nil -} +func Marshal(any) ([]byte, error) { return nil, nil } +func Unmarshal([]byte, any) error { return nil } diff --git a/testdata/src/go.work b/testdata/src/go.work index 5595c7c..8e99d40 100644 --- a/testdata/src/go.work +++ b/testdata/src/go.work @@ -1,7 +1,6 @@ -go 1.18 +go 1.19 use ( - ./builtins ./custom ./tests ) diff --git a/testdata/src/builtins/.gitignore b/testdata/src/tests/.gitignore similarity index 100% rename from testdata/src/builtins/.gitignore rename to testdata/src/tests/.gitignore diff --git a/testdata/src/tests/builtins.go b/testdata/src/tests/builtins.go new file mode 100644 index 0000000..d576da4 --- /dev/null +++ b/testdata/src/tests/builtins.go @@ -0,0 +1,163 @@ +package tests + +import ( + "encoding/json" + "encoding/xml" + + "example.com/custom" + "github.com/BurntSushi/toml" + "github.com/jmoiron/sqlx" + "github.com/mitchellh/mapstructure" + "gopkg.in/yaml.v3" +) + +type Struct struct{ NoTag string } + +type Marshaler struct{ NoTag string } + +func (Marshaler) MarshalJSON() ([]byte, error) { return nil, nil } +func (*Marshaler) UnmarshalJSON([]byte) error { return nil } +func (Marshaler) MarshalXML(e *xml.Encoder, start xml.StartElement) error { return nil } +func (*Marshaler) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { return nil } +func (Marshaler) MarshalYAML() (any, error) { return nil, nil } +func (*Marshaler) UnmarshalYAML(*yaml.Node) error { return nil } +func (*Marshaler) UnmarshalTOML(any) error { return nil } + +type TextMarshaler struct{ NoTag string } + +func (TextMarshaler) MarshalText() ([]byte, error) { return nil, nil } +func (*TextMarshaler) UnmarshalText([]byte) error { return nil } + +func testJSON() { + var st Struct + json.Marshal(st) // want "the given struct should be annotated with the `json` tag" + json.MarshalIndent(st, "", "") // want "the given struct should be annotated with the `json` tag" + json.Unmarshal(nil, &st) // want "the given struct should be annotated with the `json` tag" + json.NewEncoder(nil).Encode(st) // want "the given struct should be annotated with the `json` tag" + json.NewDecoder(nil).Decode(&st) // want "the given struct should be annotated with the `json` tag" + + var m Marshaler + json.Marshal(m) + json.MarshalIndent(m, "", "") + json.Unmarshal(nil, &m) + json.NewEncoder(nil).Encode(m) + json.NewDecoder(nil).Decode(&m) + + var tm TextMarshaler + json.Marshal(tm) + json.MarshalIndent(tm, "", "") + json.Unmarshal(nil, &tm) + json.NewEncoder(nil).Encode(tm) + json.NewDecoder(nil).Decode(&tm) +} + +func testXML() { + var st Struct + xml.Marshal(st) // want "the given struct should be annotated with the `xml` tag" + xml.MarshalIndent(st, "", "") // want "the given struct should be annotated with the `xml` tag" + xml.Unmarshal(nil, &st) // want "the given struct should be annotated with the `xml` tag" + xml.NewEncoder(nil).Encode(st) // want "the given struct should be annotated with the `xml` tag" + xml.NewDecoder(nil).Decode(&st) // want "the given struct should be annotated with the `xml` tag" + xml.NewEncoder(nil).EncodeElement(st, xml.StartElement{}) // want "the given struct should be annotated with the `xml` tag" + xml.NewDecoder(nil).DecodeElement(&st, &xml.StartElement{}) // want "the given struct should be annotated with the `xml` tag" + + var m Marshaler + xml.Marshal(m) + xml.MarshalIndent(m, "", "") + xml.Unmarshal(nil, &m) + xml.NewEncoder(nil).Encode(m) + xml.NewDecoder(nil).Decode(&m) + xml.NewEncoder(nil).EncodeElement(m, xml.StartElement{}) + xml.NewDecoder(nil).DecodeElement(&m, &xml.StartElement{}) + + var tm TextMarshaler + xml.Marshal(tm) + xml.MarshalIndent(tm, "", "") + xml.Unmarshal(nil, &tm) + xml.NewEncoder(nil).Encode(tm) + xml.NewDecoder(nil).Decode(&tm) + xml.NewEncoder(nil).EncodeElement(tm, xml.StartElement{}) + xml.NewDecoder(nil).DecodeElement(&tm, &xml.StartElement{}) +} + +func testYAML() { + var st Struct + yaml.Marshal(st) // want "the given struct should be annotated with the `yaml` tag" + yaml.Unmarshal(nil, &st) // want "the given struct should be annotated with the `yaml` tag" + yaml.NewEncoder(nil).Encode(st) // want "the given struct should be annotated with the `yaml` tag" + yaml.NewDecoder(nil).Decode(&st) // want "the given struct should be annotated with the `yaml` tag" + + var m Marshaler + yaml.Marshal(m) + yaml.Unmarshal(nil, &m) + yaml.NewEncoder(nil).Encode(m) + yaml.NewDecoder(nil).Decode(&m) +} + +func testTOML() { + var st Struct + toml.Unmarshal(nil, &st) // want "the given struct should be annotated with the `toml` tag" + toml.Decode("", &st) // want "the given struct should be annotated with the `toml` tag" + toml.DecodeFS(nil, "", &st) // want "the given struct should be annotated with the `toml` tag" + toml.DecodeFile("", &st) // want "the given struct should be annotated with the `toml` tag" + toml.NewEncoder(nil).Encode(st) // want "the given struct should be annotated with the `toml` tag" + toml.NewDecoder(nil).Decode(&st) // want "the given struct should be annotated with the `toml` tag" + + var m Marshaler + toml.Unmarshal(nil, &m) + toml.Decode("", &m) + toml.DecodeFS(nil, "", &m) + toml.DecodeFile("", &m) + toml.NewDecoder(nil).Decode(&m) + + var tm TextMarshaler + toml.Unmarshal(nil, &tm) + toml.Decode("", &tm) + toml.DecodeFS(nil, "", &tm) + toml.DecodeFile("", &tm) + toml.NewEncoder(nil).Encode(tm) + toml.NewDecoder(nil).Decode(&tm) +} + +func testMapstructure() { + var st Struct + mapstructure.Decode(nil, &st) // want "the given struct should be annotated with the `mapstructure` tag" + mapstructure.DecodeMetadata(nil, &st, nil) // want "the given struct should be annotated with the `mapstructure` tag" + mapstructure.WeakDecode(nil, &st) // want "the given struct should be annotated with the `mapstructure` tag" + mapstructure.WeakDecodeMetadata(nil, &st, nil) // want "the given struct should be annotated with the `mapstructure` tag" +} + +func testSQLX() { + var st Struct + sqlx.Get(nil, &st, "") // want "the given struct should be annotated with the `db` tag" + sqlx.GetContext(nil, nil, &st, "") // want "the given struct should be annotated with the `db` tag" + sqlx.Select(nil, &st, "") // want "the given struct should be annotated with the `db` tag" + sqlx.SelectContext(nil, nil, &st, "") // want "the given struct should be annotated with the `db` tag" + sqlx.StructScan(nil, &st) // want "the given struct should be annotated with the `db` tag" + new(sqlx.Conn).GetContext(nil, &st, "") // want "the given struct should be annotated with the `db` tag" + new(sqlx.Conn).SelectContext(nil, &st, "") // want "the given struct should be annotated with the `db` tag" + new(sqlx.DB).Get(&st, "") // want "the given struct should be annotated with the `db` tag" + new(sqlx.DB).GetContext(nil, &st, "") // want "the given struct should be annotated with the `db` tag" + new(sqlx.DB).Select(&st, "") // want "the given struct should be annotated with the `db` tag" + new(sqlx.DB).SelectContext(nil, &st, "") // want "the given struct should be annotated with the `db` tag" + new(sqlx.NamedStmt).Get(&st, nil) // want "the given struct should be annotated with the `db` tag" + new(sqlx.NamedStmt).GetContext(nil, &st, nil) // want "the given struct should be annotated with the `db` tag" + new(sqlx.NamedStmt).Select(&st, nil) // want "the given struct should be annotated with the `db` tag" + new(sqlx.NamedStmt).SelectContext(nil, &st, nil) // want "the given struct should be annotated with the `db` tag" + new(sqlx.Row).StructScan(&st) // want "the given struct should be annotated with the `db` tag" + new(sqlx.Rows).StructScan(&st) // want "the given struct should be annotated with the `db` tag" + new(sqlx.Stmt).Get(&st) // want "the given struct should be annotated with the `db` tag" + new(sqlx.Stmt).GetContext(nil, &st) // want "the given struct should be annotated with the `db` tag" + new(sqlx.Stmt).Select(&st) // want "the given struct should be annotated with the `db` tag" + new(sqlx.Stmt).SelectContext(nil, &st) // want "the given struct should be annotated with the `db` tag" + new(sqlx.Tx).Get(&st, "") // want "the given struct should be annotated with the `db` tag" + new(sqlx.Tx).GetContext(nil, &st, "") // want "the given struct should be annotated with the `db` tag" + new(sqlx.Tx).Select(&st, "") // want "the given struct should be annotated with the `db` tag" + new(sqlx.Tx).SelectContext(nil, &st, "") // want "the given struct should be annotated with the `db` tag" +} + +func testCustom() { + var st Struct + custom.Marshal(st) // want "the given struct should be annotated with the `custom` tag" + custom.Unmarshal(nil, &st) // want "the given struct should be annotated with the `custom` tag" +} diff --git a/testdata/src/tests/go.mod b/testdata/src/tests/go.mod index 95a8796..76894ba 100644 --- a/testdata/src/tests/go.mod +++ b/testdata/src/tests/go.mod @@ -1,3 +1,13 @@ module tests go 1.19 + +require ( + example.com/custom v0.1.0 + github.com/BurntSushi/toml v1.3.2 + github.com/jmoiron/sqlx v1.3.5 + github.com/mitchellh/mapstructure v1.5.0 + gopkg.in/yaml.v3 v3.0.1 +) + +replace example.com/custom => ../custom diff --git a/testdata/src/builtins/go.sum b/testdata/src/tests/go.sum similarity index 100% rename from testdata/src/builtins/go.sum rename to testdata/src/tests/go.sum diff --git a/testdata/src/tests/tests.go b/testdata/src/tests/tests.go index 93c8b89..9112dab 100644 --- a/testdata/src/tests/tests.go +++ b/testdata/src/tests/tests.go @@ -1,67 +1,67 @@ -package testdata +package tests import "encoding/json" func namedType() { - type Foo struct { // want `json.Marshal` `json.Unmarshal` `json.Encoder.Encode` `json.Decoder.Decode` + type Foo struct { NoTag string } var foo Foo - json.Marshal(foo) - json.Unmarshal(nil, &foo) - json.NewEncoder(nil).Encode(Foo{}) - json.NewDecoder(nil).Decode(&Foo{}) + json.Marshal(foo) // want "the given struct should be annotated with the `json` tag" + json.Marshal(&foo) // want "the given struct should be annotated with the `json` tag" + json.Marshal(Foo{}) // want "the given struct should be annotated with the `json` tag" + json.Marshal(&Foo{}) // want "the given struct should be annotated with the `json` tag" } func anonymousType() { - var foo struct { // want `json.Marshal` `json.Unmarshal` + var foo struct { NoTag string } - json.Marshal(foo) - json.Unmarshal(nil, &foo) - json.NewEncoder(nil).Encode(struct{ NoTag int }{}) // want `json.Encoder.Encode` - json.NewDecoder(nil).Decode(&struct{ NoTag int }{}) // want `json.Decoder.Decode` + json.Marshal(foo) // want "the given struct should be annotated with the `json` tag" + json.Marshal(&foo) // want "the given struct should be annotated with the `json` tag" + json.Marshal(struct{ NoTag int }{}) // want "the given struct should be annotated with the `json` tag" + json.Marshal(&struct{ NoTag int }{}) // want "the given struct should be annotated with the `json` tag" } func nestedType() { - type Bar struct { // want `json.Marshal` `json.Unmarshal` `json.Encoder.Encode` `json.Decoder.Decode` - Baz struct{ NoTag string } `json:"baz"` + type Bar struct { + NoTag string } type Foo struct { - Bar ***Bar `json:"bar"` + Bar Bar `json:"bar"` } var foo Foo - json.Marshal(foo) - json.Unmarshal(nil, &foo) - json.NewEncoder(nil).Encode(Foo{}) - json.NewDecoder(nil).Decode(&Foo{}) + json.Marshal(foo) // want "the given struct should be annotated with the `json` tag" + json.Marshal(&foo) // want "the given struct should be annotated with the `json` tag" + json.Marshal(Foo{}) // want "the given struct should be annotated with the `json` tag" + json.Marshal(&Foo{}) // want "the given struct should be annotated with the `json` tag" } func embeddedType() { - type Bar struct { // want `json.Marshal` `json.Unmarshal` `json.Encoder.Encode` `json.Decoder.Decode` + type Bar struct { NoTag string } type Foo struct { Bar } var foo Foo - json.Marshal(foo) - json.Unmarshal(nil, &foo) - json.NewEncoder(nil).Encode(Foo{}) - json.NewDecoder(nil).Decode(&Foo{}) + json.Marshal(foo) // want "the given struct should be annotated with the `json` tag" + json.Marshal(&foo) // want "the given struct should be annotated with the `json` tag" + json.Marshal(Foo{}) // want "the given struct should be annotated with the `json` tag" + json.Marshal(&Foo{}) // want "the given struct should be annotated with the `json` tag" } -// should not cause panic; see issue #16. func recursiveType() { - type Foo struct { // want `json.Marshal` `json.Unmarshal` `json.Encoder.Encode` `json.Decoder.Decode` - Foo2 *Foo `json:"foo2"` + // should not cause panic; see issue #16. + type Foo struct { + Foo *Foo `json:"foo"` NoTag string } var foo Foo - json.Marshal(foo) - json.Unmarshal(nil, &foo) - json.NewEncoder(nil).Encode(Foo{}) - json.NewDecoder(nil).Decode(&Foo{}) + json.Marshal(foo) // want "the given struct should be annotated with the `json` tag" + json.Marshal(&foo) // want "the given struct should be annotated with the `json` tag" + json.Marshal(Foo{}) // want "the given struct should be annotated with the `json` tag" + json.Marshal(&Foo{}) // want "the given struct should be annotated with the `json` tag" } func shouldBeIgnored() { @@ -74,23 +74,3 @@ func shouldBeIgnored() { json.Marshal(0) // a non-struct argument. json.Marshal(nil) // nil argument, see issue #20. } - -func nothingToReport() { - type Bar struct { - B string `json:"b"` - Bar struct { - C string `json:"c"` - } `json:"bar"` - } - type Foo struct { - Bar - A string `json:"a"` - private string - _ string - } - var foo Foo - json.Marshal(foo) - json.Unmarshal(nil, &foo) - json.NewEncoder(nil).Encode(Foo{}) - json.NewDecoder(nil).Decode(&Foo{}) -} From 94a82903f9bce69dc26eb9c551991274d45a880c Mon Sep 17 00:00:00 2001 From: Tom <73077675+tmzane@users.noreply.github.com> Date: Mon, 9 Oct 2023 17:55:55 +0300 Subject: [PATCH 2/2] add TODO --- musttag.go | 1 + 1 file changed, 1 insertion(+) diff --git a/musttag.go b/musttag.go index bb44a13..43c8381 100644 --- a/musttag.go +++ b/musttag.go @@ -123,6 +123,7 @@ func run(pass *analysis.Pass, mainModule string, funcs map[string]Func) (_ any, return // no type info found. } + // TODO: check nested structs too. if implementsInterface(typ, fn.ifaceWhitelist, pass.Pkg.Imports()) { return // the type implements a Marshaler interface; see issue #64. }