From 8e48b5decd81ebeb85703de61d392af48899d896 Mon Sep 17 00:00:00 2001 From: Samuel Tatasurya Date: Wed, 6 Nov 2019 00:17:38 -0800 Subject: [PATCH 1/7] 1) Added a new function in fixer_util.py, BlankLineOrPass. 2) Modified future and itertools_imports fixers to use BlankLineOrPass instead of BlankLine. --- Lib/lib2to3/fixer_util.py | 33 ++++++++++++++++++++++ Lib/lib2to3/fixes/fix_future.py | 4 +-- Lib/lib2to3/fixes/fix_itertools_imports.py | 4 +-- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/Lib/lib2to3/fixer_util.py b/Lib/lib2to3/fixer_util.py index c2a3a47f503286e..edffad230ccdae1 100644 --- a/Lib/lib2to3/fixer_util.py +++ b/Lib/lib2to3/fixer_util.py @@ -150,6 +150,39 @@ def ImportAndCall(node, results, names): new.prefix = node.prefix return new +def BlankLineOrPass(node): + """Returns either a blank line or a pass statement depending on + the node's parent's siblings to maintain syntactic correctness + within a suite after conversion""" + skip = {token.NEWLINE, token.INDENT, token.DEDENT} + def has_significant_sibling(node, is_forward): + sibling = None + if is_forward: + sibling = node.next_sibling + else: + sibling = node.prev_sibling + while sibling: + if isinstance(sibling, Node): + if sibling.type != syms.simple_stmt: + return True + for child in sibling.children: + if child.type not in skip: + return True + elif isinstance(sibling, Leaf) and sibling.type not in skip: + return True + if is_forward: + sibling = sibling.next_sibling + else: + sibling = sibling.prev_sibling + return False + + parent = node.parent + if parent and parent.parent and parent.parent.type == syms.suite: + if (not has_significant_sibling(parent, False) + and not has_significant_sibling(parent, True)): + return Name("pass") + return BlankLine() + ########################################################### ### Determine whether a node represents a given literal diff --git a/Lib/lib2to3/fixes/fix_future.py b/Lib/lib2to3/fixes/fix_future.py index fbcb86af0791338..cd8e25cccc152b6 100644 --- a/Lib/lib2to3/fixes/fix_future.py +++ b/Lib/lib2to3/fixes/fix_future.py @@ -6,7 +6,7 @@ # Local imports from .. import fixer_base -from ..fixer_util import BlankLine +from ..fixer_util import BlankLineOrPass class FixFuture(fixer_base.BaseFix): BM_compatible = True @@ -17,6 +17,6 @@ class FixFuture(fixer_base.BaseFix): run_order = 10 def transform(self, node, results): - new = BlankLine() + new = BlankLineOrPass(node) new.prefix = node.prefix return new diff --git a/Lib/lib2to3/fixes/fix_itertools_imports.py b/Lib/lib2to3/fixes/fix_itertools_imports.py index 0ddbc7b8422991b..8007dd81423585d 100644 --- a/Lib/lib2to3/fixes/fix_itertools_imports.py +++ b/Lib/lib2to3/fixes/fix_itertools_imports.py @@ -2,7 +2,7 @@ # Local imports from lib2to3 import fixer_base -from lib2to3.fixer_util import BlankLine, syms, token +from lib2to3.fixer_util import BlankLineOrPass, syms, token class FixItertoolsImports(fixer_base.BaseFix): @@ -52,6 +52,6 @@ def transform(self, node, results): if (not (imports.children or getattr(imports, 'value', None)) or imports.parent is None): p = node.prefix - node = BlankLine() + node = BlankLineOrPass(node) node.prefix = p return node From 8c08166dcd9ae01f530434ec45d867e19da9efd2 Mon Sep 17 00:00:00 2001 From: Samuel Tatasurya Date: Fri, 8 Nov 2019 21:10:08 -0800 Subject: [PATCH 2/7] Added test cases for 'future' and 'itertools_imports' fixers for BlankLineOrPass conversion. --- Lib/lib2to3/tests/test_fixers.py | 215 ++++++++++++++++++++++++++++++- 1 file changed, 209 insertions(+), 6 deletions(-) diff --git a/Lib/lib2to3/tests/test_fixers.py b/Lib/lib2to3/tests/test_fixers.py index 3da5dd845c93c66..81da057a0168175 100644 --- a/Lib/lib2to3/tests/test_fixers.py +++ b/Lib/lib2to3/tests/test_fixers.py @@ -25,15 +25,16 @@ def setUp(self, fix_list=None, fixer_pkg="lib2to3", options=None): self.refactor.post_order): fixer.log = self.fixer_log - def _check(self, before, after): - before = support.reformat(before) - after = support.reformat(after) + def _check(self, before, after, reformat=True): + if reformat: + before = support.reformat(before) + after = support.reformat(after) tree = self.refactor.refactor_string(before, self.filename) self.assertEqual(after, str(tree)) return tree - def check(self, before, after, ignore_warnings=False): - tree = self._check(before, after) + def check(self, before, after, ignore_warnings=False, reformat=True): + tree = self._check(before, after, reformat=reformat) self.assertTrue(tree.was_changed) if not ignore_warnings: self.assertEqual(self.fixer_log, []) @@ -3656,9 +3657,130 @@ def test_future(self): a = """\n# comment""" self.check(b, a) + def test_suite_try_blank(self): + b = ( + "try:\n" + " from __future__ import with_statement\n" + " from sys import exit\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " \n" + " from sys import exit\n" + "except ImportError:\n" + " pass\n") + self.check(b, a, reformat=False) # to avoid dedent + + def test_suite_try_pass(self): + b = ( + "try:\n" + " from __future__ import with_statement\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " pass\n" + "except ImportError:\n" + " pass\n") + self.check(b, a) + + def test_suite_if_blank(self): + b = ( + "if sys.version_info < (3, 0):\n" + " from __future__ import with_statement\n" + " from sys import exit\n") + a = ( + "if sys.version_info < (3, 0):\n" + " \n" + " from sys import exit\n") + self.check(b, a, reformat=False) # to avoid dedent + + def test_suite_if_pass(self): + b = ( + "if sys.version_info < (3, 0):\n" + " from __future__ import with_statement\n") + a = ( + "if sys.version_info < (3, 0):\n" + " pass\n") + self.check(b, a) # to avoid dedent + + def test_pass_with_comments(self): + b = ( + "try:\n" + " # this comment\n" + " from __future__ import with_statement # that comment\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " # this comment\n" + " pass # that comment\n" + "except ImportError:\n" + " pass\n") + self.check(b, a) + + def test_pass_with_newlines(self): + b = ( + "try:\n" + " \n" + " \n" + " from __future__ import with_statement\n" + " \n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " \n" + " \n" + " pass\n" + " \n" + "except ImportError:\n" + " pass\n") + self.check(b, a) + def test_run_order(self): self.assert_runs_after('print') + +class Test_future_with_itertools_imports(FixerTestCase): + + def setUp(self): + fix_list = ["future", "itertools_imports"] + super(Test_future_with_itertools_imports, self).setUp(fix_list) + + def test_double_transform(self): + """Note the difference between the two conversion results, due to + the fact that 'future' fixer runs last""" + b = ( + "try:\n" + " from __future__ import with_statement\n" + " from itertools import imap\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " pass\n" + " \n" + "except ImportError:\n" + " pass\n") + self.check(b, a, reformat=False) # to avoid dedent + + b = ( + "try:\n" + " from itertools import imap\n" + " from __future__ import with_statement\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " \n" + " pass\n" + "except ImportError:\n" + " pass\n") + self.check(b, a, reformat=False) # to avoid dedent + + class Test_itertools(FixerTestCase): fixer = "itertools" @@ -3784,11 +3906,92 @@ def test_ifilter_and_zip_longest(self): a = "from itertools import bar, %s, foo" % (name,) self.check(b, a) + def test_suite_try_blank(self): + b = ( + "try:\n" + " from itertools import imap\n" + " from sys import exit\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " \n" + " from sys import exit\n" + "except ImportError:\n" + " pass\n") + self.check(b, a, reformat=False) # to avoid dedent + + def test_suite_try_pass(self): + b = ( + "try:\n" + " from itertools import imap\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " pass\n" + "except ImportError:\n" + " pass\n") + self.check(b, a) + + def test_suite_if_blank(self): + b = ( + "if sys.version_info < (3, 0):\n" + " from itertools import imap\n" + " from sys import exit\n") + a = ( + "if sys.version_info < (3, 0):\n" + " \n" + " from sys import exit\n") + self.check(b, a, reformat=False) # to avoid dedent + + def test_suite_if_pass(self): + b = ( + "if sys.version_info < (3, 0):\n" + " from itertools import imap\n") + a = ( + "if sys.version_info < (3, 0):\n" + " pass\n") + self.check(b, a) # to avoid dedent + + def test_pass_with_comments(self): + b = ( + "try:\n" + " # this comment\n" + " from itertools import imap # that comment\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " # this comment\n" + " pass # that comment\n" + "except ImportError:\n" + " pass\n") + self.check(b, a) + + def test_pass_with_newlines(self): + b = ( + "try:\n" + " \n" + " \n" + " from itertools import imap\n" + " \n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " \n" + " \n" + " pass\n" + " \n" + "except ImportError:\n" + " pass\n") + self.check(b, a) + def test_import_star(self): s = "from itertools import *" self.unchanged(s) - def test_unchanged(self): s = "from itertools import foo" self.unchanged(s) From 13f11e525f4d86b2fba60f77b7d17ea6cf4e090a Mon Sep 17 00:00:00 2001 From: Samuel Tatasurya Date: Wed, 6 Nov 2019 00:17:38 -0800 Subject: [PATCH 3/7] 1) Added a new function in fixer_util.py, BlankLineOrPass. 2) Modified future and itertools_imports fixers to use BlankLineOrPass instead of BlankLine. --- Lib/lib2to3/fixer_util.py | 33 ++++++++++++++++++++++ Lib/lib2to3/fixes/fix_future.py | 4 +-- Lib/lib2to3/fixes/fix_itertools_imports.py | 4 +-- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/Lib/lib2to3/fixer_util.py b/Lib/lib2to3/fixer_util.py index c2a3a47f503286e..edffad230ccdae1 100644 --- a/Lib/lib2to3/fixer_util.py +++ b/Lib/lib2to3/fixer_util.py @@ -150,6 +150,39 @@ def ImportAndCall(node, results, names): new.prefix = node.prefix return new +def BlankLineOrPass(node): + """Returns either a blank line or a pass statement depending on + the node's parent's siblings to maintain syntactic correctness + within a suite after conversion""" + skip = {token.NEWLINE, token.INDENT, token.DEDENT} + def has_significant_sibling(node, is_forward): + sibling = None + if is_forward: + sibling = node.next_sibling + else: + sibling = node.prev_sibling + while sibling: + if isinstance(sibling, Node): + if sibling.type != syms.simple_stmt: + return True + for child in sibling.children: + if child.type not in skip: + return True + elif isinstance(sibling, Leaf) and sibling.type not in skip: + return True + if is_forward: + sibling = sibling.next_sibling + else: + sibling = sibling.prev_sibling + return False + + parent = node.parent + if parent and parent.parent and parent.parent.type == syms.suite: + if (not has_significant_sibling(parent, False) + and not has_significant_sibling(parent, True)): + return Name("pass") + return BlankLine() + ########################################################### ### Determine whether a node represents a given literal diff --git a/Lib/lib2to3/fixes/fix_future.py b/Lib/lib2to3/fixes/fix_future.py index fbcb86af0791338..cd8e25cccc152b6 100644 --- a/Lib/lib2to3/fixes/fix_future.py +++ b/Lib/lib2to3/fixes/fix_future.py @@ -6,7 +6,7 @@ # Local imports from .. import fixer_base -from ..fixer_util import BlankLine +from ..fixer_util import BlankLineOrPass class FixFuture(fixer_base.BaseFix): BM_compatible = True @@ -17,6 +17,6 @@ class FixFuture(fixer_base.BaseFix): run_order = 10 def transform(self, node, results): - new = BlankLine() + new = BlankLineOrPass(node) new.prefix = node.prefix return new diff --git a/Lib/lib2to3/fixes/fix_itertools_imports.py b/Lib/lib2to3/fixes/fix_itertools_imports.py index 0ddbc7b8422991b..8007dd81423585d 100644 --- a/Lib/lib2to3/fixes/fix_itertools_imports.py +++ b/Lib/lib2to3/fixes/fix_itertools_imports.py @@ -2,7 +2,7 @@ # Local imports from lib2to3 import fixer_base -from lib2to3.fixer_util import BlankLine, syms, token +from lib2to3.fixer_util import BlankLineOrPass, syms, token class FixItertoolsImports(fixer_base.BaseFix): @@ -52,6 +52,6 @@ def transform(self, node, results): if (not (imports.children or getattr(imports, 'value', None)) or imports.parent is None): p = node.prefix - node = BlankLine() + node = BlankLineOrPass(node) node.prefix = p return node From 6b82ea0679ad3c79e96d3b0a4b0564cee246ee23 Mon Sep 17 00:00:00 2001 From: Samuel Tatasurya Date: Fri, 8 Nov 2019 21:10:08 -0800 Subject: [PATCH 4/7] Added test cases for 'future' and 'itertools_imports' fixers for BlankLineOrPass conversion. --- Lib/lib2to3/tests/test_fixers.py | 215 ++++++++++++++++++++++++++++++- 1 file changed, 209 insertions(+), 6 deletions(-) diff --git a/Lib/lib2to3/tests/test_fixers.py b/Lib/lib2to3/tests/test_fixers.py index 3da5dd845c93c66..81da057a0168175 100644 --- a/Lib/lib2to3/tests/test_fixers.py +++ b/Lib/lib2to3/tests/test_fixers.py @@ -25,15 +25,16 @@ def setUp(self, fix_list=None, fixer_pkg="lib2to3", options=None): self.refactor.post_order): fixer.log = self.fixer_log - def _check(self, before, after): - before = support.reformat(before) - after = support.reformat(after) + def _check(self, before, after, reformat=True): + if reformat: + before = support.reformat(before) + after = support.reformat(after) tree = self.refactor.refactor_string(before, self.filename) self.assertEqual(after, str(tree)) return tree - def check(self, before, after, ignore_warnings=False): - tree = self._check(before, after) + def check(self, before, after, ignore_warnings=False, reformat=True): + tree = self._check(before, after, reformat=reformat) self.assertTrue(tree.was_changed) if not ignore_warnings: self.assertEqual(self.fixer_log, []) @@ -3656,9 +3657,130 @@ def test_future(self): a = """\n# comment""" self.check(b, a) + def test_suite_try_blank(self): + b = ( + "try:\n" + " from __future__ import with_statement\n" + " from sys import exit\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " \n" + " from sys import exit\n" + "except ImportError:\n" + " pass\n") + self.check(b, a, reformat=False) # to avoid dedent + + def test_suite_try_pass(self): + b = ( + "try:\n" + " from __future__ import with_statement\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " pass\n" + "except ImportError:\n" + " pass\n") + self.check(b, a) + + def test_suite_if_blank(self): + b = ( + "if sys.version_info < (3, 0):\n" + " from __future__ import with_statement\n" + " from sys import exit\n") + a = ( + "if sys.version_info < (3, 0):\n" + " \n" + " from sys import exit\n") + self.check(b, a, reformat=False) # to avoid dedent + + def test_suite_if_pass(self): + b = ( + "if sys.version_info < (3, 0):\n" + " from __future__ import with_statement\n") + a = ( + "if sys.version_info < (3, 0):\n" + " pass\n") + self.check(b, a) # to avoid dedent + + def test_pass_with_comments(self): + b = ( + "try:\n" + " # this comment\n" + " from __future__ import with_statement # that comment\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " # this comment\n" + " pass # that comment\n" + "except ImportError:\n" + " pass\n") + self.check(b, a) + + def test_pass_with_newlines(self): + b = ( + "try:\n" + " \n" + " \n" + " from __future__ import with_statement\n" + " \n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " \n" + " \n" + " pass\n" + " \n" + "except ImportError:\n" + " pass\n") + self.check(b, a) + def test_run_order(self): self.assert_runs_after('print') + +class Test_future_with_itertools_imports(FixerTestCase): + + def setUp(self): + fix_list = ["future", "itertools_imports"] + super(Test_future_with_itertools_imports, self).setUp(fix_list) + + def test_double_transform(self): + """Note the difference between the two conversion results, due to + the fact that 'future' fixer runs last""" + b = ( + "try:\n" + " from __future__ import with_statement\n" + " from itertools import imap\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " pass\n" + " \n" + "except ImportError:\n" + " pass\n") + self.check(b, a, reformat=False) # to avoid dedent + + b = ( + "try:\n" + " from itertools import imap\n" + " from __future__ import with_statement\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " \n" + " pass\n" + "except ImportError:\n" + " pass\n") + self.check(b, a, reformat=False) # to avoid dedent + + class Test_itertools(FixerTestCase): fixer = "itertools" @@ -3784,11 +3906,92 @@ def test_ifilter_and_zip_longest(self): a = "from itertools import bar, %s, foo" % (name,) self.check(b, a) + def test_suite_try_blank(self): + b = ( + "try:\n" + " from itertools import imap\n" + " from sys import exit\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " \n" + " from sys import exit\n" + "except ImportError:\n" + " pass\n") + self.check(b, a, reformat=False) # to avoid dedent + + def test_suite_try_pass(self): + b = ( + "try:\n" + " from itertools import imap\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " pass\n" + "except ImportError:\n" + " pass\n") + self.check(b, a) + + def test_suite_if_blank(self): + b = ( + "if sys.version_info < (3, 0):\n" + " from itertools import imap\n" + " from sys import exit\n") + a = ( + "if sys.version_info < (3, 0):\n" + " \n" + " from sys import exit\n") + self.check(b, a, reformat=False) # to avoid dedent + + def test_suite_if_pass(self): + b = ( + "if sys.version_info < (3, 0):\n" + " from itertools import imap\n") + a = ( + "if sys.version_info < (3, 0):\n" + " pass\n") + self.check(b, a) # to avoid dedent + + def test_pass_with_comments(self): + b = ( + "try:\n" + " # this comment\n" + " from itertools import imap # that comment\n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " # this comment\n" + " pass # that comment\n" + "except ImportError:\n" + " pass\n") + self.check(b, a) + + def test_pass_with_newlines(self): + b = ( + "try:\n" + " \n" + " \n" + " from itertools import imap\n" + " \n" + "except ImportError:\n" + " pass\n") + a = ( + "try:\n" + " \n" + " \n" + " pass\n" + " \n" + "except ImportError:\n" + " pass\n") + self.check(b, a) + def test_import_star(self): s = "from itertools import *" self.unchanged(s) - def test_unchanged(self): s = "from itertools import foo" self.unchanged(s) From d13980728fe8ac2a6de64a1ccab0cd6c853b3b04 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 9 Nov 2019 06:16:25 +0000 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2019-11-09-06-16-24.bpo-38681.jJobMS.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2019-11-09-06-16-24.bpo-38681.jJobMS.rst diff --git a/Misc/NEWS.d/next/Library/2019-11-09-06-16-24.bpo-38681.jJobMS.rst b/Misc/NEWS.d/next/Library/2019-11-09-06-16-24.bpo-38681.jJobMS.rst new file mode 100644 index 000000000000000..fb6fedc033a2b21 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-11-09-06-16-24.bpo-38681.jJobMS.rst @@ -0,0 +1,2 @@ +Transformation performed by certain fixers (e.g. future, itertools_imports) that causes a statement to be replaced by a blank line will generate a Python file that contains syntax error. Enhancement applied checks whether the statement to be replaced has any siblings or not. If no sibling is found, then the statement gets replaced with a "pass" statement instead of a blank line. +By doing this, Python source files generated by 2to3 are more readily runnable right after the transformation. \ No newline at end of file From cd480b0c72bc212ffd855c5eb98de0f098904f97 Mon Sep 17 00:00:00 2001 From: Samuel Tatasurya Date: Sat, 7 Mar 2020 14:55:52 -0800 Subject: [PATCH 6/7] Update Misc/NEWS.d/next/Library/2019-11-09-06-16-24.bpo-38681.jJobMS.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Batuhan Taşkaya <47358913+isidentical@users.noreply.github.com> --- .../next/Library/2019-11-09-06-16-24.bpo-38681.jJobMS.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2019-11-09-06-16-24.bpo-38681.jJobMS.rst b/Misc/NEWS.d/next/Library/2019-11-09-06-16-24.bpo-38681.jJobMS.rst index fb6fedc033a2b21..5250d5a4ed00443 100644 --- a/Misc/NEWS.d/next/Library/2019-11-09-06-16-24.bpo-38681.jJobMS.rst +++ b/Misc/NEWS.d/next/Library/2019-11-09-06-16-24.bpo-38681.jJobMS.rst @@ -1,2 +1,2 @@ -Transformation performed by certain fixers (e.g. future, itertools_imports) that causes a statement to be replaced by a blank line will generate a Python file that contains syntax error. Enhancement applied checks whether the statement to be replaced has any siblings or not. If no sibling is found, then the statement gets replaced with a "pass" statement instead of a blank line. -By doing this, Python source files generated by 2to3 are more readily runnable right after the transformation. \ No newline at end of file +Transformation performed by certain fixers (e.g. future, itertools_imports) that causes a statement to be replaced by a blank line will generate a Python file that contains a syntax error. Enhancement applied checks whether the statement to be replaced has any siblings or not. If no sibling is found, then the statement gets replaced with a "pass" statement instead of a blank line. +By doing this, Python source files generated by 2to3 are more readily runnable right after the transformation. From 47fe2d069952676faa3b97f64a3c9f4d0f0ea82d Mon Sep 17 00:00:00 2001 From: Samuel Tatasurya Date: Sat, 7 Mar 2020 21:07:39 -0800 Subject: [PATCH 7/7] Removed unnecessary assignment to 'sibling'. --- Lib/lib2to3/fixer_util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/lib2to3/fixer_util.py b/Lib/lib2to3/fixer_util.py index edffad230ccdae1..66d9204b349115f 100644 --- a/Lib/lib2to3/fixer_util.py +++ b/Lib/lib2to3/fixer_util.py @@ -156,7 +156,6 @@ def BlankLineOrPass(node): within a suite after conversion""" skip = {token.NEWLINE, token.INDENT, token.DEDENT} def has_significant_sibling(node, is_forward): - sibling = None if is_forward: sibling = node.next_sibling else: