Skip to content

Commit

Permalink
Classes and methods inherit docstrings from their parent
Browse files Browse the repository at this point in the history
Closes #203
  • Loading branch information
AWhetter committed May 16, 2020
1 parent b52378f commit fca36aa
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Expand Up @@ -14,6 +14,9 @@ Features
* `#201 <https://github.com/readthedocs/sphinx-autoapi/issues/201>`: (Python)
Added the ``autoapi_member_order`` option to allow the order that members
are documentated to be configurable.
* `#203 <https://github.com/readthedocs/sphinx-autoapi/issues/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
^^^^^^^^^
Expand Down
39 changes: 39 additions & 0 deletions autoapi/mappers/python/astroid_utils.py
Expand Up @@ -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 ""
4 changes: 2 additions & 2 deletions autoapi/mappers/python/parser.py
Expand Up @@ -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": [],
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion tests/python/pyexample/example/example.py
Expand Up @@ -105,4 +105,5 @@ def wrapper(*args, **kwargs):


class Bar(Foo):
pass
def method_okay(self, foo=None, bar=None):
pass
4 changes: 4 additions & 0 deletions tests/python/test_pyintegration.py
Expand Up @@ -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"
Expand Down

0 comments on commit fca36aa

Please sign in to comment.