Skip to content

Commit 92d8305

Browse files
committed
fix(validate.py): Allows the validator to check AST constructor docstrings compliance with parent class
The abstract syntax tree doesn't provide any link to a parent class at node level, so special traversal is required to check constructor parent implementation of docstrings.
1 parent c7da072 commit 92d8305

File tree

1 file changed

+49
-12
lines changed

1 file changed

+49
-12
lines changed

numpydoc/validate.py

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,14 @@ def _check_desc(desc, code_no_desc, code_no_upper, code_no_period, **kwargs):
573573
return errs
574574

575575

576+
def _find_class_node(module_node: ast.AST, cls_name) -> ast.ClassDef:
577+
# Find the class node within a module, when checking constructor docstrings.
578+
for node in ast.walk(module_node):
579+
if isinstance(node, ast.ClassDef) and node.name == cls_name:
580+
return node
581+
raise ValueError(f"Could not find class node {cls_name}")
582+
583+
576584
def validate(obj_name, validator_cls=None, **validator_kwargs):
577585
"""
578586
Validate the docstring.
@@ -640,22 +648,51 @@ def validate(obj_name, validator_cls=None, **validator_kwargs):
640648
report_GL08: bool = True
641649
# Check if the object is a class and has a docstring in the constructor
642650
# Also check if code_obj is defined, as undefined for the AstValidator in validate_docstrings.py.
643-
if (
644-
doc.name.endswith(".__init__")
645-
and doc.is_function_or_method
646-
and hasattr(doc, "code_obj")
647-
):
648-
cls_name = ".".join(
649-
doc.code_obj.__qualname__.split(".")[:-1]
650-
) # Collect all class depths before the constructor.
651-
cls = Validator._load_obj(f"{doc.code_obj.__module__}.{cls_name}")
652-
# cls = Validator._load_obj(f"{doc.name[:-9]}.{cls_name}") ## Alternative
653-
cls_doc = Validator(get_doc_object(cls))
651+
if doc.name.endswith(".__init__") and doc.is_function_or_method:
652+
from numpydoc.hooks.validate_docstrings import (
653+
AstValidator, # Support abstract syntax tree hook.
654+
)
655+
656+
if hasattr(doc, "code_obj"):
657+
cls_name = ".".join(
658+
doc.code_obj.__qualname__.split(".")[:-1]
659+
) # Collect all class depths before the constructor.
660+
cls = Validator._load_obj(f"{doc.code_obj.__module__}.{cls_name}")
661+
# cls = Validator._load_obj(f"{doc.name[:-9]}.{cls_name}") ## Alternative
662+
cls_doc = Validator(get_doc_object(cls))
663+
elif isinstance(doc, AstValidator): # Supports class traversal for ASTs.
664+
names = doc._name.split(".")
665+
666+
if len(names) > 2: # i.e. module.class.__init__
667+
nested_cls_names = names[1:-1] # class1,class2 etc.
668+
cls_name = names[-2]
669+
filename = doc.source_file_name # from the AstValidator object
670+
671+
# Use AST to find the class node...
672+
with open(filename) as file:
673+
module_node = ast.parse(file.read(), filename)
674+
675+
# Recursively find each subclass from the module node.
676+
next_node = module_node
677+
for name in nested_cls_names:
678+
next_node = _find_class_node(next_node, name)
679+
# Get the documentation.
680+
cls_doc = AstValidator(
681+
ast_node=next_node, filename=filename, obj_name=cls_name
682+
)
683+
else:
684+
# Ignore edge case: __init__ functions that don't belong to a class.
685+
cls_doc = None
686+
else:
687+
raise TypeError(
688+
f"Cannot load {doc.name} as a usable Validator object (Validator does not have `doc_obj` attr or type `AstValidator`)."
689+
)
654690

655691
# Parameter_mismatches, PR01, PR02, PR03 are checked for the class docstring.
656692
# If cls_doc has PR01, PR02, PR03 errors, i.e. invalid class docstring,
657693
# then we also report missing constructor docstring, GL08.
658-
report_GL08 = len(cls_doc.parameter_mismatches) > 0
694+
if cls_doc:
695+
report_GL08 = len(cls_doc.parameter_mismatches) > 0
659696

660697
# Check if GL08 is to be ignored:
661698
if "GL08" in ignore_validation_comments:

0 commit comments

Comments
 (0)