mirror of
https://github.com/mgechev/revive.git
synced 2025-11-25 22:12:38 +02:00
feature: add configuration notation in struct-tag rule to omit checking a tag (#1515)
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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{})
|
||||
}
|
||||
|
||||
98
testdata/struct_tag_user_options_omit.go
vendored
Normal file
98
testdata/struct_tag_user_options_omit.go
vendored
Normal 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/
|
||||
}
|
||||
Reference in New Issue
Block a user