Skip to content

Commit 9cb5b88

Browse files
committed
feat(analysis/maxlength): extend linter to support all limits from validation-gen
1 parent 622d35a commit 9cb5b88

5 files changed

Lines changed: 215 additions & 77 deletions

File tree

pkg/analysis/maxlength/analyzer.go

Lines changed: 134 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,51 @@ limitations under the License.
1616
package maxlength
1717

1818
import (
19-
"fmt"
20-
"go/ast"
21-
22-
"golang.org/x/tools/go/analysis"
23-
kalerrors "sigs.k8s.io/kube-api-linter/pkg/analysis/errors"
24-
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags"
25-
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/inspector"
26-
markershelper "sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
27-
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
28-
"sigs.k8s.io/kube-api-linter/pkg/markers"
19+
"fmt"
20+
"go/ast"
21+
22+
"golang.org/x/tools/go/analysis"
23+
kalerrors "sigs.k8s.io/kube-api-linter/pkg/analysis/errors"
24+
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags"
25+
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/inspector"
26+
markershelper "sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
27+
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
28+
"sigs.k8s.io/kube-api-linter/pkg/markers"
2929
)
3030

3131
func init() {
3232
markershelper.DefaultRegistry().Register(
33-
markers.KubebuilderMaxLengthMarker,
34-
markers.KubebuilderMaxItemsMarker,
35-
markers.KubebuilderItemsMaxLengthMarker,
36-
markers.KubebuilderEnumMarker,
37-
markers.KubebuilderItemsEnumMarker,
38-
markers.KubebuilderFormatMarker,
39-
markers.KubebuilderItemsFormatMarker,
40-
markers.K8sMaxLengthMarker,
41-
markers.K8sMaxItemsMarker,
42-
markers.K8sEnumMarker,
43-
)
33+
markers.KubebuilderMaxLengthMarker,
34+
markers.KubebuilderMaxItemsMarker,
35+
markers.KubebuilderItemsMaxLengthMarker,
36+
markers.KubebuilderEnumMarker,
37+
markers.KubebuilderItemsEnumMarker,
38+
markers.KubebuilderFormatMarker,
39+
markers.KubebuilderItemsFormatMarker,
40+
markers.KubebuilderMaxPropertiesMarker,
41+
markers.KubebuilderMaximumMarker,
42+
markers.K8sMaxLengthMarker,
43+
markers.K8sMaxItemsMarker,
44+
markers.K8sEnumMarker,
45+
markers.K8sMinLengthMarker,
46+
markers.K8sMinItemsMarker,
47+
markers.K8sMinimumMarker,
48+
markers.K8sMaxPropertiesMarker,
49+
markers.K8sMinPropertiesMarker,
50+
markers.K8sMaximumMarker,
51+
markers.K8sMaxBytesMarker,
52+
)
4453
}
4554

4655
const (
47-
name = "maxlength"
56+
name = "maxlength"
4857
)
4958

5059
type analyzer struct {
51-
preferredMaxLengthMarker string
52-
preferredMaxItemsMarker string
60+
preferredMaxLengthMarker string
61+
preferredMaxItemsMarker string
62+
preferredMaxPropertiesMarker string
63+
preferredMaximumMarker string
5364
}
5465

5566
// newAnalyzer creates a new analyzer with the given configuration.
@@ -61,8 +72,10 @@ func newAnalyzer(cfg *MaxLengthConfig) *analysis.Analyzer {
6172
defaultConfig(cfg)
6273

6374
a := &analyzer{
64-
preferredMaxLengthMarker: cfg.PreferredMaxLengthMarker,
65-
preferredMaxItemsMarker: cfg.PreferredMaxItemsMarker,
75+
preferredMaxLengthMarker: cfg.PreferredMaxLengthMarker,
76+
preferredMaxItemsMarker: cfg.PreferredMaxItemsMarker,
77+
preferredMaxPropertiesMarker: cfg.PreferredMaxPropertiesMarker,
78+
preferredMaximumMarker: cfg.PreferredMaximumMarker,
6679
}
6780

6881
return &analysis.Analyzer{
@@ -73,25 +86,15 @@ func newAnalyzer(cfg *MaxLengthConfig) *analysis.Analyzer {
7386
}
7487
}
7588

76-
func defaultConfig(cfg *MaxLengthConfig) {
77-
if cfg.PreferredMaxLengthMarker == "" {
78-
cfg.PreferredMaxLengthMarker = markers.KubebuilderMaxLengthMarker
79-
}
80-
81-
if cfg.PreferredMaxItemsMarker == "" {
82-
cfg.PreferredMaxItemsMarker = markers.KubebuilderMaxItemsMarker
83-
}
84-
}
85-
8689
func (a *analyzer) run(pass *analysis.Pass) (any, error) {
8790
inspect, ok := pass.ResultOf[inspector.Analyzer].(inspector.Inspector)
8891
if !ok {
8992
return nil, kalerrors.ErrCouldNotGetInspector
9093
}
9194

9295
inspect.InspectFields(func(field *ast.Field, _ extractjsontags.FieldTagInfo, markersAccess markershelper.Markers, qualifiedFieldName string) {
93-
a.checkField(pass, field, markersAccess, qualifiedFieldName)
94-
})
96+
a.checkField(pass, field, markersAccess, qualifiedFieldName)
97+
})
9598

9699
return nil, nil //nolint:nilnil
97100
}
@@ -104,7 +107,11 @@ func (a *analyzer) checkField(pass *analysis.Pass, field *ast.Field, markersAcce
104107

105108
func (a *analyzer) checkIdent(pass *analysis.Pass, ident *ast.Ident, node ast.Node, aliases []*ast.TypeSpec, markersAccess markershelper.Markers, prefix, marker string, needsMaxLength func(markershelper.MarkerSet) bool) {
106109
if utils.IsBasicType(pass, ident) { // Built-in type
107-
a.checkString(pass, ident, node, aliases, markersAccess, prefix, marker, needsMaxLength)
110+
if ident.Name == "string" {
111+
a.checkString(pass, ident, node, aliases, markersAccess, prefix, marker, needsMaxLength)
112+
} else if isNumericType(ident.Name) {
113+
a.checkNumeric(pass, ident, node, aliases, markersAccess, prefix)
114+
}
108115

109116
return
110117
}
@@ -129,6 +136,14 @@ func (a *analyzer) checkString(pass *analysis.Pass, ident *ast.Ident, node ast.N
129136
}
130137
}
131138

139+
func (a *analyzer) checkNumeric(pass *analysis.Pass, ident *ast.Ident, node ast.Node, aliases []*ast.TypeSpec, markersAccess markershelper.Markers, prefix string) {
140+
markerSet := getCombinedMarkers(markersAccess, node, aliases)
141+
142+
if needsMaximum(markerSet) {
143+
pass.Reportf(node.Pos(), "%s must have a maximum value, add %s marker", prefix, a.preferredMaximumMarker)
144+
}
145+
}
146+
132147
func (a *analyzer) checkTypeSpec(pass *analysis.Pass, tSpec *ast.TypeSpec, node ast.Node, aliases []*ast.TypeSpec, markersAccess markershelper.Markers, prefix, marker string, needsMaxLength func(markershelper.MarkerSet) bool) {
133148
if tSpec.Name == nil {
134149
return
@@ -148,6 +163,8 @@ func (a *analyzer) checkTypeExpr(pass *analysis.Pass, typeExpr ast.Expr, node as
148163
a.checkTypeExpr(pass, typ.X, node, aliases, markersAccess, prefix, marker, needsMaxLength)
149164
case *ast.ArrayType:
150165
a.checkArrayType(pass, typ, node, aliases, markersAccess, prefix)
166+
case *ast.MapType:
167+
a.checkMapType(pass, typ, node, aliases, markersAccess, prefix)
151168
}
152169
}
153170

@@ -177,6 +194,14 @@ func (a *analyzer) checkArrayType(pass *analysis.Pass, arrayType *ast.ArrayType,
177194
}
178195
}
179196

197+
func (a *analyzer) checkMapType(pass *analysis.Pass, mapType *ast.MapType, node ast.Node, aliases []*ast.TypeSpec, markersAccess markershelper.Markers, prefix string) {
198+
markerSet := getCombinedMarkers(markersAccess, node, aliases)
199+
200+
if needsMaxProperties(markerSet) {
201+
pass.Reportf(node.Pos(), "%s must have a maximum number of properties, add %s marker", prefix, a.preferredMaxPropertiesMarker)
202+
}
203+
}
204+
180205
func (a *analyzer) checkArrayElementIdent(pass *analysis.Pass, ident *ast.Ident, node ast.Node, aliases []*ast.TypeSpec, markersAccess markershelper.Markers, prefix string) {
181206
if ident.Obj == nil { // Built-in type
182207
a.checkString(pass, ident, node, aliases, markersAccess, prefix, markers.KubebuilderItemsMaxLengthMarker, needsItemsMaxLength)
@@ -190,68 +215,100 @@ func (a *analyzer) checkArrayElementIdent(pass *analysis.Pass, ident *ast.Ident,
190215
}
191216

192217
// If the array element wasn't directly a string, allow a string alias to be used
193-
// with either the items style markers or the on alias style markers.
194-
a.checkTypeSpec(pass, tSpec, node, append(aliases, tSpec), markersAccess, fmt.Sprintf("%s type", prefix), a.preferredMaxLengthMarker, func(ms markershelper.MarkerSet) bool {
195-
return needsStringMaxLength(ms) && needsItemsMaxLength(ms)
196-
})
218+
// with either the items style markers or the on alias style markers.
219+
a.checkTypeSpec(pass, tSpec, node, append(aliases, tSpec), markersAccess, fmt.Sprintf("%s type", prefix), a.preferredMaxLengthMarker, func(ms markershelper.MarkerSet) bool {
220+
return needsStringMaxLength(ms) && needsItemsMaxLength(ms)
221+
})
197222
}
198223

199224
func getCombinedMarkers(markersAccess markershelper.Markers, node ast.Node, aliases []*ast.TypeSpec) markershelper.MarkerSet {
200-
base := markershelper.NewMarkerSet(getMarkers(markersAccess, node).UnsortedList()...)
225+
base := markershelper.NewMarkerSet(getMarkers(markersAccess, node).UnsortedList()...)
201226

202-
for _, a := range aliases {
203-
base.Insert(getMarkers(markersAccess, a).UnsortedList()...)
204-
}
227+
for _, a := range aliases {
228+
base.Insert(getMarkers(markersAccess, a).UnsortedList()...)
229+
}
205230

206-
return base
231+
return base
207232
}
208233

209234
func getMarkers(markersAccess markershelper.Markers, node ast.Node) markershelper.MarkerSet {
210-
switch t := node.(type) {
211-
case *ast.Field:
212-
return markersAccess.FieldMarkers(t)
213-
case *ast.TypeSpec:
214-
return markersAccess.TypeMarkers(t)
215-
}
235+
switch t := node.(type) {
236+
case *ast.Field:
237+
return markersAccess.FieldMarkers(t)
238+
case *ast.TypeSpec:
239+
return markersAccess.TypeMarkers(t)
240+
}
216241

217-
return nil
242+
return nil
218243
}
219244

220-
// needsMaxLength returns true if the field needs a maximum length.
245+
// needsStringMaxLength returns true if the field needs a maximum length.
221246
// Fields do not need a maximum length if they are already marked with a maximum length,
222247
// or if they are an enum, or if they are a date, date-time or duration.
223248
func needsStringMaxLength(markerSet markershelper.MarkerSet) bool {
224-
switch {
225-
case markerSet.Has(markers.KubebuilderMaxLengthMarker),
226-
markerSet.Has(markers.K8sMaxLengthMarker),
227-
markerSet.Has(markers.KubebuilderEnumMarker),
228-
markerSet.Has(markers.K8sEnumMarker),
229-
markerSet.HasWithValue(kubebuilderFormatWithValue("date")),
230-
markerSet.HasWithValue(kubebuilderFormatWithValue("date-time")),
231-
markerSet.HasWithValue(kubebuilderFormatWithValue("duration")):
232-
return false
233-
}
249+
switch {
250+
case markerSet.Has(markers.KubebuilderMaxLengthMarker),
251+
markerSet.Has(markers.K8sMaxLengthMarker),
252+
markerSet.Has(markers.K8sMaxBytesMarker),
253+
markerSet.Has(markers.KubebuilderEnumMarker),
254+
markerSet.Has(markers.K8sEnumMarker),
255+
markerSet.HasWithValue(kubebuilderFormatWithValue("date")),
256+
markerSet.HasWithValue(kubebuilderFormatWithValue("date-time")),
257+
markerSet.HasWithValue(kubebuilderFormatWithValue("duration")):
258+
return false
259+
}
234260

235-
return true
261+
return true
236262
}
237263

238264
func needsItemsMaxLength(markerSet markershelper.MarkerSet) bool {
239-
switch {
240-
case markerSet.Has(markers.KubebuilderItemsMaxLengthMarker),
241-
markerSet.Has(markers.KubebuilderItemsEnumMarker),
242-
markerSet.HasWithValue(kubebuilderItemsFormatWithValue("date")),
243-
markerSet.HasWithValue(kubebuilderItemsFormatWithValue("date-time")),
244-
markerSet.HasWithValue(kubebuilderItemsFormatWithValue("duration")):
245-
return false
265+
switch {
266+
case markerSet.Has(markers.KubebuilderItemsMaxLengthMarker),
267+
markerSet.Has(markers.KubebuilderItemsEnumMarker),
268+
markerSet.HasWithValue(kubebuilderItemsFormatWithValue("date")),
269+
markerSet.HasWithValue(kubebuilderItemsFormatWithValue("date-time")),
270+
markerSet.HasWithValue(kubebuilderItemsFormatWithValue("duration")):
271+
return false
272+
}
273+
274+
return true
246275
}
247276

248-
return true
277+
func needsMaxProperties(markerSet markershelper.MarkerSet) bool {
278+
switch {
279+
case markerSet.Has(markers.KubebuilderMaxPropertiesMarker),
280+
markerSet.Has(markers.K8sMaxPropertiesMarker):
281+
return false
282+
}
283+
284+
return true
285+
}
286+
287+
func needsMaximum(markerSet markershelper.MarkerSet) bool {
288+
switch {
289+
case markerSet.Has(markers.KubebuilderMaximumMarker),
290+
markerSet.Has(markers.K8sMaximumMarker):
291+
return false
292+
}
293+
294+
return true
249295
}
250296

251297
func kubebuilderFormatWithValue(value string) string {
252-
return fmt.Sprintf("%s:=%s", markers.KubebuilderFormatMarker, value)
298+
return fmt.Sprintf("%s:=%s", markers.KubebuilderFormatMarker, value)
253299
}
254300

255301
func kubebuilderItemsFormatWithValue(value string) string {
256-
return fmt.Sprintf("%s:=%s", markers.KubebuilderItemsFormatMarker, value)
302+
return fmt.Sprintf("%s:=%s", markers.KubebuilderItemsFormatMarker, value)
303+
}
304+
305+
func isNumericType(name string) bool {
306+
switch name {
307+
case "int", "int8", "int16", "int32", "int64",
308+
"uint", "uint8", "uint16", "uint32", "uint64",
309+
"float32", "float64":
310+
return true
311+
}
312+
313+
return false
257314
}

pkg/analysis/maxlength/config.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ limitations under the License.
1515
*/
1616
package maxlength
1717

18+
import "sigs.k8s.io/kube-api-linter/pkg/markers"
19+
1820
// MaxLengthConfig contains configuration for the maxlength linter.
1921
type MaxLengthConfig struct {
2022
// PreferredMaxLengthMarker is the preferred marker identifier to use for maximum length on strings.
@@ -26,4 +28,32 @@ type MaxLengthConfig struct {
2628
// If this field is not set, the default value is "kubebuilder:validation:MaxItems".
2729
// Valid values are "kubebuilder:validation:MaxItems" and "k8s:maxItems".
2830
PreferredMaxItemsMarker string `json:"preferredMaxItemsMarker"`
31+
32+
// PreferredMaxPropertiesMarker is the preferred marker identifier to use for maximum properties on maps.
33+
// If this field is not set, the default value is "kubebuilder:validation:MaxProperties".
34+
// Valid values are "kubebuilder:validation:MaxProperties" and "k8s:maxProperties".
35+
PreferredMaxPropertiesMarker string `json:"preferredMaxPropertiesMarker"`
36+
37+
// PreferredMaximumMarker is the preferred marker identifier to use for maximum value on numbers.
38+
// If this field is not set, the default value is "kubebuilder:validation:Maximum".
39+
// Valid values are "kubebuilder:validation:Maximum" and "k8s:maximum".
40+
PreferredMaximumMarker string `json:"preferredMaximumMarker"`
41+
}
42+
43+
func defaultConfig(cfg *MaxLengthConfig) {
44+
if cfg.PreferredMaxLengthMarker == "" {
45+
cfg.PreferredMaxLengthMarker = markers.KubebuilderMaxLengthMarker
46+
}
47+
48+
if cfg.PreferredMaxItemsMarker == "" {
49+
cfg.PreferredMaxItemsMarker = markers.KubebuilderMaxItemsMarker
50+
}
51+
52+
if cfg.PreferredMaxPropertiesMarker == "" {
53+
cfg.PreferredMaxPropertiesMarker = markers.KubebuilderMaxPropertiesMarker
54+
}
55+
56+
if cfg.PreferredMaximumMarker == "" {
57+
cfg.PreferredMaximumMarker = markers.KubebuilderMaximumMarker
58+
}
2959
}

pkg/analysis/maxlength/initializer.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,17 @@ func validateConfig(config *MaxLengthConfig, fldPath *field.Path) field.ErrorLis
6464
fieldErrors = append(fieldErrors, field.Invalid(fldPath.Child("preferredMaxItemsMarker"), config.PreferredMaxItemsMarker, fmt.Sprintf("invalid value, must be one of %q, %q or omitted", markers.KubebuilderMaxItemsMarker, markers.K8sMaxItemsMarker)))
6565
}
6666

67+
switch config.PreferredMaxPropertiesMarker {
68+
case "", markers.KubebuilderMaxPropertiesMarker, markers.K8sMaxPropertiesMarker:
69+
default:
70+
fieldErrors = append(fieldErrors, field.Invalid(fldPath.Child("preferredMaxPropertiesMarker"), config.PreferredMaxPropertiesMarker, fmt.Sprintf("invalid value, must be one of %q, %q or omitted", markers.KubebuilderMaxPropertiesMarker, markers.K8sMaxPropertiesMarker)))
71+
}
72+
73+
switch config.PreferredMaximumMarker {
74+
case "", markers.KubebuilderMaximumMarker, markers.K8sMaximumMarker:
75+
default:
76+
fieldErrors = append(fieldErrors, field.Invalid(fldPath.Child("preferredMaximumMarker"), config.PreferredMaximumMarker, fmt.Sprintf("invalid value, must be one of %q, %q or omitted", markers.KubebuilderMaximumMarker, markers.K8sMaximumMarker)))
77+
}
78+
6779
return fieldErrors
6880
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package a
2+
3+
type Limits struct {
4+
// +k8s:maxLength=256
5+
StringWithK8sMaxLength string
6+
7+
// +k8s:maxBytes=512
8+
StringWithK8sMaxBytes string
9+
10+
// +k8s:maxItems=128
11+
ArrayWithK8sMaxItems []int
12+
13+
// +k8s:maxProperties=64
14+
MapWithK8sMaxProperties map[string]string
15+
16+
// +k8s:maximum=100
17+
IntWithK8sMaximum int32
18+
19+
// +kubebuilder:validation:MaxProperties=32
20+
MapWithKubebuilderMaxProperties map[string]int
21+
22+
// +kubebuilder:validation:Maximum=50
23+
IntWithKubebuilderMaximum int64
24+
25+
// Missing limits
26+
StringWithoutLimit string // want "field Limits.StringWithoutLimit must have a maximum length, add kubebuilder:validation:MaxLength marker"
27+
ArrayWithoutLimit []int // want "field Limits.ArrayWithoutLimit must have a maximum items, add kubebuilder:validation:MaxItems marker"
28+
MapWithoutLimit map[string]string // want "field Limits.MapWithoutLimit must have a maximum number of properties, add kubebuilder:validation:MaxProperties marker"
29+
IntWithoutLimit int32 // want "field Limits.IntWithoutLimit must have a maximum value, add kubebuilder:validation:Maximum marker"
30+
}

0 commit comments

Comments
 (0)