diff options
| author | Taras Madan <tarasmadan@google.com> | 2023-02-22 22:16:50 +0100 |
|---|---|---|
| committer | Taras Madan <tarasmadan@google.com> | 2023-02-24 12:47:23 +0100 |
| commit | 4165372ec8fd142475a4e35fd0cf4f8042132208 (patch) | |
| tree | 21cd62211b4dd80bee469054c5b65db77342333c /vendor/github.com/junk1tm | |
| parent | 2b3ed821a493b8936c8bacfa6f8b4f1c90a00855 (diff) | |
dependencies: update
set go min requirements to 1.19
update dependencies
update vendor
Diffstat (limited to 'vendor/github.com/junk1tm')
| -rw-r--r-- | vendor/github.com/junk1tm/musttag/.golangci.yml | 23 | ||||
| -rw-r--r-- | vendor/github.com/junk1tm/musttag/.goreleaser.yml | 30 | ||||
| -rw-r--r-- | vendor/github.com/junk1tm/musttag/LICENSE | 21 | ||||
| -rw-r--r-- | vendor/github.com/junk1tm/musttag/README.md | 93 | ||||
| -rw-r--r-- | vendor/github.com/junk1tm/musttag/builtins.go | 40 | ||||
| -rw-r--r-- | vendor/github.com/junk1tm/musttag/musttag.go | 251 | ||||
| -rw-r--r-- | vendor/github.com/junk1tm/musttag/utils.go | 40 |
7 files changed, 498 insertions, 0 deletions
diff --git a/vendor/github.com/junk1tm/musttag/.golangci.yml b/vendor/github.com/junk1tm/musttag/.golangci.yml new file mode 100644 index 000000000..641471cc4 --- /dev/null +++ b/vendor/github.com/junk1tm/musttag/.golangci.yml @@ -0,0 +1,23 @@ +linters: + disable-all: true + enable: + # enabled by default: + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - typecheck + - unused + # disabled by default: + - gocritic + - gofumpt + +linters-settings: + gocritic: + enabled-tags: + - diagnostic + - style + - performance + - experimental + - opinionated diff --git a/vendor/github.com/junk1tm/musttag/.goreleaser.yml b/vendor/github.com/junk1tm/musttag/.goreleaser.yml new file mode 100644 index 000000000..6f85d818f --- /dev/null +++ b/vendor/github.com/junk1tm/musttag/.goreleaser.yml @@ -0,0 +1,30 @@ +builds: + - main: ./cmd/musttag + env: + - CGO_ENABLED=0 + flags: + - -trimpath + ldflags: + - -s -w -X main.version={{.Version}} + targets: + - darwin_amd64 + - darwin_arm64 + - linux_amd64 + - windows_amd64 + +archives: + - replacements: + darwin: macOS + format_overrides: + - goos: windows + format: zip + +brews: + - tap: + owner: junk1tm + name: homebrew-tap + branch: main + token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" + homepage: "https://github.com/junk1tm/musttag" + description: "A Go linter that enforces field tags in (un)marshaled structs" + license: "MIT" diff --git a/vendor/github.com/junk1tm/musttag/LICENSE b/vendor/github.com/junk1tm/musttag/LICENSE new file mode 100644 index 000000000..38baef8d4 --- /dev/null +++ b/vendor/github.com/junk1tm/musttag/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 junk1tm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/junk1tm/musttag/README.md b/vendor/github.com/junk1tm/musttag/README.md new file mode 100644 index 000000000..c04eae7c3 --- /dev/null +++ b/vendor/github.com/junk1tm/musttag/README.md @@ -0,0 +1,93 @@ +# musttag + +[](https://github.com/junk1tm/musttag/actions/workflows/go.yml) +[](https://pkg.go.dev/github.com/junk1tm/musttag) +[](https://goreportcard.com/report/github.com/junk1tm/musttag) +[](https://codecov.io/gh/junk1tm/musttag) + +A Go linter that enforces field tags in (un)marshaled structs + +## ๐ About + +`musttag` checks that exported fields of a struct passed to a `Marshal`-like function are annotated with the relevant tag: + +```go +// BAD: +var user struct { + Name string +} +data, err := json.Marshal(user) + +// GOOD: +var user struct { + Name string `json:"name"` +} +data, err := json.Marshal(user) +``` + +The rational from [Uber Style Guide][1]: + +> The serialized form of the structure is a contract between different systems. +> Changes to the structure of the serialized form, including field names, break this contract. +> Specifying field names inside tags makes the contract explicit, +> and it guards against accidentally breaking the contract by refactoring or renaming fields. + +## ๐ Features + +`musttag` supports these packages out of the box: + +* `encoding/json` +* `encoding/xml` +* `gopkg.in/yaml.v3` +* `github.com/BurntSushi/toml` +* `github.com/mitchellh/mapstructure` +* ...and any [custom one](#custom-packages) + +## ๐ฆ Install + +### Go + +```shell +go install github.com/junk1tm/musttag/cmd/musttag@latest +``` + +### Brew + +```shell +brew install junk1tm/tap/musttag +``` + +### Manual + +Download a prebuilt binary from the [Releases][2] page. + +## ๐ Usage + +As a standalone binary: + +```shell +musttag ./... +``` + +Via `go vet`: + +```shell +go vet -vettool=$(which musttag) ./... +``` + +### Custom packages + +The `-fn=name:tag:argpos` flag can be used to report functions from custom packages, where + +* `name` is the full name of the function, including the package +* `tag` is the struct tag whose presence should be ensured +* `argpos` is the position of the argument to check + +For example, to support the `sqlx.Get` function: + +```shell +musttag -fn="github.com/jmoiron/sqlx.Get:db:1" ./... +``` + +[1]: https://github.com/uber-go/guide/blob/master/style.md#use-field-tags-in-marshaled-structs +[2]: https://github.com/junk1tm/musttag/releases diff --git a/vendor/github.com/junk1tm/musttag/builtins.go b/vendor/github.com/junk1tm/musttag/builtins.go new file mode 100644 index 000000000..50573f834 --- /dev/null +++ b/vendor/github.com/junk1tm/musttag/builtins.go @@ -0,0 +1,40 @@ +package musttag + +// builtins is a set of functions supported out of the box. +var builtins = []Func{ + // https://pkg.go.dev/encoding/json + {Name: "encoding/json.Marshal", Tag: "json", ArgPos: 0}, + {Name: "encoding/json.MarshalIndent", Tag: "json", ArgPos: 0}, + {Name: "encoding/json.Unmarshal", Tag: "json", ArgPos: 1}, + {Name: "(*encoding/json.Encoder).Encode", Tag: "json", ArgPos: 0}, + {Name: "(*encoding/json.Decoder).Decode", Tag: "json", ArgPos: 0}, + + // https://pkg.go.dev/encoding/xml + {Name: "encoding/xml.Marshal", Tag: "xml", ArgPos: 0}, + {Name: "encoding/xml.MarshalIndent", Tag: "xml", ArgPos: 0}, + {Name: "encoding/xml.Unmarshal", Tag: "xml", ArgPos: 1}, + {Name: "(*encoding/xml.Encoder).Encode", Tag: "xml", ArgPos: 0}, + {Name: "(*encoding/xml.Decoder).Decode", Tag: "xml", ArgPos: 0}, + {Name: "(*encoding/xml.Encoder).EncodeElement", Tag: "xml", ArgPos: 0}, + {Name: "(*encoding/xml.Decoder).DecodeElement", Tag: "xml", ArgPos: 0}, + + // https://github.com/go-yaml/yaml + {Name: "gopkg.in/yaml.v3.Marshal", Tag: "yaml", ArgPos: 0}, + {Name: "gopkg.in/yaml.v3.Unmarshal", Tag: "yaml", ArgPos: 1}, + {Name: "(*gopkg.in/yaml.v3.Encoder).Encode", Tag: "yaml", ArgPos: 0}, + {Name: "(*gopkg.in/yaml.v3.Decoder).Decode", Tag: "yaml", ArgPos: 0}, + + // https://github.com/BurntSushi/toml + {Name: "github.com/BurntSushi/toml.Unmarshal", Tag: "toml", ArgPos: 1}, + {Name: "github.com/BurntSushi/toml.Decode", Tag: "toml", ArgPos: 1}, + {Name: "github.com/BurntSushi/toml.DecodeFS", Tag: "toml", ArgPos: 2}, + {Name: "github.com/BurntSushi/toml.DecodeFile", Tag: "toml", ArgPos: 1}, + {Name: "(*github.com/BurntSushi/toml.Encoder).Encode", Tag: "toml", ArgPos: 0}, + {Name: "(*github.com/BurntSushi/toml.Decoder).Decode", Tag: "toml", ArgPos: 0}, + + // https://github.com/mitchellh/mapstructure + {Name: "github.com/mitchellh/mapstructure.Decode", Tag: "mapstructure", ArgPos: 1}, + {Name: "github.com/mitchellh/mapstructure.DecodeMetadata", Tag: "mapstructure", ArgPos: 1}, + {Name: "github.com/mitchellh/mapstructure.WeakDecode", Tag: "mapstructure", ArgPos: 1}, + {Name: "github.com/mitchellh/mapstructure.WeakDecodeMetadata", Tag: "mapstructure", ArgPos: 1}, +} diff --git a/vendor/github.com/junk1tm/musttag/musttag.go b/vendor/github.com/junk1tm/musttag/musttag.go new file mode 100644 index 000000000..1254288f2 --- /dev/null +++ b/vendor/github.com/junk1tm/musttag/musttag.go @@ -0,0 +1,251 @@ +// Package musttag implements the musttag analyzer. +package musttag + +import ( + "flag" + "go/ast" + "go/token" + "go/types" + "path" + "path/filepath" + "reflect" + "strconv" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/go/types/typeutil" +) + +// 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. +} + +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. +func New(funcs ...Func) *analysis.Analyzer { + var flagFuncs []Func + return &analysis.Analyzer{ + Name: "musttag", + Doc: "enforce field tags in (un)marshaled structs", + Flags: flags(&flagFuncs), + Requires: []*analysis.Analyzer{inspect.Analyzer}, + Run: func(pass *analysis.Pass) (any, error) { + l := len(builtins) + len(funcs) + len(flagFuncs) + m := make(map[string]Func, l) + toMap := func(slice []Func) { + for _, fn := range slice { + m[fn.Name] = fn + } + } + toMap(builtins) + toMap(funcs) + toMap(flagFuncs) + return run(pass, m) + }, + } +} + +// 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 { + parts := strings.Split(s, ":") + if len(parts) != 3 || parts[0] == "" || parts[1] == "" { + return strconv.ErrSyntax + } + pos, err := strconv.Atoi(parts[2]) + if err != nil { + return err + } + *funcs = append(*funcs, Func{ + Name: parts[0], + Tag: parts[1], + ArgPos: pos, + }) + return nil + }) + return *fs +} + +// for tests only. +var ( + reportf = 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) + } + + // HACK(junk1tm): mainModulePackages() does not return packages from `testdata`, + // because it is ignored by the go tool, and thus, by the `go list` command. + // For tests to pass we need to add the packages with tests to the main module manually. + testPackages []string +) + +// run starts the analysis. +func run(pass *analysis.Pass, funcs map[string]Func) (any, error) { + moduleDir, modulePackages, err := mainModule() + if err != nil { + return nil, err + } + for _, pkg := range testPackages { + modulePackages[pkg] = struct{}{} + } + + walk := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + filter := []ast.Node{(*ast.CallExpr)(nil)} + + walk.Preorder(filter, func(n ast.Node) { + call, ok := n.(*ast.CallExpr) + if !ok { + return // not a function call. + } + + caller := typeutil.StaticCallee(pass.TypesInfo, call) + if caller == nil { + return // not a static call. + } + + fn, ok := funcs[caller.FullName()] + if !ok { + return // the function is not supported. + } + + if len(call.Args) <= fn.ArgPos { + return // TODO(junk1tm): return a proper error. + } + + 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() + } + + checker := checker{ + mainModule: modulePackages, + seenTypes: make(map[string]struct{}), + } + + t := pass.TypesInfo.TypeOf(arg) + st, ok := checker.parseStructType(t, initialPos) + if !ok { + return // not a struct argument. + } + + result, ok := checker.checkStructType(st, fn.Tag) + if ok { + return // nothing to report. + } + + p := pass.Fset.Position(call.Pos()) + p.Filename, _ = filepath.Rel(moduleDir, p.Filename) + reportf(pass, result, fn, p) + }) + + return nil, nil +} + +// 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 map[string]struct{} // do not check types outside of the main module; see issue #17. + seenTypes map[string]struct{} // prevent panic on recursive types; see issue #16. +} + +// parseStructType parses the given types.Type, returning the underlying struct type. +func (c *checker) parseStructType(t types.Type, pos token.Pos) (*structType, bool) { + for { + // unwrap pointers (if any) first. + ptr, ok := t.(*types.Pointer) + if !ok { + break + } + t = ptr.Elem() + } + + switch t := t.(type) { + case *types.Named: // a struct of the named type. + pkg := t.Obj().Pkg().Path() + if _, ok := c.mainModule[pkg]; !ok { + return nil, false + } + s, ok := t.Underlying().(*types.Struct) + if !ok { + return nil, false + } + return &structType{ + Struct: s, + Pos: t.Obj().Pos(), + Name: t.Obj().Name(), + }, true + + case *types.Struct: // an anonymous struct. + return &structType{ + Struct: t, + Pos: pos, + Name: "anonymous struct", + }, true + } + + 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{}{} + + for i := 0; i < st.NumFields(); i++ { + field := st.Field(i) + if !field.Exported() { + continue + } + + if _, ok := reflect.StructTag(st.Tag(i)).Lookup(tag); !ok { + // tag is not required for embedded types; see issue #12. + if !field.Embedded() { + return st, false + } + } + + nested, ok := c.parseStructType(field.Type(), st.Pos) // TODO(junk1tm): or field.Pos()? + if !ok { + continue + } + if _, ok := c.seenTypes[nested.String()]; ok { + continue + } + if result, ok := c.checkStructType(nested, tag); !ok { + return result, false + } + } + + return nil, true +} diff --git a/vendor/github.com/junk1tm/musttag/utils.go b/vendor/github.com/junk1tm/musttag/utils.go new file mode 100644 index 000000000..fecfc43b0 --- /dev/null +++ b/vendor/github.com/junk1tm/musttag/utils.go @@ -0,0 +1,40 @@ +package musttag + +import ( + "fmt" + "os/exec" + "strings" +) + +// mainModule returns the directory and the set of packages of the main module. +func mainModule() (dir string, packages map[string]struct{}, _ error) { + // https://pkg.go.dev/cmd/go#hdr-Package_lists_and_patterns + // > When using modules, "all" expands to all packages in the main module + // > and their dependencies, including dependencies needed by tests of any of those. + + // NOTE(junk1tm): the command may run out of file descriptors if go version <= 1.18, + // especially on macOS, which has the default soft limit set to 256 (ulimit -nS). + // Since go1.19 the limit is automatically increased to the maximum allowed value; + // see https://github.com/golang/go/issues/46279 for details. + cmd := [...]string{"go", "list", "-f={{if and (not .Standard) .Module.Main}}{{.ImportPath}}{{end}}", "all"} + + out, err := exec.Command(cmd[0], cmd[1:]...).Output() + if err != nil { + return "", nil, fmt.Errorf("running `go list all`: %w", err) + } + + list := strings.TrimSpace(string(out)) + packages = make(map[string]struct{}, len(list)) + for _, pkg := range strings.Split(list, "\n") { + packages[pkg] = struct{}{} + packages[pkg+"_test"] = struct{}{} // `*_test` packages belong to the main module, see issue #24. + } + + out, err = exec.Command("go", "list", "-m", "-f={{.Dir}}").Output() + if err != nil { + return "", nil, fmt.Errorf("running `go list -m`: %w", err) + } + + dir = strings.TrimSpace(string(out)) + return dir, packages, nil +} |
