35
35
)
36
36
from .jinja2 .environment import doc_environment
37
37
from .lint_helpers import load_collection_info
38
+ from .markup .semantic_helper import split_option_like_name
38
39
from .plugin_docs import walk_plugin_docs_texts
39
40
from .process_docs import (
40
41
get_collection_contents ,
@@ -261,6 +262,15 @@ def is_valid_return_value(
261
262
)
262
263
263
264
265
+ def _create_lookup (
266
+ opt : dom .OptionNamePart | dom .ReturnValuePart , link : list [str ]
267
+ ) -> str :
268
+ lookup = _NAME_SEPARATOR .join (link )
269
+ if opt .entrypoint is not None :
270
+ lookup = f"{ opt .entrypoint } { _ROLE_ENTRYPOINT_SEPARATOR } { lookup } "
271
+ return lookup
272
+
273
+
264
274
class _MarkupValidator :
265
275
errors : list [str ]
266
276
@@ -301,31 +311,78 @@ def _validate_plugin_fqcn(
301
311
self ._report_disallowed_collection (part , plugin_fqcn , key )
302
312
return False
303
313
314
+ def _validate_option_like_name (
315
+ self ,
316
+ key : str ,
317
+ opt : dom .OptionNamePart | dom .ReturnValuePart ,
318
+ what : t .Literal ["option" , "return value" ],
319
+ ):
320
+ try :
321
+ split_option_like_name (opt .name )
322
+ except ValueError as exc :
323
+ self .errors .append (
324
+ f"{ key } : { opt .source } : { what } name { opt .name !r} cannot be parsed: { exc } "
325
+ )
326
+
327
+ def _validate_link (
328
+ self ,
329
+ key : str ,
330
+ opt : dom .OptionNamePart | dom .ReturnValuePart ,
331
+ what : t .Literal ["option" , "return value" ],
332
+ lookup : t .Callable [[str ], str | None ],
333
+ ):
334
+ try :
335
+ name = split_option_like_name (opt .name )
336
+ except ValueError :
337
+ return
338
+ link : list [str ] = []
339
+ for index , part in enumerate (name ):
340
+ link .append (part [0 ])
341
+ lookup_value = _create_lookup (opt , link )
342
+ part_type = lookup (lookup_value )
343
+ if part_type == "list" and part [1 ] is None and index + 1 < len (name ):
344
+ self .errors .append (
345
+ f"{ key } : { opt .source } : { what } name { opt .name !r} refers to"
346
+ f" list { '.' .join (link )} without `[]`"
347
+ )
348
+ if part_type not in ("list" , "dict" , "dictionary" ) and part [1 ] is not None :
349
+ self .errors .append (
350
+ f"{ key } : { opt .source } : { what } name { opt .name !r} refers to"
351
+ f" { '.' .join (link )} - which is neither list nor dictionary - with `[]`"
352
+ )
353
+
304
354
def _validate_option_name (self , opt : dom .OptionNamePart , key : str ) -> None :
355
+ self ._validate_option_like_name (key , opt , "option" )
305
356
plugin = opt .plugin
306
357
if plugin is None :
307
358
return
308
359
if not self ._validate_plugin_fqcn (opt , plugin .fqcn , plugin .type , key ):
309
360
return
310
- lookup = _NAME_SEPARATOR .join (opt .link )
311
- if opt .entrypoint is not None :
312
- lookup = f"{ opt .entrypoint } { _ROLE_ENTRYPOINT_SEPARATOR } { lookup } "
361
+ lookup = _create_lookup (opt , opt .link )
313
362
if not self ._name_collector .is_valid_option (plugin .fqcn , plugin .type , lookup ):
314
363
prefix = "" if plugin .type in ("role" , "module" ) else " plugin"
315
364
self .errors .append (
316
365
f"{ key } : { opt .source } : option name does not reference to an existing"
317
366
f" option of the { plugin .type } { prefix } { plugin .fqcn } "
318
367
)
368
+ return
369
+ self ._validate_link (
370
+ key ,
371
+ opt ,
372
+ "option" ,
373
+ lambda lookup_ : self ._name_collector .get_option_type (
374
+ plugin .fqcn , plugin .type , lookup_ # type: ignore[union-attr]
375
+ ),
376
+ )
319
377
320
378
def _validate_return_value (self , rv : dom .ReturnValuePart , key : str ) -> None :
379
+ self ._validate_option_like_name (key , rv , "return value" )
321
380
plugin = rv .plugin
322
381
if plugin is None :
323
382
return
324
383
if not self ._validate_plugin_fqcn (rv , plugin .fqcn , plugin .type , key ):
325
384
return
326
- lookup = _NAME_SEPARATOR .join (rv .link )
327
- if rv .entrypoint is not None :
328
- lookup = f"{ rv .entrypoint } { _ROLE_ENTRYPOINT_SEPARATOR } { lookup } "
385
+ lookup = _create_lookup (rv , rv .link )
329
386
if not self ._name_collector .is_valid_return_value (
330
387
plugin .fqcn , plugin .type , lookup
331
388
):
@@ -334,6 +391,15 @@ def _validate_return_value(self, rv: dom.ReturnValuePart, key: str) -> None:
334
391
f"{ key } : { rv .source } : return value name does not reference to an"
335
392
f" existing return value of the { plugin .type } { prefix } { plugin .fqcn } "
336
393
)
394
+ return
395
+ self ._validate_link (
396
+ key ,
397
+ rv ,
398
+ "return value" ,
399
+ lambda lookup_ : self ._name_collector .get_return_value_type (
400
+ plugin .fqcn , plugin .type , lookup_ # type: ignore[union-attr]
401
+ ),
402
+ )
337
403
338
404
def _validate_module (self , module : dom .ModulePart , key : str ) -> None :
339
405
self ._validate_plugin_fqcn (module , module .fqcn , "module" , key )
0 commit comments