Repository: mgechev/revive
Pull request #1241 (2025-02-18 12:23:24)

feature: add suport of mapstructure struct tags in struct-tag rule
This PR extends struct-tag rule to check mapstructure tags as defined at https://pkg.go.dev/github.com/mitchellh/mapstructure (cf https://go.dev/wiki/Well-known-struct-tags#list-of-well-known-struct-tags)


Patch:
diff --git a/rule/struct_tag.go b/rule/struct_tag.go
index 216ed80..520709d 100644
--- a/rule/struct_tag.go
+++ b/rule/struct_tag.go
@@ -98,16 +98,17 @@ func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor {
 }
 
 const (
-	keyASN1      = "asn1"
-	keyBSON      = "bson"
-	keyDatastore = "datastore"
-	keyDefault   = "default"
-	keyJSON      = "json"
-	keyProtobuf  = "protobuf"
-	keyRequired  = "required"
-	keyURL       = "url"
-	keyXML       = "xml"
-	keyYAML      = "yaml"
+	keyASN1         = "asn1"
+	keyBSON         = "bson"
+	keyDatastore    = "datastore"
+	keyDefault      = "default"
+	keyJSON         = "json"
+	keyMapstructure = "mapstructure"
+	keyProtobuf     = "protobuf"
+	keyRequired     = "required"
+	keyURL          = "url"
+	keyXML          = "xml"
+	keyYAML         = "yaml"
 )
 
 func (w lintStructTagRule) checkTagNameIfNeed(tag *structtag.Tag) (string, bool) {
@@ -196,6 +197,11 @@ func (w lintStructTagRule) checkTaggedField(f *ast.Field) {
 			if !ok {
 				w.addFailure(f.Tag, msg)
 			}
+		case keyMapstructure:
+			msg, ok := w.checkMapstructureTag(tag.Options)
+			if !ok {
+				w.addFailure(f.Tag, msg)
+			}
 		case keyProtobuf:
 			msg, ok := w.checkProtobufTag(tag)
 			if !ok {
@@ -379,6 +385,21 @@ func (w lintStructTagRule) checkDatastoreTag(options []string) (string, bool) {
 	return "", true
 }
 
+func (w lintStructTagRule) checkMapstructureTag(options []string) (string, bool) {
+	for _, opt := range options {
+		switch opt {
+		case "omitempty", "reminder", "squash":
+		default:
+			if w.isUserDefined(keyMapstructure, opt) {
+				continue
+			}
+			return fmt.Sprintf("unknown option '%s' in Mapstructure 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 3c4eab1..5386f0e 100644
--- a/test/struct_tag_test.go
+++ b/test/struct_tag_test.go
@@ -18,6 +18,7 @@ func TestStructTagWithUserOptions(t *testing.T) {
 			"bson,gnu",
 			"url,myURLOption",
 			"datastore,myDatastoreOption",
+			"mapstructure,myMapstructureOption",
 		},
 	})
 }
diff --git a/testdata/struct_tag.go b/testdata/struct_tag.go
index ec5e42e..3d4ec6b 100644
--- a/testdata/struct_tag.go
+++ b/testdata/struct_tag.go
@@ -141,3 +141,8 @@ type Fields struct {
 	Field      string `datastore:",noindex,flatten,omitempty"`
 	OtherField string `datastore:",unknownOption"` // MATCH /unknown option 'unknownOption' in Datastore tag/
 }
+
+type MapStruct struct {
+	Field1     string `mapstructure:",squash,reminder,omitempty"`
+	OtherField string `mapstructure:",unknownOption"` // MATCH /unknown option 'unknownOption' in Mapstructure tag/
+}
diff --git a/testdata/struct_tag_user_options.go b/testdata/struct_tag_user_options.go
index bbdabc3..cd07373 100644
--- a/testdata/struct_tag_user_options.go
+++ b/testdata/struct_tag_user_options.go
@@ -24,3 +24,8 @@ 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/
+}