aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/junk1tm
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2023-02-22 22:16:50 +0100
committerTaras Madan <tarasmadan@google.com>2023-02-24 12:47:23 +0100
commit4165372ec8fd142475a4e35fd0cf4f8042132208 (patch)
tree21cd62211b4dd80bee469054c5b65db77342333c /vendor/github.com/junk1tm
parent2b3ed821a493b8936c8bacfa6f8b4f1c90a00855 (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.yml23
-rw-r--r--vendor/github.com/junk1tm/musttag/.goreleaser.yml30
-rw-r--r--vendor/github.com/junk1tm/musttag/LICENSE21
-rw-r--r--vendor/github.com/junk1tm/musttag/README.md93
-rw-r--r--vendor/github.com/junk1tm/musttag/builtins.go40
-rw-r--r--vendor/github.com/junk1tm/musttag/musttag.go251
-rw-r--r--vendor/github.com/junk1tm/musttag/utils.go40
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
+
+[![ci](https://github.com/junk1tm/musttag/actions/workflows/go.yml/badge.svg)](https://github.com/junk1tm/musttag/actions/workflows/go.yml)
+[![docs](https://pkg.go.dev/badge/github.com/junk1tm/musttag.svg)](https://pkg.go.dev/github.com/junk1tm/musttag)
+[![report](https://goreportcard.com/badge/github.com/junk1tm/musttag)](https://goreportcard.com/report/github.com/junk1tm/musttag)
+[![codecov](https://codecov.io/gh/junk1tm/musttag/branch/main/graph/badge.svg)](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
+}