@@ -573,6 +573,14 @@ def _check_desc(desc, code_no_desc, code_no_upper, code_no_period, **kwargs):
573
573
return errs
574
574
575
575
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
+
576
584
def validate (obj_name , validator_cls = None , ** validator_kwargs ):
577
585
"""
578
586
Validate the docstring.
@@ -640,22 +648,51 @@ def validate(obj_name, validator_cls=None, **validator_kwargs):
640
648
report_GL08 : bool = True
641
649
# Check if the object is a class and has a docstring in the constructor
642
650
# 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
+ )
654
690
655
691
# Parameter_mismatches, PR01, PR02, PR03 are checked for the class docstring.
656
692
# If cls_doc has PR01, PR02, PR03 errors, i.e. invalid class docstring,
657
693
# 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
659
696
660
697
# Check if GL08 is to be ignored:
661
698
if "GL08" in ignore_validation_comments :
0 commit comments