Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ What's New in astroid 2.12.0?
=============================
Release date: TBA


* Add ``orelse_lineno`` and ``orelse_col_offset`` attributes to ``nodes.If``.

What's New in astroid 2.11.1?
=============================
Expand Down
15 changes: 15 additions & 0 deletions astroid/nodes/node_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3034,6 +3034,12 @@ def __init__(
self.is_orelse: bool = False
"""Whether the if-statement is the orelse-block of another if statement."""

self.orelse_lineno: Optional[int] = None
"""The line number of the ``else`` keyword."""

self.orelse_col_offset: Optional[int] = None
"""The column offset of the ``else`` keyword."""

super().__init__(
lineno=lineno,
col_offset=col_offset,
Expand All @@ -3047,6 +3053,9 @@ def postinit(
test: Optional[NodeNG] = None,
body: Optional[typing.List[NodeNG]] = None,
orelse: Optional[typing.List[NodeNG]] = None,
*,
orelse_lineno: Optional[int] = None,
orelse_col_offset: Optional[int] = None,
) -> None:
"""Do some setup after initialisation.

Expand All @@ -3055,6 +3064,10 @@ def postinit(
:param body: The contents of the block.

:param orelse: The contents of the ``else`` block.

:param orelse_lineno: The line number of the ``else`` keyword.

:param orelse_lineno: The column offset of the ``else`` keyword.
"""
self.test = test
if body is not None:
Expand All @@ -3063,6 +3076,8 @@ def postinit(
self.orelse = orelse
if isinstance(self.parent, If) and self in self.parent.orelse:
self.is_orelse = True
self.orelse_lineno = orelse_lineno
self.orelse_col_offset = orelse_col_offset

@cached_property
def blockstart_tolineno(self):
Expand Down
19 changes: 19 additions & 0 deletions astroid/rebuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1382,6 +1382,20 @@ def visit_global(self, node: "ast.Global", parent: NodeNG) -> nodes.Global:
self._global_names[-1].setdefault(name, []).append(newnode)
return newnode

def _find_else_keyword(self, node: "ast.If") -> Tuple[Optional[int], Optional[int]]:
"""Get the line number and column offset of the `else` keyword."""
if not self._data or not node.orelse:
return None, None

end_lineno = node.orelse[0].lineno - 1

# pylint: disable-next=unsubscriptable-object
data = "\n".join(self._data[node.lineno - 1 : end_lineno])
for t in generate_tokens(StringIO(data).readline):
if t.type == token.NAME and t.string == "else":
return node.lineno + t.start[0] - 1, t.start[1]
return None, None

def visit_if(self, node: "ast.If", parent: NodeNG) -> nodes.If:
"""visit an If node by returning a fresh instance of it"""
newnode = nodes.If(
Expand All @@ -1392,10 +1406,15 @@ def visit_if(self, node: "ast.If", parent: NodeNG) -> nodes.If:
end_col_offset=getattr(node, "end_col_offset", None),
parent=parent,
)

orelse_lineno, orelse_col_offset = self._find_else_keyword(node)

newnode.postinit(
self.visit(node.test, newnode),
[self.visit(child, newnode) for child in node.body],
[self.visit(child, newnode) for child in node.orelse],
orelse_lineno=orelse_lineno,
orelse_col_offset=orelse_col_offset,
)
return newnode

Expand Down
11 changes: 11 additions & 0 deletions tests/unittest_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,17 @@ def test_block_range(self) -> None:
self.assertEqual(self.astroid.body[1].orelse[0].block_range(7), (7, 8))
self.assertEqual(self.astroid.body[1].orelse[0].block_range(8), (8, 8))

def test_orelse_line_numbering(self) -> None:
"""Test the position info for the `else` keyword."""
assert self.astroid.body[0].orelse_lineno is None
assert self.astroid.body[0].orelse_col_offset is None
assert self.astroid.body[1].orelse_lineno == 7
assert self.astroid.body[1].orelse_col_offset == 0
assert self.astroid.body[2].orelse_lineno is None
assert self.astroid.body[2].orelse_col_offset is None
assert self.astroid.body[3].orelse[0].orelse[0].orelse_lineno == 21
assert self.astroid.body[3].orelse[0].orelse[0].orelse_col_offset == 0

@staticmethod
@pytest.mark.filterwarnings("ignore:.*is_sys_guard:DeprecationWarning")
def test_if_sys_guard() -> None:
Expand Down