Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201028153306-37f0764111ff / go / analysis / passes / structtag / structtag.go
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201028153306-37f0764111ff/go/analysis/passes/structtag/structtag.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201028153306-37f0764111ff/go/analysis/passes/structtag/structtag.go
new file mode 100644 (file)
index 0000000..f0b1505
--- /dev/null
@@ -0,0 +1,313 @@
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package structtag defines an Analyzer that checks struct field tags
+// are well formed.
+package structtag
+
+import (
+       "errors"
+       "go/ast"
+       "go/token"
+       "go/types"
+       "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"
+)
+
+const Doc = `check that struct field tags conform to reflect.StructTag.Get
+
+Also report certain struct tags (json, xml) used with unexported fields.`
+
+var Analyzer = &analysis.Analyzer{
+       Name:             "structtag",
+       Doc:              Doc,
+       Requires:         []*analysis.Analyzer{inspect.Analyzer},
+       RunDespiteErrors: true,
+       Run:              run,
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+       inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+
+       nodeFilter := []ast.Node{
+               (*ast.StructType)(nil),
+       }
+       inspect.Preorder(nodeFilter, func(n ast.Node) {
+               styp, ok := pass.TypesInfo.Types[n.(*ast.StructType)].Type.(*types.Struct)
+               // Type information may be incomplete.
+               if !ok {
+                       return
+               }
+               var seen namesSeen
+               for i := 0; i < styp.NumFields(); i++ {
+                       field := styp.Field(i)
+                       tag := styp.Tag(i)
+                       checkCanonicalFieldTag(pass, field, tag, &seen)
+               }
+       })
+       return nil, nil
+}
+
+// namesSeen keeps track of encoding tags by their key, name, and nested level
+// from the initial struct. The level is taken into account because equal
+// encoding key names only conflict when at the same level; otherwise, the lower
+// level shadows the higher level.
+type namesSeen map[uniqueName]token.Pos
+
+type uniqueName struct {
+       key   string // "xml" or "json"
+       name  string // the encoding name
+       level int    // anonymous struct nesting level
+}
+
+func (s *namesSeen) Get(key, name string, level int) (token.Pos, bool) {
+       if *s == nil {
+               *s = make(map[uniqueName]token.Pos)
+       }
+       pos, ok := (*s)[uniqueName{key, name, level}]
+       return pos, ok
+}
+
+func (s *namesSeen) Set(key, name string, level int, pos token.Pos) {
+       if *s == nil {
+               *s = make(map[uniqueName]token.Pos)
+       }
+       (*s)[uniqueName{key, name, level}] = pos
+}
+
+var checkTagDups = []string{"json", "xml"}
+var checkTagSpaces = map[string]bool{"json": true, "xml": true, "asn1": true}
+
+// checkCanonicalFieldTag checks a single struct field tag.
+func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, seen *namesSeen) {
+       switch pass.Pkg.Path() {
+       case "encoding/json", "encoding/xml":
+               // These packages know how to use their own APIs.
+               // Sometimes they are testing what happens to incorrect programs.
+               return
+       }
+
+       for _, key := range checkTagDups {
+               checkTagDuplicates(pass, tag, key, field, field, seen, 1)
+       }
+
+       if err := validateStructTag(tag); err != nil {
+               pass.Reportf(field.Pos(), "struct field tag %#q not compatible with reflect.StructTag.Get: %s", tag, err)
+       }
+
+       // Check for use of json or xml tags with unexported fields.
+
+       // Embedded struct. Nothing to do for now, but that
+       // may change, depending on what happens with issue 7363.
+       // TODO(adonovan): investigate, now that that issue is fixed.
+       if field.Anonymous() {
+               return
+       }
+
+       if field.Exported() {
+               return
+       }
+
+       for _, enc := range [...]string{"json", "xml"} {
+               switch reflect.StructTag(tag).Get(enc) {
+               // Ignore warning if the field not exported and the tag is marked as
+               // ignored.
+               case "", "-":
+               default:
+                       pass.Reportf(field.Pos(), "struct field %s has %s tag but is not exported", field.Name(), enc)
+                       return
+               }
+       }
+}
+
+// checkTagDuplicates checks a single struct field tag to see if any tags are
+// duplicated. nearest is the field that's closest to the field being checked,
+// while still being part of the top-level struct type.
+func checkTagDuplicates(pass *analysis.Pass, tag, key string, nearest, field *types.Var, seen *namesSeen, level int) {
+       val := reflect.StructTag(tag).Get(key)
+       if val == "-" {
+               // Ignored, even if the field is anonymous.
+               return
+       }
+       if val == "" || val[0] == ',' {
+               if !field.Anonymous() {
+                       // Ignored if the field isn't anonymous.
+                       return
+               }
+               typ, ok := field.Type().Underlying().(*types.Struct)
+               if !ok {
+                       return
+               }
+               for i := 0; i < typ.NumFields(); i++ {
+                       field := typ.Field(i)
+                       if !field.Exported() {
+                               continue
+                       }
+                       tag := typ.Tag(i)
+                       checkTagDuplicates(pass, tag, key, nearest, field, seen, level+1)
+               }
+               return
+       }
+       if key == "xml" && field.Name() == "XMLName" {
+               // XMLName defines the XML element name of the struct being
+               // checked. That name cannot collide with element or attribute
+               // names defined on other fields of the struct. Vet does not have a
+               // check for untagged fields of type struct defining their own name
+               // by containing a field named XMLName; see issue 18256.
+               return
+       }
+       if i := strings.Index(val, ","); i >= 0 {
+               if key == "xml" {
+                       // Use a separate namespace for XML attributes.
+                       for _, opt := range strings.Split(val[i:], ",") {
+                               if opt == "attr" {
+                                       key += " attribute" // Key is part of the error message.
+                                       break
+                               }
+                       }
+               }
+               val = val[:i]
+       }
+       if pos, ok := seen.Get(key, val, level); ok {
+               alsoPos := pass.Fset.Position(pos)
+               alsoPos.Column = 0
+
+               // Make the "also at" position relative to the current position,
+               // to ensure that all warnings are unambiguous and correct. For
+               // example, via anonymous struct fields, it's possible for the
+               // two fields to be in different packages and directories.
+               thisPos := pass.Fset.Position(field.Pos())
+               rel, err := filepath.Rel(filepath.Dir(thisPos.Filename), alsoPos.Filename)
+               if err != nil {
+                       // Possibly because the paths are relative; leave the
+                       // filename alone.
+               } else {
+                       alsoPos.Filename = rel
+               }
+
+               pass.Reportf(nearest.Pos(), "struct field %s repeats %s tag %q also at %s", field.Name(), key, val, alsoPos)
+       } else {
+               seen.Set(key, val, level, field.Pos())
+       }
+}
+
+var (
+       errTagSyntax      = errors.New("bad syntax for struct tag pair")
+       errTagKeySyntax   = errors.New("bad syntax for struct tag key")
+       errTagValueSyntax = errors.New("bad syntax for struct tag value")
+       errTagValueSpace  = errors.New("suspicious space in struct tag value")
+       errTagSpace       = errors.New("key:\"value\" pairs not separated by spaces")
+)
+
+// validateStructTag parses the struct tag and returns an error if it is not
+// in the canonical format, which is a space-separated list of key:"value"
+// settings. The value may contain spaces.
+func validateStructTag(tag string) error {
+       // This code is based on the StructTag.Get code in package reflect.
+
+       n := 0
+       for ; tag != ""; n++ {
+               if n > 0 && tag != "" && tag[0] != ' ' {
+                       // More restrictive than reflect, but catches likely mistakes
+                       // like `x:"foo",y:"bar"`, which parses as `x:"foo" ,y:"bar"` with second key ",y".
+                       return errTagSpace
+               }
+               // Skip leading space.
+               i := 0
+               for i < len(tag) && tag[i] == ' ' {
+                       i++
+               }
+               tag = tag[i:]
+               if tag == "" {
+                       break
+               }
+
+               // Scan to colon. A space, a quote or a control character is a syntax error.
+               // Strictly speaking, control chars include the range [0x7f, 0x9f], not just
+               // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters
+               // as it is simpler to inspect the tag's bytes than the tag's runes.
+               i = 0
+               for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f {
+                       i++
+               }
+               if i == 0 {
+                       return errTagKeySyntax
+               }
+               if i+1 >= len(tag) || tag[i] != ':' {
+                       return errTagSyntax
+               }
+               if tag[i+1] != '"' {
+                       return errTagValueSyntax
+               }
+               key := tag[:i]
+               tag = tag[i+1:]
+
+               // Scan quoted string to find value.
+               i = 1
+               for i < len(tag) && tag[i] != '"' {
+                       if tag[i] == '\\' {
+                               i++
+                       }
+                       i++
+               }
+               if i >= len(tag) {
+                       return errTagValueSyntax
+               }
+               qvalue := tag[:i+1]
+               tag = tag[i+1:]
+
+               value, err := strconv.Unquote(qvalue)
+               if err != nil {
+                       return errTagValueSyntax
+               }
+
+               if !checkTagSpaces[key] {
+                       continue
+               }
+
+               switch key {
+               case "xml":
+                       // If the first or last character in the XML tag is a space, it is
+                       // suspicious.
+                       if strings.Trim(value, " ") != value {
+                               return errTagValueSpace
+                       }
+
+                       // If there are multiple spaces, they are suspicious.
+                       if strings.Count(value, " ") > 1 {
+                               return errTagValueSpace
+                       }
+
+                       // If there is no comma, skip the rest of the checks.
+                       comma := strings.IndexRune(value, ',')
+                       if comma < 0 {
+                               continue
+                       }
+
+                       // If the character before a comma is a space, this is suspicious.
+                       if comma > 0 && value[comma-1] == ' ' {
+                               return errTagValueSpace
+                       }
+                       value = value[comma+1:]
+               case "json":
+                       // JSON allows using spaces in the name, so skip it.
+                       comma := strings.IndexRune(value, ',')
+                       if comma < 0 {
+                               continue
+                       }
+                       value = value[comma+1:]
+               }
+
+               if strings.IndexByte(value, ' ') >= 0 {
+                       return errTagValueSpace
+               }
+       }
+       return nil
+}