Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/sphinx-doc/sphinx.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakeshi KOMIYA <i.tkomiya@gmail.com>2020-04-26 15:42:55 +0300
committerTakeshi KOMIYA <i.tkomiya@gmail.com>2020-04-27 19:24:29 +0300
commit9c98b92c6a477c7a21fb6a70331f56fc20ae3e50 (patch)
tree497dc828c215e23e4508fdd878336a9c22937041 /sphinx/pycode
parentf388114d10753a201b961a2b440601667da615d6 (diff)
pycode: Detect @final decorators
Diffstat (limited to 'sphinx/pycode')
-rw-r--r--sphinx/pycode/__init__.py2
-rw-r--r--sphinx/pycode/parser.py38
2 files changed, 40 insertions, 0 deletions
diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py
index 55d5d2c1d..0a6ff5214 100644
--- a/sphinx/pycode/__init__.py
+++ b/sphinx/pycode/__init__.py
@@ -144,6 +144,7 @@ class ModuleAnalyzer:
# will be filled by parse()
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.tagorder = None # type: Dict[str, int]
self.tags = None # type: Dict[str, Tuple[str, int, int]]
@@ -161,6 +162,7 @@ class ModuleAnalyzer:
self.attr_docs[scope] = ['']
self.annotations = parser.annotations
+ self.finals = parser.finals
self.tags = parser.definitions
self.tagorder = parser.deforders
except Exception as exc:
diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py
index 3ef1c4605..3762c72cc 100644
--- a/sphinx/pycode/parser.py
+++ b/sphinx/pycode/parser.py
@@ -231,6 +231,9 @@ class VariableCommentPicker(ast.NodeVisitor):
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.typing = None # type: str
+ self.typing_final = None # type: str
super().__init__()
def get_qualname_for(self, name: str) -> Optional[List[str]]:
@@ -249,6 +252,11 @@ class VariableCommentPicker(ast.NodeVisitor):
if qualname:
self.deforders[".".join(qualname)] = next(self.counter)
+ def add_final_entry(self, name: str) -> None:
+ qualname = self.get_qualname_for(name)
+ if qualname:
+ self.finals.append(".".join(qualname))
+
def add_variable_comment(self, name: str, comment: str) -> None:
qualname = self.get_qualname_for(name)
if qualname:
@@ -261,6 +269,22 @@ class VariableCommentPicker(ast.NodeVisitor):
basename = ".".join(qualname[:-1])
self.annotations[(basename, name)] = unparse(annotation)
+ def is_final(self, decorators: List[ast.expr]) -> bool:
+ final = []
+ if self.typing:
+ final.append('%s.final' % self.typing)
+ if self.typing_final:
+ final.append(self.typing_final)
+
+ for decorator in decorators:
+ try:
+ if unparse(decorator) in final:
+ return True
+ except NotImplementedError:
+ pass
+
+ return False
+
def get_self(self) -> ast.arg:
"""Returns the name of first argument if in function."""
if self.current_function and self.current_function.args.args:
@@ -282,11 +306,19 @@ class VariableCommentPicker(ast.NodeVisitor):
for name in node.names:
self.add_entry(name.asname or name.name)
+ if name.name == 'typing':
+ self.typing = name.asname or name.name
+ elif name.name == 'typing.final':
+ self.typing_final = name.asname or name.name
+
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
"""Handles Import node and record it to definition orders."""
for name in node.names:
self.add_entry(name.asname or name.name)
+ if node.module == 'typing' and name.name == 'final':
+ self.typing_final = name.asname or name.name
+
def visit_Assign(self, node: ast.Assign) -> None:
"""Handles Assign node and pick up a variable comment."""
try:
@@ -370,6 +402,8 @@ class VariableCommentPicker(ast.NodeVisitor):
"""Handles ClassDef node and set context."""
self.current_classes.append(node.name)
self.add_entry(node.name)
+ if self.is_final(node.decorator_list):
+ self.add_final_entry(node.name)
self.context.append(node.name)
self.previous = node
for child in node.body:
@@ -381,6 +415,8 @@ class VariableCommentPicker(ast.NodeVisitor):
"""Handles FunctionDef node and set context."""
if self.current_function is None:
self.add_entry(node.name) # should be called before setting self.current_function
+ if self.is_final(node.decorator_list):
+ self.add_final_entry(node.name)
self.context.append(node.name)
self.current_function = node
for child in node.body:
@@ -481,6 +517,7 @@ class Parser:
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]
def parse(self) -> None:
"""Parse the source code."""
@@ -495,6 +532,7 @@ class Parser:
self.annotations = picker.annotations
self.comments = picker.comments
self.deforders = picker.deforders
+ self.finals = picker.finals
def parse_definition(self) -> None:
"""Parse the location of definitions from the code."""