github.com/sbinet/go@v0.0.0-20160827155028-54d7de7dd62b/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  }