pass.Report(Diagnostic{Pos: pos, Message: msg})
}
+// reportNodef is a helper function that reports a Diagnostic using the
+// range denoted by the AST node.
+//
+// WARNING: This is an experimental API and may change in the future.
+func (pass *Pass) reportNodef(node ast.Node, format string, args ...interface{}) {
+ msg := fmt.Sprintf(format, args...)
+ pass.Report(Diagnostic{Pos: node.Pos(), End: node.End(), Message: msg})
+}
+
func (pass *Pass) String() string {
return fmt.Sprintf("%s@%s", pass.Analyzer.Name, pass.Pkg.Path())
}
AFact() // dummy method to avoid type errors
}
-// A Diagnostic is a message associated with a source location.
+// A Diagnostic is a message associated with a source location or range.
//
// An Analyzer may return a variety of diagnostics; the optional Category,
// which should be a constant, may be used to classify them.
// It is primarily intended to make it easy to look up documentation.
+//
+// If End is provided, the diagnostic is specified to apply to the range between
+// Pos and End.
type Diagnostic struct {
Pos token.Pos
- Category string // optional
+ End token.Pos // optional
+ Category string // optional
Message string
}
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
styp := pass.TypesInfo.Types[n.(*ast.StructType)].Type.(*types.Struct)
- var seen map[[2]string]token.Pos
+ var seen namesSeen
for i := 0; i < styp.NumFields(); i++ {
field := styp.Field(i)
tag := styp.Tag(i)
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 *map[[2]string]token.Pos) {
+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.
}
for _, key := range checkTagDups {
- checkTagDuplicates(pass, tag, key, field, field, seen)
+ checkTagDuplicates(pass, tag, key, field, field, seen, 1)
}
if err := validateStructTag(tag); err != nil {
// 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 *map[[2]string]token.Pos) {
+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() {
- 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)
+ 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)
}
- // Ignored if the field isn't anonymous.
return
}
if key == "xml" && field.Name() == "XMLName" {
}
val = val[:i]
}
- if *seen == nil {
- *seen = map[[2]string]token.Pos{}
- }
- if pos, ok := (*seen)[[2]string{key, val}]; ok {
+ if pos, ok := seen.Get(key, val, level); ok {
alsoPos := pass.Fset.Position(pos)
alsoPos.Column = 0
pass.Reportf(nearest.Pos(), "struct field %s repeats %s tag %q also at %s", field.Name(), key, val, alsoPos)
} else {
- (*seen)[[2]string{key, val}] = field.Pos()
+ seen.Set(key, val, level, field.Pos())
}
}