-
-
Notifications
You must be signed in to change notification settings - Fork 296
Add position
attribute for nodes
#1393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
630f5ea
21c0c36
2f117d2
c95410d
2b8c7fc
ad1f3d3
40561e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from typing import NamedTuple | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great utility class ! |
||
|
||
|
||
class Position(NamedTuple): | ||
"""Position with line and column information.""" | ||
|
||
lineno: int | ||
col_offset: int | ||
end_lineno: int | ||
end_col_offset: int |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,9 @@ | |
""" | ||
|
||
import sys | ||
import token | ||
from io import StringIO | ||
from tokenize import TokenInfo, generate_tokens | ||
from typing import ( | ||
TYPE_CHECKING, | ||
Callable, | ||
|
@@ -48,9 +51,10 @@ | |
|
||
from astroid import nodes | ||
from astroid._ast import ParserModule, get_parser_module, parse_function_type_comment | ||
from astroid.const import PY38, PY38_PLUS, Context | ||
from astroid.const import PY36, PY38, PY38_PLUS, Context | ||
from astroid.manager import AstroidManager | ||
from astroid.nodes import NodeNG | ||
from astroid.nodes.utils import Position | ||
|
||
if sys.version_info >= (3, 8): | ||
from typing import Final | ||
|
@@ -88,9 +92,13 @@ class TreeRebuilder: | |
"""Rebuilds the _ast tree to become an Astroid tree""" | ||
|
||
def __init__( | ||
self, manager: AstroidManager, parser_module: Optional[ParserModule] = None | ||
): | ||
self, | ||
manager: AstroidManager, | ||
parser_module: Optional[ParserModule] = None, | ||
data: Optional[str] = None, | ||
) -> None: | ||
self._manager = manager | ||
self._data = data.split("\n") if data else None | ||
self._global_names: List[Dict[str, List[nodes.Global]]] = [] | ||
self._import_from_nodes: List[nodes.ImportFrom] = [] | ||
self._delayed_assattr: List[nodes.AssignAttr] = [] | ||
|
@@ -133,6 +141,68 @@ def _get_context( | |
) -> Context: | ||
return self._parser_module.context_classes.get(type(node.ctx), Context.Load) | ||
|
||
def _get_position_info( | ||
self, | ||
node: Union["ast.ClassDef", "ast.FunctionDef", "ast.AsyncFunctionDef"], | ||
parent: Union[nodes.ClassDef, nodes.FunctionDef, nodes.AsyncFunctionDef], | ||
) -> Optional[Position]: | ||
"""Return position information for ClassDef and FunctionDef nodes. | ||
|
||
In contrast to AST positions, these only include the actual keyword(s) | ||
and the class / function name. | ||
|
||
>>> @decorator | ||
>>> async def some_func(var: int) -> None: | ||
>>> ^^^^^^^^^^^^^^^^^^^ | ||
""" | ||
if not self._data: | ||
return None | ||
end_lineno: Optional[int] = getattr(node, "end_lineno", None) | ||
if node.body: | ||
end_lineno = node.body[0].lineno | ||
# pylint: disable-next=unsubscriptable-object | ||
data = "\n".join(self._data[node.lineno - 1 : end_lineno]) | ||
|
||
start_token: Optional[TokenInfo] = None | ||
keyword_tokens: Tuple[int, ...] = (token.NAME,) | ||
if isinstance(parent, nodes.AsyncFunctionDef): | ||
search_token = "async" | ||
if PY36: | ||
# In Python 3.6, the token type for 'async' was 'ASYNC' | ||
# In Python 3.7, the type was changed to 'NAME' and 'ASYNC' removed | ||
# Python 3.8 added it back. However, if we use it unconditionally | ||
# we would break 3.7. | ||
keyword_tokens = (token.NAME, token.ASYNC) | ||
Comment on lines
+170
to
+175
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're going to remove python 3.6 support in 2.11, I guess we'll just have to remove that part when we actually do it ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah. For a moment I thought about just ignoring the error in Python 3.6. Then again, that would be cheating. In the end, when updating we're gonna search for |
||
elif isinstance(parent, nodes.FunctionDef): | ||
search_token = "def" | ||
else: | ||
search_token = "class" | ||
|
||
for t in generate_tokens(StringIO(data).readline): | ||
if ( | ||
start_token is not None | ||
and t.type == token.NAME | ||
and t.string == node.name | ||
): | ||
break | ||
if t.type in keyword_tokens: | ||
if t.string == search_token: | ||
start_token = t | ||
continue | ||
if t.string in {"def"}: | ||
continue | ||
start_token = None | ||
else: | ||
return None | ||
|
||
# pylint: disable=undefined-loop-variable | ||
return Position( | ||
lineno=node.lineno - 1 + start_token.start[0], | ||
col_offset=start_token.start[1], | ||
end_lineno=node.lineno - 1 + t.end[0], | ||
end_col_offset=t.end[1], | ||
) | ||
|
||
def visit_module( | ||
self, node: "ast.Module", modname: str, modpath: str, package: bool | ||
) -> nodes.Module: | ||
|
@@ -1203,6 +1273,7 @@ def visit_classdef( | |
for kwd in node.keywords | ||
if kwd.arg != "metaclass" | ||
], | ||
position=self._get_position_info(node, newnode), | ||
) | ||
return newnode | ||
|
||
|
@@ -1551,6 +1622,7 @@ def _visit_functiondef( | |
returns=returns, | ||
type_comment_returns=type_comment_returns, | ||
type_comment_args=type_comment_args, | ||
position=self._get_position_info(node, newnode), | ||
) | ||
self._global_names.pop() | ||
return newnode | ||
|
Uh oh!
There was an error while loading. Please reload this page.