Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / go / analysis / passes / structtag / structtag.go
1 // Copyright 2010 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Package structtag defines an Analyzer that checks struct field tags
6 // are well formed.
7 package structtag
8
9 import (
10         "errors"
11         "go/ast"
12         "go/token"
13         "go/types"
14         "path/filepath"
15         "reflect"
16         "strconv"
17         "strings"
18
19         "golang.org/x/tools/go/analysis"
20         "golang.org/x/tools/go/analysis/passes/inspect"
21         "golang.org/x/tools/go/ast/inspector"
22 )
23
24 const Doc = `check that struct field tags conform to reflect.StructTag.Get
25
26 Also report certain struct tags (json, xml) used with unexported fields.`
27
28 var Analyzer = &analysis.Analyzer{
29         Name:             "structtag",
30         Doc:              Doc,
31         Requires:         []*analysis.Analyzer{inspect.Analyzer},
32         RunDespiteErrors: true,
33         Run:              run,
34 }
35
36 func run(pass *analysis.Pass) (interface{}, error) {
37         inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
38
39         nodeFilter := []ast.Node{
40                 (*ast.StructType)(nil),
41         }
42         inspect.Preorder(nodeFilter, func(n ast.Node) {
43                 styp, ok := pass.TypesInfo.Types[n.(*ast.StructType)].Type.(*types.Struct)
44                 // Type information may be incomplete.
45                 if !ok {
46                         return
47                 }
48                 var seen namesSeen
49                 for i := 0; i < styp.NumFields(); i++ {
50                         field := styp.Field(i)
51                         tag := styp.Tag(i)
52                         checkCanonicalFieldTag(pass, field, tag, &seen)
53                 }
54         })
55         return nil, nil
56 }
57
58 // namesSeen keeps track of encoding tags by their key, name, and nested level
59 // from the initial struct. The level is taken into account because equal
60 // encoding key names only conflict when at the same level; otherwise, the lower
61 // level shadows the higher level.
62 type namesSeen map[uniqueName]token.Pos
63
64 type uniqueName struct {
65         key   string // "xml" or "json"
66         name  string // the encoding name
67         level int    // anonymous struct nesting level
68 }
69
70 func (s *namesSeen) Get(key, name string, level int) (token.Pos, bool) {
71         if *s == nil {
72                 *s = make(map[uniqueName]token.Pos)
73         }
74         pos, ok := (*s)[uniqueName{key, name, level}]
75         return pos, ok
76 }
77
78 func (s *namesSeen) Set(key, name string, level int, pos token.Pos) {
79         if *s == nil {
80                 *s = make(map[uniqueName]token.Pos)
81         }
82         (*s)[uniqueName{key, name, level}] = pos
83 }
84
85 var checkTagDups = []string{"json", "xml"}
86 var checkTagSpaces = map[string]bool{"json": true, "xml": true, "asn1": true}
87
88 // checkCanonicalFieldTag checks a single struct field tag.
89 func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, seen *namesSeen) {
90         switch pass.Pkg.Path() {
91         case "encoding/json", "encoding/xml":
92                 // These packages know how to use their own APIs.
93                 // Sometimes they are testing what happens to incorrect programs.
94                 return
95         }
96
97         for _, key := range checkTagDups {
98                 checkTagDuplicates(pass, tag, key, field, field, seen, 1)
99         }
100
101         if err := validateStructTag(tag); err != nil {
102                 pass.Reportf(field.Pos(), "struct field tag %#q not compatible with reflect.StructTag.Get: %s", tag, err)
103         }
104
105         // Check for use of json or xml tags with unexported fields.
106
107         // Embedded struct. Nothing to do for now, but that
108         // may change, depending on what happens with issue 7363.
109         // TODO(adonovan): investigate, now that that issue is fixed.
110         if field.Anonymous() {
111                 return
112         }
113
114         if field.Exported() {
115                 return
116         }
117
118         for _, enc := range [...]string{"json", "xml"} {
119                 switch reflect.StructTag(tag).Get(enc) {
120                 // Ignore warning if the field not exported and the tag is marked as
121                 // ignored.
122                 case "", "-":
123                 default:
124                         pass.Reportf(field.Pos(), "struct field %s has %s tag but is not exported", field.Name(), enc)
125                         return
126                 }
127         }
128 }
129
130 // checkTagDuplicates checks a single struct field tag to see if any tags are
131 // duplicated. nearest is the field that's closest to the field being checked,
132 // while still being part of the top-level struct type.
133 func checkTagDuplicates(pass *analysis.Pass, tag, key string, nearest, field *types.Var, seen *namesSeen, level int) {
134         val := reflect.StructTag(tag).Get(key)
135         if val == "-" {
136                 // Ignored, even if the field is anonymous.
137                 return
138         }
139         if val == "" || val[0] == ',' {
140                 if !field.Anonymous() {
141                         // Ignored if the field isn't anonymous.
142                         return
143                 }
144                 typ, ok := field.Type().Underlying().(*types.Struct)
145                 if !ok {
146                         return
147                 }
148                 for i := 0; i < typ.NumFields(); i++ {
149                         field := typ.Field(i)
150                         if !field.Exported() {
151                                 continue
152                         }
153                         tag := typ.Tag(i)
154                         checkTagDuplicates(pass, tag, key, nearest, field, seen, level+1)
155                 }
156                 return
157         }
158         if key == "xml" && field.Name() == "XMLName" {
159                 // XMLName defines the XML element name of the struct being
160                 // checked. That name cannot collide with element or attribute
161                 // names defined on other fields of the struct. Vet does not have a
162                 // check for untagged fields of type struct defining their own name
163                 // by containing a field named XMLName; see issue 18256.
164                 return
165         }
166         if i := strings.Index(val, ","); i >= 0 {
167                 if key == "xml" {
168                         // Use a separate namespace for XML attributes.
169                         for _, opt := range strings.Split(val[i:], ",") {
170                                 if opt == "attr" {
171                                         key += " attribute" // Key is part of the error message.
172                                         break
173                                 }
174                         }
175                 }
176                 val = val[:i]
177         }
178         if pos, ok := seen.Get(key, val, level); ok {
179                 alsoPos := pass.Fset.Position(pos)
180                 alsoPos.Column = 0
181
182                 // Make the "also at" position relative to the current position,
183                 // to ensure that all warnings are unambiguous and correct. For
184                 // example, via anonymous struct fields, it's possible for the
185                 // two fields to be in different packages and directories.
186                 thisPos := pass.Fset.Position(field.Pos())
187                 rel, err := filepath.Rel(filepath.Dir(thisPos.Filename), alsoPos.Filename)
188                 if err != nil {
189                         // Possibly because the paths are relative; leave the
190                         // filename alone.
191                 } else {
192                         alsoPos.Filename = rel
193                 }
194
195                 pass.Reportf(nearest.Pos(), "struct field %s repeats %s tag %q also at %s", field.Name(), key, val, alsoPos)
196         } else {
197                 seen.Set(key, val, level, field.Pos())
198         }
199 }
200
201 var (
202         errTagSyntax      = errors.New("bad syntax for struct tag pair")
203         errTagKeySyntax   = errors.New("bad syntax for struct tag key")
204         errTagValueSyntax = errors.New("bad syntax for struct tag value")
205         errTagValueSpace  = errors.New("suspicious space in struct tag value")
206         errTagSpace       = errors.New("key:\"value\" pairs not separated by spaces")
207 )
208
209 // validateStructTag parses the struct tag and returns an error if it is not
210 // in the canonical format, which is a space-separated list of key:"value"
211 // settings. The value may contain spaces.
212 func validateStructTag(tag string) error {
213         // This code is based on the StructTag.Get code in package reflect.
214
215         n := 0
216         for ; tag != ""; n++ {
217                 if n > 0 && tag != "" && tag[0] != ' ' {
218                         // More restrictive than reflect, but catches likely mistakes
219                         // like `x:"foo",y:"bar"`, which parses as `x:"foo" ,y:"bar"` with second key ",y".
220                         return errTagSpace
221                 }
222                 // Skip leading space.
223                 i := 0
224                 for i < len(tag) && tag[i] == ' ' {
225                         i++
226                 }
227                 tag = tag[i:]
228                 if tag == "" {
229                         break
230                 }
231
232                 // Scan to colon. A space, a quote or a control character is a syntax error.
233                 // Strictly speaking, control chars include the range [0x7f, 0x9f], not just
234                 // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters
235                 // as it is simpler to inspect the tag's bytes than the tag's runes.
236                 i = 0
237                 for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f {
238                         i++
239                 }
240                 if i == 0 {
241                         return errTagKeySyntax
242                 }
243                 if i+1 >= len(tag) || tag[i] != ':' {
244                         return errTagSyntax
245                 }
246                 if tag[i+1] != '"' {
247                         return errTagValueSyntax
248                 }
249                 key := tag[:i]
250                 tag = tag[i+1:]
251
252                 // Scan quoted string to find value.
253                 i = 1
254                 for i < len(tag) && tag[i] != '"' {
255                         if tag[i] == '\\' {
256                                 i++
257                         }
258                         i++
259                 }
260                 if i >= len(tag) {
261                         return errTagValueSyntax
262                 }
263                 qvalue := tag[:i+1]
264                 tag = tag[i+1:]
265
266                 value, err := strconv.Unquote(qvalue)
267                 if err != nil {
268                         return errTagValueSyntax
269                 }
270
271                 if !checkTagSpaces[key] {
272                         continue
273                 }
274
275                 switch key {
276                 case "xml":
277                         // If the first or last character in the XML tag is a space, it is
278                         // suspicious.
279                         if strings.Trim(value, " ") != value {
280                                 return errTagValueSpace
281                         }
282
283                         // If there are multiple spaces, they are suspicious.
284                         if strings.Count(value, " ") > 1 {
285                                 return errTagValueSpace
286                         }
287
288                         // If there is no comma, skip the rest of the checks.
289                         comma := strings.IndexRune(value, ',')
290                         if comma < 0 {
291                                 continue
292                         }
293
294                         // If the character before a comma is a space, this is suspicious.
295                         if comma > 0 && value[comma-1] == ' ' {
296                                 return errTagValueSpace
297                         }
298                         value = value[comma+1:]
299                 case "json":
300                         // JSON allows using spaces in the name, so skip it.
301                         comma := strings.IndexRune(value, ',')
302                         if comma < 0 {
303                                 continue
304                         }
305                         value = value[comma+1:]
306                 }
307
308                 if strings.IndexByte(value, ' ') >= 0 {
309                         return errTagValueSpace
310                 }
311         }
312         return nil
313 }