Skip to content

Commit be4a0d9

Browse files
committed
Handle min properties for structs
1 parent a285833 commit be4a0d9

File tree

7 files changed

+208
-11
lines changed

7 files changed

+208
-11
lines changed

pkg/analysis/optionalfields/analyzer.go

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -246,26 +246,60 @@ func (a *analyzer) checkFieldPointersPreferenceWhenRequiredIdentObj(pass *analys
246246

247247
func (a *analyzer) checkFieldPointersPreferenceWhenRequiredStructType(pass *analysis.Pass, field *ast.Field, fieldName string, isStarExpr bool, typeExpr *ast.StructType, markersAccess markers.Markers, jsonTags extractjsontags.FieldTagInfo) {
248248
if a.omitEmptyPolicy == config.OptionalFieldsOmitEmptyPolicyIgnore && !jsonTags.OmitEmpty {
249-
if isStarExpr {
250-
reportShouldRemovePointer(pass, field, a.pointerPolicy, fieldName, "field %s is an optional struct without omitempty. It should not be a pointer")
251-
}
249+
a.checkFieldPointersPreferenceWhenRequiredStructTypeWithoutOmitEmpty(pass, field, fieldName, isStarExpr, typeExpr, markersAccess)
250+
return
251+
}
252+
253+
hasRequiredFields := structContainsRequiredFields(typeExpr, markersAccess)
252254

255+
hasMinimumProperties, err := structHasGreaterThanZeroMinProperties(typeExpr, markersAccess.StructMarkers(typeExpr))
256+
if err != nil {
257+
pass.Reportf(field.Pos(), "error checking struct for min properties: %v", err)
253258
return
254259
}
255260

256-
mustBePointer := structContainsRequiredFields(typeExpr, markersAccess)
257-
if mustBePointer == isStarExpr {
258-
// The field is already a pointer and should be a pointer, or it should not be a pointer and isn't a pointer.
261+
fieldHasMinimumProperties, err := structHasGreaterThanZeroMinProperties(typeExpr, markersAccess.FieldMarkers(field))
262+
if err != nil {
263+
pass.Reportf(field.Pos(), "error checking field for min properties: %v", err)
259264
return
260265
}
261266

262-
if mustBePointer {
267+
switch {
268+
case hasRequiredFields && !isStarExpr:
263269
reportShouldAddPointer(pass, field, a.pointerPolicy, fieldName, "field %s is optional, but contains required field(s) and should be a pointer")
264-
} else {
270+
case hasMinimumProperties && !isStarExpr, fieldHasMinimumProperties && !isStarExpr:
271+
reportShouldAddPointer(pass, field, a.pointerPolicy, fieldName, "field %s has a greater than zero minimum number of properties and should be a pointer")
272+
case isStarExpr && !hasRequiredFields && !hasMinimumProperties && !fieldHasMinimumProperties:
265273
reportShouldRemovePointer(pass, field, a.pointerPolicy, fieldName, "field %s is optional, and contains no required field(s) and does not need to be a pointer")
266274
}
267275
}
268276

277+
func (a *analyzer) checkFieldPointersPreferenceWhenRequiredStructTypeWithoutOmitEmpty(pass *analysis.Pass, field *ast.Field, fieldName string, isStarExpr bool, typeExpr *ast.StructType, markersAccess markers.Markers) {
278+
hasMinimumProperties, err := structHasGreaterThanZeroMinProperties(typeExpr, markersAccess.StructMarkers(typeExpr))
279+
if err != nil {
280+
pass.Reportf(field.Pos(), "error checking struct for min properties: %v", err)
281+
return
282+
}
283+
284+
fieldHasMinimumProperties, err := structHasGreaterThanZeroMinProperties(typeExpr, markersAccess.FieldMarkers(field))
285+
if err != nil {
286+
pass.Reportf(field.Pos(), "error checking field for min properties: %v", err)
287+
return
288+
}
289+
290+
switch {
291+
case hasMinimumProperties && isStarExpr, fieldHasMinimumProperties && isStarExpr:
292+
// The field is already a pointer and should be a pointer, so we don't need to do anything.
293+
case hasMinimumProperties && !isStarExpr:
294+
reportShouldAddPointer(pass, field, a.pointerPolicy, fieldName, "field %s has a greater than zero minimum number of properties and should be a pointer")
295+
case fieldHasMinimumProperties && !isStarExpr:
296+
reportShouldRemoveMarker(pass, field, markersAccess.FieldMarkers(field).Get(minPropertiesMarker)[0], fieldName, "field %s has a greater than zero minimum number of properties without omitempty. The minimum number of properties should be removed.")
297+
case isStarExpr:
298+
// The field is a pointer and should not be a pointer, so we need to remove the pointer.
299+
reportShouldRemovePointer(pass, field, a.pointerPolicy, fieldName, "field %s is an optional struct without omitempty. It should not be a pointer")
300+
}
301+
}
302+
269303
func (a *analyzer) checkFieldPointersPreferenceWhenRequiredString(pass *analysis.Pass, field *ast.Field, fieldName string, isStarExpr bool, markersAccess markers.Markers, jsonTags extractjsontags.FieldTagInfo) {
270304
fieldMarkers := markersAccess.FieldMarkers(field)
271305

@@ -626,3 +660,24 @@ func getMarkerIntegerValue(marker markers.Marker) (int, error) {
626660

627661
return value, nil
628662
}
663+
664+
func structHasGreaterThanZeroMinProperties(structType *ast.StructType, structMarkers markers.MarkerSet) (bool, error) {
665+
if structType == nil {
666+
return false, nil
667+
}
668+
669+
if structMarkers.Has(minPropertiesMarker) {
670+
for _, marker := range structMarkers.Get(minPropertiesMarker) {
671+
markerValue, err := getMarkerIntegerValue(marker)
672+
if err != nil {
673+
return false, fmt.Errorf("error getting marker value: %w", err)
674+
}
675+
676+
if markerValue > 0 {
677+
return true, nil
678+
}
679+
}
680+
}
681+
682+
return false, nil
683+
}

pkg/analysis/optionalfields/testdata/src/a/a.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ type A struct {
4141
// +optional
4242
NonOmittedStruct B `json:"nonOmittedStruct"` // want "field NonOmittedStruct is optional and should be a pointer" "field NonOmittedStruct is optional and should be omitempty"
4343

44+
// structWithMinProperties is a struct field with a minimum number of properties.
45+
// +kubebuilder:validation:MinProperties=1
46+
// +optional
47+
StructWithMinProperties B `json:"structWithMinProperties,omitempty"` // want "field StructWithMinProperties is optional and should be a pointer"
48+
49+
// structWithMinPropertiesOnStruct is a struct field with a minimum number of properties on the struct.
50+
// +optional
51+
StructWithMinPropertiesOnStruct D `json:"structWithMinPropertiesOnStruct,omitempty"` // want "field StructWithMinPropertiesOnStruct is optional and should be a pointer"
52+
4453
// slice is a slice field.
4554
// +optional
4655
Slice []string `json:"slice,omitempty"`
@@ -67,3 +76,15 @@ type B struct {
6776
// +optional
6877
PointerString *string `json:"pointerString,omitempty"`
6978
}
79+
80+
// +kubebuilder:validation:MinProperties=1
81+
type D struct {
82+
// string is a string field.
83+
// +optional
84+
String *string `json:"string,omitempty"`
85+
86+
// stringWithMinLength1 with minimum length is a string field.
87+
// +kubebuilder:validation:MinLength=1
88+
// +optional
89+
StringWithMinLength1 *string `json:"stringWithMinLength1,omitempty"`
90+
}

pkg/analysis/optionalfields/testdata/src/a/a.go.golden

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ type A struct {
4141
// +optional
4242
NonOmittedStruct *B `json:"nonOmittedStruct,omitempty"` // want "field NonOmittedStruct is optional and should be a pointer" "field NonOmittedStruct is optional and should be omitempty"
4343

44+
// structWithMinProperties is a struct field with a minimum number of properties.
45+
// +kubebuilder:validation:MinProperties=1
46+
// +optional
47+
StructWithMinProperties *B `json:"structWithMinProperties,omitempty"` // want "field StructWithMinProperties is optional and should be a pointer"
48+
49+
// structWithMinPropertiesOnStruct is a struct field with a minimum number of properties on the struct.
50+
// +optional
51+
StructWithMinPropertiesOnStruct *D `json:"structWithMinPropertiesOnStruct,omitempty"` // want "field StructWithMinPropertiesOnStruct is optional and should be a pointer"
52+
4453
// slice is a slice field.
4554
// +optional
4655
Slice []string `json:"slice,omitempty"`
@@ -67,3 +76,15 @@ type B struct {
6776
// +optional
6877
PointerString *string `json:"pointerString,omitempty"`
6978
}
79+
80+
// +kubebuilder:validation:MinProperties=1
81+
type D struct {
82+
// string is a string field.
83+
// +optional
84+
String *string `json:"string,omitempty"`
85+
86+
// stringWithMinLength1 with minimum length is a string field.
87+
// +kubebuilder:validation:MinLength=1
88+
// +optional
89+
StringWithMinLength1 *string `json:"stringWithMinLength1,omitempty"`
90+
}

pkg/analysis/optionalfields/testdata/src/b/a.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,15 @@ type A struct {
166166
// +optional
167167
StructWithOptionalFields B `json:"structWithOptionalFields,omitempty"`
168168

169+
// structWithMinProperties is a struct field with a minimum number of properties.
170+
// +kubebuilder:validation:MinProperties=1
171+
// +optional
172+
StructWithMinProperties B `json:"structWithMinProperties,omitempty"` // want "field StructWithMinProperties has a greater than zero minimum number of properties and should be a pointer"
173+
174+
// structWithMinPropertiesOnStruct is a struct field with a minimum number of properties on the struct.
175+
// +optional
176+
StructWithMinPropertiesOnStruct D `json:"structWithMinPropertiesOnStruct,omitempty"` // want "field StructWithMinPropertiesOnStruct has a greater than zero minimum number of properties and should be a pointer"
177+
169178
// structWithRequiredFields is a struct field.
170179
// +optional
171180
StructWithRequiredFields C `json:"structWithRequiredFields,omitempty"` // want "field StructWithRequiredFields is optional, but contains required field\\(s\\) and should be a pointer"
@@ -219,3 +228,15 @@ type C struct {
219228
// +required
220229
String string `json:"string"`
221230
}
231+
232+
// +kubebuilder:validation:MinProperties=1
233+
type D struct {
234+
// string is a string field.
235+
// +optional
236+
String string `json:"string,omitempty"` // want "field String is an optional string and does not have a minimum length. Either set a minimum length or make String a pointer where the difference between omitted and the empty string is significant"
237+
238+
// stringWithMinLength1 with minimum length is a string field.
239+
// +kubebuilder:validation:MinLength=1
240+
// +optional
241+
StringWithMinLength1 string `json:"stringWithMinLength1,omitempty"`
242+
}

pkg/analysis/optionalfields/testdata/src/b/a.go.golden

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ type A struct {
4848
// +optional
4949
PointerIntWithPositiveMaximumValue *int `json:"pointerIntWithPositiveMaximumValue,omitempty"` // want "field PointerIntWithPositiveMaximumValue has a positive maximum value and does not have a minimum value. A minimum value should be set"
5050

51-
5251
// pointerIntWithRange is a pointer int field with a range of values including 0.
5352
// +kubebuilder:validation:Minimum=-10
5453
// +kubebuilder:validation:Maximum=10
@@ -168,6 +167,15 @@ type A struct {
168167
// +optional
169168
StructWithOptionalFields B `json:"structWithOptionalFields,omitempty"`
170169

170+
// structWithMinProperties is a struct field with a minimum number of properties.
171+
// +kubebuilder:validation:MinProperties=1
172+
// +optional
173+
StructWithMinProperties *B `json:"structWithMinProperties,omitempty"` // want "field StructWithMinProperties has a greater than zero minimum number of properties and should be a pointer"
174+
175+
// structWithMinPropertiesOnStruct is a struct field with a minimum number of properties on the struct.
176+
// +optional
177+
StructWithMinPropertiesOnStruct *D `json:"structWithMinPropertiesOnStruct,omitempty"` // want "field StructWithMinPropertiesOnStruct has a greater than zero minimum number of properties and should be a pointer"
178+
171179
// structWithRequiredFields is a struct field.
172180
// +optional
173181
StructWithRequiredFields *C `json:"structWithRequiredFields,omitempty"` // want "field StructWithRequiredFields is optional, but contains required field\\(s\\) and should be a pointer"
@@ -221,3 +229,15 @@ type C struct {
221229
// +required
222230
String string `json:"string"`
223231
}
232+
233+
// +kubebuilder:validation:MinProperties=1
234+
type D struct {
235+
// string is a string field.
236+
// +optional
237+
String string `json:"string,omitempty"` // want "field String is an optional string and does not have a minimum length. Either set a minimum length or make String a pointer where the difference between omitted and the empty string is significant"
238+
239+
// stringWithMinLength1 with minimum length is a string field.
240+
// +kubebuilder:validation:MinLength=1
241+
// +optional
242+
StringWithMinLength1 string `json:"stringWithMinLength1,omitempty"`
243+
}

pkg/analysis/optionalfields/testdata/src/c/a.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,24 @@ type A struct {
298298
// +optional
299299
StructWithOptionalFieldsWithoutOmitEmpty B `json:"structWithOptionalFieldsWithoutOmitEmpty"`
300300

301+
// structWithMinProperties is a struct field with a minimum number of properties.
302+
// +kubebuilder:validation:MinProperties=1
303+
// +optional
304+
StructWithMinProperties B `json:"structWithMinProperties,omitempty"` // want "field StructWithMinProperties has a greater than zero minimum number of properties and should be a pointer"
305+
306+
// structWithMinPropertiesWithoutOmitEmpty is a struct field with a minimum number of properties without omitempty.
307+
// +kubebuilder:validation:MinProperties=1
308+
// +optional
309+
StructWithMinPropertiesWithoutOmitEmpty B `json:"structWithMinPropertiesWithoutOmitEmpty"` // want "field StructWithMinPropertiesWithoutOmitEmpty has a greater than zero minimum number of properties without omitempty. The minimum number of properties should be removed."
310+
311+
// structWithMinPropertiesOnStruct is a struct field with a minimum number of properties on the struct.
312+
// +optional
313+
StructWithMinPropertiesOnStruct D `json:"structWithMinPropertiesOnStruct,omitempty"` // want "field StructWithMinPropertiesOnStruct has a greater than zero minimum number of properties and should be a pointer"
314+
315+
// structWithMinPropertiesOnStructWithoutOmitEmpty is a struct field with a minimum number of properties on the struct without omitempty.
316+
// +optional
317+
StructWithMinPropertiesOnStructWithoutOmitEmpty D `json:"structWithMinPropertiesOnStructWithoutOmitEmpty"` // want "field StructWithMinPropertiesOnStructWithoutOmitEmpty has a greater than zero minimum number of properties and should be a pointer"
318+
301319
// structWithRequiredFields is a struct field.
302320
// +optional
303321
StructWithRequiredFields C `json:"structWithRequiredFields,omitempty"` // want "field StructWithRequiredFields is optional, but contains required field\\(s\\) and should be a pointer"
@@ -387,7 +405,19 @@ type B struct {
387405
}
388406

389407
type C struct {
390-
// tsring is a string field.
408+
// string is a string field.
391409
// +required
392410
String string `json:"string"`
393411
}
412+
413+
// +kubebuilder:validation:MinProperties=1
414+
type D struct {
415+
// string is a string field.
416+
// +optional
417+
String string `json:"string"`
418+
419+
// stringWithMinLength1 with minimum length is a string field.
420+
// +kubebuilder:validation:MinLength=1
421+
// +optional
422+
StringWithMinLength1 string `json:"stringWithMinLength1,omitempty"`
423+
}

pkg/analysis/optionalfields/testdata/src/c/a.go.golden

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,23 @@ type A struct {
290290
// +optional
291291
StructWithOptionalFieldsWithoutOmitEmpty B `json:"structWithOptionalFieldsWithoutOmitEmpty"`
292292

293+
// structWithMinProperties is a struct field with a minimum number of properties.
294+
// +kubebuilder:validation:MinProperties=1
295+
// +optional
296+
StructWithMinProperties *B `json:"structWithMinProperties,omitempty"` // want "field StructWithMinProperties has a greater than zero minimum number of properties and should be a pointer"
297+
298+
// structWithMinPropertiesWithoutOmitEmpty is a struct field with a minimum number of properties without omitempty.
299+
// +optional
300+
StructWithMinPropertiesWithoutOmitEmpty B `json:"structWithMinPropertiesWithoutOmitEmpty"` // want "field StructWithMinPropertiesWithoutOmitEmpty has a greater than zero minimum number of properties without omitempty. The minimum number of properties should be removed."
301+
302+
// structWithMinPropertiesOnStruct is a struct field with a minimum number of properties on the struct.
303+
// +optional
304+
StructWithMinPropertiesOnStruct *D `json:"structWithMinPropertiesOnStruct,omitempty"` // want "field StructWithMinPropertiesOnStruct has a greater than zero minimum number of properties and should be a pointer"
305+
306+
// structWithMinPropertiesOnStructWithoutOmitEmpty is a struct field with a minimum number of properties on the struct without omitempty.
307+
// +optional
308+
StructWithMinPropertiesOnStructWithoutOmitEmpty *D `json:"structWithMinPropertiesOnStructWithoutOmitEmpty"` // want "field StructWithMinPropertiesOnStructWithoutOmitEmpty has a greater than zero minimum number of properties and should be a pointer"
309+
293310
// structWithRequiredFields is a struct field.
294311
// +optional
295312
StructWithRequiredFields *C `json:"structWithRequiredFields,omitempty"` // want "field StructWithRequiredFields is optional, but contains required field\\(s\\) and should be a pointer"
@@ -377,7 +394,19 @@ type B struct {
377394
}
378395

379396
type C struct {
380-
// tsring is a string field.
397+
// string is a string field.
381398
// +required
382399
String string `json:"string"`
383400
}
401+
402+
// +kubebuilder:validation:MinProperties=1
403+
type D struct {
404+
// string is a string field.
405+
// +optional
406+
String string `json:"string"`
407+
408+
// stringWithMinLength1 with minimum length is a string field.
409+
// +kubebuilder:validation:MinLength=1
410+
// +optional
411+
StringWithMinLength1 string `json:"stringWithMinLength1,omitempty"`
412+
}

0 commit comments

Comments
 (0)