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