Commit 4940a837 authored by Alex Browne's avatar Alex Browne Committed by Russ Cox

cmd/vet: check for duplicate json, xml struct field tags

It is easy to make the mistake of duplicating json struct field
tags especially when copy/pasting. This commit causes go vet to
report the mistake. Only field tags in the same struct type are
considered, because that is the only case which is undoubtedly an
error.

Fixes #12791.

Change-Id: I4130e4c04b177694cc0daf8f1acaf0751d4f062b
Reviewed-on: https://go-review.googlesource.com/16704
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarRuss Cox <rsc@golang.org>
parent ab019da7
...@@ -133,13 +133,13 @@ var ( ...@@ -133,13 +133,13 @@ var (
callExpr *ast.CallExpr callExpr *ast.CallExpr
compositeLit *ast.CompositeLit compositeLit *ast.CompositeLit
exprStmt *ast.ExprStmt exprStmt *ast.ExprStmt
field *ast.Field
funcDecl *ast.FuncDecl funcDecl *ast.FuncDecl
funcLit *ast.FuncLit funcLit *ast.FuncLit
genDecl *ast.GenDecl genDecl *ast.GenDecl
interfaceType *ast.InterfaceType interfaceType *ast.InterfaceType
rangeStmt *ast.RangeStmt rangeStmt *ast.RangeStmt
returnStmt *ast.ReturnStmt returnStmt *ast.ReturnStmt
structType *ast.StructType
// checkers is a two-level map. // checkers is a two-level map.
// The outer level is keyed by a nil pointer, one of the AST vars above. // The outer level is keyed by a nil pointer, one of the AST vars above.
...@@ -478,8 +478,6 @@ func (f *File) Visit(node ast.Node) ast.Visitor { ...@@ -478,8 +478,6 @@ func (f *File) Visit(node ast.Node) ast.Visitor {
key = compositeLit key = compositeLit
case *ast.ExprStmt: case *ast.ExprStmt:
key = exprStmt key = exprStmt
case *ast.Field:
key = field
case *ast.FuncDecl: case *ast.FuncDecl:
key = funcDecl key = funcDecl
case *ast.FuncLit: case *ast.FuncLit:
...@@ -492,6 +490,8 @@ func (f *File) Visit(node ast.Node) ast.Visitor { ...@@ -492,6 +490,8 @@ func (f *File) Visit(node ast.Node) ast.Visitor {
key = rangeStmt key = rangeStmt
case *ast.ReturnStmt: case *ast.ReturnStmt:
key = returnStmt key = returnStmt
case *ast.StructType:
key = structType
} }
for _, fn := range f.checkers[key] { for _, fn := range f.checkers[key] {
fn(f, node) fn(f, node)
......
...@@ -9,20 +9,31 @@ package main ...@@ -9,20 +9,31 @@ package main
import ( import (
"errors" "errors"
"go/ast" "go/ast"
"go/token"
"reflect" "reflect"
"strconv" "strconv"
"strings"
) )
func init() { func init() {
register("structtags", register("structtags",
"check that struct field tags have canonical format and apply to exported fields as needed", "check that struct field tags have canonical format and apply to exported fields as needed",
checkCanonicalFieldTag, checkStructFieldTags,
field) structType)
} }
// checkCanonicalFieldTag checks a struct field tag. // checkStructFieldTags checks all the field tags of a struct, including checking for duplicates.
func checkCanonicalFieldTag(f *File, node ast.Node) { func checkStructFieldTags(f *File, node ast.Node) {
field := node.(*ast.Field) var seen map[[2]string]token.Pos
for _, field := range node.(*ast.StructType).Fields.List {
checkCanonicalFieldTag(f, field, &seen)
}
}
var checkTagDups = []string{"json", "xml"}
// checkCanonicalFieldTag checks a single struct field tag.
func checkCanonicalFieldTag(f *File, field *ast.Field, seen *map[[2]string]token.Pos) {
if field.Tag == nil { if field.Tag == nil {
return return
} }
...@@ -38,6 +49,24 @@ func checkCanonicalFieldTag(f *File, node ast.Node) { ...@@ -38,6 +49,24 @@ func checkCanonicalFieldTag(f *File, node ast.Node) {
f.Badf(field.Pos(), "struct field tag %q not compatible with reflect.StructTag.Get: %s", raw, err) f.Badf(field.Pos(), "struct field tag %q not compatible with reflect.StructTag.Get: %s", raw, err)
} }
for _, key := range checkTagDups {
val := reflect.StructTag(tag).Get(key)
if val == "" || val == "-" || val[0] == ',' {
continue
}
if i := strings.Index(val, ","); i >= 0 {
val = val[:i]
}
if *seen == nil {
*seen = map[[2]string]token.Pos{}
}
if pos, ok := (*seen)[[2]string{key, val}]; ok {
f.Badf(field.Pos(), "struct field %s repeats %s tag %q also at %s", field.Names[0].Name, key, val, f.loc(pos))
} else {
(*seen)[[2]string{key, val}] = field.Pos()
}
}
// Check for use of json or xml tags with unexported fields. // Check for use of json or xml tags with unexported fields.
// Embedded struct. Nothing to do for now, but that // Embedded struct. Nothing to do for now, but that
...@@ -50,9 +79,8 @@ func checkCanonicalFieldTag(f *File, node ast.Node) { ...@@ -50,9 +79,8 @@ func checkCanonicalFieldTag(f *File, node ast.Node) {
return return
} }
st := reflect.StructTag(tag)
for _, enc := range [...]string{"json", "xml"} { for _, enc := range [...]string{"json", "xml"} {
if st.Get(enc) != "" { if reflect.StructTag(tag).Get(enc) != "" {
f.Badf(field.Pos(), "struct field %s has %s tag but is not exported", field.Names[0].Name, enc) f.Badf(field.Pos(), "struct field %s has %s tag but is not exported", field.Names[0].Name, enc)
return return
} }
......
...@@ -34,3 +34,31 @@ type JSONEmbeddedField struct { ...@@ -34,3 +34,31 @@ type JSONEmbeddedField struct {
UnexportedEncodingTagTest `is:"embedded"` UnexportedEncodingTagTest `is:"embedded"`
unexp `is:"embedded,notexported" json:"unexp"` // OK for now, see issue 7363 unexp `is:"embedded,notexported" json:"unexp"` // OK for now, see issue 7363
} }
type DuplicateJSONFields struct {
JSON int `json:"a"`
DuplicateJSON int `json:"a"` // ERROR "struct field DuplicateJSON repeats json tag .a. also at testdata/structtag.go:39"
IgnoredJSON int `json:"-"`
OtherIgnoredJSON int `json:"-"`
OmitJSON int `json:",omitempty"`
OtherOmitJSON int `json:",omitempty"`
DuplicateOmitJSON int `json:"a,omitempty"` // ERROR "struct field DuplicateOmitJSON repeats json tag .a. also at testdata/structtag.go:39"
NonJSON int `foo:"a"`
DuplicateNonJSON int `foo:"a"`
Embedded struct {
DuplicateJSON int `json:"a"` // OK because its not in the same struct type
}
XML int `xml:"a"`
DuplicateXML int `xml:"a"` // ERROR "struct field DuplicateXML repeats xml tag .a. also at testdata/structtag.go:52"
IgnoredXML int `xml:"-"`
OtherIgnoredXML int `xml:"-"`
OmitXML int `xml:",omitempty"`
OtherOmitXML int `xml:",omitempty"`
DuplicateOmitXML int `xml:"a,omitempty"` // ERROR "struct field DuplicateOmitXML repeats xml tag .a. also at testdata/structtag.go:52"
NonXML int `foo:"a"`
DuplicateNonXML int `foo:"a"`
Embedded struct {
DuplicateXML int `xml:"a"` // OK because its not in the same struct type
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment