diff --git a/rule/struct_tag.go b/rule/struct_tag.go index 73d0d0e..edb6f95 100644 --- a/rule/struct_tag.go +++ b/rule/struct_tag.go @@ -180,6 +180,10 @@ func (w lintStructTagRule) checkTaggedField(checkCtx *checkContext, f *ast.Field w.addFailureWithTagKey(f.Tag, msg, tag.Key) } + if msg, ok := checkOptionsOnIgnoredField(tag); !ok { + w.addFailureWithTagKey(f.Tag, msg, tag.Key) + } + checker, ok := w.tagCheckers[tagKey(tag.Key)] if !ok { continue // we don't have a checker for the tag @@ -561,13 +565,6 @@ func checkYAMLTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (messa } func checkSpannerTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (message string, succeeded bool) { - if tag.Name == "-" { - if len(tag.Options) > 0 { - return fmt.Sprintf("useless option(s) %s for ignored field", strings.Join(tag.Options, ",")), false - } - return "", true - } - for _, opt := range tag.Options { if !checkCtx.isUserDefined(keySpanner, opt) { return fmt.Sprintf(msgUnknownOption, opt), false @@ -577,6 +574,28 @@ func checkSpannerTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (me return "", true } +// checkOptionsOnIgnoredField checks if an ignored struct field (tag name "-") has any options specified. +// It returns a message and false if there are useless options present, or an empty message and true if valid. +func checkOptionsOnIgnoredField(tag *structtag.Tag) (message string, succeeded bool) { + if tag.Name != "-" { + return "", true + } + + switch len(tag.Options) { + case 0: + return "", true + case 1: + opt := strings.TrimSpace(tag.Options[0]) + if opt == "" { + return "", true // accept "-," as options + } + + return fmt.Sprintf("useless option %s for ignored field", opt), false + default: + return fmt.Sprintf("useless options %s for ignored field", strings.Join(tag.Options, ",")), false + } +} + func checkValidateOptionsAlternatives(checkCtx *checkContext, alternatives []string) (message string, succeeded bool) { for _, alternative := range alternatives { alternative := strings.TrimSpace(alternative) diff --git a/testdata/struct_tag.go b/testdata/struct_tag.go index 2382008..432fc51 100644 --- a/testdata/struct_tag.go +++ b/testdata/struct_tag.go @@ -192,7 +192,7 @@ type PropertiesTags struct { type SpannerUser struct { ID int `spanner:"user_id"` Name string `spanner:"full_name"` - Email string `spanner:"-"` // Valid: ignore field + Email string `spanner:"-"` // Valid: ignore field CreatedAt time.Time `spanner:"created_at"` - UpdatedAt time.Time `spanner:"updated_at,unknown"` // MATCH /unknown option "unknown" in spanner tag/ -} \ No newline at end of file + UpdatedAt time.Time `spanner:"updated_at,unknown"` // MATCH /unknown option "unknown" in spanner tag/ +} diff --git a/testdata/struct_tag_user_options.go b/testdata/struct_tag_user_options.go index 76f8600..32bd830 100644 --- a/testdata/struct_tag_user_options.go +++ b/testdata/struct_tag_user_options.go @@ -1,5 +1,7 @@ package fixtures +import "time" + type RangeAllocation struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -49,6 +51,49 @@ type TomlUser struct { type SpannerUserOptions struct { ID int `spanner:"user_id,mySpannerOption"` - A int `spanner:"-,mySpannerOption"` // MATCH /useless option(s) mySpannerOption for ignored field in spanner tag/ + A int `spanner:"-,mySpannerOption"` // MATCH /useless option mySpannerOption for ignored field in spanner tag/ Name string `spanner:"full_name,unknownOption"` // MATCH /unknown option "unknownOption" in spanner tag/ } + +type uselessOptions struct { + A int `bson:"-,"` + B int `bson:"-,omitempty"` // MATCH /useless option omitempty for ignored field in bson tag/ + C int `bson:"-,omitempty,omitempty"` // MATCH /useless options omitempty,omitempty for ignored field in bson tag/ + D int `datastore:"-,"` + E int `datastore:"-,omitempty"` // MATCH /useless option omitempty for ignored field in datastore tag/ + F int `datastore:"-,omitempty,omitempty"` // MATCH /useless options omitempty,omitempty for ignored field in datastore tag/ + G int `json:"-,"` + H int `json:"-,omitempty"` // MATCH /useless option omitempty for ignored field in json tag/ + I int `json:"-,omitempty,omitempty"` // MATCH /useless options omitempty,omitempty for ignored field in json tag/ + J int `mapstructure:"-,"` + K int `mapstructure:"-,squash"` // MATCH /useless option squash for ignored field in mapstructure tag/ + L int `mapstructure:"-,omitempty,omitempty"` // MATCH /useless options omitempty,omitempty for ignored field in mapstructure tag/ + M int `properties:"-,"` + N int `properties:"-,default=15"` // MATCH /useless option default=15 for ignored field in properties tag/ + O time.Time `properties:"-,layout=2006-01-02,default=2006-01-02"` // MATCH /useless options layout=2006-01-02,default=2006-01-02 for ignored field in properties tag/ + P int `spanner:"-,"` + Q int `spanner:"-,mySpannerOption"` // MATCH /useless option mySpannerOption for ignored field in spanner tag/ + R int `spanner:"-,mySpannerOption,mySpannerOption"` // MATCH /useless options mySpannerOption,mySpannerOption for ignored field in spanner tag/ + S int `toml:"-,"` + T int `toml:"-,omitempty"` // MATCH /useless option omitempty for ignored field in toml tag/ + U int `toml:"-,omitempty,omitempty"` // MATCH /useless options omitempty,omitempty for ignored field in toml tag/ + V int `url:"-,"` + W int `url:"-,omitempty"` // MATCH /useless option omitempty for ignored field in url tag/ + X int `url:"-,omitempty,omitempty"` // MATCH /useless options omitempty,omitempty for ignored field in url tag/ + Y int `xml:"-,"` + Z int `xml:"-,omitempty"` // MATCH /useless option omitempty for ignored field in xml tag/ + Aa int `xml:"-,omitempty,omitempty"` // MATCH /useless options omitempty,omitempty for ignored field in xml tag/ + Ba int `yaml:"-,"` + Ca int `yaml:"-,omitempty"` // MATCH /useless option omitempty for ignored field in yaml tag/ + Da int `yaml:"-,omitempty,omitempty"` // MATCH /useless options omitempty,omitempty for ignored field in yaml tag/ + + // MATCH:59 /unknown option "" in bson tag/ + // MATCH:62 /unknown option "" in datastore tag/ + // MATCH:68 /unknown option "" in mapstructure tag/ + // MATCH:71 /unknown or malformed option "" in properties tag/ + // MATCH:74 /unknown option "" in spanner tag/ + // MATCH:77 /unknown option "" in toml tag/ + // MATCH:80 /unknown option "" in url tag/ + // MATCH:83 /unknown option "" in xml tag/ + // MATCH:86 /unknown option "" in yaml tag/ +}