Skip to content

Commit 4608fe4

Browse files
committed
Add more tests and include more nodes
1 parent 46d73d8 commit 4608fe4

File tree

4 files changed

+139
-23
lines changed

4 files changed

+139
-23
lines changed

ChangeLog

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ Release date: TBA
88

99
* ``astroid`` now requires Python 3.7.2 to run.
1010

11-
* Add ``orelse_lineno`` and ``orelse_col_offset`` attributes to ``nodes.If``.
11+
* Add ``orelse_lineno`` and ``orelse_col_offset`` attributes to ``nodes.If``,
12+
``nodes.For``, ``nodes.While``, and ``nodes.TryExcept``.
1213

1314
* Fix ``re`` brain on Python ``3.11``. The flags now come from ``re._compile``.
1415

astroid/nodes/node_classes.py

+45
Original file line numberDiff line numberDiff line change
@@ -2661,6 +2661,12 @@ def __init__(
26612661
self.orelse: typing.List[NodeNG] = []
26622662
"""The contents of the ``else`` block of the loop."""
26632663

2664+
self.orelse_lineno: Optional[int] = None
2665+
"""The line number of the ``else`` keyword."""
2666+
2667+
self.orelse_col_offset: Optional[int] = None
2668+
"""The column offset of the ``else`` keyword."""
2669+
26642670
self.type_annotation: Optional[NodeNG] = None # can be None
26652671
"""If present, this will contain the type annotation passed by a type comment"""
26662672

@@ -2680,6 +2686,9 @@ def postinit(
26802686
body: Optional[typing.List[NodeNG]] = None,
26812687
orelse: Optional[typing.List[NodeNG]] = None,
26822688
type_annotation: Optional[NodeNG] = None,
2689+
*,
2690+
orelse_lineno: Optional[int] = None,
2691+
orelse_col_offset: Optional[int] = None,
26832692
) -> None:
26842693
"""Do some setup after initialisation.
26852694
@@ -2690,13 +2699,19 @@ def postinit(
26902699
:param body: The contents of the body of the loop.
26912700
26922701
:param orelse: The contents of the ``else`` block of the loop.
2702+
2703+
:param orelse_lineno: The line number of the ``else`` keyword.
2704+
2705+
:param orelse_lineno: The column offset of the ``else`` keyword.
26932706
"""
26942707
self.target = target
26952708
self.iter = iter
26962709
if body is not None:
26972710
self.body = body
26982711
if orelse is not None:
26992712
self.orelse = orelse
2713+
self.orelse_lineno = orelse_lineno
2714+
self.orelse_col_offset = orelse_col_offset
27002715
self.type_annotation = type_annotation
27012716

27022717
assigned_stmts: ClassVar[AssignedStmtsCall["For"]]
@@ -3979,6 +3994,12 @@ def __init__(
39793994
self.orelse: typing.List[NodeNG] = []
39803995
"""The contents of the ``else`` block."""
39813996

3997+
self.orelse_lineno: Optional[int] = None
3998+
"""The line number of the ``else`` or ``elif`` keyword."""
3999+
4000+
self.orelse_col_offset: Optional[int] = None
4001+
"""The column offset of the ``else`` or ``elif`` keyword."""
4002+
39824003
super().__init__(
39834004
lineno=lineno,
39844005
col_offset=col_offset,
@@ -3992,6 +4013,9 @@ def postinit(
39924013
body: Optional[typing.List[NodeNG]] = None,
39934014
handlers: Optional[typing.List[ExceptHandler]] = None,
39944015
orelse: Optional[typing.List[NodeNG]] = None,
4016+
*,
4017+
orelse_lineno: Optional[int] = None,
4018+
orelse_col_offset: Optional[int] = None,
39954019
) -> None:
39964020
"""Do some setup after initialisation.
39974021
@@ -4000,13 +4024,19 @@ def postinit(
40004024
:param handlers: The exception handlers.
40014025
40024026
:param orelse: The contents of the ``else`` block.
4027+
4028+
:param orelse_lineno: The line number of the ``else`` keyword.
4029+
4030+
:param orelse_lineno: The column offset of the ``else`` keyword.
40034031
"""
40044032
if body is not None:
40054033
self.body = body
40064034
if handlers is not None:
40074035
self.handlers = handlers
40084036
if orelse is not None:
40094037
self.orelse = orelse
4038+
self.orelse_lineno = orelse_lineno
4039+
self.orelse_col_offset = orelse_col_offset
40104040

40114041
def _infer_name(self, frame, name):
40124042
return name
@@ -4341,6 +4371,12 @@ def __init__(
43414371
self.orelse: typing.List[NodeNG] = []
43424372
"""The contents of the ``else`` block."""
43434373

4374+
self.orelse_lineno: Optional[int] = None
4375+
"""The line number of the ``else`` or ``elif`` keyword."""
4376+
4377+
self.orelse_col_offset: Optional[int] = None
4378+
"""The column offset of the ``else`` or ``elif`` keyword."""
4379+
43444380
super().__init__(
43454381
lineno=lineno,
43464382
col_offset=col_offset,
@@ -4354,6 +4390,9 @@ def postinit(
43544390
test: Optional[NodeNG] = None,
43554391
body: Optional[typing.List[NodeNG]] = None,
43564392
orelse: Optional[typing.List[NodeNG]] = None,
4393+
*,
4394+
orelse_lineno: Optional[int] = None,
4395+
orelse_col_offset: Optional[int] = None,
43574396
) -> None:
43584397
"""Do some setup after initialisation.
43594398
@@ -4362,12 +4401,18 @@ def postinit(
43624401
:param body: The contents of the loop.
43634402
43644403
:param orelse: The contents of the ``else`` block.
4404+
4405+
:param orelse_lineno: The line number of the ``else`` keyword.
4406+
4407+
:param orelse_lineno: The column offset of the ``else`` keyword.
43654408
"""
43664409
self.test = test
43674410
if body is not None:
43684411
self.body = body
43694412
if orelse is not None:
43704413
self.orelse = orelse
4414+
self.orelse_lineno = orelse_lineno
4415+
self.orelse_col_offset = orelse_col_offset
43714416

43724417
@cached_property
43734418
def blockstart_tolineno(self):

astroid/rebuilder.py

+34-18
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,26 @@ def _reset_end_lineno(self, newnode: nodes.NodeNG) -> None:
248248
for child_node in newnode.get_children():
249249
self._reset_end_lineno(child_node)
250250

251+
def _find_orelse_keyword(
252+
self, node: Union["ast.If", "ast.Try", "ast.For", "ast.AsyncFor", "ast.While"]
253+
) -> Tuple[Optional[int], Optional[int]]:
254+
"""Get the line number and column offset of the `else` or `elif` keyword."""
255+
if not self._data or not node.orelse:
256+
return None, None
257+
258+
start = node.orelse[0].lineno
259+
260+
# pylint: disable-next=unsubscriptable-object
261+
for index, line in enumerate(self._data[start - 1 :: -1]):
262+
if line.lstrip().startswith("else"):
263+
# if start - index == 37:
264+
# breakpoint()
265+
return start - index, line.index("else")
266+
if line.lstrip().startswith("elif"):
267+
return start - index, line.index("elif")
268+
269+
return None, None
270+
251271
def visit_module(
252272
self, node: "ast.Module", modname: str, modpath: str, package: bool
253273
) -> nodes.Module:
@@ -1175,6 +1195,8 @@ def _visit_for(
11751195
# pylint: disable-next=unsubscriptable-object
11761196
col_offset = self._data[node.lineno - 1].index("async")
11771197

1198+
orelse_lineno, orelse_col_offset = self._find_orelse_keyword(node)
1199+
11781200
newnode = cls(
11791201
lineno=node.lineno,
11801202
col_offset=col_offset,
@@ -1190,6 +1212,8 @@ def _visit_for(
11901212
body=[self.visit(child, newnode) for child in node.body],
11911213
orelse=[self.visit(child, newnode) for child in node.orelse],
11921214
type_annotation=type_annotation,
1215+
orelse_lineno=orelse_lineno,
1216+
orelse_col_offset=orelse_col_offset,
11931217
)
11941218
return newnode
11951219

@@ -1371,24 +1395,6 @@ def visit_global(self, node: "ast.Global", parent: NodeNG) -> nodes.Global:
13711395
self._global_names[-1].setdefault(name, []).append(newnode)
13721396
return newnode
13731397

1374-
def _find_orelse_keyword(
1375-
self, node: "ast.If"
1376-
) -> Tuple[Optional[int], Optional[int]]:
1377-
"""Get the line number and column offset of the `else` or `elif` keyword."""
1378-
if not self._data or not node.orelse:
1379-
return None, None
1380-
1381-
start = node.orelse[0].lineno
1382-
1383-
# pylint: disable-next=unsubscriptable-object
1384-
for index, line in enumerate(self._data[start:node.lineno:-1]):
1385-
if line.rstrip().startswith("else"):
1386-
return start - index + 1, line.index("else")
1387-
if line.rstrip().startswith("elif"):
1388-
return start - index + 1, line.index("elif")
1389-
1390-
return None, None
1391-
13921398
def visit_if(self, node: "ast.If", parent: NodeNG) -> nodes.If:
13931399
"""visit an If node by returning a fresh instance of it"""
13941400
newnode = nodes.If(
@@ -1820,10 +1826,15 @@ def visit_tryexcept(self, node: "ast.Try", parent: NodeNG) -> nodes.TryExcept:
18201826
)
18211827
else:
18221828
newnode = nodes.TryExcept(node.lineno, node.col_offset, parent)
1829+
1830+
orelse_lineno, orelse_col_offset = self._find_orelse_keyword(node)
1831+
18231832
newnode.postinit(
18241833
[self.visit(child, newnode) for child in node.body],
18251834
[self.visit(child, newnode) for child in node.handlers],
18261835
[self.visit(child, newnode) for child in node.orelse],
1836+
orelse_lineno=orelse_lineno,
1837+
orelse_col_offset=orelse_col_offset,
18271838
)
18281839
return newnode
18291840

@@ -1891,10 +1902,15 @@ def visit_while(self, node: "ast.While", parent: NodeNG) -> nodes.While:
18911902
end_col_offset=getattr(node, "end_col_offset", None),
18921903
parent=parent,
18931904
)
1905+
1906+
orelse_lineno, orelse_col_offset = self._find_orelse_keyword(node)
1907+
18941908
newnode.postinit(
18951909
self.visit(node.test, newnode),
18961910
[self.visit(child, newnode) for child in node.body],
18971911
[self.visit(child, newnode) for child in node.orelse],
1912+
orelse_lineno=orelse_lineno,
1913+
orelse_col_offset=orelse_col_offset,
18981914
)
18991915
return newnode
19001916

tests/unittest_nodes.py

+58-4
Original file line numberDiff line numberDiff line change
@@ -334,12 +334,52 @@ class IfNodeTest(_NodeTest):
334334
# This is using else in a comment
335335
raise
336336
337+
for x in range(3):
338+
print()
339+
else:
340+
print()
341+
342+
for x in range(3):
343+
print()
344+
if x == 3:
345+
break
346+
else:
347+
print()
348+
else:
349+
print()
350+
351+
while True:
352+
print()
353+
else:
354+
print()
355+
356+
try:
357+
1 / 0
358+
except ZeroDivisionError:
359+
print()
360+
else:
361+
print()
362+
finally:
363+
print()
364+
365+
try:
366+
1 / 0
367+
except ZeroDivisionError:
368+
try:
369+
1 / 0
370+
except:
371+
print()
372+
else:
373+
print()
374+
else:
375+
print()
376+
337377
"""
338378

339379
def test_if_elif_else_node(self) -> None:
340380
"""test transformation for If node"""
341-
self.assertEqual(len(self.astroid.body), 5)
342-
for stmt in self.astroid.body:
381+
self.assertEqual(len(self.astroid.body), 10)
382+
for stmt in self.astroid.body[:5]:
343383
self.assertIsInstance(stmt, nodes.If)
344384
self.assertFalse(self.astroid.body[0].orelse) # simple If
345385
self.assertIsInstance(self.astroid.body[1].orelse[0], nodes.Pass) # If / else
@@ -348,8 +388,8 @@ def test_if_elif_else_node(self) -> None:
348388

349389
def test_block_range(self) -> None:
350390
# XXX ensure expected values
351-
self.assertEqual(self.astroid.block_range(1), (0, 33))
352-
self.assertEqual(self.astroid.block_range(10), (0, 33)) # XXX (10, 33) ?
391+
self.assertEqual(self.astroid.block_range(1), (0, 73))
392+
self.assertEqual(self.astroid.block_range(10), (0, 73)) # XXX (10, 73) ?
353393
self.assertEqual(self.astroid.body[1].block_range(5), (5, 6))
354394
self.assertEqual(self.astroid.body[1].block_range(6), (6, 6))
355395
self.assertEqual(self.astroid.body[1].orelse[0].block_range(7), (7, 8))
@@ -373,6 +413,20 @@ def test_orelse_line_numbering(self) -> None:
373413
assert self.astroid.body[4].orelse_col_offset == 0
374414
assert self.astroid.body[4].orelse[0].orelse_lineno == 31
375415
assert self.astroid.body[4].orelse[0].orelse_col_offset == 0
416+
assert self.astroid.body[5].orelse_lineno == 37
417+
assert self.astroid.body[5].orelse_col_offset == 0
418+
assert self.astroid.body[6].orelse_lineno == 46
419+
assert self.astroid.body[6].orelse_col_offset == 0
420+
assert self.astroid.body[6].body[1].orelse_lineno == 44
421+
assert self.astroid.body[6].body[1].orelse_col_offset == 4
422+
assert self.astroid.body[7].orelse_lineno == 51
423+
assert self.astroid.body[7].orelse_col_offset == 0
424+
assert self.astroid.body[8].body[0].orelse_lineno == 58
425+
assert self.astroid.body[8].body[0].orelse_col_offset == 0
426+
assert self.astroid.body[9].orelse_lineno == 72
427+
assert self.astroid.body[9].orelse_col_offset == 0
428+
assert self.astroid.body[9].handlers[0].body[0].orelse_lineno == 70
429+
assert self.astroid.body[9].handlers[0].body[0].orelse_col_offset == 4
376430

377431
@staticmethod
378432
@pytest.mark.filterwarnings("ignore:.*is_sys_guard:DeprecationWarning")

0 commit comments

Comments
 (0)