github.com/panjjo/go@v0.0.0-20161104043856-d62b31386338/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 i := strings.Index(val, ","); i >= 0 {
    58  			val = val[:i]
    59  		}
    60  		if *seen == nil {
    61  			*seen = map[[2]string]token.Pos{}
    62  		}
    63  		if pos, ok := (*seen)[[2]string{key, val}]; ok {
    64  			f.Badf(field.Pos(), "struct field %s repeats %s tag %q also at %s", field.Names[0].Name, key, val, f.loc(pos))
    65  		} else {
    66  			(*seen)[[2]string{key, val}] = field.Pos()
    67  		}
    68  	}
    69  
    70  	// Check for use of json or xml tags with unexported fields.
    71  
    72  	// Embedded struct. Nothing to do for now, but that
    73  	// may change, depending on what happens with issue 7363.
    74  	if len(field.Names) == 0 {
    75  		return
    76  	}
    77  
    78  	if field.Names[0].IsExported() {
    79  		return
    80  	}
    81  
    82  	for _, enc := range [...]string{"json", "xml"} {
    83  		if reflect.StructTag(tag).Get(enc) != "" {
    84  			f.Badf(field.Pos(), "struct field %s has %s tag but is not exported", field.Names[0].Name, enc)
    85  			return
    86  		}
    87  	}
    88  }
    89  
    90  var (
    91  	errTagSyntax      = errors.New("bad syntax for struct tag pair")
    92  	errTagKeySyntax   = errors.New("bad syntax for struct tag key")
    93  	errTagValueSyntax = errors.New("bad syntax for struct tag value")
    94  	errTagSpace       = errors.New("key:\"value\" pairs not separated by spaces")
    95  )
    96  
    97  // validateStructTag parses the struct tag and returns an error if it is not
    98  // in the canonical format, which is a space-separated list of key:"value"
    99  // settings. The value may contain spaces.
   100  func validateStructTag(tag string) error {
   101  	// This code is based on the StructTag.Get code in package reflect.
   102  
   103  	n := 0
   104  	for ; tag != ""; n++ {
   105  		if n > 0 && tag != "" && tag[0] != ' ' {
   106  			// More restrictive than reflect, but catches likely mistakes
   107  			// like `x:"foo",y:"bar"`, which parses as `x:"foo" ,y:"bar"` with second key ",y".
   108  			return errTagSpace
   109  		}
   110  		// Skip leading space.
   111  		i := 0
   112  		for i < len(tag) && tag[i] == ' ' {
   113  			i++
   114  		}
   115  		tag = tag[i:]
   116  		if tag == "" {
   117  			break
   118  		}
   119  
   120  		// Scan to colon. A space, a quote or a control character is a syntax error.
   121  		// Strictly speaking, control chars include the range [0x7f, 0x9f], not just
   122  		// [0x00, 0x1f], but in practice, we ignore the multi-byte control characters
   123  		// as it is simpler to inspect the tag's bytes than the tag's runes.
   124  		i = 0
   125  		for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f {
   126  			i++
   127  		}
   128  		if i == 0 {
   129  			return errTagKeySyntax
   130  		}
   131  		if i+1 >= len(tag) || tag[i] != ':' {
   132  			return errTagSyntax
   133  		}
   134  		if tag[i+1] != '"' {
   135  			return errTagValueSyntax
   136  		}
   137  		tag = tag[i+1:]
   138  
   139  		// Scan quoted string to find value.
   140  		i = 1
   141  		for i < len(tag) && tag[i] != '"' {
   142  			if tag[i] == '\\' {
   143  				i++
   144  			}
   145  			i++
   146  		}
   147  		if i >= len(tag) {
   148  			return errTagValueSyntax
   149  		}
   150  		qvalue := tag[:i+1]
   151  		tag = tag[i+1:]
   152  
   153  		if _, err := strconv.Unquote(qvalue); err != nil {
   154  			return errTagValueSyntax
   155  		}
   156  	}
   157  	return nil
   158  }