mirror of
				https://github.com/mgechev/revive.git
				synced 2025-10-30 23:37:49 +02:00 
			
		
		
		
	struct-tag: support codec struct tag (#1507)
This commit is contained in:
		| @@ -23,6 +23,7 @@ type tagKey string | |||||||
| const ( | const ( | ||||||
| 	keyASN1         tagKey = "asn1" | 	keyASN1         tagKey = "asn1" | ||||||
| 	keyBSON         tagKey = "bson" | 	keyBSON         tagKey = "bson" | ||||||
|  | 	keyCodec        tagKey = "codec" | ||||||
| 	keyDatastore    tagKey = "datastore" | 	keyDatastore    tagKey = "datastore" | ||||||
| 	keyDefault      tagKey = "default" | 	keyDefault      tagKey = "default" | ||||||
| 	keyJSON         tagKey = "json" | 	keyJSON         tagKey = "json" | ||||||
| @@ -38,11 +39,12 @@ const ( | |||||||
| 	keyYAML         tagKey = "yaml" | 	keyYAML         tagKey = "yaml" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type tagChecker func(checkCtx *checkContext, tag *structtag.Tag, fieldType ast.Expr) (message string, succeeded bool) | type tagChecker func(checkCtx *checkContext, tag *structtag.Tag, field *ast.Field) (message string, succeeded bool) | ||||||
|  |  | ||||||
| var tagCheckers = map[tagKey]tagChecker{ | var tagCheckers = map[tagKey]tagChecker{ | ||||||
| 	keyASN1:         checkASN1Tag, | 	keyASN1:         checkASN1Tag, | ||||||
| 	keyBSON:         checkBSONTag, | 	keyBSON:         checkBSONTag, | ||||||
|  | 	keyCodec:        checkCodecTag, | ||||||
| 	keyDatastore:    checkDatastoreTag, | 	keyDatastore:    checkDatastoreTag, | ||||||
| 	keyDefault:      checkDefaultTag, | 	keyDefault:      checkDefaultTag, | ||||||
| 	keyJSON:         checkJSONTag, | 	keyJSON:         checkJSONTag, | ||||||
| @@ -62,6 +64,7 @@ type checkContext struct { | |||||||
| 	userDefined    map[tagKey][]string // map: key -> []option | 	userDefined    map[tagKey][]string // map: key -> []option | ||||||
| 	usedTagNbr     map[int]bool        // list of used tag numbers | 	usedTagNbr     map[int]bool        // list of used tag numbers | ||||||
| 	usedTagName    map[string]bool     // list of used tag keys | 	usedTagName    map[string]bool     // list of used tag keys | ||||||
|  | 	commonOptions  map[string]bool     // list of options defined for all fields | ||||||
| 	isAtLeastGo124 bool | 	isAtLeastGo124 bool | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -74,6 +77,23 @@ func (checkCtx checkContext) isUserDefined(key tagKey, opt string) bool { | |||||||
| 	return slices.Contains(options, opt) | 	return slices.Contains(options, opt) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (checkCtx *checkContext) isCommonOption(opt string) bool { | ||||||
|  | 	if checkCtx.commonOptions == nil { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	_, ok := checkCtx.commonOptions[opt] | ||||||
|  | 	return ok | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (checkCtx *checkContext) addCommonOption(opt string) { | ||||||
|  | 	if checkCtx.commonOptions == nil { | ||||||
|  | 		checkCtx.commonOptions = map[string]bool{} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	checkCtx.commonOptions[opt] = true | ||||||
|  | } | ||||||
|  |  | ||||||
| // Configure validates the rule configuration, and configures the rule accordingly. | // Configure validates the rule configuration, and configures the rule accordingly. | ||||||
| // | // | ||||||
| // Configuration implements the [lint.ConfigurableRule] interface. | // Configuration implements the [lint.ConfigurableRule] interface. | ||||||
| @@ -164,36 +184,66 @@ func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor { | |||||||
|  |  | ||||||
| // checkTaggedField checks the tag of the given field. | // checkTaggedField checks the tag of the given field. | ||||||
| // precondition: the field has a tag | // precondition: the field has a tag | ||||||
| func (w lintStructTagRule) checkTaggedField(checkCtx *checkContext, f *ast.Field) { | func (w lintStructTagRule) checkTaggedField(checkCtx *checkContext, field *ast.Field) { | ||||||
| 	if len(f.Names) > 0 && !f.Names[0].IsExported() { | 	tags, err := structtag.Parse(strings.Trim(field.Tag.Value, "`")) | ||||||
| 		w.addFailuref(f, "tag on not-exported field %s", f.Names[0].Name) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	tags, err := structtag.Parse(strings.Trim(f.Tag.Value, "`")) |  | ||||||
| 	if err != nil || tags == nil { | 	if err != nil || tags == nil { | ||||||
| 		w.addFailuref(f.Tag, "malformed tag") | 		w.addFailuref(field.Tag, "malformed tag") | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	analyzedTags := map[tagKey]struct{}{} | ||||||
| 	for _, tag := range tags.Tags() { | 	for _, tag := range tags.Tags() { | ||||||
| 		if msg, ok := w.checkTagNameIfNeed(checkCtx, tag); !ok { | 		if msg, ok := w.checkTagNameIfNeed(checkCtx, tag); !ok { | ||||||
| 			w.addFailureWithTagKey(f.Tag, msg, tag.Key) | 			w.addFailureWithTagKey(field.Tag, msg, tag.Key) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if msg, ok := checkOptionsOnIgnoredField(tag); !ok { | 		if msg, ok := checkOptionsOnIgnoredField(tag); !ok { | ||||||
| 			w.addFailureWithTagKey(f.Tag, msg, tag.Key) | 			w.addFailureWithTagKey(field.Tag, msg, tag.Key) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		checker, ok := w.tagCheckers[tagKey(tag.Key)] | 		key := tagKey(tag.Key) | ||||||
|  | 		checker, ok := w.tagCheckers[key] | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			continue // we don't have a checker for the tag | 			continue // we don't have a checker for the tag | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		msg, ok := checker(checkCtx, tag, f.Type) | 		msg, ok := checker(checkCtx, tag, field) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			w.addFailureWithTagKey(f.Tag, msg, tag.Key) | 			w.addFailureWithTagKey(field.Tag, msg, tag.Key) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		analyzedTags[key] = struct{}{} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if w.shallWarnOnUnexportedField(field.Names, analyzedTags) { | ||||||
|  | 		w.addFailuref(field, "tag on not-exported field %s", field.Names[0].Name) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // tagKeyToSpecialField maps tag keys to their "special" meaning struct fields. | ||||||
|  | var tagKeyToSpecialField = map[tagKey]string{ | ||||||
|  | 	"codec": structTagCodecSpecialField, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (lintStructTagRule) shallWarnOnUnexportedField(fieldNames []*ast.Ident, tags map[tagKey]struct{}) bool { | ||||||
|  | 	if len(fieldNames) != 1 { // only handle the case of single field  name (99.999% of cases) | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if fieldNames[0].IsExported() { | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	fieldNameStr := fieldNames[0].Name | ||||||
|  |  | ||||||
|  | 	for key := range tags { | ||||||
|  | 		specialField, ok := tagKeyToSpecialField[key] | ||||||
|  | 		if ok && specialField == fieldNameStr { | ||||||
|  | 			return false | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	return true | ||||||
| } | } | ||||||
|  |  | ||||||
| func (w lintStructTagRule) checkTagNameIfNeed(checkCtx *checkContext, tag *structtag.Tag) (message string, succeeded bool) { | func (w lintStructTagRule) checkTagNameIfNeed(checkCtx *checkContext, tag *structtag.Tag) (message string, succeeded bool) { | ||||||
| @@ -204,7 +254,7 @@ func (w lintStructTagRule) checkTagNameIfNeed(checkCtx *checkContext, tag *struc | |||||||
|  |  | ||||||
| 	key := tagKey(tag.Key) | 	key := tagKey(tag.Key) | ||||||
| 	switch key { | 	switch key { | ||||||
| 	case keyBSON, keyJSON, keyXML, keyYAML, keyProtobuf, keySpanner: | 	case keyBSON, keyCodec, keyJSON, keyProtobuf, keySpanner, keyXML, keyYAML: // keys that need to check for duplicated tags | ||||||
| 	default: | 	default: | ||||||
| 		return "", true | 		return "", true | ||||||
| 	} | 	} | ||||||
| @@ -241,7 +291,8 @@ func (lintStructTagRule) getTagName(tag *structtag.Tag) string { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkASN1Tag(checkCtx *checkContext, tag *structtag.Tag, fieldType ast.Expr) (message string, succeeded bool) { | func checkASN1Tag(checkCtx *checkContext, tag *structtag.Tag, field *ast.Field) (message string, succeeded bool) { | ||||||
|  | 	fieldType := field.Type | ||||||
| 	checkList := slices.Concat(tag.Options, []string{tag.Name}) | 	checkList := slices.Concat(tag.Options, []string{tag.Name}) | ||||||
| 	for _, opt := range checkList { | 	for _, opt := range checkList { | ||||||
| 		switch opt { | 		switch opt { | ||||||
| @@ -282,7 +333,7 @@ func checkCompoundANS1Option(checkCtx *checkContext, opt string, fieldType ast.E | |||||||
| 	return "", true | 	return "", true | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkDatastoreTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) { | func checkDatastoreTag(checkCtx *checkContext, tag *structtag.Tag, _ *ast.Field) (message string, succeeded bool) { | ||||||
| 	for _, opt := range tag.Options { | 	for _, opt := range tag.Options { | ||||||
| 		switch opt { | 		switch opt { | ||||||
| 		case "flatten", "noindex", "omitempty": | 		case "flatten", "noindex", "omitempty": | ||||||
| @@ -297,15 +348,15 @@ func checkDatastoreTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) ( | |||||||
| 	return "", true | 	return "", true | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkDefaultTag(_ *checkContext, tag *structtag.Tag, fieldType ast.Expr) (message string, succeeded bool) { | func checkDefaultTag(_ *checkContext, tag *structtag.Tag, field *ast.Field) (message string, succeeded bool) { | ||||||
| 	if !typeValueMatch(fieldType, tag.Name) { | 	if !typeValueMatch(field.Type, tag.Name) { | ||||||
| 		return msgTypeMismatch, false | 		return msgTypeMismatch, false | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return "", true | 	return "", true | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkBSONTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) { | func checkBSONTag(checkCtx *checkContext, tag *structtag.Tag, _ *ast.Field) (message string, succeeded bool) { | ||||||
| 	for _, opt := range tag.Options { | 	for _, opt := range tag.Options { | ||||||
| 		switch opt { | 		switch opt { | ||||||
| 		case "inline", "minsize", "omitempty": | 		case "inline", "minsize", "omitempty": | ||||||
| @@ -320,7 +371,32 @@ func checkBSONTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (messa | |||||||
| 	return "", true | 	return "", true | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkJSONTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) { | const structTagCodecSpecialField = "_struct" | ||||||
|  |  | ||||||
|  | func checkCodecTag(checkCtx *checkContext, tag *structtag.Tag, field *ast.Field) (message string, succeeded bool) { | ||||||
|  | 	fieldNames := field.Names | ||||||
|  | 	mustAddToCommonOptions := len(fieldNames) == 1 && fieldNames[0].Name == structTagCodecSpecialField // see https://github.com/mgechev/revive/issues/1477#issuecomment-3191493076 | ||||||
|  | 	for _, opt := range tag.Options { | ||||||
|  | 		if mustAddToCommonOptions { | ||||||
|  | 			checkCtx.addCommonOption(opt) | ||||||
|  | 		} else if checkCtx.isCommonOption(opt) { | ||||||
|  | 			return fmt.Sprintf("redundant option %q, already set for all fields", opt), false | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		switch opt { | ||||||
|  | 		case "omitempty", "toarray", "int", "uint", "float", "-", "omitemptyarray": | ||||||
|  | 		default: | ||||||
|  | 			if checkCtx.isUserDefined(keyCodec, opt) { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			return fmt.Sprintf(msgUnknownOption, opt), false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return "", true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func checkJSONTag(checkCtx *checkContext, tag *structtag.Tag, _ *ast.Field) (message string, succeeded bool) { | ||||||
| 	for _, opt := range tag.Options { | 	for _, opt := range tag.Options { | ||||||
| 		switch opt { | 		switch opt { | ||||||
| 		case "omitempty", "string": | 		case "omitempty", "string": | ||||||
| @@ -345,7 +421,7 @@ func checkJSONTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (messa | |||||||
| 	return "", true | 	return "", true | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkMapstructureTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) { | func checkMapstructureTag(checkCtx *checkContext, tag *structtag.Tag, _ *ast.Field) (message string, succeeded bool) { | ||||||
| 	for _, opt := range tag.Options { | 	for _, opt := range tag.Options { | ||||||
| 		switch opt { | 		switch opt { | ||||||
| 		case "omitempty", "reminder", "squash": | 		case "omitempty", "reminder", "squash": | ||||||
| @@ -360,13 +436,14 @@ func checkMapstructureTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr | |||||||
| 	return "", true | 	return "", true | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkPropertiesTag(_ *checkContext, tag *structtag.Tag, fieldType ast.Expr) (message string, succeeded bool) { | func checkPropertiesTag(_ *checkContext, tag *structtag.Tag, field *ast.Field) (message string, succeeded bool) { | ||||||
| 	options := tag.Options | 	options := tag.Options | ||||||
| 	if len(options) == 0 { | 	if len(options) == 0 { | ||||||
| 		return "", true | 		return "", true | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	seenOptions := map[string]bool{} | 	seenOptions := map[string]bool{} | ||||||
|  | 	fieldType := field.Type | ||||||
| 	for _, opt := range options { | 	for _, opt := range options { | ||||||
| 		msg, ok := fmt.Sprintf("unknown or malformed option %q", opt), false | 		msg, ok := fmt.Sprintf("unknown or malformed option %q", opt), false | ||||||
| 		if key, value, found := strings.Cut(opt, "="); found { | 		if key, value, found := strings.Cut(opt, "="); found { | ||||||
| @@ -405,7 +482,7 @@ func checkCompoundPropertiesOption(key, value string, fieldType ast.Expr, seenOp | |||||||
| 	return "", true | 	return "", true | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkProtobufTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) { | func checkProtobufTag(checkCtx *checkContext, tag *structtag.Tag, _ *ast.Field) (message string, succeeded bool) { | ||||||
| 	// check name | 	// check name | ||||||
| 	switch tag.Name { | 	switch tag.Name { | ||||||
| 	case "bytes", "fixed32", "fixed64", "group", "varint", "zigzag32", "zigzag64": | 	case "bytes", "fixed32", "fixed64", "group", "varint", "zigzag32", "zigzag64": | ||||||
| @@ -458,7 +535,7 @@ func checkProtobufOptions(checkCtx *checkContext, options []string) (message str | |||||||
| 	return "", true | 	return "", true | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkRequiredTag(_ *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) { | func checkRequiredTag(_ *checkContext, tag *structtag.Tag, _ *ast.Field) (message string, succeeded bool) { | ||||||
| 	switch tag.Name { | 	switch tag.Name { | ||||||
| 	case "true", "false": | 	case "true", "false": | ||||||
| 		return "", true | 		return "", true | ||||||
| @@ -467,7 +544,7 @@ func checkRequiredTag(_ *checkContext, tag *structtag.Tag, _ ast.Expr) (message | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkTOMLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) { | func checkTOMLTag(checkCtx *checkContext, tag *structtag.Tag, _ *ast.Field) (message string, succeeded bool) { | ||||||
| 	for _, opt := range tag.Options { | 	for _, opt := range tag.Options { | ||||||
| 		switch opt { | 		switch opt { | ||||||
| 		case "omitempty": | 		case "omitempty": | ||||||
| @@ -482,7 +559,7 @@ func checkTOMLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (messa | |||||||
| 	return "", true | 	return "", true | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkURLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) { | func checkURLTag(checkCtx *checkContext, tag *structtag.Tag, _ *ast.Field) (message string, succeeded bool) { | ||||||
| 	var delimiter = "" | 	var delimiter = "" | ||||||
| 	for _, opt := range tag.Options { | 	for _, opt := range tag.Options { | ||||||
| 		switch opt { | 		switch opt { | ||||||
| @@ -505,7 +582,7 @@ func checkURLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (messag | |||||||
| 	return "", true | 	return "", true | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkValidateTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) { | func checkValidateTag(checkCtx *checkContext, tag *structtag.Tag, _ *ast.Field) (message string, succeeded bool) { | ||||||
| 	previousOption := "" | 	previousOption := "" | ||||||
| 	seenKeysOption := false | 	seenKeysOption := false | ||||||
| 	options := append([]string{tag.Name}, tag.Options...) | 	options := append([]string{tag.Name}, tag.Options...) | ||||||
| @@ -534,7 +611,7 @@ func checkValidateTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (m | |||||||
| 	return "", true | 	return "", true | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkXMLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) { | func checkXMLTag(checkCtx *checkContext, tag *structtag.Tag, _ *ast.Field) (message string, succeeded bool) { | ||||||
| 	for _, opt := range tag.Options { | 	for _, opt := range tag.Options { | ||||||
| 		switch opt { | 		switch opt { | ||||||
| 		case "any", "attr", "cdata", "chardata", "comment", "innerxml", "omitempty", "typeattr": | 		case "any", "attr", "cdata", "chardata", "comment", "innerxml", "omitempty", "typeattr": | ||||||
| @@ -549,7 +626,7 @@ func checkXMLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (messag | |||||||
| 	return "", true | 	return "", true | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkYAMLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) { | func checkYAMLTag(checkCtx *checkContext, tag *structtag.Tag, _ *ast.Field) (message string, succeeded bool) { | ||||||
| 	for _, opt := range tag.Options { | 	for _, opt := range tag.Options { | ||||||
| 		switch opt { | 		switch opt { | ||||||
| 		case "flow", "inline", "omitempty": | 		case "flow", "inline", "omitempty": | ||||||
| @@ -564,7 +641,7 @@ func checkYAMLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (messa | |||||||
| 	return "", true | 	return "", true | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkSpannerTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) { | func checkSpannerTag(checkCtx *checkContext, tag *structtag.Tag, _ *ast.Field) (message string, succeeded bool) { | ||||||
| 	for _, opt := range tag.Options { | 	for _, opt := range tag.Options { | ||||||
| 		if !checkCtx.isUserDefined(keySpanner, opt) { | 		if !checkCtx.isUserDefined(keySpanner, opt) { | ||||||
| 			return fmt.Sprintf(msgUnknownOption, opt), false | 			return fmt.Sprintf(msgUnknownOption, opt), false | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ func TestStructTagWithUserOptions(t *testing.T) { | |||||||
| 			"validate,displayName", | 			"validate,displayName", | ||||||
| 			"toml,unknown", | 			"toml,unknown", | ||||||
| 			"spanner,mySpannerOption", | 			"spanner,mySpannerOption", | ||||||
|  | 			"codec,myCodecOption", | ||||||
| 		}, | 		}, | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								testdata/struct_tag.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								testdata/struct_tag.go
									
									
									
									
										vendored
									
									
								
							| @@ -196,3 +196,21 @@ type SpannerUser struct { | |||||||
| 	CreatedAt time.Time `spanner:"created_at"` | 	CreatedAt time.Time `spanner:"created_at"` | ||||||
| 	UpdatedAt time.Time `spanner:"updated_at,unknown"` // MATCH /unknown option "unknown" in spanner tag/ | 	UpdatedAt time.Time `spanner:"updated_at,unknown"` // MATCH /unknown option "unknown" in spanner tag/ | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type Codec struct { | ||||||
|  | 	_something struct{} `codec:",omitempty,int"` // MATCH /tag on not-exported field _something/ | ||||||
|  | 	_struct    struct{} `codec:",omitempty,int"` // do not match, _struct has special meaning for codec tag | ||||||
|  | 	Field1     string   `codec:"-"` | ||||||
|  | 	Field2     int      `codec:"myName"` | ||||||
|  | 	Field3     int32    `codec:",omitempty"` // MATCH /redundant option "omitempty", already set for all fields in codec tag/ | ||||||
|  | 	Field4     bool     `codec:"f4,int"`     // MATCH /redundant option "int", already set for all fields in codec tag/ | ||||||
|  | 	field5     bool     // unexported, so skipped | ||||||
|  | 	Anon | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type TestDuplicatedCodecTags struct { | ||||||
|  | 	_struct struct{} `json:",omitempty"` // MATCH /tag on not-exported field _struct/ | ||||||
|  | 	A       int      `codec:"field_a"` | ||||||
|  | 	B       int      `codec:"field_a"` // MATCH /duplicated tag name "field_a" in codec tag/ | ||||||
|  | 	C       int      `codec:"field_c"` | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								testdata/struct_tag_user_options.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								testdata/struct_tag_user_options.go
									
									
									
									
										vendored
									
									
								
							| @@ -97,3 +97,8 @@ type uselessOptions struct { | |||||||
| 	// MATCH:83 /unknown option "" in xml tag/ | 	// MATCH:83 /unknown option "" in xml tag/ | ||||||
| 	// MATCH:86 /unknown option "" in yaml tag/ | 	// MATCH:86 /unknown option "" in yaml tag/ | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type CodecUserOptions struct { | ||||||
|  | 	ID   int    `codec:"user_id,myCodecOption"` | ||||||
|  | 	Name string `codec:"full_name,unknownOption"` // MATCH /unknown option "unknownOption" in codec tag/ | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user