Repository: mgechev/revive
Pull request #1255 (2025-03-03 07:55:44)

feature: add support for TOML struct tags in struct-tag rule
This PR extends struct-tag rule to check TOML tags as defined at https://pkg.go.dev/github.com/pelletier/go-toml (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 3a0e4f2..a154c1c 100644
--- a/rule/struct_tag.go
+++ b/rule/struct_tag.go
@@ -106,6 +106,7 @@ const (
 	keyMapstructure = "mapstructure"
 	keyProtobuf     = "protobuf"
 	keyRequired     = "required"
+	keyTOML         = "toml"
 	keyURL          = "url"
 	keyValidate     = "validate"
 	keyXML          = "xml"
@@ -212,6 +213,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 keyTOML:
+			msg, ok := w.checkTOMLTag(tag.Options)
+			if !ok {
+				w.addFailure(f.Tag, msg)
+			}
 		case keyURL:
 			msg, ok := w.checkURLTag(tag.Options)
 			if !ok {
@@ -435,6 +441,21 @@ func (w lintStructTagRule) checkValidateTag(options []string) (string, bool) {
 	return "", true
 }
 
+func (w lintStructTagRule) checkTOMLTag(options []string) (string, bool) {
+	for _, opt := range options {
+		switch opt {
+		case "omitempty":
+		default:
+			if w.isUserDefined(keyTOML, opt) {
+				continue
+			}
+			return fmt.Sprintf("unknown option '%s' in TOML tag", opt), false
+		}
+	}
+
+	return "", true
+}
+
 func (w lintStructTagRule) checkValidateOptionsAlternatives(alternatives []string) (string, bool) {
 	for _, alternative := range alternatives {
 		alternative := strings.TrimSpace(alternative)


Test patch:
diff --git a/test/struct_tag_test.go b/test/struct_tag_test.go
index 3be021b..389f0a8 100644
--- a/test/struct_tag_test.go
+++ b/test/struct_tag_test.go
@@ -20,6 +20,7 @@ func TestStructTagWithUserOptions(t *testing.T) {
 			"datastore,myDatastoreOption",
 			"mapstructure,myMapstructureOption",
 			"validate,displayName",
+			"toml,unknown",
 		},
 	})
 }
diff --git a/testdata/struct_tag.go b/testdata/struct_tag.go
index 045f4cd..330d631 100644
--- a/testdata/struct_tag.go
+++ b/testdata/struct_tag.go
@@ -158,3 +158,8 @@ type ValidateUser struct {
 	BadComplex2 string `validate:"gt=0,dive,eq=1|eq=2,endkeys,required"`              // MATCH /option 'endkeys' without a previous 'keys' option in validate tag/
 	BadComplex3 string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,endkeys,required"` // MATCH /option 'endkeys' without a previous 'keys' option in validate tag/
 }
+
+type TomlUser struct {
+	Username string `toml:"username,omitempty"`
+	Location string `toml:"location,unknown"` // MATCH /unknown option 'unknown' in TOML tag/
+}
diff --git a/testdata/struct_tag_user_options.go b/testdata/struct_tag_user_options.go
index c9997bb..66585e6 100644
--- a/testdata/struct_tag_user_options.go
+++ b/testdata/struct_tag_user_options.go
@@ -41,3 +41,8 @@ type ValidateUser struct {
 	BadComplex2 string `validate:"gt=0,dive,eq=1|eq=2,endkeys,required"`              // MATCH /option 'endkeys' without a previous 'keys' option in validate tag/
 	BadComplex3 string `validate:"gt=0,dive,keys,eq=1|eq=2,endkeys,endkeys,required"` // MATCH /option 'endkeys' without a previous 'keys' option in validate tag/
 }
+
+type TomlUser struct {
+	Username string `toml:"username,omitempty"`
+	Location string `toml:"location,unknown"`
+}