Repository: mgechev/revive
Pull request #1239 (2025-02-16 10:39:19)

feature: add support for URL struct tags in struct-tag rule
This PR extends struct-tag rule to check URL tags as defined at https://github.com/google/go-querystring (cf https://go.dev/wiki/Well-known-struct-tags#list-of-well-known-struct-tags)

denisvmedia: Thanks. Looks nice and simple. Just a few comments.

Patch:
diff --git a/rule/struct_tag.go b/rule/struct_tag.go
index 00a2b96..d35058c 100644
--- a/rule/struct_tag.go
+++ b/rule/struct_tag.go
@@ -104,6 +104,7 @@ const (
 	keyJSON     = "json"
 	keyProtobuf = "protobuf"
 	keyRequired = "required"
+	keyURL      = "url"
 	keyXML      = "xml"
 	keyYAML     = "yaml"
 )
@@ -198,6 +199,11 @@ func (w lintStructTagRule) checkTaggedField(f *ast.Field) {
 			if tag.Name != "true" && tag.Name != "false" {
 				w.addFailure(f.Tag, "required should be 'true' or 'false'")
 			}
+		case keyURL:
+			msg, ok := w.checkURLTag(tag.Options)
+			if !ok {
+				w.addFailure(f.Tag, msg)
+			}
 		case keyXML:
 			msg, ok := w.checkXMLTag(tag.Options)
 			if !ok {
@@ -329,6 +335,29 @@ func (w lintStructTagRule) checkYAMLTag(options []string) (string, bool) {
 	return "", true
 }
 
+func (w lintStructTagRule) checkURLTag(options []string) (string, bool) {
+	var delimiter = ""
+	for _, opt := range options {
+		switch opt {
+		case "int", "omitempty", "numbered", "brackets":
+		case "unix", "unixmilli", "unixnano": // TODO : check that the field is of type time.Time
+		case "comma", "semicolon", "space":
+			if delimiter == "" {
+				delimiter = opt
+				continue
+			}
+			return fmt.Sprintf("can not set both '%s' and '%s' as delimiters in URL tag", opt, delimiter), false
+		default:
+			if w.isUserDefined(keyURL, opt) {
+				continue
+			}
+			return fmt.Sprintf("unknown option '%s' in URL tag", opt), false
+		}
+	}
+
+	return "", true
+}
+
 func (lintStructTagRule) typeValueMatch(t ast.Expr, val string) bool {
 	tID, ok := t.(*ast.Ident)
 	if !ok {


Test patch:
diff --git a/test/struct_tag_test.go b/test/struct_tag_test.go
index 5593635..9ce43e6 100644
--- a/test/struct_tag_test.go
+++ b/test/struct_tag_test.go
@@ -13,7 +13,7 @@ func TestStructTag(t *testing.T) {
 
 func TestStructTagWithUserOptions(t *testing.T) {
 	testRule(t, "struct_tag_user_options", &rule.StructTagRule{}, &lint.RuleConfig{
-		Arguments: []any{"json,inline,outline", "bson,gnu"},
+		Arguments: []any{"json,inline,outline", "bson,gnu", "url,myURLOption"},
 	})
 }
 
diff --git a/testdata/struct_tag.go b/testdata/struct_tag.go
index 1c27597..47fb092 100644
--- a/testdata/struct_tag.go
+++ b/testdata/struct_tag.go
@@ -123,3 +123,16 @@ type Simple struct {
 	XXX_unrecognized     []byte   `json:"-"`
 	XXX_sizecache        int32    `json:"-"`
 }
+
+type RequestQueryOption struct {
+	Properties           []string `url:"properties,comma,omitempty"`
+	CustomProperties     []string `url:"-"`
+	Associations         []string `url:"associations,brackets,omitempty"`
+	Associations2        []string `url:"associations2,semicolon,omitempty"`
+	Associations3        []string `url:"associations3,space,brackets,omitempty"`
+	Associations4        []string `url:"associations4,numbered,omitempty"`
+	Associations5        []string `url:"associations5,space,semicolon,omitempty"` // MATCH /can not set both 'semicolon' and 'space' as delimiters in URL tag/
+	PaginateAssociations bool     `url:"paginateAssociations,int,omitempty"`
+	Archived             bool     `url:"archived,myURLOption"` // MATCH /unknown option 'myURLOption' in URL tag/
+	IDProperty           string   `url:"idProperty,omitempty"`
+}
diff --git a/testdata/struct_tag_user_options.go b/testdata/struct_tag_user_options.go
index 0b48c4a..31bdbfc 100644
--- a/testdata/struct_tag_user_options.go
+++ b/testdata/struct_tag_user_options.go
@@ -13,3 +13,9 @@ type RangeAllocation struct {
 	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"`
+}