Skip to content

Commit 8ece20b

Browse files
authored
feature: add support for omitzero in rule struct-tag (#1238)
1 parent a59a228 commit 8ece20b

File tree

6 files changed

+154
-8
lines changed

6 files changed

+154
-8
lines changed

lint/package.go

+6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ var (
3838
go115 = goversion.Must(goversion.NewVersion("1.15"))
3939
go121 = goversion.Must(goversion.NewVersion("1.21"))
4040
go122 = goversion.Must(goversion.NewVersion("1.22"))
41+
go124 = goversion.Must(goversion.NewVersion("1.24"))
4142
)
4243

4344
// Files return package's files.
@@ -210,6 +211,11 @@ func (p *Package) IsAtLeastGo122() bool {
210211
return p.goVersion.GreaterThanOrEqual(go122)
211212
}
212213

214+
// IsAtLeastGo124 returns true if the Go version for this package is 1.24 or higher, false otherwise
215+
func (p *Package) IsAtLeastGo124() bool {
216+
return p.goVersion.GreaterThanOrEqual(go124)
217+
}
218+
213219
func getSortableMethodFlagForFunction(fn *ast.FuncDecl) sortableMethodsFlags {
214220
switch {
215221
case astutils.FuncSignatureIs(fn, "Len", []string{}, []string{"int"}):

rule/struct_tag.go

+13-6
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ func (r *StructTagRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure
5454
}
5555

5656
w := lintStructTagRule{
57-
onFailure: onFailure,
58-
userDefined: r.userDefined,
57+
onFailure: onFailure,
58+
userDefined: r.userDefined,
59+
isAtLeastGo124: file.Pkg.IsAtLeastGo124(),
5960
}
6061

6162
ast.Walk(w, file.AST)
@@ -69,10 +70,11 @@ func (*StructTagRule) Name() string {
6970
}
7071

7172
type lintStructTagRule struct {
72-
onFailure func(lint.Failure)
73-
userDefined map[string][]string // map: key -> []option
74-
usedTagNbr map[int]bool // list of used tag numbers
75-
usedTagName map[string]bool // list of used tag keys
73+
onFailure func(lint.Failure)
74+
userDefined map[string][]string // map: key -> []option
75+
usedTagNbr map[int]bool // list of used tag numbers
76+
usedTagName map[string]bool // list of used tag keys
77+
isAtLeastGo124 bool
7678
}
7779

7880
func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor {
@@ -281,6 +283,11 @@ func (w lintStructTagRule) checkJSONTag(name string, options []string) (string,
281283
if name != "-" {
282284
return "option can not be empty in JSON tag", false
283285
}
286+
case "omitzero":
287+
if w.isAtLeastGo124 {
288+
continue
289+
}
290+
fallthrough
284291
default:
285292
if w.isUserDefined(keyJSON, opt) {
286293
continue

test/struct_tag_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,7 @@ func TestStructTagWithUserOptions(t *testing.T) {
1616
Arguments: []any{"json,inline,outline", "bson,gnu"},
1717
})
1818
}
19+
20+
func TestStructTagAfterGo1_24(t *testing.T) {
21+
testRule(t, "go1.24/struct_tag", &rule.StructTagRule{})
22+
}

testdata/go1.24/go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/mgechev/revive/testdata
2+
3+
go 1.24

testdata/go1.24/struct_tag.go

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package fixtures
2+
3+
import "time"
4+
5+
type decodeAndValidateRequest struct {
6+
// BEAWRE : the flag of URLParam should match the const string URLParam
7+
URLParam string `json:"-" path:"url_param" validate:"numeric"`
8+
Text string `json:"text" validate:"max=10"`
9+
DefaultInt int `json:"defaultInt" default:"10.0"` // MATCH /field's type and default value's type mismatch/
10+
DefaultInt2 int `json:"defaultInt2" default:"10"`
11+
// MATCH:12 /unknown option 'inline' in JSON tag/
12+
DefaultInt3 int `json:"defaultInt2,inline" default:"11"` // MATCH /duplicate tag name: 'defaultInt2'/
13+
DefaultString string `json:"defaultString" default:"foo"`
14+
DefaultBool bool `json:"defaultBool" default:"trues"` // MATCH /field's type and default value's type mismatch/
15+
DefaultBool2 bool `json:"defaultBool2" default:"true"`
16+
DefaultBool3 bool `json:"defaultBool3" default:"false"`
17+
DefaultFloat float64 `json:"defaultFloat" default:"f10.0"` // MATCH /field's type and default value's type mismatch/
18+
DefaultFloat2 float64 `json:"defaultFloat2" default:"10.0"`
19+
MandatoryStruct mandatoryStruct `json:"mandatoryStruct" required:"trues"` // MATCH /required should be 'true' or 'false'/
20+
MandatoryStruct2 mandatoryStruct `json:"mandatoryStruct2" required:"true"`
21+
MandatoryStruct4 mandatoryStruct `json:"mandatoryStruct4" required:"false"`
22+
OptionalStruct *optionalStruct `json:"optionalStruct,omitempty"`
23+
OptionalQuery string `json:"-" querystring:"queryfoo"`
24+
optionalQuery string `json:"-" querystring:"queryfoo"` // MATCH /tag on not-exported field optionalQuery/
25+
// No-reg test for bug https://github.com/mgechev/revive/issues/208
26+
Tiret string `json:"-,"`
27+
BadTiret string `json:"other,"` // MATCH /option can not be empty in JSON tag/
28+
ForOmitzero string `json:"forOmitZero,omitzero"` // 'omitzero' is valid in go 1.24
29+
}
30+
31+
type RangeAllocation struct {
32+
metav1.TypeMeta `json:",inline"` // MATCH /unknown option 'inline' in JSON tag/
33+
metav1.ObjectMeta `json:"metadata,omitempty"`
34+
Range string `json:"range,flow"` // MATCH /unknown option 'flow' in JSON tag/
35+
Data []byte `json:"data,inline"` // MATCH /unknown option 'inline' in JSON tag/
36+
}
37+
38+
type RangeAllocation struct {
39+
metav1.TypeMeta `bson:",minsize"`
40+
metav1.ObjectMeta `bson:"metadata,omitempty"`
41+
Range string `bson:"range,flow"` // MATCH /unknown option 'flow' in BSON tag/
42+
Data []byte `bson:"data,inline"`
43+
}
44+
45+
type TestContextSpecificTags2 struct {
46+
A int `asn1:"explicit,tag:1"`
47+
B int `asn1:"tag:2"`
48+
S string `asn1:"tag:0,utf8"`
49+
Ints []int `asn1:"set"`
50+
Version int `asn1:"optional,explicit,default:0,tag:000"` // MATCH /duplicated tag number 0/
51+
Time time.Time `asn1:"explicit,tag:4,other"` // MATCH /unknown option 'other' in ASN1 tag/
52+
X int `asn1:"explicit,tag:invalid"` // MATCH /ASN1 tag must be a number, got 'invalid'/
53+
}
54+
55+
type VirtualMachineRelocateSpecDiskLocator struct {
56+
DynamicData
57+
58+
DiskId int32 `xml:"diskId,attr,cdata"`
59+
Datastore ManagedObjectReference `xml:"datastore,chardata,innerxml"`
60+
DiskMoveType string `xml:"diskMoveType,omitempty,comment"`
61+
DiskBackingInfo BaseVirtualDeviceBackingInfo `xml:"diskBackingInfo,omitempty,any"`
62+
Profile []BaseVirtualMachineProfileSpec `xml:"profile,omitempty,other"` // MATCH /unknown option 'other' in XML tag/
63+
}
64+
65+
type TestDuplicatedXMLTags struct {
66+
A int `xml:"a"`
67+
B int `xml:"a"` // MATCH /duplicate tag name: 'a'/
68+
C int `xml:"c"`
69+
}
70+
71+
type TestDuplicatedBSONTags struct {
72+
A int `bson:"b"`
73+
B int `bson:"b"` // MATCH /duplicate tag name: 'b'/
74+
C int `bson:"c"`
75+
}
76+
77+
type TestDuplicatedYAMLTags struct {
78+
A int `yaml:"b"`
79+
B int `yaml:"c"`
80+
C int `yaml:"c"` // MATCH /duplicate tag name: 'c'/
81+
}
82+
83+
type TestDuplicatedProtobufTags struct {
84+
A int `protobuf:"varint,name=b"`
85+
B int `protobuf:"varint,name=c"`
86+
C int `protobuf:"varint,name=c"` // MATCH /duplicate tag name: 'c'/
87+
}
88+
89+
// test case from
90+
// sigs.k8s.io/kustomize/api/types/helmchartargs.go
91+
92+
type HelmChartArgs struct {
93+
ChartName string `json:"chartName,omitempty" yaml:"chartName,omitempty"`
94+
ChartVersion string `json:"chartVersion,omitempty" yaml:"chartVersion,omitempty"`
95+
ChartRepoURL string `json:"chartRepoUrl,omitempty" yaml:"chartRepoUrl,omitempty"`
96+
ChartHome string `json:"chartHome,omitempty" yaml:"chartHome,omitempty"`
97+
ChartRepoName string `json:"chartRepoName,omitempty" yaml:"chartRepoName,omitempty"`
98+
HelmBin string `json:"helmBin,omitempty" yaml:"helmBin,omitempty"`
99+
HelmHome string `json:"helmHome,omitempty" yaml:"helmHome,omitempty"`
100+
Values string `json:"values,omitempty" yaml:"values,omitempty"`
101+
ValuesLocal map[string]interface{} `json:"valuesLocal,omitempty" yaml:"valuesLocal,omitempty"`
102+
ValuesMerge string `json:"valuesMerge,omitempty" yaml:"valuesMerge,omitempty"`
103+
ReleaseName string `json:"releaseName,omitempty" yaml:"releaseName,omitempty"`
104+
ReleaseNamespace string `json:"releaseNamespace,omitempty" yaml:"releaseNamespace,omitempty"`
105+
ExtraArgs []string `json:"extraArgs,omitempty" yaml:"extraArgs,omitempty"`
106+
}
107+
108+
// Test message for holding primitive types.
109+
type Simple struct {
110+
OBool *bool `protobuf:"varint,1,req,json=oBool"` // MATCH /protobuf tag lacks mandatory option 'name'/
111+
OInt32 *int32 `protobuf:"varint,2,opt,name=o_int32,jsonx=oInt32"` // MATCH /unknown option 'jsonx' in protobuf tag/
112+
OInt32Str *int32 `protobuf:"varint,3,rep,name=o_int32_str,name=oInt32Str"` // MATCH /protobuf tag has duplicated option 'name'/
113+
OInt64 *int64 `protobuf:"varint,4,opt,json=oInt64,name=o_int64,json=oInt64"` // MATCH /protobuf tag has duplicated option 'json'/
114+
OSint32Str *int32 `protobuf:"zigzag32,11,opt,name=o_sint32_str,json=oSint32Str"`
115+
OSint64Str *int64 `protobuf:"zigzag64,13,opt,name=o_sint32_str,json=oSint64Str"` // MATCH /duplicate tag name: 'o_sint32_str'/
116+
OFloat *float32 `protobuf:"fixed32,14,opt,name=o_float,json=oFloat"`
117+
ODouble *float64 `protobuf:"fixed64,014,opt,name=o_double,json=oDouble"` // MATCH /duplicated tag number 14/
118+
ODoubleStr *float64 `protobuf:"fixed6,17,opt,name=o_double_str,json=oDoubleStr"` // MATCH /invalid protobuf tag name 'fixed6'/
119+
OString *string `protobuf:"bytes,18,opt,name=o_string,json=oString"`
120+
OString2 *string `protobuf:"bytes,name=ameno"`
121+
OString3 *string `protobuf:"bytes,name=ameno"` // MATCH /duplicate tag name: 'ameno'/
122+
XXX_NoUnkeyedLiteral struct{} `json:"-"`
123+
XXX_unrecognized []byte `json:"-"`
124+
XXX_sizecache int32 `json:"-"`
125+
}

testdata/struct_tag.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ type decodeAndValidateRequest struct {
2323
OptionalQuery string `json:"-" querystring:"queryfoo"`
2424
optionalQuery string `json:"-" querystring:"queryfoo"` // MATCH /tag on not-exported field optionalQuery/
2525
// No-reg test for bug https://github.com/mgechev/revive/issues/208
26-
Tiret string `json:"-,"`
27-
BadTiret string `json:"other,"` // MATCH /option can not be empty in JSON tag/
26+
Tiret string `json:"-,"`
27+
BadTiret string `json:"other,"` // MATCH /option can not be empty in JSON tag/
28+
ForOmitzero string `json:"forOmitZero,omitzero"` // MATCH /unknown option 'omitzero' in JSON tag/
2829
}
2930

3031
type RangeAllocation struct {

0 commit comments

Comments
 (0)