Repository: mgechev/revive
Pull request #1240 (2025-02-17 06:26:30)

feature: add support of datastore struct tags in struct-tag rule
This PR extends struct-tag rule to check datastore tags as defined at https://pkg.go.dev/cloud.google.com/go/datastore (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 d35058c..216ed80 100644
--- a/rule/struct_tag.go
+++ b/rule/struct_tag.go
@@ -98,15 +98,16 @@ func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor {
 }
 
 const (
-	keyASN1     = "asn1"
-	keyBSON     = "bson"
-	keyDefault  = "default"
-	keyJSON     = "json"
-	keyProtobuf = "protobuf"
-	keyRequired = "required"
-	keyURL      = "url"
-	keyXML      = "xml"
-	keyYAML     = "yaml"
+	keyASN1      = "asn1"
+	keyBSON      = "bson"
+	keyDatastore = "datastore"
+	keyDefault   = "default"
+	keyJSON      = "json"
+	keyProtobuf  = "protobuf"
+	keyRequired  = "required"
+	keyURL       = "url"
+	keyXML       = "xml"
+	keyYAML      = "yaml"
 )
 
 func (w lintStructTagRule) checkTagNameIfNeed(tag *structtag.Tag) (string, bool) {
@@ -181,6 +182,11 @@ func (w lintStructTagRule) checkTaggedField(f *ast.Field) {
 			if !ok {
 				w.addFailure(f.Tag, msg)
 			}
+		case keyDatastore:
+			msg, ok := w.checkDatastoreTag(tag.Options)
+			if !ok {
+				w.addFailure(f.Tag, msg)
+			}
 		case keyDefault:
 			if !w.typeValueMatch(f.Type, tag.Name) {
 				w.addFailure(f.Tag, "field's type and default value's type mismatch")
@@ -358,6 +364,21 @@ func (w lintStructTagRule) checkURLTag(options []string) (string, bool) {
 	return "", true
 }
 
+func (w lintStructTagRule) checkDatastoreTag(options []string) (string, bool) {
+	for _, opt := range options {
+		switch opt {
+		case "flatten", "noindex", "omitempty":
+		default:
+			if w.isUserDefined(keyDatastore, opt) {
+				continue
+			}
+			return fmt.Sprintf("unknown option '%s' in Datastore 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 9ce43e6..3c4eab1 100644
--- a/test/struct_tag_test.go
+++ b/test/struct_tag_test.go
@@ -13,7 +13,12 @@ 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", "url,myURLOption"},
+		Arguments: []any{
+			"json,inline,outline",
+			"bson,gnu",
+			"url,myURLOption",
+			"datastore,myDatastoreOption",
+		},
 	})
 }
 
diff --git a/testdata/struct_tag.go b/testdata/struct_tag.go
index 47fb092..ec5e42e 100644
--- a/testdata/struct_tag.go
+++ b/testdata/struct_tag.go
@@ -136,3 +136,8 @@ type RequestQueryOption struct {
 	Archived             bool     `url:"archived,myURLOption"` // MATCH /unknown option 'myURLOption' in URL tag/
 	IDProperty           string   `url:"idProperty,omitempty"`
 }
+
+type Fields struct {
+	Field      string `datastore:",noindex,flatten,omitempty"`
+	OtherField string `datastore:",unknownOption"` // MATCH /unknown option 'unknownOption' in Datastore tag/
+}
diff --git a/testdata/struct_tag_user_options.go b/testdata/struct_tag_user_options.go
index 31bdbfc..bbdabc3 100644
--- a/testdata/struct_tag_user_options.go
+++ b/testdata/struct_tag_user_options.go
@@ -19,3 +19,8 @@ type RequestQueryOptions struct {
 	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/
+}