summaryrefslogtreecommitdiff
path: root/sphinx/pycode
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/pycode')
-rw-r--r--sphinx/pycode/__init__.py41
-rw-r--r--sphinx/pycode/ast.py45
-rw-r--r--sphinx/pycode/parser.py69
3 files changed, 73 insertions, 82 deletions
diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py
index ab0dfdbf8..c55a4fe4a 100644
--- a/sphinx/pycode/__init__.py
+++ b/sphinx/pycode/__init__.py
@@ -19,14 +19,14 @@ from os import path
from typing import IO, Any, Dict, List, Optional, Tuple
from zipfile import ZipFile
-from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning
+from sphinx.deprecation import RemovedInSphinx50Warning
from sphinx.errors import PycodeError
from sphinx.pycode.parser import Parser
class ModuleAnalyzer:
# cache for analyzer objects -- caches both by module and file name
- cache = {} # type: Dict[Tuple[str, str], Any]
+ cache: Dict[Tuple[str, str], Any] = {}
@staticmethod
def get_module_source(modname: str) -> Tuple[Optional[str], Optional[str]]:
@@ -79,7 +79,7 @@ class ModuleAnalyzer:
@classmethod
def for_string(cls, string: str, modname: str, srcname: str = '<string>'
) -> "ModuleAnalyzer":
- return cls(StringIO(string), modname, srcname, decoded=True)
+ return cls(StringIO(string), modname, srcname)
@classmethod
def for_file(cls, filename: str, modname: str) -> "ModuleAnalyzer":
@@ -87,7 +87,7 @@ class ModuleAnalyzer:
return cls.cache['file', filename]
try:
with tokenize.open(filename) as f:
- obj = cls(f, modname, filename, decoded=True)
+ obj = cls(f, modname, filename)
cls.cache['file', filename] = obj
except Exception as err:
if '.egg' + path.sep in filename:
@@ -127,29 +127,20 @@ class ModuleAnalyzer:
cls.cache['module', modname] = obj
return obj
- def __init__(self, source: IO, modname: str, srcname: str, decoded: bool = False) -> None:
+ def __init__(self, source: IO, modname: str, srcname: str) -> None:
self.modname = modname # name of the module
self.srcname = srcname # name of the source file
# cache the source code as well
- pos = source.tell()
- if not decoded:
- warnings.warn('decode option for ModuleAnalyzer is deprecated.',
- RemovedInSphinx40Warning, stacklevel=2)
- self._encoding, _ = tokenize.detect_encoding(source.readline)
- source.seek(pos)
- self.code = source.read().decode(self._encoding)
- else:
- self._encoding = None
- self.code = source.read()
+ self.code = source.read()
# will be filled by analyze()
- self.annotations = None # type: Dict[Tuple[str, str], str]
- self.attr_docs = None # type: Dict[Tuple[str, str], List[str]]
- self.finals = None # type: List[str]
- self.overloads = None # type: Dict[str, List[Signature]]
- self.tagorder = None # type: Dict[str, int]
- self.tags = None # type: Dict[str, Tuple[str, int, int]]
+ self.annotations: Dict[Tuple[str, str], str] = None
+ self.attr_docs: Dict[Tuple[str, str], List[str]] = None
+ self.finals: List[str] = None
+ self.overloads: Dict[str, List[Signature]] = None
+ self.tagorder: Dict[str, int] = None
+ self.tags: Dict[str, Tuple[str, int, int]] = None
self._analyzed = False
def parse(self) -> None:
@@ -164,7 +155,7 @@ class ModuleAnalyzer:
return None
try:
- parser = Parser(self.code, self._encoding)
+ parser = Parser(self.code)
parser.parse()
self.attr_docs = OrderedDict()
@@ -192,9 +183,3 @@ class ModuleAnalyzer:
"""Find class, function and method definitions and their location."""
self.analyze()
return self.tags
-
- @property
- def encoding(self) -> str:
- warnings.warn('ModuleAnalyzer.encoding is deprecated.',
- RemovedInSphinx40Warning, stacklevel=2)
- return self._encoding
diff --git a/sphinx/pycode/ast.py b/sphinx/pycode/ast.py
index 65534f958..f541ec0a9 100644
--- a/sphinx/pycode/ast.py
+++ b/sphinx/pycode/ast.py
@@ -9,7 +9,7 @@
"""
import sys
-from typing import Dict, List, Optional, Type
+from typing import Dict, List, Optional, Type, overload
if sys.version_info > (3, 8):
import ast
@@ -21,7 +21,7 @@ else:
import ast # type: ignore
-OPERATORS = {
+OPERATORS: Dict[Type[ast.AST], str] = {
ast.Add: "+",
ast.And: "and",
ast.BitAnd: "&",
@@ -41,7 +41,7 @@ OPERATORS = {
ast.Sub: "-",
ast.UAdd: "+",
ast.USub: "-",
-} # type: Dict[Type[ast.AST], str]
+}
def parse(code: str, mode: str = 'exec') -> "ast.AST":
@@ -62,6 +62,16 @@ def parse(code: str, mode: str = 'exec') -> "ast.AST":
return ast.parse(code, mode=mode)
+@overload
+def unparse(node: None, code: str = '') -> None:
+ ...
+
+
+@overload
+def unparse(node: ast.AST, code: str = '') -> str:
+ ...
+
+
def unparse(node: Optional[ast.AST], code: str = '') -> Optional[str]:
"""Unparse an AST to string."""
if node is None:
@@ -98,7 +108,7 @@ class _UnparseVisitor(ast.NodeVisitor):
return name
def visit_arguments(self, node: ast.arguments) -> str:
- defaults = list(node.defaults)
+ defaults: List[Optional[ast.expr]] = list(node.defaults)
positionals = len(node.args)
posonlyargs = 0
if hasattr(node, "posonlyargs"): # for py38+
@@ -107,11 +117,11 @@ class _UnparseVisitor(ast.NodeVisitor):
for _ in range(len(defaults), positionals):
defaults.insert(0, None)
- kw_defaults = list(node.kw_defaults)
+ kw_defaults: List[Optional[ast.expr]] = list(node.kw_defaults)
for _ in range(len(kw_defaults), len(node.kwonlyargs)):
kw_defaults.insert(0, None)
- args = [] # type: List[str]
+ args: List[str] = []
if hasattr(node, "posonlyargs"): # for py38+
for i, arg in enumerate(node.posonlyargs): # type: ignore
args.append(self._visit_arg_with_default(arg, defaults[i]))
@@ -150,6 +160,17 @@ class _UnparseVisitor(ast.NodeVisitor):
["%s=%s" % (k.arg, self.visit(k.value)) for k in node.keywords])
return "%s(%s)" % (self.visit(node.func), ", ".join(args))
+ def visit_Constant(self, node: ast.Constant) -> str: # type: ignore
+ if node.value is Ellipsis:
+ return "..."
+ elif isinstance(node.value, (int, float, complex)):
+ if self.code and sys.version_info > (3, 8):
+ return ast.get_source_segment(self.code, node) # type: ignore
+ else:
+ return repr(node.value)
+ else:
+ return repr(node.value)
+
def visit_Dict(self, node: ast.Dict) -> str:
keys = (self.visit(k) for k in node.keys)
values = (self.visit(v) for v in node.values)
@@ -197,18 +218,6 @@ class _UnparseVisitor(ast.NodeVisitor):
else:
return "()"
- if sys.version_info >= (3, 6):
- def visit_Constant(self, node: ast.Constant) -> str:
- if node.value is Ellipsis:
- return "..."
- elif isinstance(node.value, (int, float, complex)):
- if self.code and sys.version_info > (3, 8):
- return ast.get_source_segment(self.code, node)
- else:
- return repr(node.value)
- else:
- return repr(node.value)
-
if sys.version_info < (3, 8):
# these ast nodes were deprecated in python 3.8
def visit_Bytes(self, node: ast.Bytes) -> str:
diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py
index dca59acd4..fa249d8c5 100644
--- a/sphinx/pycode/parser.py
+++ b/sphinx/pycode/parser.py
@@ -10,7 +10,6 @@
import inspect
import itertools
import re
-import sys
import tokenize
from collections import OrderedDict
from inspect import Signature
@@ -26,12 +25,6 @@ indent_re = re.compile('^\\s*$')
emptyline_re = re.compile('^\\s*(#.*)?$')
-if sys.version_info >= (3, 6):
- ASSIGN_NODES = (ast.Assign, ast.AnnAssign)
-else:
- ASSIGN_NODES = (ast.Assign)
-
-
def filter_whitespace(code: str) -> str:
return code.replace('\f', ' ') # replace FF (form feed) with whitespace
@@ -94,7 +87,10 @@ def dedent_docstring(s: str) -> str:
dummy.__doc__ = s
docstring = inspect.getdoc(dummy)
- return docstring.lstrip("\r\n").rstrip("\r\n")
+ if docstring:
+ return docstring.lstrip("\r\n").rstrip("\r\n")
+ else:
+ return ""
class Token:
@@ -133,8 +129,8 @@ class TokenProcessor:
lines = iter(buffers)
self.buffers = buffers
self.tokens = tokenize.generate_tokens(lambda: next(lines))
- self.current = None # type: Token
- self.previous = None # type: Token
+ self.current: Token = None
+ self.previous: Token = None
def get_line(self, lineno: int) -> str:
"""Returns specified line."""
@@ -182,7 +178,7 @@ class AfterCommentParser(TokenProcessor):
def __init__(self, lines: List[str]) -> None:
super().__init__(lines)
- self.comment = None # type: str
+ self.comment: str = None
def fetch_rvalue(self) -> List[Token]:
"""Fetch right-hand value of assignment."""
@@ -225,18 +221,18 @@ class VariableCommentPicker(ast.NodeVisitor):
self.counter = itertools.count()
self.buffers = buffers
self.encoding = encoding
- self.context = [] # type: List[str]
- self.current_classes = [] # type: List[str]
- self.current_function = None # type: ast.FunctionDef
- self.comments = OrderedDict() # type: Dict[Tuple[str, str], str]
- self.annotations = {} # type: Dict[Tuple[str, str], str]
- self.previous = None # type: ast.AST
- self.deforders = {} # type: Dict[str, int]
- self.finals = [] # type: List[str]
- self.overloads = {} # type: Dict[str, List[Signature]]
- self.typing = None # type: str
- self.typing_final = None # type: str
- self.typing_overload = None # type: str
+ self.context: List[str] = []
+ self.current_classes: List[str] = []
+ self.current_function: ast.FunctionDef = None
+ self.comments: Dict[Tuple[str, str], str] = OrderedDict()
+ self.annotations: Dict[Tuple[str, str], str] = {}
+ self.previous: ast.AST = None
+ self.deforders: Dict[str, int] = {}
+ self.finals: List[str] = []
+ self.overloads: Dict[str, List[Signature]] = {}
+ self.typing: str = None
+ self.typing_final: str = None
+ self.typing_overload: str = None
super().__init__()
def get_qualname_for(self, name: str) -> Optional[List[str]]:
@@ -354,7 +350,7 @@ class VariableCommentPicker(ast.NodeVisitor):
"""Handles Assign node and pick up a variable comment."""
try:
targets = get_assign_targets(node)
- varnames = sum([get_lvar_names(t, self=self.get_self()) for t in targets], []) # type: List[str] # NOQA
+ varnames: List[str] = sum([get_lvar_names(t, self=self.get_self()) for t in targets], []) # NOQA
current_line = self.get_line(node.lineno)
except TypeError:
return # this assignment is not new definition!
@@ -398,13 +394,14 @@ class VariableCommentPicker(ast.NodeVisitor):
for varname in varnames:
self.add_entry(varname)
- def visit_AnnAssign(self, node: ast.AST) -> None: # Note: ast.AnnAssign not found in py35
+ def visit_AnnAssign(self, node: ast.AnnAssign) -> None:
"""Handles AnnAssign node and pick up a variable comment."""
self.visit_Assign(node) # type: ignore
def visit_Expr(self, node: ast.Expr) -> None:
"""Handles Expr node and pick up a comment if string."""
- if (isinstance(self.previous, ASSIGN_NODES) and isinstance(node.value, ast.Str)):
+ if (isinstance(self.previous, (ast.Assign, ast.AnnAssign)) and
+ isinstance(node.value, ast.Str)):
try:
targets = get_assign_targets(self.previous)
varnames = get_lvar_names(targets[0], self.get_self())
@@ -469,10 +466,10 @@ class DefinitionFinder(TokenProcessor):
def __init__(self, lines: List[str]) -> None:
super().__init__(lines)
- self.decorator = None # type: Token
- self.context = [] # type: List[str]
- self.indents = [] # type: List
- self.definitions = {} # type: Dict[str, Tuple[str, int, int]]
+ self.decorator: Token = None
+ self.context: List[str] = []
+ self.indents: List = []
+ self.definitions: Dict[str, Tuple[str, int, int]] = {}
def add_definition(self, name: str, entry: Tuple[str, int, int]) -> None:
"""Add a location of definition."""
@@ -546,12 +543,12 @@ class Parser:
def __init__(self, code: str, encoding: str = 'utf-8') -> None:
self.code = filter_whitespace(code)
self.encoding = encoding
- self.annotations = {} # type: Dict[Tuple[str, str], str]
- self.comments = {} # type: Dict[Tuple[str, str], str]
- self.deforders = {} # type: Dict[str, int]
- self.definitions = {} # type: Dict[str, Tuple[str, int, int]]
- self.finals = [] # type: List[str]
- self.overloads = {} # type: Dict[str, List[Signature]]
+ self.annotations: Dict[Tuple[str, str], str] = {}
+ self.comments: Dict[Tuple[str, str], str] = {}
+ self.deforders: Dict[str, int] = {}
+ self.definitions: Dict[str, Tuple[str, int, int]] = {}
+ self.finals: List[str] = []
+ self.overloads: Dict[str, List[Signature]] = {}
def parse(self) -> None:
"""Parse the source code."""