github.com/karrick/go@v0.0.0-20170817181416-d5b0ec858b37/src/cmd/vet/structtag.go (about) 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 // This file contains the test for canonical struct tags. 6 7 package main 8 9 import ( 10 "errors" 11 "go/ast" 12 "go/token" 13 "reflect" 14 "strconv" 15 "strings" 16 ) 17 18 func init() { 19 register("structtags", 20 "check that struct field tags have canonical format and apply to exported fields as needed", 21 checkStructFieldTags, 22 structType) 23 } 24 25 // checkStructFieldTags checks all the field tags of a struct, including checking for duplicates. 26 func checkStructFieldTags(f *File, node ast.Node) { 27 var seen map[[2]string]token.Pos 28 for _, field := range node.(*ast.StructType).Fields.List { 29 checkCanonicalFieldTag(f, field, &seen) 30 } 31 } 32 33 var checkTagDups = []string{"json", "xml"} 34 35 // checkCanonicalFieldTag checks a single struct field tag. 36 func checkCanonicalFieldTag(f *File, field *ast.Field, seen *map[[2]string]token.Pos) { 37 if field.Tag == nil { 38 return 39 } 40 41 tag, err := strconv.Unquote(field.Tag.Value) 42 if err != nil { 43 f.Badf(field.Pos(), "unable to read struct tag %s", field.Tag.Value) 44 return 45 } 46 47 if err := validateStructTag(tag); err != nil { 48 raw, _ := strconv.Unquote(field.Tag.Value) // field.Tag.Value is known to be a quoted string 49 f.Badf(field.Pos(), "struct field tag %#q not compatible with reflect.StructTag.Get: %s", raw, err) 50 } 51 52 for _, key := range checkTagDups { 53 val := reflect.StructTag(tag).Get(key) 54 if val == "" || val == "-" || val[0] == ',' { 55 continue 56 } 57 if key == "xml" && len(field.Names) > 0 && field.Names[0].Name == "XMLName" { 58 // XMLName defines the XML element name of the struct being 59 // checked. That name cannot collide with element or attribute 60 // names defined on other fields of the struct. Vet does not have a 61 // check for untagged fields of type struct defining their own name 62 // by containing a field named XMLName; see issue 18256. 63 continue 64 } 65 if i := strings.Index(val, ","); i >= 0 { 66 if key == "xml" { 67 // Use a separate namespace for XML attributes. 68 for _, opt := range strings.Split(val[i:], ",") { 69 if opt == "attr" { 70 key += " attribute" // Key is part of the error message. 71 break 72 } 73 } 74 } 75 val = val[:i] 76 } 77 if *seen == nil { 78 *seen = map[[2]string]token.Pos{} 79 } 80 if pos, ok := (*seen)[[2]string{key, val}]; ok { 81 var name string 82 if len(field.Names) > 0 { 83 name = field.Names[0].Name 84 } else { 85 name = field.Type.(*ast.Ident).Name 86 } 87 f.Badf(field.Pos(), "struct field %s repeats %s tag %q also at %s", name, key, val, f.loc(pos)) 88 } else { 89 (*seen)[[2]string{key, val}] = field.Pos() 90 } 91 } 92 93 // Check for use of json or xml tags with unexported fields. 94 95 // Embedded struct. Nothing to do for now, but that 96 // may change, depending on what happens with issue 7363. 97 if len(field.Names) == 0 { 98 return 99 } 100 101 if field.Names[0].IsExported() { 102 return 103 } 104 105 for _, enc := range [...]string{"json", "xml"} { 106 if reflect.StructTag(tag).Get(enc) != "" { 107 f.Badf(field.Pos(), "struct field %s has %s tag but is not exported", field.Names[0].Name, enc) 108 return 109 } 110 } 111 } 112 113 var ( 114 errTagSyntax = errors.New("bad syntax for struct tag pair") 115 errTagKeySyntax = errors.New("bad syntax for struct tag key") 116 errTagValueSyntax = errors.New("bad syntax for struct tag value") 117 errTagSpace = errors.New("key:\"value\" pairs not separated by spaces") 118 ) 119 120 // validateStructTag parses the struct tag and returns an error if it is not 121 // in the canonical format, which is a space-separated list of key:"value" 122 // settings. The value may contain spaces. 123 func validateStructTag(tag string) error { 124 // This code is based on the StructTag.Get code in package reflect. 125 126 n := 0 127 for ; tag != ""; n++ { 128 if n > 0 && tag != "" && tag[0] != ' ' { 129 // More restrictive than reflect, but catches likely mistakes 130 // like `x:"foo",y:"bar"`, which parses as `x:"foo" ,y:"bar"` with second key ",y". 131 return errTagSpace 132 } 133 // Skip leading space. 134 i := 0 135 for i < len(tag) && tag[i] == ' ' { 136 i++ 137 } 138 tag = tag[i:] 139 if tag == "" { 140 break 141 } 142 143 // Scan to colon. A space, a quote or a control character is a syntax error. 144 // Strictly speaking, control chars include the range [0x7f, 0x9f], not just 145 // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters 146 // as it is simpler to inspect the tag's bytes than the tag's runes. 147 i = 0 148 for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { 149 i++ 150 } 151 if i == 0 { 152 return errTagKeySyntax 153 } 154 if i+1 >= len(tag) || tag[i] != ':' { 155 return errTagSyntax 156 } 157 if tag[i+1] != '"' { 158 return errTagValueSyntax 159 } 160 tag = tag[i+1:] 161 162 // Scan quoted string to find value. 163 i = 1 164 for i < len(tag) && tag[i] != '"' { 165 if tag[i] == '\\' { 166 i++ 167 } 168 i++ 169 } 170 if i >= len(tag) { 171 return errTagValueSyntax 172 } 173 qvalue := tag[:i+1] 174 tag = tag[i+1:] 175 176 if _, err := strconv.Unquote(qvalue); err != nil { 177 return errTagValueSyntax 178 } 179 } 180 return nil 181 }