Skip to content

Commit d169c37

Browse files
committed
Add test cases and logic for MinItems=0 without a pointer
Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
1 parent e7697dc commit d169c37

File tree

9 files changed

+93
-23
lines changed

9 files changed

+93
-23
lines changed

pkg/analysis/utils/serialization/serialization_check.go

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags"
2424
markershelper "sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
2525
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
26+
"sigs.k8s.io/kube-api-linter/pkg/markers"
2627
)
2728

2829
// SerializationCheck is an interface for checking serialization of fields.
@@ -208,18 +209,9 @@ func (s *serializationCheck) handleFieldShouldHaveOmitZero(pass *analysis.Pass,
208209
func (s *serializationCheck) handleFieldShouldBePointer(pass *analysis.Pass, field *ast.Field, fieldName string, isPointer bool, underlying ast.Expr, markersAccess markershelper.Markers, reason string) {
209210
if utils.IsPointerType(pass, underlying) {
210211
if isPointer {
211-
// Check if this is a pointer-to-slice/map with explicit MinItems=0 or MinProperties=0
212-
// In this case, the pointer is intentional to distinguish nil from empty
213-
if hasExplicitZeroMinValidation(pass, field, underlying, markersAccess) {
214-
return
215-
}
216-
217-
switch s.pointerPolicy {
218-
case PointersPolicySuggestFix:
219-
reportShouldRemovePointer(pass, field, PointersPolicySuggestFix, fieldName, "field %s underlying type does not need to be a pointer. The pointer should be removed.", fieldName)
220-
case PointersPolicyWarn:
221-
pass.Reportf(field.Pos(), "field %s underlying type does not need to be a pointer. The pointer should be removed.", fieldName)
222-
}
212+
s.handlePointerToPointerType(pass, field, fieldName, underlying, markersAccess)
213+
} else if s.pointerPreference == PointersPreferenceAlways {
214+
s.handleNonPointerToPointerType(pass, field, fieldName, underlying, markersAccess)
223215
}
224216

225217
return
@@ -229,6 +221,35 @@ func (s *serializationCheck) handleFieldShouldBePointer(pass *analysis.Pass, fie
229221
return
230222
}
231223

224+
s.reportShouldAddPointerMessage(pass, field, fieldName, reason)
225+
}
226+
227+
func (s *serializationCheck) handlePointerToPointerType(pass *analysis.Pass, field *ast.Field, fieldName string, underlying ast.Expr, markersAccess markershelper.Markers) {
228+
// Check if this is a pointer-to-slice/map with explicit MinItems=0 or MinProperties=0
229+
// In this case, the pointer is intentional to distinguish nil from empty
230+
if hasExplicitZeroMinValidation(pass, field, underlying, markersAccess) {
231+
return
232+
}
233+
234+
switch s.pointerPolicy {
235+
case PointersPolicySuggestFix:
236+
reportShouldRemovePointer(pass, field, PointersPolicySuggestFix, fieldName, "field %s underlying type does not need to be a pointer. The pointer should be removed.", fieldName)
237+
case PointersPolicyWarn:
238+
pass.Reportf(field.Pos(), "field %s underlying type does not need to be a pointer. The pointer should be removed.", fieldName)
239+
}
240+
}
241+
242+
func (s *serializationCheck) handleNonPointerToPointerType(pass *analysis.Pass, field *ast.Field, fieldName string, underlying ast.Expr, markersAccess markershelper.Markers) {
243+
// Check if this is a slice/map WITHOUT a pointer but with explicit MinItems=0 or MinProperties=0
244+
// In this case, we should suggest adding a pointer to distinguish nil from empty
245+
if !hasExplicitZeroMinValidation(pass, field, underlying, markersAccess) {
246+
return
247+
}
248+
249+
s.reportShouldAddPointerMessage(pass, field, fieldName, "with MinItems=0/MinProperties=0, underlying type should be a pointer to distinguish nil (unset) from empty.")
250+
}
251+
252+
func (s *serializationCheck) reportShouldAddPointerMessage(pass *analysis.Pass, field *ast.Field, fieldName, reason string) {
232253
switch s.pointerPolicy {
233254
case PointersPolicySuggestFix:
234255
reportShouldAddPointer(pass, field, PointersPolicySuggestFix, fieldName, "field %s %s", fieldName, reason)
@@ -252,17 +273,22 @@ func (s *serializationCheck) handleFieldShouldNotBePointer(pass *analysis.Pass,
252273
}
253274

254275
// hasExplicitZeroMinValidation checks if a field has an explicit MinItems=0 or MinProperties=0 marker.
255-
// This indicates the developer intentionally wants to distinguish between nil and empty for slices/maps.
276+
// This indicates the developer intentionally wants to distinguish between nil and empty for slices/maps:
277+
// - nil: field not provided by the user, use defaults or treat as unset
278+
// - []/{}: explicitly set to empty by the user
279+
//
280+
// Using a pointer allows preserving this semantic difference, which is why MinItems=0/MinProperties=0
281+
// combined with a pointer is a valid pattern despite slices/maps being reference types.
256282
func hasExplicitZeroMinValidation(pass *analysis.Pass, field *ast.Field, underlying ast.Expr, markersAccess markershelper.Markers) bool {
257283
fieldMarkers := utils.TypeAwareMarkerCollectionForField(pass, markersAccess, field)
258284

259285
switch underlying.(type) {
260286
case *ast.ArrayType:
261287
// Check for explicit MinItems=0
262-
return fieldMarkers.HasWithValue("kubebuilder:validation:MinItems=0")
288+
return fieldMarkers.HasWithValue(markers.KubebuilderMinItemsMarker + "=0")
263289
case *ast.MapType:
264290
// Check for explicit MinProperties=0
265-
return fieldMarkers.HasWithValue("kubebuilder:validation:MinProperties=0")
291+
return fieldMarkers.HasWithValue(markers.KubebuilderMinPropertiesMarker + "=0")
266292
}
267293

268294
return false

pkg/analysis/utils/serialization/testdata/src/pointers_always/arrays.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ type TestArrays struct {
1616
ArrayWithPositiveMinItemsWithOmitEmpty []string `json:"arrayWithPositiveMinItemsWithOmitEmpty,omitempty"`
1717

1818
// +kubebuilder:validation:MinItems=0
19-
ArrayWithZeroMinItems []string `json:"arrayWithZeroMinItems"` // want "field ArrayWithZeroMinItems should have the omitempty tag."
19+
ArrayWithZeroMinItems []string `json:"arrayWithZeroMinItems"` // want "field ArrayWithZeroMinItems should have the omitempty tag." "field ArrayWithZeroMinItems with MinItems=0/MinProperties=0, underlying type should be a pointer to distinguish nil \\(unset\\) from empty."
2020

2121
// +kubebuilder:validation:MinItems=0
22-
ArrayWithZeroMinItemsWithOmitEmpty []string `json:"arrayWithZeroMinItemsWithOmitEmpty,omitempty"`
22+
ArrayWithZeroMinItemsWithOmitEmpty []string `json:"arrayWithZeroMinItemsWithOmitEmpty,omitempty"` // want "field ArrayWithZeroMinItemsWithOmitEmpty with MinItems=0/MinProperties=0, underlying type should be a pointer to distinguish nil \\(unset\\) from empty."
2323

2424
ByteArray []byte `json:"byteArray"` // want "field ByteArray should have the omitempty tag."
2525

pkg/analysis/utils/serialization/testdata/src/pointers_always/arrays.go.golden

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ type TestArrays struct {
1616
ArrayWithPositiveMinItemsWithOmitEmpty []string `json:"arrayWithPositiveMinItemsWithOmitEmpty,omitempty"`
1717

1818
// +kubebuilder:validation:MinItems=0
19-
ArrayWithZeroMinItems []string `json:"arrayWithZeroMinItems,omitempty"` // want "field ArrayWithZeroMinItems should have the omitempty tag."
19+
ArrayWithZeroMinItems *[]string `json:"arrayWithZeroMinItems,omitempty"` // want "field ArrayWithZeroMinItems should have the omitempty tag." "field ArrayWithZeroMinItems with MinItems=0/MinProperties=0, underlying type should be a pointer to distinguish nil \\(unset\\) from empty."
2020

2121
// +kubebuilder:validation:MinItems=0
22-
ArrayWithZeroMinItemsWithOmitEmpty []string `json:"arrayWithZeroMinItemsWithOmitEmpty,omitempty"`
22+
ArrayWithZeroMinItemsWithOmitEmpty *[]string `json:"arrayWithZeroMinItemsWithOmitEmpty,omitempty"` // want "field ArrayWithZeroMinItemsWithOmitEmpty with MinItems=0/MinProperties=0, underlying type should be a pointer to distinguish nil \\(unset\\) from empty."
2323

2424
ByteArray []byte `json:"byteArray,omitempty"` // want "field ByteArray should have the omitempty tag."
2525

pkg/analysis/utils/serialization/testdata/src/pointers_always/maps.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ type TestMaps struct {
1616
MapWithPositiveMinPropertiesWithOmitEmpty map[string]string `json:"mapWithPositiveMinPropertiesWithOmitEmpty,omitempty"`
1717

1818
// +kubebuilder:validation:MinProperties=0
19-
MapWithZeroMinProperties map[string]string `json:"mapWithZeroMinProperties"` // want "field MapWithZeroMinProperties should have the omitempty tag."
19+
MapWithZeroMinProperties map[string]string `json:"mapWithZeroMinProperties"` // want "field MapWithZeroMinProperties should have the omitempty tag." "field MapWithZeroMinProperties with MinItems=0/MinProperties=0, underlying type should be a pointer to distinguish nil \\(unset\\) from empty."
2020

2121
// +kubebuilder:validation:MinProperties=0
22-
MapWithZeroMinPropertiesWithOmitEmpty map[string]string `json:"mapWithZeroMinPropertiesWithOmitEmpty,omitempty"`
22+
MapWithZeroMinPropertiesWithOmitEmpty map[string]string `json:"mapWithZeroMinPropertiesWithOmitEmpty,omitempty"` // want "field MapWithZeroMinPropertiesWithOmitEmpty with MinItems=0/MinProperties=0, underlying type should be a pointer to distinguish nil \\(unset\\) from empty."
2323
}

pkg/analysis/utils/serialization/testdata/src/pointers_always/maps.go.golden

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ type TestMaps struct {
1616
MapWithPositiveMinPropertiesWithOmitEmpty map[string]string `json:"mapWithPositiveMinPropertiesWithOmitEmpty,omitempty"`
1717

1818
// +kubebuilder:validation:MinProperties=0
19-
MapWithZeroMinProperties map[string]string `json:"mapWithZeroMinProperties,omitempty"` // want "field MapWithZeroMinProperties should have the omitempty tag."
19+
MapWithZeroMinProperties *map[string]string `json:"mapWithZeroMinProperties,omitempty"` // want "field MapWithZeroMinProperties should have the omitempty tag." "field MapWithZeroMinProperties with MinItems=0/MinProperties=0, underlying type should be a pointer to distinguish nil \\(unset\\) from empty."
2020

2121
// +kubebuilder:validation:MinProperties=0
22-
MapWithZeroMinPropertiesWithOmitEmpty map[string]string `json:"mapWithZeroMinPropertiesWithOmitEmpty,omitempty"`
22+
MapWithZeroMinPropertiesWithOmitEmpty *map[string]string `json:"mapWithZeroMinPropertiesWithOmitEmpty,omitempty"` // want "field MapWithZeroMinPropertiesWithOmitEmpty with MinItems=0/MinProperties=0, underlying type should be a pointer to distinguish nil \\(unset\\) from empty."
2323
}

pkg/analysis/utils/serialization/testdata/src/pointers_always/pointer_to_slice_with_minzero.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package a
22

33
type TestPointerToSliceWithMinZeroAlways struct {
4+
// Pointer to slice with MinItems=0 allows distinguishing:
5+
// - nil (unset, use defaults)
6+
// - [] (explicitly empty)
47
// +kubebuilder:validation:MinItems=0
58
PtrArrayWithZeroMinItems *[]string `json:"ptrArrayWithZeroMinItems,omitempty"`
69

@@ -9,11 +12,24 @@ type TestPointerToSliceWithMinZeroAlways struct {
912
}
1013

1114
type TestPointerToMapWithMinZeroAlways struct {
15+
// Pointer to map with MinProperties=0 allows distinguishing:
16+
// - nil (unset, use defaults)
17+
// - {} (explicitly empty)
1218
// +kubebuilder:validation:MinProperties=0
1319
MapPtrWithZeroMinProperties *map[string]string `json:"mapPtrWithZeroMinProperties,omitempty"`
1420

1521
// +kubebuilder:validation:MinProperties=0
1622
MapPtrWithZeroMinPropertiesNoOmitEmpty *map[string]string `json:"mapPtrWithZeroMinPropertiesNoOmitEmpty"` // want "field MapPtrWithZeroMinPropertiesNoOmitEmpty should have the omitempty tag."
23+
24+
// Non-pointer version should suggest adding pointer
25+
// +kubebuilder:validation:MinProperties=0
26+
MapWithZeroMinPropertiesNoPtr map[string]string `json:"mapWithZeroMinPropertiesNoPtr,omitempty"` // want "field MapWithZeroMinPropertiesNoPtr with MinItems=0/MinProperties=0, underlying type should be a pointer to distinguish nil \\(unset\\) from empty."
27+
}
28+
29+
// Test that slices WITHOUT pointers are flagged when MinItems is zero
30+
type TestSliceWithMinZeroAlways struct {
31+
// +kubebuilder:validation:MinItems=0
32+
ArrayWithZeroMinItemsNoPtr []string `json:"arrayWithZeroMinItemsNoPtr,omitempty"` // want "field ArrayWithZeroMinItemsNoPtr with MinItems=0/MinProperties=0, underlying type should be a pointer to distinguish nil \\(unset\\) from empty."
1733
}
1834

1935
// Test that pointers ARE still flagged when MinItems/MinProperties is NOT zero

pkg/analysis/utils/serialization/testdata/src/pointers_always/pointer_to_slice_with_minzero.go.golden

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package a
22

33
type TestPointerToSliceWithMinZeroAlways struct {
4+
// Pointer to slice with MinItems=0 allows distinguishing:
5+
// - nil (unset, use defaults)
6+
// - [] (explicitly empty)
47
// +kubebuilder:validation:MinItems=0
58
PtrArrayWithZeroMinItems *[]string `json:"ptrArrayWithZeroMinItems,omitempty"`
69

@@ -9,11 +12,24 @@ type TestPointerToSliceWithMinZeroAlways struct {
912
}
1013

1114
type TestPointerToMapWithMinZeroAlways struct {
15+
// Pointer to map with MinProperties=0 allows distinguishing:
16+
// - nil (unset, use defaults)
17+
// - {} (explicitly empty)
1218
// +kubebuilder:validation:MinProperties=0
1319
MapPtrWithZeroMinProperties *map[string]string `json:"mapPtrWithZeroMinProperties,omitempty"`
1420

1521
// +kubebuilder:validation:MinProperties=0
1622
MapPtrWithZeroMinPropertiesNoOmitEmpty *map[string]string `json:"mapPtrWithZeroMinPropertiesNoOmitEmpty,omitempty"` // want "field MapPtrWithZeroMinPropertiesNoOmitEmpty should have the omitempty tag."
23+
24+
// Non-pointer version should suggest adding pointer
25+
// +kubebuilder:validation:MinProperties=0
26+
MapWithZeroMinPropertiesNoPtr *map[string]string `json:"mapWithZeroMinPropertiesNoPtr,omitempty"` // want "field MapWithZeroMinPropertiesNoPtr with MinItems=0/MinProperties=0, underlying type should be a pointer to distinguish nil \\(unset\\) from empty."
27+
}
28+
29+
// Test that slices WITHOUT pointers are flagged when MinItems is zero
30+
type TestSliceWithMinZeroAlways struct {
31+
// +kubebuilder:validation:MinItems=0
32+
ArrayWithZeroMinItemsNoPtr *[]string `json:"arrayWithZeroMinItemsNoPtr,omitempty"` // want "field ArrayWithZeroMinItemsNoPtr with MinItems=0/MinProperties=0, underlying type should be a pointer to distinguish nil \\(unset\\) from empty."
1733
}
1834

1935
// Test that pointers ARE still flagged when MinItems/MinProperties is NOT zero

pkg/analysis/utils/serialization/testdata/src/pointers_when_required/pointer_to_slice_with_minzero.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package a
22

33
type TestPointerToSliceWithMinZero struct {
4+
// Pointer to slice with MinItems=0 allows distinguishing:
5+
// - nil (unset, use defaults)
6+
// - [] (explicitly empty)
47
// +kubebuilder:validation:MinItems=0
58
PtrArrayWithZeroMinItems *[]string `json:"ptrArrayWithZeroMinItems,omitempty"`
69

@@ -9,6 +12,9 @@ type TestPointerToSliceWithMinZero struct {
912
}
1013

1114
type TestPointerToMapWithMinZero struct {
15+
// Pointer to map with MinProperties=0 allows distinguishing:
16+
// - nil (unset, use defaults)
17+
// - {} (explicitly empty)
1218
// +kubebuilder:validation:MinProperties=0
1319
MapPtrWithZeroMinProperties *map[string]string `json:"mapPtrWithZeroMinProperties,omitempty"`
1420

pkg/analysis/utils/serialization/testdata/src/pointers_when_required/pointer_to_slice_with_minzero.go.golden

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package a
22

33
type TestPointerToSliceWithMinZero struct {
4+
// Pointer to slice with MinItems=0 allows distinguishing:
5+
// - nil (unset, use defaults)
6+
// - [] (explicitly empty)
47
// +kubebuilder:validation:MinItems=0
58
PtrArrayWithZeroMinItems *[]string `json:"ptrArrayWithZeroMinItems,omitempty"`
69

@@ -9,6 +12,9 @@ type TestPointerToSliceWithMinZero struct {
912
}
1013

1114
type TestPointerToMapWithMinZero struct {
15+
// Pointer to map with MinProperties=0 allows distinguishing:
16+
// - nil (unset, use defaults)
17+
// - {} (explicitly empty)
1218
// +kubebuilder:validation:MinProperties=0
1319
MapPtrWithZeroMinProperties *map[string]string `json:"mapPtrWithZeroMinProperties,omitempty"`
1420

0 commit comments

Comments
 (0)