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:
		| @@ -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