diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fa4b573d..4068925c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,9 @@ Features * `#201 `: (Python) Added the ``autoapi_member_order`` option to allow the order that members are documentated to be configurable. +* `#203 `: (Python) + A class without a docstring inherits one from its parent. + A methods without a docstring inherits one from the method that it overrides. Bug Fixes ^^^^^^^^^ diff --git a/autoapi/mappers/python/astroid_utils.py b/autoapi/mappers/python/astroid_utils.py index f49760cc..2391b116 100644 --- a/autoapi/mappers/python/astroid_utils.py +++ b/autoapi/mappers/python/astroid_utils.py @@ -526,3 +526,42 @@ def format_args(args_node): # pylint: disable=too-many-branches,too-many-statem result.append(kwarg_result) return ", ".join(result) + + +def get_func_docstring(node): + """Get the docstring of a node, using a parent docstring if needed. + + :param node: The node to get a docstring for. + :type node: astroid.nodes.FunctionDef + """ + doc = node.doc + + if doc is None and isinstance(node.parent, astroid.nodes.ClassDef): + for base in node.parent.ancestors(): + for child in base.get_children(): + if ( + isinstance(child, node.__class__) + and child.name == node.name + and child.doc is not None + ): + return child.doc + + return doc or "" + + +def get_class_docstring(node): + """Get the docstring of a node, using a parent docstring if needed. + + :param node: The node to get a docstring for. + :type node: astroid.nodes.ClassDef + """ + doc = node.doc + + if doc is None: + for base in node.ancestors(): + if base.qname() in ("__builtins__.object", "builtins.object"): + continue + if base.doc is not None: + return base.doc + + return doc or "" diff --git a/autoapi/mappers/python/parser.py b/autoapi/mappers/python/parser.py index 58949a41..d62daa8b 100644 --- a/autoapi/mappers/python/parser.py +++ b/autoapi/mappers/python/parser.py @@ -120,7 +120,7 @@ def parse_classdef(self, node, data=None): "full_name": self._get_full_name(node.name), "args": args, "bases": basenames, - "doc": self._decode(node.doc or ""), + "doc": self._decode(astroid_utils.get_class_docstring(node)), "from_line_no": node.fromlineno, "to_line_no": node.tolineno, "children": [], @@ -188,7 +188,7 @@ def parse_functiondef(self, node): # pylint: disable=too-many-branches "name": node.name, "full_name": self._get_full_name(node.name), "args": arg_string, - "doc": self._decode(node.doc or ""), + "doc": self._decode(astroid_utils.get_func_docstring(node)), "from_line_no": node.fromlineno, "to_line_no": node.tolineno, "return_annotation": return_annotation, diff --git a/tests/python/pyexample/example/example.py b/tests/python/pyexample/example/example.py index cd6b81f2..81a28094 100644 --- a/tests/python/pyexample/example/example.py +++ b/tests/python/pyexample/example/example.py @@ -105,4 +105,5 @@ def wrapper(*args, **kwargs): class Bar(Foo): - pass + def method_okay(self, foo=None, bar=None): + pass diff --git a/tests/python/test_pyintegration.py b/tests/python/test_pyintegration.py index f60d29c7..aef98af3 100644 --- a/tests/python/test_pyintegration.py +++ b/tests/python/test_pyintegration.py @@ -79,6 +79,10 @@ def check_integration(self, example_path): # "self" should not be included in constructor arguments assert "Foo(self" not in example_file + # Overridden methods without their own docstring + # should inherit the parent's docstring + assert example_file.count("This method should parse okay") == 2 + assert not os.path.exists("_build/text/autoapi/method_multiline") index_path = "_build/text/index.txt"