@@ -16,10 +16,7 @@ limitations under the License.
1616package optionalfields
1717
1818import (
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-
5548type analyzer struct {
5649 pointerPolicy OptionalFieldsPointerPolicy
5750 pointerPreference OptionalFieldsPointerPreference
@@ -135,10 +128,10 @@ func defaultConfig(cfg *OptionalFieldsConfig) {
135128}
136129
137130func (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