From eb1ba3b6eba7fdada39e6100ea386ca3dfb2c445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 19 Mar 2022 12:38:46 +0100 Subject: [PATCH 01/13] Add ``orelse_lineno`` and ``orelse_col_offset`` to ``nodes.If`` --- ChangeLog | 2 +- astroid/nodes/node_classes.py | 15 +++++++++++++++ astroid/rebuilder.py | 19 +++++++++++++++++++ tests/unittest_nodes.py | 11 +++++++++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 1a7852f889..c310ec6358 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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? ============================= diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index b7175c8878..93e399d9f7 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -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, @@ -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. @@ -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: @@ -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): diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index ac8e4a255c..90da04e831 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -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( @@ -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 diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index 0268c27de6..aba71783df 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -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: From b3039062a4e7e9b1a3c262fb0e707f98c387c191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 21 Mar 2022 13:25:03 +0100 Subject: [PATCH 02/13] Also find the `elif` keyword --- astroid/nodes/node_classes.py | 8 ++++---- astroid/rebuilder.py | 17 ++++++++++------- tests/unittest_nodes.py | 8 ++++++-- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 93e399d9f7..18e4f9d42a 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -3035,10 +3035,10 @@ def __init__( """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.""" + """The line number of the ``else`` or ``elif`` keyword.""" self.orelse_col_offset: Optional[int] = None - """The column offset of the ``else`` keyword.""" + """The column offset of the ``else`` or ``elif`` keyword.""" super().__init__( lineno=lineno, @@ -3065,9 +3065,9 @@ def postinit( :param orelse: The contents of the ``else`` block. - :param orelse_lineno: The line number of the ``else`` keyword. + :param orelse_lineno: The line number of the ``else`` or ``elif`` keyword. - :param orelse_lineno: The column offset of the ``else`` keyword. + :param orelse_lineno: The column offset of the ``else`` or ``elif`` keyword. """ self.test = test if body is not None: diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index 90da04e831..28ca4195a5 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -6,13 +6,13 @@ order to get a single Astroid representation """ +import ast import sys import token import tokenize from io import StringIO from tokenize import TokenInfo, generate_tokens from typing import ( - TYPE_CHECKING, Callable, Dict, Generator, @@ -39,9 +39,6 @@ else: from typing_extensions import Final -if TYPE_CHECKING: - import ast - REDIRECT: Final[Dict[str, str]] = { "arguments": "Arguments", @@ -1382,11 +1379,17 @@ 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.""" + def _find_orelse_keyword( + self, node: "ast.If" + ) -> Tuple[Optional[int], Optional[int]]: + """Get the line number and column offset of the `else` or `elif` keyword.""" if not self._data or not node.orelse: return None, None + # If the first child in orelse is an If node the orelse is an elif block + if isinstance(node.orelse[0], ast.If): + return node.orelse[0].lineno, node.orelse[0].col_offset + end_lineno = node.orelse[0].lineno - 1 # pylint: disable-next=unsubscriptable-object @@ -1407,7 +1410,7 @@ def visit_if(self, node: "ast.If", parent: NodeNG) -> nodes.If: parent=parent, ) - orelse_lineno, orelse_col_offset = self._find_else_keyword(node) + orelse_lineno, orelse_col_offset = self._find_orelse_keyword(node) newnode.postinit( self.visit(node.test, newnode), diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index aba71783df..c791bf912a 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -349,8 +349,12 @@ def test_orelse_line_numbering(self) -> 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[2].orelse_lineno == 12 + assert self.astroid.body[2].orelse_col_offset == 0 + assert self.astroid.body[3].orelse_lineno == 17 + assert self.astroid.body[3].orelse_col_offset == 0 + assert self.astroid.body[3].orelse[0].orelse_lineno == 19 + assert self.astroid.body[3].orelse[0].orelse_col_offset == 0 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 From 8bcf9144ce223de21b8d7eb18f4c2dbdb554bdd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 22 Mar 2022 09:56:59 +0100 Subject: [PATCH 03/13] Fix for python 3.7 --- astroid/rebuilder.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index 28ca4195a5..7c564ddfc6 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -6,13 +6,13 @@ order to get a single Astroid representation """ -import ast import sys import token import tokenize from io import StringIO from tokenize import TokenInfo, generate_tokens from typing import ( + TYPE_CHECKING, Callable, Dict, Generator, @@ -39,6 +39,9 @@ else: from typing_extensions import Final +if TYPE_CHECKING: + import ast + REDIRECT: Final[Dict[str, str]] = { "arguments": "Arguments", @@ -1386,18 +1389,25 @@ def _find_orelse_keyword( if not self._data or not node.orelse: return None, None - # If the first child in orelse is an If node the orelse is an elif block - if isinstance(node.orelse[0], ast.If): - return node.orelse[0].lineno, node.orelse[0].col_offset - - end_lineno = node.orelse[0].lineno - 1 + end_lineno = node.orelse[0].lineno + + def find_keyword(begin: int, end: int) -> Tuple[Optional[int], Optional[int]]: + # pylint: disable-next=unsubscriptable-object + data = "\n".join(self._data[begin:end]) + + try: + tokens = list(generate_tokens(StringIO(data).readline)) + except tokenize.TokenError: + # If we cut-off in the middle of multi-line if statements we + # generate a TokenError here. We just keep trying + # until the multi-line statement is closed. + return find_keyword(begin, end + 1) + for t in tokens[::-1]: + if t.type == token.NAME and t.string in {"else", "elif"}: + return node.lineno + t.start[0] - 1, t.start[1] + return None, None - # 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 + return find_keyword(node.lineno - 1, end_lineno) def visit_if(self, node: "ast.If", parent: NodeNG) -> nodes.If: """visit an If node by returning a fresh instance of it""" From 885ea01edd929ce1263ef41ed42a4c74f089093c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 22 Mar 2022 10:04:51 +0100 Subject: [PATCH 04/13] Add pragma --- astroid/rebuilder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index 7c564ddfc6..203f6b9316 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -1405,7 +1405,7 @@ def find_keyword(begin: int, end: int) -> Tuple[Optional[int], Optional[int]]: for t in tokens[::-1]: if t.type == token.NAME and t.string in {"else", "elif"}: return node.lineno + t.start[0] - 1, t.start[1] - return None, None + raise AssertionError() # pragma: no cover # Shouldn't be reached. return find_keyword(node.lineno - 1, end_lineno) From 280191cf7a8d4529499a0172f3948367c5b1d867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 25 Apr 2022 19:47:56 +0200 Subject: [PATCH 05/13] New approach --- astroid/rebuilder.py | 29 ++++++++++------------------- tests/unittest_nodes.py | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index 203f6b9316..cf7e9359ea 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -1389,25 +1389,16 @@ def _find_orelse_keyword( if not self._data or not node.orelse: return None, None - end_lineno = node.orelse[0].lineno - - def find_keyword(begin: int, end: int) -> Tuple[Optional[int], Optional[int]]: - # pylint: disable-next=unsubscriptable-object - data = "\n".join(self._data[begin:end]) - - try: - tokens = list(generate_tokens(StringIO(data).readline)) - except tokenize.TokenError: - # If we cut-off in the middle of multi-line if statements we - # generate a TokenError here. We just keep trying - # until the multi-line statement is closed. - return find_keyword(begin, end + 1) - for t in tokens[::-1]: - if t.type == token.NAME and t.string in {"else", "elif"}: - return node.lineno + t.start[0] - 1, t.start[1] - raise AssertionError() # pragma: no cover # Shouldn't be reached. - - return find_keyword(node.lineno - 1, end_lineno) + start = node.orelse[0].lineno + + # pylint: disable-next=unsubscriptable-object + for index, line in enumerate(self._data[start::-1]): + if line.rstrip().startswith("else"): + return start - index + 1, line.index("else") + if line.rstrip().startswith("elif"): + return start - index + 1, line.index("elif") + + 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""" diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index c791bf912a..a23259247f 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -322,6 +322,18 @@ class IfNodeTest(_NodeTest): pass else: raise + + if 1: + print() + elif ( + 2 + and 3 + ): + print() + else: + # This is using else in a comment + raise + """ def test_if_elif_else_node(self) -> None: @@ -357,6 +369,10 @@ def test_orelse_line_numbering(self) -> None: assert self.astroid.body[3].orelse[0].orelse_col_offset == 0 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 + assert self.astroid.body[4].orelse_lineno == 26 + assert self.astroid.body[4].orelse_col_offset == 0 + assert self.astroid.body[4].orelse[0].orelse_lineno == 31 + assert self.astroid.body[4].orelse[0].orelse_col_offset == 0 @staticmethod @pytest.mark.filterwarnings("ignore:.*is_sys_guard:DeprecationWarning") From cf0cfb05991048b544269c8d3b85eaa94401732b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 25 Apr 2022 19:53:16 +0200 Subject: [PATCH 06/13] Update tests after adding new tests --- tests/unittest_nodes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index 0637a0028c..c3e1ca680a 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -338,7 +338,7 @@ class IfNodeTest(_NodeTest): def test_if_elif_else_node(self) -> None: """test transformation for If node""" - self.assertEqual(len(self.astroid.body), 4) + self.assertEqual(len(self.astroid.body), 5) for stmt in self.astroid.body: self.assertIsInstance(stmt, nodes.If) self.assertFalse(self.astroid.body[0].orelse) # simple If @@ -348,8 +348,8 @@ def test_if_elif_else_node(self) -> None: def test_block_range(self) -> None: # XXX ensure expected values - self.assertEqual(self.astroid.block_range(1), (0, 22)) - self.assertEqual(self.astroid.block_range(10), (0, 22)) # XXX (10, 22) ? + self.assertEqual(self.astroid.block_range(1), (0, 33)) + self.assertEqual(self.astroid.block_range(10), (0, 33)) # XXX (10, 33) ? self.assertEqual(self.astroid.body[1].block_range(5), (5, 6)) self.assertEqual(self.astroid.body[1].block_range(6), (6, 6)) self.assertEqual(self.astroid.body[1].orelse[0].block_range(7), (7, 8)) From 46d73d8e9b5a1d8602b09523daf0d638c357b2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 11 May 2022 15:53:54 +0200 Subject: [PATCH 07/13] Update astroid/rebuilder.py Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com> --- astroid/rebuilder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index d43f0d1c29..9cce313142 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -1381,7 +1381,7 @@ def _find_orelse_keyword( start = node.orelse[0].lineno # pylint: disable-next=unsubscriptable-object - for index, line in enumerate(self._data[start::-1]): + for index, line in enumerate(self._data[start:node.lineno:-1]): if line.rstrip().startswith("else"): return start - index + 1, line.index("else") if line.rstrip().startswith("elif"): From 4608fe45f56b91132558a00502952d6f14e4a881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 11 May 2022 16:40:35 +0200 Subject: [PATCH 08/13] Add more tests and include more nodes --- ChangeLog | 3 +- astroid/nodes/node_classes.py | 45 +++++++++++++++++++++++++ astroid/rebuilder.py | 52 +++++++++++++++++++---------- tests/unittest_nodes.py | 62 ++++++++++++++++++++++++++++++++--- 4 files changed, 139 insertions(+), 23 deletions(-) diff --git a/ChangeLog b/ChangeLog index faccf29301..6fa85b63e5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -8,7 +8,8 @@ Release date: TBA * ``astroid`` now requires Python 3.7.2 to run. -* Add ``orelse_lineno`` and ``orelse_col_offset`` attributes to ``nodes.If``. +* Add ``orelse_lineno`` and ``orelse_col_offset`` attributes to ``nodes.If``, + ``nodes.For``, ``nodes.While``, and ``nodes.TryExcept``. * Fix ``re`` brain on Python ``3.11``. The flags now come from ``re._compile``. diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 3ff880f1aa..1220ba81a4 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -2661,6 +2661,12 @@ def __init__( self.orelse: typing.List[NodeNG] = [] """The contents of the ``else`` block of the loop.""" + 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.""" + self.type_annotation: Optional[NodeNG] = None # can be None """If present, this will contain the type annotation passed by a type comment""" @@ -2680,6 +2686,9 @@ def postinit( body: Optional[typing.List[NodeNG]] = None, orelse: Optional[typing.List[NodeNG]] = None, type_annotation: Optional[NodeNG] = None, + *, + orelse_lineno: Optional[int] = None, + orelse_col_offset: Optional[int] = None, ) -> None: """Do some setup after initialisation. @@ -2690,6 +2699,10 @@ def postinit( :param body: The contents of the body of the loop. :param orelse: The contents of the ``else`` block of the loop. + + :param orelse_lineno: The line number of the ``else`` keyword. + + :param orelse_lineno: The column offset of the ``else`` keyword. """ self.target = target self.iter = iter @@ -2697,6 +2710,8 @@ def postinit( self.body = body if orelse is not None: self.orelse = orelse + self.orelse_lineno = orelse_lineno + self.orelse_col_offset = orelse_col_offset self.type_annotation = type_annotation assigned_stmts: ClassVar[AssignedStmtsCall["For"]] @@ -3979,6 +3994,12 @@ def __init__( self.orelse: typing.List[NodeNG] = [] """The contents of the ``else`` block.""" + self.orelse_lineno: Optional[int] = None + """The line number of the ``else`` or ``elif`` keyword.""" + + self.orelse_col_offset: Optional[int] = None + """The column offset of the ``else`` or ``elif`` keyword.""" + super().__init__( lineno=lineno, col_offset=col_offset, @@ -3992,6 +4013,9 @@ def postinit( body: Optional[typing.List[NodeNG]] = None, handlers: Optional[typing.List[ExceptHandler]] = None, orelse: Optional[typing.List[NodeNG]] = None, + *, + orelse_lineno: Optional[int] = None, + orelse_col_offset: Optional[int] = None, ) -> None: """Do some setup after initialisation. @@ -4000,6 +4024,10 @@ def postinit( :param handlers: The exception handlers. :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. """ if body is not None: self.body = body @@ -4007,6 +4035,8 @@ def postinit( self.handlers = handlers if orelse is not None: self.orelse = orelse + self.orelse_lineno = orelse_lineno + self.orelse_col_offset = orelse_col_offset def _infer_name(self, frame, name): return name @@ -4341,6 +4371,12 @@ def __init__( self.orelse: typing.List[NodeNG] = [] """The contents of the ``else`` block.""" + self.orelse_lineno: Optional[int] = None + """The line number of the ``else`` or ``elif`` keyword.""" + + self.orelse_col_offset: Optional[int] = None + """The column offset of the ``else`` or ``elif`` keyword.""" + super().__init__( lineno=lineno, col_offset=col_offset, @@ -4354,6 +4390,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. @@ -4362,12 +4401,18 @@ def postinit( :param body: The contents of the loop. :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: self.body = body if orelse is not None: self.orelse = orelse + self.orelse_lineno = orelse_lineno + self.orelse_col_offset = orelse_col_offset @cached_property def blockstart_tolineno(self): diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index 9cce313142..99b1ec936f 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -248,6 +248,26 @@ def _reset_end_lineno(self, newnode: nodes.NodeNG) -> None: for child_node in newnode.get_children(): self._reset_end_lineno(child_node) + def _find_orelse_keyword( + self, node: Union["ast.If", "ast.Try", "ast.For", "ast.AsyncFor", "ast.While"] + ) -> Tuple[Optional[int], Optional[int]]: + """Get the line number and column offset of the `else` or `elif` keyword.""" + if not self._data or not node.orelse: + return None, None + + start = node.orelse[0].lineno + + # pylint: disable-next=unsubscriptable-object + for index, line in enumerate(self._data[start - 1 :: -1]): + if line.lstrip().startswith("else"): + # if start - index == 37: + # breakpoint() + return start - index, line.index("else") + if line.lstrip().startswith("elif"): + return start - index, line.index("elif") + + return None, None + def visit_module( self, node: "ast.Module", modname: str, modpath: str, package: bool ) -> nodes.Module: @@ -1175,6 +1195,8 @@ def _visit_for( # pylint: disable-next=unsubscriptable-object col_offset = self._data[node.lineno - 1].index("async") + orelse_lineno, orelse_col_offset = self._find_orelse_keyword(node) + newnode = cls( lineno=node.lineno, col_offset=col_offset, @@ -1190,6 +1212,8 @@ def _visit_for( body=[self.visit(child, newnode) for child in node.body], orelse=[self.visit(child, newnode) for child in node.orelse], type_annotation=type_annotation, + orelse_lineno=orelse_lineno, + orelse_col_offset=orelse_col_offset, ) return newnode @@ -1371,24 +1395,6 @@ def visit_global(self, node: "ast.Global", parent: NodeNG) -> nodes.Global: self._global_names[-1].setdefault(name, []).append(newnode) return newnode - def _find_orelse_keyword( - self, node: "ast.If" - ) -> Tuple[Optional[int], Optional[int]]: - """Get the line number and column offset of the `else` or `elif` keyword.""" - if not self._data or not node.orelse: - return None, None - - start = node.orelse[0].lineno - - # pylint: disable-next=unsubscriptable-object - for index, line in enumerate(self._data[start:node.lineno:-1]): - if line.rstrip().startswith("else"): - return start - index + 1, line.index("else") - if line.rstrip().startswith("elif"): - return start - index + 1, line.index("elif") - - 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( @@ -1820,10 +1826,15 @@ def visit_tryexcept(self, node: "ast.Try", parent: NodeNG) -> nodes.TryExcept: ) else: newnode = nodes.TryExcept(node.lineno, node.col_offset, parent) + + orelse_lineno, orelse_col_offset = self._find_orelse_keyword(node) + newnode.postinit( [self.visit(child, newnode) for child in node.body], [self.visit(child, newnode) for child in node.handlers], [self.visit(child, newnode) for child in node.orelse], + orelse_lineno=orelse_lineno, + orelse_col_offset=orelse_col_offset, ) return newnode @@ -1891,10 +1902,15 @@ def visit_while(self, node: "ast.While", parent: NodeNG) -> nodes.While: end_col_offset=getattr(node, "end_col_offset", None), parent=parent, ) + + orelse_lineno, orelse_col_offset = self._find_orelse_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 diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index c3e1ca680a..c11f4afade 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -334,12 +334,52 @@ class IfNodeTest(_NodeTest): # This is using else in a comment raise + for x in range(3): + print() + else: + print() + + for x in range(3): + print() + if x == 3: + break + else: + print() + else: + print() + + while True: + print() + else: + print() + + try: + 1 / 0 + except ZeroDivisionError: + print() + else: + print() + finally: + print() + + try: + 1 / 0 + except ZeroDivisionError: + try: + 1 / 0 + except: + print() + else: + print() + else: + print() + """ def test_if_elif_else_node(self) -> None: """test transformation for If node""" - self.assertEqual(len(self.astroid.body), 5) - for stmt in self.astroid.body: + self.assertEqual(len(self.astroid.body), 10) + for stmt in self.astroid.body[:5]: self.assertIsInstance(stmt, nodes.If) self.assertFalse(self.astroid.body[0].orelse) # simple If self.assertIsInstance(self.astroid.body[1].orelse[0], nodes.Pass) # If / else @@ -348,8 +388,8 @@ def test_if_elif_else_node(self) -> None: def test_block_range(self) -> None: # XXX ensure expected values - self.assertEqual(self.astroid.block_range(1), (0, 33)) - self.assertEqual(self.astroid.block_range(10), (0, 33)) # XXX (10, 33) ? + self.assertEqual(self.astroid.block_range(1), (0, 73)) + self.assertEqual(self.astroid.block_range(10), (0, 73)) # XXX (10, 73) ? self.assertEqual(self.astroid.body[1].block_range(5), (5, 6)) self.assertEqual(self.astroid.body[1].block_range(6), (6, 6)) self.assertEqual(self.astroid.body[1].orelse[0].block_range(7), (7, 8)) @@ -373,6 +413,20 @@ def test_orelse_line_numbering(self) -> None: assert self.astroid.body[4].orelse_col_offset == 0 assert self.astroid.body[4].orelse[0].orelse_lineno == 31 assert self.astroid.body[4].orelse[0].orelse_col_offset == 0 + assert self.astroid.body[5].orelse_lineno == 37 + assert self.astroid.body[5].orelse_col_offset == 0 + assert self.astroid.body[6].orelse_lineno == 46 + assert self.astroid.body[6].orelse_col_offset == 0 + assert self.astroid.body[6].body[1].orelse_lineno == 44 + assert self.astroid.body[6].body[1].orelse_col_offset == 4 + assert self.astroid.body[7].orelse_lineno == 51 + assert self.astroid.body[7].orelse_col_offset == 0 + assert self.astroid.body[8].body[0].orelse_lineno == 58 + assert self.astroid.body[8].body[0].orelse_col_offset == 0 + assert self.astroid.body[9].orelse_lineno == 72 + assert self.astroid.body[9].orelse_col_offset == 0 + assert self.astroid.body[9].handlers[0].body[0].orelse_lineno == 70 + assert self.astroid.body[9].handlers[0].body[0].orelse_col_offset == 4 @staticmethod @pytest.mark.filterwarnings("ignore:.*is_sys_guard:DeprecationWarning") From 7ba45474fe82e87a3c8bb6638ca44971826701e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 11 May 2022 16:44:12 +0200 Subject: [PATCH 09/13] Update docstrings --- astroid/nodes/node_classes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 1220ba81a4..76d6537c5b 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -3995,10 +3995,10 @@ def __init__( """The contents of the ``else`` block.""" self.orelse_lineno: Optional[int] = None - """The line number of the ``else`` or ``elif`` keyword.""" + """The line number of the ``else`` keyword.""" self.orelse_col_offset: Optional[int] = None - """The column offset of the ``else`` or ``elif`` keyword.""" + """The column offset of the ``else`` keyword.""" super().__init__( lineno=lineno, @@ -4372,10 +4372,10 @@ def __init__( """The contents of the ``else`` block.""" self.orelse_lineno: Optional[int] = None - """The line number of the ``else`` or ``elif`` keyword.""" + """The line number of the ``else`` keyword.""" self.orelse_col_offset: Optional[int] = None - """The column offset of the ``else`` or ``elif`` keyword.""" + """The column offset of the ``else`` keyword.""" super().__init__( lineno=lineno, From 4fb2aecb26ed653690c7360936830c60384d9bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 11 May 2022 16:50:49 +0200 Subject: [PATCH 10/13] Does this still work? --- astroid/rebuilder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index 99b1ec936f..eee39fb929 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -258,7 +258,7 @@ def _find_orelse_keyword( start = node.orelse[0].lineno # pylint: disable-next=unsubscriptable-object - for index, line in enumerate(self._data[start - 1 :: -1]): + for index, line in enumerate(self._data[start - 1 : node.lineno : -1]): if line.lstrip().startswith("else"): # if start - index == 37: # breakpoint() From f8ea1c12e504e119b14f4ea8ed0249d88136d822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 11 May 2022 16:51:13 +0200 Subject: [PATCH 11/13] Update astroid/rebuilder.py --- astroid/rebuilder.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index eee39fb929..8b90cddf51 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -260,8 +260,6 @@ def _find_orelse_keyword( # pylint: disable-next=unsubscriptable-object for index, line in enumerate(self._data[start - 1 : node.lineno : -1]): if line.lstrip().startswith("else"): - # if start - index == 37: - # breakpoint() return start - index, line.index("else") if line.lstrip().startswith("elif"): return start - index, line.index("elif") From b02fae5814e8b132641d04882660ecfbfd9d20a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 1 Jun 2022 15:27:17 +0200 Subject: [PATCH 12/13] Other approach --- ChangeLog | 4 +- astroid/mixins.py | 26 ++++++-- astroid/nodes/node_classes.py | 60 ----------------- astroid/rebuilder.py | 36 ---------- tests/unittest_nodes.py | 122 ++++++++++++---------------------- 5 files changed, 63 insertions(+), 185 deletions(-) diff --git a/ChangeLog b/ChangeLog index 6fa85b63e5..e89f3dde7a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -8,8 +8,8 @@ Release date: TBA * ``astroid`` now requires Python 3.7.2 to run. -* Add ``orelse_lineno`` and ``orelse_col_offset`` attributes to ``nodes.If``, - ``nodes.For``, ``nodes.While``, and ``nodes.TryExcept``. +* Made ``block_range`` of ``nodes.If``, ``nodes.For``, ``nodes.While``, + and ``nodes.TryExcept`` respect ``else`` and ``finally``. * Fix ``re`` brain on Python ``3.11``. The flags now come from ``re._compile``. diff --git a/astroid/mixins.py b/astroid/mixins.py index 4507a7e64e..71a6edb912 100644 --- a/astroid/mixins.py +++ b/astroid/mixins.py @@ -4,9 +4,12 @@ """This module contains some mixins for the different nodes. """ + +from __future__ import annotations + import itertools import sys -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from astroid import decorators from astroid.exceptions import AttributeInferenceError @@ -27,23 +30,32 @@ class BlockRangeMixIn: def blockstart_tolineno(self): return self.lineno - def _elsed_block_range(self, lineno, orelse, last=None): + def _elsed_block_range( + self, lineno: int, orelse: list[nodes.NodeNG], last: int | None = None + ) -> tuple[int, int]: """handle block line numbers range for try/finally, for, if and while statements """ - if lineno == self.fromlineno: + # If at the end of the node, return same line + if lineno == self.tolineno: return lineno, lineno if orelse: - if lineno >= orelse[0].fromlineno: + # If the lineno is beyond the body of the node we check the orelse + if lineno >= self.body[-1].tolineno + 1: + # If the orelse has a scope of its own we determine the block range there + if isinstance(orelse[0], BlockRangeMixIn): + return orelse[0]._elsed_block_range(lineno, orelse[0].orelse) + # Return last line of orelse return lineno, orelse[-1].tolineno - return lineno, orelse[0].fromlineno - 1 + # If the lineno is within the body we take the last line of the body + return lineno, self.body[-1].tolineno return lineno, last or self.tolineno class FilterStmtsMixin: """Mixin for statement filtering and assignment type""" - def _get_filtered_stmts(self, _, node, _stmts, mystmt: Optional["nodes.Statement"]): + def _get_filtered_stmts(self, _, node, _stmts, mystmt: nodes.Statement | None): """method used in _filter_stmts to get statements and trigger break""" if self.statement(future=True) is mystmt: # original node's statement is the assignment, only keep @@ -60,7 +72,7 @@ def assign_type(self): return self def _get_filtered_stmts( - self, lookup_node, node, _stmts, mystmt: Optional["nodes.Statement"] + self, lookup_node, node, _stmts, mystmt: nodes.Statement | None ): """method used in filter_stmts""" if self is mystmt: diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 76d6537c5b..c1cd4f886f 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -2661,12 +2661,6 @@ def __init__( self.orelse: typing.List[NodeNG] = [] """The contents of the ``else`` block of the loop.""" - 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.""" - self.type_annotation: Optional[NodeNG] = None # can be None """If present, this will contain the type annotation passed by a type comment""" @@ -2686,9 +2680,6 @@ def postinit( body: Optional[typing.List[NodeNG]] = None, orelse: Optional[typing.List[NodeNG]] = None, type_annotation: Optional[NodeNG] = None, - *, - orelse_lineno: Optional[int] = None, - orelse_col_offset: Optional[int] = None, ) -> None: """Do some setup after initialisation. @@ -2699,10 +2690,6 @@ def postinit( :param body: The contents of the body of the loop. :param orelse: The contents of the ``else`` block of the loop. - - :param orelse_lineno: The line number of the ``else`` keyword. - - :param orelse_lineno: The column offset of the ``else`` keyword. """ self.target = target self.iter = iter @@ -2710,8 +2697,6 @@ def postinit( self.body = body if orelse is not None: self.orelse = orelse - self.orelse_lineno = orelse_lineno - self.orelse_col_offset = orelse_col_offset self.type_annotation = type_annotation assigned_stmts: ClassVar[AssignedStmtsCall["For"]] @@ -3048,12 +3033,6 @@ 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`` or ``elif`` keyword.""" - - self.orelse_col_offset: Optional[int] = None - """The column offset of the ``else`` or ``elif`` keyword.""" - super().__init__( lineno=lineno, col_offset=col_offset, @@ -3067,9 +3046,6 @@ 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. @@ -3078,10 +3054,6 @@ 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`` or ``elif`` keyword. - - :param orelse_lineno: The column offset of the ``else`` or ``elif`` keyword. """ self.test = test if body is not None: @@ -3090,8 +3062,6 @@ 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): @@ -3994,12 +3964,6 @@ def __init__( self.orelse: typing.List[NodeNG] = [] """The contents of the ``else`` block.""" - 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, @@ -4013,9 +3977,6 @@ def postinit( body: Optional[typing.List[NodeNG]] = None, handlers: Optional[typing.List[ExceptHandler]] = None, orelse: Optional[typing.List[NodeNG]] = None, - *, - orelse_lineno: Optional[int] = None, - orelse_col_offset: Optional[int] = None, ) -> None: """Do some setup after initialisation. @@ -4024,10 +3985,6 @@ def postinit( :param handlers: The exception handlers. :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. """ if body is not None: self.body = body @@ -4035,8 +3992,6 @@ def postinit( self.handlers = handlers if orelse is not None: self.orelse = orelse - self.orelse_lineno = orelse_lineno - self.orelse_col_offset = orelse_col_offset def _infer_name(self, frame, name): return name @@ -4371,12 +4326,6 @@ def __init__( self.orelse: typing.List[NodeNG] = [] """The contents of the ``else`` block.""" - 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, @@ -4390,9 +4339,6 @@ 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. @@ -4401,18 +4347,12 @@ def postinit( :param body: The contents of the loop. :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: self.body = body if orelse is not None: self.orelse = orelse - self.orelse_lineno = orelse_lineno - self.orelse_col_offset = orelse_col_offset @cached_property def blockstart_tolineno(self): diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index 99b1ec936f..c89ea14ed8 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -248,26 +248,6 @@ def _reset_end_lineno(self, newnode: nodes.NodeNG) -> None: for child_node in newnode.get_children(): self._reset_end_lineno(child_node) - def _find_orelse_keyword( - self, node: Union["ast.If", "ast.Try", "ast.For", "ast.AsyncFor", "ast.While"] - ) -> Tuple[Optional[int], Optional[int]]: - """Get the line number and column offset of the `else` or `elif` keyword.""" - if not self._data or not node.orelse: - return None, None - - start = node.orelse[0].lineno - - # pylint: disable-next=unsubscriptable-object - for index, line in enumerate(self._data[start - 1 :: -1]): - if line.lstrip().startswith("else"): - # if start - index == 37: - # breakpoint() - return start - index, line.index("else") - if line.lstrip().startswith("elif"): - return start - index, line.index("elif") - - return None, None - def visit_module( self, node: "ast.Module", modname: str, modpath: str, package: bool ) -> nodes.Module: @@ -1195,8 +1175,6 @@ def _visit_for( # pylint: disable-next=unsubscriptable-object col_offset = self._data[node.lineno - 1].index("async") - orelse_lineno, orelse_col_offset = self._find_orelse_keyword(node) - newnode = cls( lineno=node.lineno, col_offset=col_offset, @@ -1212,8 +1190,6 @@ def _visit_for( body=[self.visit(child, newnode) for child in node.body], orelse=[self.visit(child, newnode) for child in node.orelse], type_annotation=type_annotation, - orelse_lineno=orelse_lineno, - orelse_col_offset=orelse_col_offset, ) return newnode @@ -1406,14 +1382,10 @@ def visit_if(self, node: "ast.If", parent: NodeNG) -> nodes.If: parent=parent, ) - orelse_lineno, orelse_col_offset = self._find_orelse_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 @@ -1827,14 +1799,10 @@ def visit_tryexcept(self, node: "ast.Try", parent: NodeNG) -> nodes.TryExcept: else: newnode = nodes.TryExcept(node.lineno, node.col_offset, parent) - orelse_lineno, orelse_col_offset = self._find_orelse_keyword(node) - newnode.postinit( [self.visit(child, newnode) for child in node.body], [self.visit(child, newnode) for child in node.handlers], [self.visit(child, newnode) for child in node.orelse], - orelse_lineno=orelse_lineno, - orelse_col_offset=orelse_col_offset, ) return newnode @@ -1903,14 +1871,10 @@ def visit_while(self, node: "ast.While", parent: NodeNG) -> nodes.While: parent=parent, ) - orelse_lineno, orelse_col_offset = self._find_orelse_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 diff --git a/tests/unittest_nodes.py b/tests/unittest_nodes.py index c11f4afade..98eb30c8e3 100644 --- a/tests/unittest_nodes.py +++ b/tests/unittest_nodes.py @@ -333,53 +333,12 @@ class IfNodeTest(_NodeTest): else: # This is using else in a comment raise - - for x in range(3): - print() - else: - print() - - for x in range(3): - print() - if x == 3: - break - else: - print() - else: - print() - - while True: - print() - else: - print() - - try: - 1 / 0 - except ZeroDivisionError: - print() - else: - print() - finally: - print() - - try: - 1 / 0 - except ZeroDivisionError: - try: - 1 / 0 - except: - print() - else: - print() - else: - print() - """ def test_if_elif_else_node(self) -> None: """test transformation for If node""" - self.assertEqual(len(self.astroid.body), 10) - for stmt in self.astroid.body[:5]: + self.assertEqual(len(self.astroid.body), 5) + for stmt in self.astroid.body: self.assertIsInstance(stmt, nodes.If) self.assertFalse(self.astroid.body[0].orelse) # simple If self.assertIsInstance(self.astroid.body[1].orelse[0], nodes.Pass) # If / else @@ -387,46 +346,49 @@ def test_if_elif_else_node(self) -> None: self.assertIsInstance(self.astroid.body[3].orelse[0].orelse[0], nodes.If) def test_block_range(self) -> None: + """Test block_range of various scope constructs""" # XXX ensure expected values - self.assertEqual(self.astroid.block_range(1), (0, 73)) - self.assertEqual(self.astroid.block_range(10), (0, 73)) # XXX (10, 73) ? + # Module + self.assertEqual(self.astroid.block_range(1), (0, 33)) + self.assertEqual(self.astroid.block_range(10), (0, 33)) # XXX (10, 33) ? + + # if + self.assertEqual(self.astroid.body[0].block_range(2), (2, 3)) + self.assertEqual(self.astroid.body[0].block_range(3), (3, 3)) + + # if ... else self.assertEqual(self.astroid.body[1].block_range(5), (5, 6)) self.assertEqual(self.astroid.body[1].block_range(6), (6, 6)) - 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 == 12 - assert self.astroid.body[2].orelse_col_offset == 0 - assert self.astroid.body[3].orelse_lineno == 17 - assert self.astroid.body[3].orelse_col_offset == 0 - assert self.astroid.body[3].orelse[0].orelse_lineno == 19 - assert self.astroid.body[3].orelse[0].orelse_col_offset == 0 - 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 - assert self.astroid.body[4].orelse_lineno == 26 - assert self.astroid.body[4].orelse_col_offset == 0 - assert self.astroid.body[4].orelse[0].orelse_lineno == 31 - assert self.astroid.body[4].orelse[0].orelse_col_offset == 0 - assert self.astroid.body[5].orelse_lineno == 37 - assert self.astroid.body[5].orelse_col_offset == 0 - assert self.astroid.body[6].orelse_lineno == 46 - assert self.astroid.body[6].orelse_col_offset == 0 - assert self.astroid.body[6].body[1].orelse_lineno == 44 - assert self.astroid.body[6].body[1].orelse_col_offset == 4 - assert self.astroid.body[7].orelse_lineno == 51 - assert self.astroid.body[7].orelse_col_offset == 0 - assert self.astroid.body[8].body[0].orelse_lineno == 58 - assert self.astroid.body[8].body[0].orelse_col_offset == 0 - assert self.astroid.body[9].orelse_lineno == 72 - assert self.astroid.body[9].orelse_col_offset == 0 - assert self.astroid.body[9].handlers[0].body[0].orelse_lineno == 70 - assert self.astroid.body[9].handlers[0].body[0].orelse_col_offset == 4 + self.assertEqual(self.astroid.body[1].block_range(7), (7, 8)) + self.assertEqual(self.astroid.body[1].block_range(8), (8, 8)) + + # if ... elif + self.assertEqual(self.astroid.body[2].block_range(10), (10, 11)) + self.assertEqual(self.astroid.body[2].block_range(11), (11, 11)) + self.assertEqual(self.astroid.body[2].block_range(12), (12, 13)) + self.assertEqual(self.astroid.body[2].block_range(13), (13, 13)) + + # if ... elif ... elif ... else + self.assertEqual(self.astroid.body[3].block_range(15), (15, 16)) + self.assertEqual(self.astroid.body[3].block_range(16), (16, 16)) + self.assertEqual(self.astroid.body[3].block_range(17), (17, 18)) + self.assertEqual(self.astroid.body[3].block_range(18), (18, 18)) + self.assertEqual(self.astroid.body[3].block_range(19), (19, 20)) + self.assertEqual(self.astroid.body[3].block_range(20), (20, 20)) + self.assertEqual(self.astroid.body[3].block_range(21), (21, 22)) + self.assertEqual(self.astroid.body[3].block_range(22), (22, 22)) + + # if ... elif ... else + self.assertEqual(self.astroid.body[4].block_range(24), (24, 25)) + self.assertEqual(self.astroid.body[4].block_range(25), (25, 25)) + self.assertEqual(self.astroid.body[4].block_range(26), (26, 30)) + self.assertEqual(self.astroid.body[4].block_range(27), (27, 30)) + self.assertEqual(self.astroid.body[4].block_range(28), (28, 30)) + self.assertEqual(self.astroid.body[4].block_range(29), (29, 30)) + self.assertEqual(self.astroid.body[4].block_range(30), (30, 30)) + self.assertEqual(self.astroid.body[4].block_range(31), (31, 33)) + self.assertEqual(self.astroid.body[4].block_range(32), (32, 33)) + self.assertEqual(self.astroid.body[4].block_range(33), (33, 33)) @staticmethod @pytest.mark.filterwarnings("ignore:.*is_sys_guard:DeprecationWarning") From 662c79820e3c92e41cb1479abb18fdd5a8d15fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Wed, 1 Jun 2022 15:33:05 +0200 Subject: [PATCH 13/13] Remove whitespaces --- astroid/rebuilder.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/astroid/rebuilder.py b/astroid/rebuilder.py index 367910446e..e4f9ee5a1a 100644 --- a/astroid/rebuilder.py +++ b/astroid/rebuilder.py @@ -1358,7 +1358,6 @@ def visit_if(self, node: ast.If, parent: NodeNG) -> nodes.If: end_col_offset=getattr(node, "end_col_offset", None), parent=parent, ) - newnode.postinit( self.visit(node.test, newnode), [self.visit(child, newnode) for child in node.body], @@ -1773,7 +1772,6 @@ def visit_tryexcept(self, node: ast.Try, parent: NodeNG) -> nodes.TryExcept: ) else: newnode = nodes.TryExcept(node.lineno, node.col_offset, parent) - newnode.postinit( [self.visit(child, newnode) for child in node.body], [self.visit(child, newnode) for child in node.handlers], @@ -1845,7 +1843,6 @@ def visit_while(self, node: ast.While, parent: NodeNG) -> nodes.While: end_col_offset=getattr(node, "end_col_offset", None), parent=parent, ) - newnode.postinit( self.visit(node.test, newnode), [self.visit(child, newnode) for child in node.body],