github.com/euank/go@v0.0.0-20160829210321-495514729181/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 "reflect" 13 "strconv" 14 ) 15 16 func init() { 17 register("structtags", 18 "check that struct field tags have canonical format and apply to exported fields as needed", 19 checkCanonicalFieldTag, 20 field) 21 } 22 23 // checkCanonicalFieldTag checks a struct field tag. 24 func checkCanonicalFieldTag(f *File, node ast.Node) { 25 field := node.(*ast.Field) 26 if field.Tag == nil { 27 return 28 } 29 30 tag, err := strconv.Unquote(field.Tag.Value) 31 if err != nil { 32 f.Badf(field.Pos(), "unable to read struct tag %s", field.Tag.Value) 33 return 34 } 35 36 if err := validateStructTag(tag); err != nil { 37 raw, _ := strconv.Unquote(field.Tag.Value) // field.Tag.Value is known to be a quoted string 38 f.Badf(field.Pos(), "struct field tag %q not compatible with reflect.StructTag.Get: %s", raw, err) 39 } 40 41 // Check for use of json or xml tags with unexported fields. 42 43 // Embedded struct. Nothing to do for now, but that 44 // may change, depending on what happens with issue 7363. 45 if len(field.Names) == 0 { 46 return 47 } 48 49 if field.Names[0].IsExported() { 50 return 51 } 52 53 st := reflect.StructTag(tag) 54 for _, enc := range [...]string{"json", "xml"} { 55 if st.Get(enc) != "" { 56 f.Badf(field.Pos(), "struct field %s has %s tag but is not exported", field.Names[0].Name, enc) 57 return 58 } 59 } 60 } 61 62 var ( 63 errTagSyntax = errors.New("bad syntax for struct tag pair") 64 errTagKeySyntax = errors.New("bad syntax for struct tag key") 65 errTagValueSyntax = errors.New("bad syntax for struct tag value") 66 ) 67 68 // validateStructTag parses the struct tag and returns an error if it is not 69 // in the canonical format, which is a space-separated list of key:"value" 70 // settings. The value may contain spaces. 71 func validateStructTag(tag string) error { 72 // This code is based on the StructTag.Get code in package reflect. 73 74 for tag != "" { 75 // Skip leading space. 76 i := 0 77 for i < len(tag) && tag[i] == ' ' { 78 i++ 79 } 80 tag = tag[i:] 81 if tag == "" { 82 break 83 } 84 85 // Scan to colon. A space, a quote or a control character is a syntax error. 86 // Strictly speaking, control chars include the range [0x7f, 0x9f], not just 87 // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters 88 // as it is simpler to inspect the tag's bytes than the tag's runes. 89 i = 0 90 for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { 91 i++ 92 } 93 if i == 0 { 94 return errTagKeySyntax 95 } 96 if i+1 >= len(tag) || tag[i] != ':' { 97 return errTagSyntax 98 } 99 if tag[i+1] != '"' { 100 return errTagValueSyntax 101 } 102 tag = tag[i+1:] 103 104 // Scan quoted string to find value. 105 i = 1 106 for i < len(tag) && tag[i] != '"' { 107 if tag[i] == '\\' { 108 i++ 109 } 110 i++ 111 } 112 if i >= len(tag) { 113 return errTagValueSyntax 114 } 115 qvalue := tag[:i+1] 116 tag = tag[i+1:] 117 118 if _, err := strconv.Unquote(qvalue); err != nil { 119 return errTagValueSyntax 120 } 121 } 122 return nil 123 }