1
0
mirror of https://github.com/mgechev/revive.git synced 2025-11-23 22:04:49 +02:00

feature: struct-tag warns on (useless) options on ignored fields (#1487)

This commit is contained in:
chavacava
2025-08-24 07:17:09 +02:00
committed by GitHub
parent cf4901574e
commit 14c91fed1d
3 changed files with 75 additions and 11 deletions

View File

@@ -180,6 +180,10 @@ func (w lintStructTagRule) checkTaggedField(checkCtx *checkContext, f *ast.Field
w.addFailureWithTagKey(f.Tag, msg, tag.Key) 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)] checker, ok := w.tagCheckers[tagKey(tag.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
@@ -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) { 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 { 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
@@ -577,6 +574,28 @@ func checkSpannerTag(checkCtx *checkContext, tag *structtag.Tag, _ ast.Expr) (me
return "", true 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) { func checkValidateOptionsAlternatives(checkCtx *checkContext, alternatives []string) (message string, succeeded bool) {
for _, alternative := range alternatives { for _, alternative := range alternatives {
alternative := strings.TrimSpace(alternative) alternative := strings.TrimSpace(alternative)

View File

@@ -192,7 +192,7 @@ type PropertiesTags struct {
type SpannerUser struct { type SpannerUser struct {
ID int `spanner:"user_id"` ID int `spanner:"user_id"`
Name string `spanner:"full_name"` Name string `spanner:"full_name"`
Email string `spanner:"-"` // Valid: ignore field Email string `spanner:"-"` // Valid: ignore field
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/
} }

View File

@@ -1,5 +1,7 @@
package fixtures package fixtures
import "time"
type RangeAllocation struct { type RangeAllocation struct {
metav1.TypeMeta `json:",inline"` metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"` metav1.ObjectMeta `json:"metadata,omitempty"`
@@ -49,6 +51,49 @@ type TomlUser struct {
type SpannerUserOptions struct { type SpannerUserOptions struct {
ID int `spanner:"user_id,mySpannerOption"` 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/ 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/
}