@@ -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,55 +161,78 @@ 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
204- func extractMarker (comment * ast.Comment ) Marker {
220+ func extractMarker (comment * ast.Comment ) ( Marker , error ) {
205221 if ! strings .HasPrefix (comment .Text , "// +" ) {
206- return Marker {}
222+ return Marker {}, nil
207223 }
208224
209225 markerContent := strings .TrimPrefix (comment .Text , "// +" )
226+
227+ if isDeclarativeValidationMarker (markerContent ) {
228+ marker , err := extractDeclarativeValidationMarker (markerContent , comment )
229+ if err != nil {
230+ return Marker {}, fmt .Errorf ("parsing declarative validation marker %q: %w" , markerContent , err )
231+ }
232+
233+ return * marker , nil
234+ }
235+
210236 id , expressions := extractMarkerIDAndExpressions (DefaultRegistry (), markerContent )
211237
212238 return Marker {
@@ -215,7 +241,7 @@ func extractMarker(comment *ast.Comment) Marker {
215241 RawComment : comment .Text ,
216242 Pos : comment .Pos (),
217243 End : comment .End (),
218- }
244+ }, nil
219245}
220246
221247func extractMarkerIDAndExpressions (knownMarkers Registry , marker string ) (string , map [string ]string ) {
@@ -226,6 +252,52 @@ func extractMarkerIDAndExpressions(knownMarkers Registry, marker string) (string
226252 return extractUnknownMarkerIDAndExpressions (marker )
227253}
228254
255+ func isDeclarativeValidationMarker (marker string ) bool {
256+ return strings .HasPrefix (marker , "k8s:" )
257+ }
258+
259+ func extractDeclarativeValidationMarker (marker string , comment * ast.Comment ) (* Marker , error ) {
260+ tag , err := codetags .Parse (marker )
261+ if err != nil {
262+ return nil , fmt .Errorf ("encountered an error parsing declarative validation marker %q: %v" , marker , err )
263+ }
264+
265+ return markerForTag (& tag , comment )
266+ }
267+
268+ func markerForTag (tag * codetags.Tag , comment * ast.Comment ) (* Marker , error ) {
269+ out := & Marker {
270+ Identifier : tag .Name ,
271+ Expressions : make (map [string ]string ),
272+ RawComment : comment .Text ,
273+ Pos : comment .Pos (),
274+ End : comment .End (),
275+ }
276+
277+ for _ , arg := range tag .Args {
278+ out .Expressions [arg .Name ] = arg .Value
279+ }
280+
281+ switch tag .ValueType {
282+ case codetags .ValueTypeString , codetags .ValueTypeInt , codetags .ValueTypeBool , codetags .ValueTypeRaw :
283+ // all resolvable to an exact string value
284+ out .Expressions ["payload" ] = tag .Value
285+ case codetags .ValueTypeNone :
286+ // nothing
287+ case codetags .ValueTypeTag :
288+ // TODO: Better position evaluation
289+ marker , err := markerForTag (tag .ValueTag , comment )
290+ if err != nil {
291+ return nil , err
292+ }
293+ out .Marker = marker
294+ default :
295+ return nil , fmt .Errorf ("unknown tag value type %v" , tag .ValueType )
296+ }
297+
298+ return out , nil
299+ }
300+
229301func extractKnownMarkerIDAndExpressions (id string , marker string ) (string , map [string ]string ) {
230302 return id , extractExpressions (strings .TrimPrefix (marker , id ))
231303}
@@ -311,9 +383,17 @@ type Marker struct {
311383 // Identifier is the value of the marker once the leading comment, '+', and expressions are trimmed.
312384 Identifier string
313385
314- // Expressions are the set of expressions that have been specified for the marker
386+ // Expressions are the set of expressions that have been specified for the marker.
387+ // Marker named and unnamed arguments are included in this map. If the marker's payload is
388+ // not another marker, the key "payload" will contain the marker payload.
315389 Expressions map [string ]string
316390
391+ // Marker is a nested marker. This is present in cases
392+ // where we've parsed a declarative validation tag
393+ // and it has another declarative validation tag in it's payload.
394+ // If this is nil, the payload was _not_ another dv tag.
395+ Marker * Marker
396+
317397 // RawComment is the raw comment line, unfiltered.
318398 RawComment string
319399
0 commit comments