Skip to content

Commit efca1f9

Browse files
committed
Refactor zero value utils out into utils package
1 parent 43a29a6 commit efca1f9

File tree

4 files changed

+447
-413
lines changed

4 files changed

+447
-413
lines changed

pkg/analysis/optionalfields/analyzer.go

Lines changed: 4 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,7 @@ limitations under the License.
1616
package optionalfields
1717

1818
import (
19-
"errors"
20-
"fmt"
2119
"go/ast"
22-
"strings"
2320

2421
"golang.org/x/tools/go/analysis"
2522
kalerrors "sigs.k8s.io/kube-api-linter/pkg/analysis/errors"
@@ -48,10 +45,6 @@ func init() {
4845
)
4946
}
5047

51-
var (
52-
errMarkerMissingValue = errors.New("marker does not have a value")
53-
)
54-
5548
type analyzer struct {
5649
pointerPolicy OptionalFieldsPointerPolicy
5750
pointerPreference OptionalFieldsPointerPreference
@@ -135,10 +128,10 @@ func defaultConfig(cfg *OptionalFieldsConfig) {
135128
}
136129

137130
func (a *analyzer) checkFieldProperties(pass *analysis.Pass, field *ast.Field, fieldName string, markersAccess markershelper.Markers, jsonTags extractjsontags.FieldTagInfo) {
138-
hasValidZeroValue, completeValidation := isZeroValueValid(pass, field, field.Type, markersAccess)
131+
hasValidZeroValue, completeValidation := utils.IsZeroValueValid(pass, field, field.Type, markersAccess)
139132
hasOmitEmpty := jsonTags.OmitEmpty
140133
isPointer, underlying := isStarExpr(field.Type)
141-
isStruct := isStructType(pass, field.Type)
134+
isStruct := utils.IsStructType(pass, field.Type)
142135

143136
if a.pointerPreference == OptionalFieldsPointerPreferenceAlways {
144137
// The field must always be a pointer, pointers require omitempty, so enforce that too.
@@ -244,135 +237,8 @@ func (a *analyzer) handleIncompleteFieldValidation(pass *analysis.Pass, field *a
244237
return
245238
}
246239

247-
zeroValue := getTypedZeroValue(pass, underlying)
248-
validationHint := getTypedValidationHint(pass, underlying)
240+
zeroValue := utils.GetTypedZeroValue(pass, underlying)
241+
validationHint := utils.GetTypedValidationHint(pass, underlying)
249242

250243
pass.Reportf(field.Pos(), "field %s is optional and has a valid zero value (%s), but the validation is not complete (e.g. %s). The field should be a pointer to allow the zero value to be set. If the zero value is not a valid use case, complete the validation and remove the pointer.", fieldName, zeroValue, validationHint)
251244
}
252-
253-
// getTypedZeroValue returns the zero value for a given type as a string representation.
254-
func getTypedZeroValue(pass *analysis.Pass, expr ast.Expr) string {
255-
switch t := expr.(type) {
256-
case *ast.Ident:
257-
return getIdentZeroValue(pass, t)
258-
case *ast.StructType:
259-
return getStructZeroValue(pass, t)
260-
case *ast.ArrayType:
261-
return "[]"
262-
case *ast.MapType:
263-
return "{}"
264-
default:
265-
return ""
266-
}
267-
}
268-
269-
// getIdentZeroValue returns the zero value for a given identifier as a string representation.
270-
// Where the ident is an alias for a type, it will look up the type spec to get the underlying type
271-
// and return the zero value for that type.
272-
func getIdentZeroValue(pass *analysis.Pass, ident *ast.Ident) string {
273-
switch {
274-
case isIntegerIdent(ident):
275-
return "0"
276-
case isStringIdent(ident):
277-
return `""`
278-
case isBoolIdent(ident):
279-
return "false"
280-
case isFloatIdent(ident):
281-
return "0.0"
282-
}
283-
284-
typeSpec, ok := utils.LookupTypeSpec(pass, ident)
285-
if !ok {
286-
return ""
287-
}
288-
289-
return getTypedZeroValue(pass, typeSpec.Type)
290-
}
291-
292-
// getStructZeroValue returns the zero value for a struct type as a string representation.
293-
// It constructs a json-like representation of the struct's zero value,
294-
// including only the fields that are not omitted (i.e., do not have the omitempty tag).
295-
func getStructZeroValue(pass *analysis.Pass, structType *ast.StructType) string {
296-
value := "{"
297-
298-
jsonTagInfo, ok := pass.ResultOf[extractjsontags.Analyzer].(extractjsontags.StructFieldTags)
299-
if !ok {
300-
panic("could not get struct field tags from pass result")
301-
}
302-
303-
for _, field := range structType.Fields.List {
304-
fieldTagInfo := jsonTagInfo.FieldTags(field)
305-
306-
if fieldTagInfo.OmitEmpty {
307-
// If the field is omitted, we can use a zero value.
308-
// For structs, if they aren't a pointer another error will be raised.
309-
continue
310-
}
311-
312-
value += fmt.Sprintf("%q: %s, ", fieldTagInfo.Name, getTypedZeroValue(pass, field.Type))
313-
}
314-
315-
value = strings.TrimSuffix(value, ", ")
316-
value += "}"
317-
318-
return value
319-
}
320-
321-
// getTypedValidationHint returns a string hint for the validation that should be applied to a given type.
322-
// This is used to suggest which markers should be applied to the field to complete the validation.
323-
func getTypedValidationHint(pass *analysis.Pass, expr ast.Expr) string {
324-
switch t := expr.(type) {
325-
case *ast.Ident:
326-
return getIdentValidationHint(pass, t)
327-
case *ast.StructType:
328-
return "min properties/adding required fields"
329-
case *ast.ArrayType:
330-
return "min items"
331-
case *ast.MapType:
332-
return "min properties"
333-
default:
334-
return ""
335-
}
336-
}
337-
338-
// getIdentValidationHint returns a string hint for the validation that should be applied to a given identifier.
339-
func getIdentValidationHint(pass *analysis.Pass, ident *ast.Ident) string {
340-
switch {
341-
case isIntegerIdent(ident):
342-
return "minimum/maximum"
343-
case isStringIdent(ident):
344-
return "minimum length"
345-
case isBoolIdent(ident):
346-
return ""
347-
case isFloatIdent(ident):
348-
return "minimum/maximum"
349-
}
350-
351-
typeSpec, ok := utils.LookupTypeSpec(pass, ident)
352-
if !ok {
353-
return ""
354-
}
355-
356-
return getTypedValidationHint(pass, typeSpec.Type)
357-
}
358-
359-
// isStructType checks if the given expression is a struct type.
360-
func isStructType(pass *analysis.Pass, expr ast.Expr) bool {
361-
_, underlying := isStarExpr(expr)
362-
363-
if _, ok := underlying.(*ast.StructType); ok {
364-
return true
365-
}
366-
367-
// Where there's an ident, recurse to find the underlying type.
368-
if ident, ok := underlying.(*ast.Ident); ok {
369-
typeSpec, ok := utils.LookupTypeSpec(pass, ident)
370-
if !ok {
371-
return false
372-
}
373-
374-
return isStructType(pass, typeSpec.Type)
375-
}
376-
377-
return false
378-
}

0 commit comments

Comments
 (0)