Skip to content
32 changes: 32 additions & 0 deletions Lib/lib2to3/fixer_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,38 @@ 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):
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
Expand Down
4 changes: 2 additions & 2 deletions Lib/lib2to3/fixes/fix_future.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
4 changes: 2 additions & 2 deletions Lib/lib2to3/fixes/fix_itertools_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
215 changes: 209 additions & 6 deletions Lib/lib2to3/tests/test_fixers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, [])
Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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 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.