Merge pull request #4813 from tk0miya/4812_type_annotated_variables

Fix #4812: autodoc ignores type annotated variables
This commit is contained in:
Takeshi KOMIYA 2018-04-09 01:55:18 +09:00 committed by GitHub
commit 9fb2aa68b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 42 additions and 4 deletions

View File

@ -22,6 +22,7 @@ Bugs fixed
* #4789: imgconverter: confused by convert.exe of Windows * #4789: imgconverter: confused by convert.exe of Windows
* #4783: On windows, Sphinx crashed when drives of srcdir and outdir are * #4783: On windows, Sphinx crashed when drives of srcdir and outdir are
different different
* #4812: autodoc ignores type annotated variables
Testing Testing
-------- --------

View File

@ -23,7 +23,7 @@ from sphinx.util.nodes import explicit_title_re, set_source_info, \
if False: if False:
# For type annotation # For type annotation
from typing import Any, Dict, List, Tuple # NOQA from typing import Any, Dict, Generator, List, Tuple # NOQA
from sphinx.application import Sphinx # NOQA from sphinx.application import Sphinx # NOQA

View File

@ -12,6 +12,7 @@ import ast
import inspect import inspect
import itertools import itertools
import re import re
import sys
import tokenize import tokenize
from token import NAME, NEWLINE, INDENT, DEDENT, NUMBER, OP, STRING from token import NAME, NEWLINE, INDENT, DEDENT, NUMBER, OP, STRING
from tokenize import COMMENT, NL from tokenize import COMMENT, NL
@ -27,6 +28,21 @@ indent_re = re.compile(u'^\\s*$')
emptyline_re = re.compile(u'^\\s*(#.*)?$') emptyline_re = re.compile(u'^\\s*(#.*)?$')
if sys.version_info >= (3, 6):
ASSIGN_NODES = (ast.Assign, ast.AnnAssign)
else:
ASSIGN_NODES = (ast.Assign)
def get_assign_targets(node):
# type: (ast.AST) -> List[ast.expr]
"""Get list of targets from Assign and AnnAssign node."""
if isinstance(node, ast.Assign):
return node.targets
else:
return [node.target] # type: ignore
def get_lvar_names(node, self=None): def get_lvar_names(node, self=None):
# type: (ast.AST, ast.expr) -> List[unicode] # type: (ast.AST, ast.expr) -> List[unicode]
"""Convert assignment-AST to variable names. """Convert assignment-AST to variable names.
@ -284,7 +300,8 @@ class VariableCommentPicker(ast.NodeVisitor):
# type: (ast.Assign) -> None # type: (ast.Assign) -> None
"""Handles Assign node and pick up a variable comment.""" """Handles Assign node and pick up a variable comment."""
try: try:
varnames = sum([get_lvar_names(t, self=self.get_self()) for t in node.targets], []) targets = get_assign_targets(node)
varnames = sum([get_lvar_names(t, self=self.get_self()) for t in targets], [])
current_line = self.get_line(node.lineno) current_line = self.get_line(node.lineno)
except TypeError: except TypeError:
return # this assignment is not new definition! return # this assignment is not new definition!
@ -320,12 +337,18 @@ class VariableCommentPicker(ast.NodeVisitor):
for varname in varnames: for varname in varnames:
self.add_entry(varname) self.add_entry(varname)
def visit_AnnAssign(self, node):
# type: (ast.AST) -> None
"""Handles AnnAssign node and pick up a variable comment."""
self.visit_Assign(node) # type: ignore
def visit_Expr(self, node): def visit_Expr(self, node):
# type: (ast.Expr) -> None # type: (ast.Expr) -> None
"""Handles Expr node and pick up a comment if string.""" """Handles Expr node and pick up a comment if string."""
if (isinstance(self.previous, ast.Assign) and isinstance(node.value, ast.Str)): if (isinstance(self.previous, ASSIGN_NODES) and isinstance(node.value, ast.Str)):
try: try:
varnames = get_lvar_names(self.previous.targets[0], self.get_self()) targets = get_assign_targets(self.previous)
varnames = get_lvar_names(targets[0], self.get_self())
for varname in varnames: for varname in varnames:
if isinstance(node.value.s, text_type): if isinstance(node.value.s, text_type):
docstring = node.value.s docstring = node.value.s

View File

@ -9,6 +9,8 @@
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
import sys
import pytest import pytest
from six import PY2 from six import PY2
@ -94,6 +96,18 @@ def test_comment_picker_location():
('Foo', 'attr3'): 'comment for attr3(3)'} ('Foo', 'attr3'): 'comment for attr3(3)'}
@pytest.mark.skipif(sys.version_info < (3, 6), reason='tests for py36+ syntax')
def test_annotated_assignment_py36():
source = ('a: str = "Sphinx" #: comment\n'
'b: int = 1\n'
'"""string on next line"""')
parser = Parser(source)
parser.parse()
assert parser.comments == {('', 'a'): 'comment',
('', 'b'): 'string on next line'}
assert parser.definitions == {}
def test_complex_assignment(): def test_complex_assignment():
source = ('a = 1 + 1; b = a #: compound statement\n' source = ('a = 1 + 1; b = a #: compound statement\n'
'c, d = (1, 1) #: unpack assignment\n' 'c, d = (1, 1) #: unpack assignment\n'