1
0
mirror of https://github.com/mgechev/revive.git synced 2025-10-30 23:37:49 +02:00

feature: add configuration notation in struct-tag rule to omit checking a tag (#1515)

This commit is contained in:
chavacava
2025-09-19 07:10:52 +02:00
committed by GitHub
parent 5736df325c
commit ec8439e646
4 changed files with 140 additions and 4 deletions

View File

@@ -1318,6 +1318,15 @@ To accept the `inline` option in JSON tags (and `outline` and `gnu` in BSON tags
arguments = ["json,inline", "bson,outline,gnu"]
```
To prevent a tag from being checked, simply add a `!` before its name.
For example, to instruct the rule not to check `validate` tags
(and accept `outline` and `gnu` in BSON tags) you can provide the following configuration
```toml
[rule.struct-tag]
arguments = ["!validate", "bson,outline,gnu"]
```
## superfluous-else
_Description_: To improve the readability of code, it is recommended to reduce the indentation as much as possible.

View File

@@ -16,6 +16,7 @@ import (
// StructTagRule lints struct tags.
type StructTagRule struct {
userDefined map[tagKey][]string // map: key -> []option
omittedTags map[tagKey]struct{} // set of tags that must not be analyzed
}
type tagKey string
@@ -107,17 +108,23 @@ func (r *StructTagRule) Configure(arguments lint.Arguments) error {
return err
}
r.userDefined = make(map[tagKey][]string, len(arguments))
r.userDefined = map[tagKey][]string{}
r.omittedTags = map[tagKey]struct{}{}
for _, arg := range arguments {
item, ok := arg.(string)
if !ok {
return fmt.Errorf("invalid argument to the %s rule. Expecting a string, got %v (of type %T)", r.Name(), arg, arg)
}
parts := strings.Split(item, ",")
if len(parts) < 2 {
return fmt.Errorf("invalid argument to the %s rule. Expecting a string of the form key[,option]+, got %s", r.Name(), item)
keyStr := strings.TrimSpace(parts[0])
keyStr, isOmitted := strings.CutPrefix(keyStr, "!")
key := tagKey(keyStr)
if isOmitted {
r.omittedTags[key] = struct{}{}
continue
}
key := tagKey(strings.TrimSpace(parts[0]))
for i := 1; i < len(parts); i++ {
option := strings.TrimSpace(parts[i])
r.userDefined[key] = append(r.userDefined[key], option)
@@ -137,6 +144,7 @@ func (r *StructTagRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure
w := lintStructTagRule{
onFailure: onFailure,
userDefined: r.userDefined,
omittedTags: r.omittedTags,
isAtLeastGo124: file.Pkg.IsAtLeastGoVersion(lint.Go124),
tagCheckers: tagCheckers,
}
@@ -154,6 +162,7 @@ func (*StructTagRule) Name() string {
type lintStructTagRule struct {
onFailure func(lint.Failure)
userDefined map[tagKey][]string // map: key -> []option
omittedTags map[tagKey]struct{}
isAtLeastGo124 bool
tagCheckers map[tagKey]tagChecker
}
@@ -193,6 +202,11 @@ func (w lintStructTagRule) checkTaggedField(checkCtx *checkContext, field *ast.F
analyzedTags := map[tagKey]struct{}{}
for _, tag := range tags.Tags() {
_, mustOmit := w.omittedTags[tagKey(tag.Key)]
if mustOmit {
continue
}
if msg, ok := w.checkTagNameIfNeed(checkCtx, tag); !ok {
w.addFailureWithTagKey(field.Tag, msg, tag.Key)
}

View File

@@ -27,6 +27,21 @@ func TestStructTagWithUserOptions(t *testing.T) {
})
}
func TestStructTagWithOmittedTags(t *testing.T) {
testRule(t, "struct_tag_user_options_omit", &rule.StructTagRule{}, &lint.RuleConfig{
Arguments: []any{
"!validate",
"!toml",
"json,inline,outline",
"bson,gnu",
"url,myURLOption",
"datastore,myDatastoreOption",
"mapstructure,myMapstructureOption",
"spanner,mySpannerOption",
},
})
}
func TestStructTagAfterGo1_24(t *testing.T) {
testRule(t, "go1.24/struct_tag", &rule.StructTagRule{})
}

View File

@@ -0,0 +1,98 @@
package fixtures
import "time"
type RangeAllocation struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Range string `json:"range,outline"`
Data []byte `json:"data,flow"` // MATCH /unknown option "flow" in json tag/
}
type RangeAllocation struct {
metav1.TypeMeta `bson:",minsize,gnu"`
metav1.ObjectMeta `bson:"metadata,omitempty"`
Range string `bson:"range,flow"` // MATCH /unknown option "flow" in bson tag/
Data []byte `bson:"data,inline"`
}
type RequestQueryOptions struct {
Properties []string `url:"properties,commmma,omitempty"` // MATCH /unknown option "commmma" in url tag/
CustomProperties []string `url:"-"`
Archived bool `url:"archived,myURLOption"`
}
type Fields struct {
Field string `datastore:",noindex,flatten,omitempty,myDatastoreOption"`
OtherField string `datastore:",unknownOption"` // MATCH /unknown option "unknownOption" in datastore tag/
}
type MapStruct struct {
Field1 string `mapstructure:",squash,reminder,omitempty,myMapstructureOption"`
OtherField string `mapstructure:",unknownOption"` // MATCH /unknown option "unknownOption" in mapstructure tag/
}
type ValidateUser struct {
Username string `validate:"required,min=3,max=32"`
Email string `validate:"required,email"`
Password string `validate:"required,min=8,max=32"`
Biography string `validate:"min=0,max=1000"`
DisplayName string `validate:"displayName,min=3,max=32"`
Complex string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,required"`
BadComplex string `validate:"gt=0,keys,eq=1|eq=2,endkeys,required"`
BadComplex2 string `validate:"gt=0,dive,eq=1|eq=2,endkeys,required"`
BadComplex3 string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,endkeys,required"`
}
type TomlUser struct {
Username string `toml:"username,omitempty"`
Location string `toml:"location,unknown"`
}
type SpannerUserOptions struct {
ID int `spanner:"user_id,mySpannerOption"`
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"`
U int `toml:"-,omitempty,omitempty"`
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:80 /unknown option "" in url tag/
// MATCH:83 /unknown option "" in xml tag/
// MATCH:86 /unknown option "" in yaml tag/
}