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,101 @@ 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
- }
146
+ switch usePatchStrategy {
147
+ case config .ConditionsUsePatchStrategySuggestFix , config .ConditionsUsePatchStrategyWarn :
148
+ if ! fieldMarkers .HasWithValue (patchStrategyMerge ) {
149
+ missingMarkers = append (missingMarkers , patchStrategyMerge )
150
+ }
142
151
143
- if ! fieldMarkers .HasWithValue (patchMergeKeyType ) {
144
- missingMarkers = append (missingMarkers , patchMergeKeyType )
152
+ if ! fieldMarkers .HasWithValue (patchMergeKeyType ) {
153
+ missingMarkers = append (missingMarkers , patchMergeKeyType )
154
+ }
155
+ case config .ConditionsUsePatchStrategyIgnore :
156
+ // If it's there, we don't care.
157
+ case config .ConditionsUsePatchStrategyForbid :
158
+ if fieldMarkers .HasWithValue (patchStrategyMerge ) {
159
+ additionalMarkers = append (additionalMarkers , fieldMarkers [patchStrategy ]... )
160
+ }
161
+
162
+ if fieldMarkers .HasWithValue (patchMergeKeyType ) {
163
+ additionalMarkers = append (additionalMarkers , fieldMarkers [patchMergeKey ]... )
164
+ }
165
+ default :
166
+ panic ("unexpected usePatchStrategy value" )
145
167
}
146
168
147
169
if ! fieldMarkers .Has (optional ) {
148
170
missingMarkers = append (missingMarkers , optional )
149
171
}
150
172
151
173
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
- },
174
+ reportMissingMarkers (pass , field , missingMarkers , usePatchStrategy )
175
+ }
176
+
177
+ if len (additionalMarkers ) != 0 {
178
+ reportAdditionalMarkers (pass , field , additionalMarkers )
179
+ }
180
+ }
181
+
182
+ func reportMissingMarkers (pass * analysis.Pass , field * ast.Field , missingMarkers []string , usePatchStrategy config.ConditionsUsePatchStrategy ) {
183
+ suggestedFixes := []analysis.SuggestedFix {}
184
+
185
+ // If patch strategy is warn, and the only markers in the list are patchStrategy and patchMergeKeyType, we don't need to suggest a fix.
186
+ if usePatchStrategy != config .ConditionsUsePatchStrategyWarn || slices .ContainsFunc [[]string , string ](missingMarkers , func (marker string ) bool {
187
+ switch marker {
188
+ case patchStrategyMerge , patchMergeKeyType :
189
+ return false
190
+ default :
191
+ return true
192
+ }
193
+ }) {
194
+ suggestedFixes = []analysis.SuggestedFix {
195
+ {
196
+ Message : "Add missing markers" ,
197
+ TextEdits : []analysis.TextEdit {
198
+ {
199
+ Pos : field .Pos (),
200
+ End : token .NoPos ,
201
+ NewText : getNewMarkers (missingMarkers ),
165
202
},
166
203
},
167
204
},
205
+ }
206
+ }
207
+
208
+ pass .Report (analysis.Diagnostic {
209
+ Pos : field .Pos (),
210
+ End : field .End (),
211
+ Message : "Conditions field is missing the following markers: " + strings .Join (missingMarkers , ", " ),
212
+ SuggestedFixes : suggestedFixes ,
213
+ })
214
+ }
215
+
216
+ func reportAdditionalMarkers (pass * analysis.Pass , field * ast.Field , additionalMarkers []markers.Marker ) {
217
+ suggestedFixes := []analysis.SuggestedFix {}
218
+ additionalMarkerValues := []string {}
219
+
220
+ for _ , marker := range additionalMarkers {
221
+ additionalMarkerValues = append (additionalMarkerValues , marker .String ())
222
+
223
+ suggestedFixes = append (suggestedFixes , analysis.SuggestedFix {
224
+ Message : "Remove additional marker" ,
225
+ TextEdits : []analysis.TextEdit {
226
+ {
227
+ Pos : marker .Pos ,
228
+ End : marker .End + 1 , // Add 1 to position to include the new line
229
+ NewText : nil ,
230
+ },
231
+ },
168
232
})
169
233
}
234
+
235
+ pass .Report (analysis.Diagnostic {
236
+ Pos : field .Pos (),
237
+ End : field .End (),
238
+ Message : "Conditions field has the following additional markers: " + strings .Join (additionalMarkerValues , ", " ),
239
+ SuggestedFixes : suggestedFixes ,
240
+ })
170
241
}
171
242
172
243
func getNewMarkers (missingMarkers []string ) []byte {
@@ -181,7 +252,7 @@ func getNewMarkers(missingMarkers []string) []byte {
181
252
182
253
func (a * analyzer ) checkFieldTags (pass * analysis.Pass , index int , field * ast.Field ) {
183
254
if field .Tag == nil {
184
- expectedTag := getExpectedTag (a .useProtobuf , a .isFirstField , index )
255
+ expectedTag := getExpectedTag (a .usePatchStrategy , a . useProtobuf , a .isFirstField , index )
185
256
186
257
pass .Report (analysis.Diagnostic {
187
258
Pos : field .Pos (),
@@ -204,9 +275,9 @@ func (a *analyzer) checkFieldTags(pass *analysis.Pass, index int, field *ast.Fie
204
275
return
205
276
}
206
277
207
- asExpected , shouldFix := tagIsAsExpected (field .Tag .Value , a .useProtobuf , a .isFirstField , index )
278
+ asExpected , shouldFix := tagIsAsExpected (field .Tag .Value , a .usePatchStrategy , a . useProtobuf , a .isFirstField , index )
208
279
if ! asExpected {
209
- expectedTag := getExpectedTag (a .useProtobuf , a .isFirstField , index )
280
+ expectedTag := getExpectedTag (a .usePatchStrategy , a . useProtobuf , a .isFirstField , index )
210
281
211
282
if ! shouldFix {
212
283
pass .Reportf (field .Tag .ValuePos , "Conditions field has incorrect tags, should be: %s" , expectedTag )
@@ -232,30 +303,65 @@ func (a *analyzer) checkFieldTags(pass *analysis.Pass, index int, field *ast.Fie
232
303
}
233
304
}
234
305
235
- func getExpectedTag (useProtobuf config.ConditionsUseProtobuf , isFirstField config.ConditionsFirstField , index int ) string {
306
+ func getExpectedTag (usePatchStrategy config.ConditionsUsePatchStrategy , useProtobuf config.ConditionsUseProtobuf , isFirstField config.ConditionsFirstField , index int ) string {
307
+ expectedTag := fmt .Sprintf ("`%s" , expectedJSONTag )
308
+
309
+ if usePatchStrategy == config .ConditionsUsePatchStrategySuggestFix || usePatchStrategy == config .ConditionsUsePatchStrategyWarn {
310
+ expectedTag += fmt .Sprintf (" %s" , expectedPatchTag )
311
+ }
312
+
236
313
if useProtobuf == config .ConditionsUseProtobufSuggestFix || useProtobuf == config .ConditionsUseProtobufWarn {
237
- i := 1
238
- if isFirstField == config .ConditionsFirstFieldIgnore {
239
- i = index + 1
240
- }
314
+ expectedTag += fmt .Sprintf (" %s" , getExpectedProtobufTag (isFirstField , index ))
315
+ }
316
+
317
+ expectedTag += "`"
318
+
319
+ return expectedTag
320
+ }
241
321
242
- return fmt .Sprintf (expectedTagWithProtobufFmt , i )
322
+ func getExpectedProtobufTag (isFirstField config.ConditionsFirstField , index int ) string {
323
+ i := 1
324
+ if isFirstField == config .ConditionsFirstFieldIgnore {
325
+ i = index + 1
243
326
}
244
327
245
- return expectedTagWithoutProtobuf
328
+ return fmt . Sprintf ( expectedProtobufTag , i )
246
329
}
247
330
248
- func tagIsAsExpected (tag string , useProtobuf config.ConditionsUseProtobuf , isFirstField config.ConditionsFirstField , index int ) (bool , bool ) {
331
+ func tagIsAsExpected (tag string , usePatchStrategy config.ConditionsUsePatchStrategy , useProtobuf config.ConditionsUseProtobuf , isFirstField config.ConditionsFirstField , index int ) (bool , bool ) {
332
+ var tagIncorrect , shouldSuggestFix bool
333
+
334
+ switch usePatchStrategy {
335
+ case config .ConditionsUsePatchStrategySuggestFix :
336
+ tagIncorrect = tagIncorrect || ! strings .Contains (tag , expectedPatchTag )
337
+ shouldSuggestFix = true
338
+ case config .ConditionsUsePatchStrategyWarn :
339
+ tagIncorrect = tagIncorrect || ! strings .Contains (tag , expectedPatchTag )
340
+ case config .ConditionsUsePatchStrategyIgnore :
341
+ // If it's there, we don't care.
342
+ case config .ConditionsUsePatchStrategyForbid :
343
+ tagIncorrect = tagIncorrect || strings .Contains (tag , expectedPatchTag )
344
+ shouldSuggestFix = true
345
+ default :
346
+ panic ("unexpected usePatchStrategy value" )
347
+ }
348
+
249
349
switch useProtobuf {
250
350
case config .ConditionsUseProtobufSuggestFix :
251
- return tag == getExpectedTag (config .ConditionsUseProtobufSuggestFix , isFirstField , index ), true
351
+ tagIncorrect = tagIncorrect || ! strings .Contains (tag , getExpectedProtobufTag (isFirstField , index ))
352
+ shouldSuggestFix = true
252
353
case config .ConditionsUseProtobufWarn :
253
- return tag == getExpectedTag ( config . ConditionsUseProtobufWarn , isFirstField , index ), false
354
+ tagIncorrect = tagIncorrect || ! strings . Contains ( tag , getExpectedProtobufTag ( isFirstField , index ))
254
355
case config .ConditionsUseProtobufIgnore :
255
- return tag == getExpectedTag (config .ConditionsUseProtobufIgnore , isFirstField , index ) || tag == getExpectedTag (config .ConditionsUseProtobufSuggestFix , isFirstField , index ), true
356
+ // If it's there, we don't care.
357
+ case config .ConditionsUseProtobufForbid :
358
+ tagIncorrect = tagIncorrect || strings .Contains (tag , getExpectedProtobufTag (isFirstField , index ))
359
+ shouldSuggestFix = true
256
360
default :
257
361
panic ("unexpected useProtobuf value" )
258
362
}
363
+
364
+ return ! tagIncorrect , shouldSuggestFix
259
365
}
260
366
261
367
func fieldIsCalledConditions (field * ast.Field ) bool {
@@ -309,4 +415,8 @@ func defaultConfig(cfg *config.ConditionsConfig) {
309
415
if cfg .UseProtobuf == "" {
310
416
cfg .UseProtobuf = config .ConditionsUseProtobufSuggestFix
311
417
}
418
+
419
+ if cfg .UsePatchStrategy == "" {
420
+ cfg .UsePatchStrategy = config .ConditionsUsePatchStrategySuggestFix
421
+ }
312
422
}
0 commit comments