1
2
3
4
5 package modernize
6
7 import (
8 "go/ast"
9 "go/types"
10 "reflect"
11 "strconv"
12 "strings"
13 "sync"
14
15 "golang.org/x/tools/go/analysis"
16 "golang.org/x/tools/go/analysis/passes/inspect"
17 "golang.org/x/tools/internal/analysis/analyzerutil"
18 "golang.org/x/tools/internal/astutil"
19 "golang.org/x/tools/internal/versions"
20 )
21
22 var OmitZeroAnalyzer = &analysis.Analyzer{
23 Name: "omitzero",
24 Doc: analyzerutil.MustExtractDoc(doc, "omitzero"),
25 Requires: []*analysis.Analyzer{inspect.Analyzer},
26 Run: omitzero,
27 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#omitzero",
28 }
29
30
31
32
33
34 func omitzero(pass *analysis.Pass) (any, error) {
35
36
37
38
39 usesKubebuilder := sync.OnceValue[bool](func() bool {
40 for _, file := range pass.Files {
41 for _, comment := range file.Comments {
42 if strings.Contains(comment.Text(), "+kubebuilder:") {
43 return true
44 }
45 }
46 }
47 return false
48 })
49
50 checkField := func(field *ast.Field) {
51 typ := pass.TypesInfo.TypeOf(field.Type)
52 _, ok := typ.Underlying().(*types.Struct)
53 if !ok {
54
55 return
56 }
57 tag := field.Tag
58 if tag == nil {
59
60 return
61 }
62
63 tagconv, _ := strconv.Unquote(tag.Value)
64 match := omitemptyRegex.FindStringSubmatchIndex(tagconv)
65 if match == nil {
66
67 return
68 }
69 omitEmpty, err := astutil.RangeInStringLiteral(field.Tag, match[2], match[3])
70 if err != nil {
71 return
72 }
73 var remove analysis.Range = omitEmpty
74
75 jsonTag := reflect.StructTag(tagconv).Get("json")
76 if jsonTag == ",omitempty" {
77
78 if match[1]-match[0] == len(tagconv) {
79 remove = field.Tag
80 } else {
81
82 remove, err = astutil.RangeInStringLiteral(field.Tag, match[0], match[1])
83 if err != nil {
84 return
85 }
86 }
87 }
88
89
90
91
92 if usesKubebuilder() {
93 return
94 }
95
96 pass.Report(analysis.Diagnostic{
97 Pos: field.Tag.Pos(),
98 End: field.Tag.End(),
99 Message: "Omitempty has no effect on nested struct fields",
100 SuggestedFixes: []analysis.SuggestedFix{
101 {
102 Message: "Remove redundant omitempty tag",
103 TextEdits: []analysis.TextEdit{
104 {
105 Pos: remove.Pos(),
106 End: remove.End(),
107 },
108 },
109 },
110 {
111 Message: "Replace omitempty with omitzero (behavior change)",
112 TextEdits: []analysis.TextEdit{
113 {
114 Pos: omitEmpty.Pos(),
115 End: omitEmpty.End(),
116 NewText: []byte(",omitzero"),
117 },
118 },
119 },
120 }})
121 }
122
123 for curFile := range filesUsingGoVersion(pass, versions.Go1_24) {
124 for curStruct := range curFile.Preorder((*ast.StructType)(nil)) {
125 for _, curField := range curStruct.Node().(*ast.StructType).Fields.List {
126 checkField(curField)
127 }
128 }
129 }
130
131 return nil, nil
132 }
133
View as plain text