Skip to content

Commit 34e3071

Browse files
committed
Handle min properties for structs
1 parent cc3f8e5 commit 34e3071

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
@@ -247,26 +247,60 @@ func (a *analyzer) checkFieldPointersPreferenceWhenRequiredIdentObj(pass *analys
247247

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

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

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

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

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

@@ -627,3 +661,24 @@ func getMarkerIntegerValue(marker markers.Marker) (int, error) {
627661

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

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)