@@ -16,6 +16,7 @@ limitations under the License.
1616package markers
1717
1818import (
19+ "fmt"
1920 "go/ast"
2021 "go/token"
2122 "reflect"
@@ -26,6 +27,8 @@ import (
2627 "golang.org/x/tools/go/analysis/passes/inspect"
2728 "golang.org/x/tools/go/ast/inspector"
2829
30+ "k8s.io/gengo/v2/codetags"
31+
2932 kalerrors "sigs.k8s.io/kube-api-linter/pkg/analysis/errors"
3033)
3134
@@ -158,47 +161,60 @@ func run(pass *analysis.Pass) (any, error) {
158161 return results , nil
159162}
160163
161- func extractGenDeclMarkers (typ * ast.GenDecl , results * markers ) {
164+ func extractGenDeclMarkers (typ * ast.GenDecl , results * markers ) error {
162165 declMarkers := NewMarkerSet ()
163166
164167 if typ .Doc != nil {
165168 for _ , comment := range typ .Doc .List {
166- if marker := extractMarker (comment ); marker .Identifier != "" {
169+ marker , err := extractMarker (comment )
170+ if err != nil {
171+ return err
172+ }
173+
174+ if marker .Identifier != "" {
167175 declMarkers .Insert (marker )
168176 }
169177 }
170178 }
171179
172180 if len (typ .Specs ) == 0 {
173- return
181+ return nil
174182 }
175183
176184 tSpec , ok := typ .Specs [0 ].(* ast.TypeSpec )
177185 if ! ok {
178- return
186+ return nil
179187 }
180188
181189 results .insertTypeMarkers (tSpec , declMarkers )
182190
183191 if sTyp , ok := tSpec .Type .(* ast.StructType ); ok {
184192 results .insertStructMarkers (sTyp , declMarkers )
185193 }
194+
195+ return nil
186196}
187197
188- func extractFieldMarkers (field * ast.Field , results * markers ) {
198+ func extractFieldMarkers (field * ast.Field , results * markers ) error {
189199 if field == nil || field .Doc == nil {
190- return
200+ return nil
191201 }
192202
193203 fieldMarkers := NewMarkerSet ()
194204
195205 for _ , comment := range field .Doc .List {
196- if marker := extractMarker (comment ); marker .Identifier != "" {
206+ marker , err := extractMarker (comment )
207+ if err != nil {
208+ return err
209+ }
210+
211+ if marker .Identifier != "" {
197212 fieldMarkers .Insert (marker )
198213 }
199214 }
200215
201216 results .insertFieldMarkers (field , fieldMarkers )
217+ return nil
202218}
203219
204220// validMarkerStart validates that a marker starts with an alphabetic character
@@ -207,9 +223,9 @@ func extractFieldMarkers(field *ast.Field, results *markers) {
207223// while supporting declarative validation tags with parentheses and nested markers.
208224var validMarkerStart = regexp .MustCompile (`^[a-zA-Z]([a-zA-Z0-9:\(\)\"\" ,])+=?` )
209225
210- func extractMarker (comment * ast.Comment ) Marker {
226+ func extractMarker (comment * ast.Comment ) ( Marker , error ) {
211227 if ! strings .HasPrefix (comment .Text , "// +" ) {
212- return Marker {}
228+ return Marker {}, nil
213229 }
214230
215231 markerContent := strings .TrimPrefix (comment .Text , "// +" )
@@ -218,7 +234,16 @@ func extractMarker(comment *ast.Comment) Marker {
218234 // This excludes markdown tables (e.g., "// +-------") and other non-marker content,
219235 // while supporting declarative validation tags that may include parentheses and nested markers.
220236 if ! validMarkerStart .MatchString (markerContent ) {
221- return Marker {}
237+ return Marker {}, nil
238+ }
239+
240+ if isDeclarativeValidationMarker (markerContent ) {
241+ marker , err := extractDeclarativeValidationMarker (markerContent , comment )
242+ if err != nil {
243+ return Marker {}, fmt .Errorf ("parsing declarative validation marker %q: %w" , markerContent , err )
244+ }
245+
246+ return * marker , nil
222247 }
223248
224249 id , expressions := extractMarkerIDAndExpressions (DefaultRegistry (), markerContent )
@@ -229,7 +254,7 @@ func extractMarker(comment *ast.Comment) Marker {
229254 RawComment : comment .Text ,
230255 Pos : comment .Pos (),
231256 End : comment .End (),
232- }
257+ }, nil
233258}
234259
235260func extractMarkerIDAndExpressions (knownMarkers Registry , marker string ) (string , map [string ]string ) {
@@ -240,6 +265,52 @@ func extractMarkerIDAndExpressions(knownMarkers Registry, marker string) (string
240265 return extractUnknownMarkerIDAndExpressions (marker )
241266}
242267
268+ func isDeclarativeValidationMarker (marker string ) bool {
269+ return strings .HasPrefix (marker , "k8s:" )
270+ }
271+
272+ func extractDeclarativeValidationMarker (marker string , comment * ast.Comment ) (* Marker , error ) {
273+ tag , err := codetags .Parse (marker )
274+ if err != nil {
275+ return nil , fmt .Errorf ("encountered an error parsing declarative validation marker %q: %v" , marker , err )
276+ }
277+
278+ return markerForTag (& tag , comment )
279+ }
280+
281+ func markerForTag (tag * codetags.Tag , comment * ast.Comment ) (* Marker , error ) {
282+ out := & Marker {
283+ Identifier : tag .Name ,
284+ Expressions : make (map [string ]string ),
285+ RawComment : comment .Text ,
286+ Pos : comment .Pos (),
287+ End : comment .End (),
288+ }
289+
290+ for _ , arg := range tag .Args {
291+ out .Expressions [arg .Name ] = arg .Value
292+ }
293+
294+ switch tag .ValueType {
295+ case codetags .ValueTypeString , codetags .ValueTypeInt , codetags .ValueTypeBool , codetags .ValueTypeRaw :
296+ // all resolvable to an exact string value
297+ out .Expressions ["payload" ] = tag .Value
298+ case codetags .ValueTypeNone :
299+ // nothing
300+ case codetags .ValueTypeTag :
301+ // TODO: Better position evaluation
302+ marker , err := markerForTag (tag .ValueTag , comment )
303+ if err != nil {
304+ return nil , err
305+ }
306+ out .Marker = marker
307+ default :
308+ return nil , fmt .Errorf ("unknown tag value type %v" , tag .ValueType )
309+ }
310+
311+ return out , nil
312+ }
313+
243314func extractKnownMarkerIDAndExpressions (id string , marker string ) (string , map [string ]string ) {
244315 return id , extractExpressions (strings .TrimPrefix (marker , id ))
245316}
@@ -325,9 +396,17 @@ type Marker struct {
325396 // Identifier is the value of the marker once the leading comment, '+', and expressions are trimmed.
326397 Identifier string
327398
328- // Expressions are the set of expressions that have been specified for the marker
399+ // Expressions are the set of expressions that have been specified for the marker.
400+ // Marker named and unnamed arguments are included in this map. If the marker's payload is
401+ // not another marker, the key "payload" will contain the marker payload.
329402 Expressions map [string ]string
330403
404+ // Marker is a nested marker. This is present in cases
405+ // where we've parsed a declarative validation tag
406+ // and it has another declarative validation tag in it's payload.
407+ // If this is nil, the payload was _not_ another dv tag.
408+ Marker * Marker
409+
331410 // RawComment is the raw comment line, unfiltered.
332411 RawComment string
333412
0 commit comments