5
5
"fmt"
6
6
"go/ast"
7
7
"go/token"
8
+ "slices"
8
9
"strings"
9
10
10
11
"github.com/JoelSpeed/kal/pkg/analysis/helpers/extractjsontags"
@@ -23,14 +24,17 @@ const (
23
24
patchStrategyMarkerID = "patchStrategy"
24
25
patchMergeKeyMarkerID = "patchMergeKey"
25
26
26
- listTypeMap = "listType=map"
27
- listMapKeyType = "listMapKey=type"
28
- patchStrategy = "patchStrategy=merge"
29
- patchMergeKeyType = "patchMergeKey=type"
30
- optional = "optional"
31
-
32
- expectedTagWithProtobufFmt = "`json:\" conditions,omitempty\" patchStrategy:\" merge\" patchMergeKey:\" type\" protobuf:\" bytes,%d,rep,name=conditions\" `"
33
- expectedTagWithoutProtobuf = "`json:\" conditions,omitempty\" patchStrategy:\" merge\" patchMergeKey:\" type\" `"
27
+ listTypeMap = "listType=map"
28
+ listMapKeyType = "listMapKey=type"
29
+ patchStrategy = "patchStrategy"
30
+ patchStrategyMerge = "patchStrategy=merge"
31
+ patchMergeKey = "patchMergeKey"
32
+ patchMergeKeyType = "patchMergeKey=type"
33
+ optional = "optional"
34
+
35
+ expectedJSONTag = "json:\" conditions,omitempty\" "
36
+ expectedPatchTag = "patchStrategy:\" merge\" patchMergeKey:\" type\" "
37
+ expectedProtobufTag = "protobuf:\" bytes,%d,rep,name=conditions\" "
34
38
)
35
39
36
40
func init () {
@@ -49,17 +53,19 @@ var (
49
53
)
50
54
51
55
type analyzer struct {
52
- isFirstField config.ConditionsFirstField
53
- useProtobuf config.ConditionsUseProtobuf
56
+ isFirstField config.ConditionsFirstField
57
+ useProtobuf config.ConditionsUseProtobuf
58
+ usePatchStrategy config.ConditionsUsePatchStrategy
54
59
}
55
60
56
61
// newAnalyzer creates a new analyzer.
57
62
func newAnalyzer (cfg config.ConditionsConfig ) * analysis.Analyzer {
58
63
defaultConfig (& cfg )
59
64
60
65
a := & analyzer {
61
- isFirstField : cfg .IsFirstField ,
62
- useProtobuf : cfg .UseProtobuf ,
66
+ isFirstField : cfg .IsFirstField ,
67
+ useProtobuf : cfg .UseProtobuf ,
68
+ usePatchStrategy : cfg .UsePatchStrategy ,
63
69
}
64
70
65
71
return & analysis.Analyzer {
@@ -117,16 +123,17 @@ func (a *analyzer) checkField(pass *analysis.Pass, index int, field *ast.Field,
117
123
return
118
124
}
119
125
120
- checkFieldMarkers (pass , field , fieldMarkers )
126
+ checkFieldMarkers (pass , field , fieldMarkers , a . usePatchStrategy )
121
127
a .checkFieldTags (pass , index , field )
122
128
123
129
if a .isFirstField == config .ConditionsFirstFieldWarn && index != 0 {
124
130
pass .Reportf (field .Pos (), "Conditions field must be the first field in the struct" )
125
131
}
126
132
}
127
133
128
- func checkFieldMarkers (pass * analysis.Pass , field * ast.Field , fieldMarkers markers.MarkerSet ) {
134
+ func checkFieldMarkers (pass * analysis.Pass , field * ast.Field , fieldMarkers markers.MarkerSet , usePatchStrategy config. ConditionsUsePatchStrategy ) {
129
135
missingMarkers := []string {}
136
+ additionalMarkers := []markers.Marker {}
130
137
131
138
if ! fieldMarkers .HasWithValue (listTypeMap ) {
132
139
missingMarkers = append (missingMarkers , listTypeMap )
@@ -136,37 +143,112 @@ func checkFieldMarkers(pass *analysis.Pass, field *ast.Field, fieldMarkers marke
136
143
missingMarkers = append (missingMarkers , listMapKeyType )
137
144
}
138
145
139
- if ! fieldMarkers .HasWithValue (patchStrategy ) {
140
- missingMarkers = append (missingMarkers , patchStrategy )
141
- }
142
-
143
- if ! fieldMarkers .HasWithValue (patchMergeKeyType ) {
144
- missingMarkers = append (missingMarkers , patchMergeKeyType )
145
- }
146
+ patchMissingMarkers , patchAdditionalMarkers := checkPatchStrategyMarkers (fieldMarkers , usePatchStrategy )
147
+ missingMarkers = append (missingMarkers , patchMissingMarkers ... )
148
+ additionalMarkers = append (additionalMarkers , patchAdditionalMarkers ... )
146
149
147
150
if ! fieldMarkers .Has (optional ) {
148
151
missingMarkers = append (missingMarkers , optional )
149
152
}
150
153
151
154
if len (missingMarkers ) != 0 {
152
- pass .Report (analysis.Diagnostic {
153
- Pos : field .Pos (),
154
- End : field .End (),
155
- Message : "Conditions field is missing the following markers: " + strings .Join (missingMarkers , ", " ),
156
- SuggestedFixes : []analysis.SuggestedFix {
157
- {
158
- Message : "Add missing markers" ,
159
- TextEdits : []analysis.TextEdit {
160
- {
161
- Pos : field .Pos (),
162
- End : token .NoPos ,
163
- NewText : getNewMarkers (missingMarkers ),
164
- },
155
+ reportMissingMarkers (pass , field , missingMarkers , usePatchStrategy )
156
+ }
157
+
158
+ if len (additionalMarkers ) != 0 {
159
+ reportAdditionalMarkers (pass , field , additionalMarkers )
160
+ }
161
+ }
162
+
163
+ func checkPatchStrategyMarkers (fieldMarkers markers.MarkerSet , usePatchStrategy config.ConditionsUsePatchStrategy ) ([]string , []markers.Marker ) {
164
+ missingMarkers := []string {}
165
+ additionalMarkers := []markers.Marker {}
166
+
167
+ switch usePatchStrategy {
168
+ case config .ConditionsUsePatchStrategySuggestFix , config .ConditionsUsePatchStrategyWarn :
169
+ if ! fieldMarkers .HasWithValue (patchStrategyMerge ) {
170
+ missingMarkers = append (missingMarkers , patchStrategyMerge )
171
+ }
172
+
173
+ if ! fieldMarkers .HasWithValue (patchMergeKeyType ) {
174
+ missingMarkers = append (missingMarkers , patchMergeKeyType )
175
+ }
176
+ case config .ConditionsUsePatchStrategyIgnore :
177
+ // If it's there, we don't care.
178
+ case config .ConditionsUsePatchStrategyForbid :
179
+ if fieldMarkers .HasWithValue (patchStrategyMerge ) {
180
+ additionalMarkers = append (additionalMarkers , fieldMarkers [patchStrategy ]... )
181
+ }
182
+
183
+ if fieldMarkers .HasWithValue (patchMergeKeyType ) {
184
+ additionalMarkers = append (additionalMarkers , fieldMarkers [patchMergeKey ]... )
185
+ }
186
+ default :
187
+ panic ("unexpected usePatchStrategy value" )
188
+ }
189
+
190
+ return missingMarkers , additionalMarkers
191
+ }
192
+
193
+ func reportMissingMarkers (pass * analysis.Pass , field * ast.Field , missingMarkers []string , usePatchStrategy config.ConditionsUsePatchStrategy ) {
194
+ suggestedFixes := []analysis.SuggestedFix {}
195
+
196
+ // If patch strategy is warn, and the only markers in the list are patchStrategy and patchMergeKeyType, we don't need to suggest a fix.
197
+ if usePatchStrategy != config .ConditionsUsePatchStrategyWarn || slices .ContainsFunc [[]string , string ](missingMarkers , func (marker string ) bool {
198
+ switch marker {
199
+ case patchStrategyMerge , patchMergeKeyType :
200
+ return false
201
+ default :
202
+ return true
203
+ }
204
+ }) {
205
+ suggestedFixes = []analysis.SuggestedFix {
206
+ {
207
+ Message : "Add missing markers" ,
208
+ TextEdits : []analysis.TextEdit {
209
+ {
210
+ Pos : field .Pos (),
211
+ End : token .NoPos ,
212
+ NewText : getNewMarkers (missingMarkers ),
165
213
},
166
214
},
167
215
},
216
+ }
217
+ }
218
+
219
+ pass .Report (analysis.Diagnostic {
220
+ Pos : field .Pos (),
221
+ End : field .End (),
222
+ Message : "Conditions field is missing the following markers: " + strings .Join (missingMarkers , ", " ),
223
+ SuggestedFixes : suggestedFixes ,
224
+ })
225
+ }
226
+
227
+ func reportAdditionalMarkers (pass * analysis.Pass , field * ast.Field , additionalMarkers []markers.Marker ) {
228
+ suggestedFixes := []analysis.SuggestedFix {}
229
+ additionalMarkerValues := []string {}
230
+
231
+ for _ , marker := range additionalMarkers {
232
+ additionalMarkerValues = append (additionalMarkerValues , marker .String ())
233
+
234
+ suggestedFixes = append (suggestedFixes , analysis.SuggestedFix {
235
+ Message : "Remove additional marker" ,
236
+ TextEdits : []analysis.TextEdit {
237
+ {
238
+ Pos : marker .Pos ,
239
+ End : marker .End + 1 , // Add 1 to position to include the new line
240
+ NewText : nil ,
241
+ },
242
+ },
168
243
})
169
244
}
245
+
246
+ pass .Report (analysis.Diagnostic {
247
+ Pos : field .Pos (),
248
+ End : field .End (),
249
+ Message : "Conditions field has the following additional markers: " + strings .Join (additionalMarkerValues , ", " ),
250
+ SuggestedFixes : suggestedFixes ,
251
+ })
170
252
}
171
253
172
254
func getNewMarkers (missingMarkers []string ) []byte {
@@ -181,7 +263,7 @@ func getNewMarkers(missingMarkers []string) []byte {
181
263
182
264
func (a * analyzer ) checkFieldTags (pass * analysis.Pass , index int , field * ast.Field ) {
183
265
if field .Tag == nil {
184
- expectedTag := getExpectedTag (a .useProtobuf , a .isFirstField , index )
266
+ expectedTag := getExpectedTag (a .usePatchStrategy , a . useProtobuf , a .isFirstField , index )
185
267
186
268
pass .Report (analysis.Diagnostic {
187
269
Pos : field .Pos (),
@@ -204,9 +286,9 @@ func (a *analyzer) checkFieldTags(pass *analysis.Pass, index int, field *ast.Fie
204
286
return
205
287
}
206
288
207
- asExpected , shouldFix := tagIsAsExpected (field .Tag .Value , a .useProtobuf , a .isFirstField , index )
289
+ asExpected , shouldFix := tagIsAsExpected (field .Tag .Value , a .usePatchStrategy , a . useProtobuf , a .isFirstField , index )
208
290
if ! asExpected {
209
- expectedTag := getExpectedTag (a .useProtobuf , a .isFirstField , index )
291
+ expectedTag := getExpectedTag (a .usePatchStrategy , a . useProtobuf , a .isFirstField , index )
210
292
211
293
if ! shouldFix {
212
294
pass .Reportf (field .Tag .ValuePos , "Conditions field has incorrect tags, should be: %s" , expectedTag )
@@ -232,27 +314,63 @@ func (a *analyzer) checkFieldTags(pass *analysis.Pass, index int, field *ast.Fie
232
314
}
233
315
}
234
316
235
- func getExpectedTag (useProtobuf config.ConditionsUseProtobuf , isFirstField config.ConditionsFirstField , index int ) string {
317
+ func getExpectedTag (usePatchStrategy config.ConditionsUsePatchStrategy , useProtobuf config.ConditionsUseProtobuf , isFirstField config.ConditionsFirstField , index int ) string {
318
+ expectedTag := fmt .Sprintf ("`%s" , expectedJSONTag )
319
+
320
+ if usePatchStrategy == config .ConditionsUsePatchStrategySuggestFix || usePatchStrategy == config .ConditionsUsePatchStrategyWarn {
321
+ expectedTag += fmt .Sprintf (" %s" , expectedPatchTag )
322
+ }
323
+
236
324
if useProtobuf == config .ConditionsUseProtobufSuggestFix || useProtobuf == config .ConditionsUseProtobufWarn {
237
- i := 1
238
- if isFirstField == config .ConditionsFirstFieldIgnore {
239
- i = index + 1
240
- }
325
+ expectedTag += fmt .Sprintf (" %s" , getExpectedProtobufTag (isFirstField , index ))
326
+ }
241
327
242
- return fmt .Sprintf (expectedTagWithProtobufFmt , i )
328
+ expectedTag += "`"
329
+
330
+ return expectedTag
331
+ }
332
+
333
+ func getExpectedProtobufTag (isFirstField config.ConditionsFirstField , index int ) string {
334
+ i := 1
335
+ if isFirstField == config .ConditionsFirstFieldIgnore {
336
+ i = index + 1
243
337
}
244
338
245
- return expectedTagWithoutProtobuf
339
+ return fmt .Sprintf (expectedProtobufTag , i )
340
+ }
341
+
342
+ func tagIsAsExpected (tag string , usePatchStrategy config.ConditionsUsePatchStrategy , useProtobuf config.ConditionsUseProtobuf , isFirstField config.ConditionsFirstField , index int ) (bool , bool ) {
343
+ patchTagCorrect , patchShouldSuggestFix := patchStrategyTagIsAsExpected (tag , usePatchStrategy )
344
+ protoTagCorrect , protoShouldSuggestFix := protobufTagIsAsExpected (tag , useProtobuf , isFirstField , index )
345
+
346
+ return patchTagCorrect && protoTagCorrect , patchShouldSuggestFix || protoShouldSuggestFix
347
+ }
348
+
349
+ func patchStrategyTagIsAsExpected (tag string , usePatchStrategy config.ConditionsUsePatchStrategy ) (bool , bool ) {
350
+ switch usePatchStrategy {
351
+ case config .ConditionsUsePatchStrategySuggestFix :
352
+ return strings .Contains (tag , expectedPatchTag ), true
353
+ case config .ConditionsUsePatchStrategyWarn :
354
+ return strings .Contains (tag , expectedPatchTag ), false
355
+ case config .ConditionsUsePatchStrategyIgnore :
356
+ return true , false
357
+ case config .ConditionsUsePatchStrategyForbid :
358
+ return ! strings .Contains (tag , expectedPatchTag ), true
359
+ default :
360
+ panic ("unexpected usePatchStrategy value" )
361
+ }
246
362
}
247
363
248
- func tagIsAsExpected (tag string , useProtobuf config.ConditionsUseProtobuf , isFirstField config.ConditionsFirstField , index int ) (bool , bool ) {
364
+ func protobufTagIsAsExpected (tag string , useProtobuf config.ConditionsUseProtobuf , isFirstField config.ConditionsFirstField , index int ) (bool , bool ) {
249
365
switch useProtobuf {
250
366
case config .ConditionsUseProtobufSuggestFix :
251
- return tag == getExpectedTag ( config . ConditionsUseProtobufSuggestFix , isFirstField , index ), true
367
+ return strings . Contains ( tag , getExpectedProtobufTag ( isFirstField , index ) ), true
252
368
case config .ConditionsUseProtobufWarn :
253
- return tag == getExpectedTag ( config . ConditionsUseProtobufWarn , isFirstField , index ), false
369
+ return strings . Contains ( tag , getExpectedProtobufTag ( isFirstField , index ) ), false
254
370
case config .ConditionsUseProtobufIgnore :
255
- return tag == getExpectedTag (config .ConditionsUseProtobufIgnore , isFirstField , index ) || tag == getExpectedTag (config .ConditionsUseProtobufSuggestFix , isFirstField , index ), true
371
+ return true , false
372
+ case config .ConditionsUseProtobufForbid :
373
+ return ! strings .Contains (tag , getExpectedProtobufTag (isFirstField , index )), true
256
374
default :
257
375
panic ("unexpected useProtobuf value" )
258
376
}
@@ -309,4 +427,8 @@ func defaultConfig(cfg *config.ConditionsConfig) {
309
427
if cfg .UseProtobuf == "" {
310
428
cfg .UseProtobuf = config .ConditionsUseProtobufSuggestFix
311
429
}
430
+
431
+ if cfg .UsePatchStrategy == "" {
432
+ cfg .UsePatchStrategy = config .ConditionsUsePatchStrategySuggestFix
433
+ }
312
434
}
0 commit comments