Index: lib2to3/tests/test_fixers.py =================================================================== --- lib2to3/tests/test_fixers.py (revision 79811) +++ lib2to3/tests/test_fixers.py (working copy) @@ -423,6 +423,17 @@ a = """print((a, b, c))""" self.check(b, a) + def test_double_bare_print(self): + b = """print ; print""" + a = """print() ; print()""" + self.check(b, a) + + def test_print_then_stdout_idiom(self): + #from bug 8029 + b = """print "ASD",; sys.stdout.write("")""" + a = """print("ASD", end='')""" + self.check(b, a) + # trailing commas def test_trailing_comma_1(self): Index: lib2to3/fixes/fix_print.py =================================================================== --- lib2to3/fixes/fix_print.py (revision 79811) +++ lib2to3/fixes/fix_print.py (working copy) @@ -8,6 +8,7 @@ 'print ...' into 'print(...)' 'print ... ,' into 'print(..., end=" ")' 'print >>x, ...' into 'print(..., file=x)' + 'print ... , ; sys.stdout.write("")' into 'print(..., end="")' No changes are applied if print_function is imported from __future__ @@ -18,7 +19,7 @@ from .. import pytree from ..pgen2 import token from .. import fixer_base -from ..fixer_util import Name, Call, Comma, String, is_tuple +from ..fixer_util import Name, Call, Comma, String, is_tuple, syms parend_expr = patcomp.compile_pattern( @@ -29,48 +30,71 @@ class FixPrint(fixer_base.BaseFix): PATTERN = """ - simple_stmt< any* bare='print' any* > | print_stmt + simple_stmt< print_stmt< any* ','> scol=';' power< 'sys' trailer< '.' 'stdout' > trailer< '.' 'write' > trailer< '(' sval=STRING ')' > > any* > + | simple_stmt< any* (print_stmt | 'print') any* > """ def transform(self, node, results): assert results - bare_print = results.get("bare") + string_node = results.get("sval") + semicolon_node = results.get("scol") + + print_stdout_case = False + if string_node and (string_node.__unicode__() == 'u""' or string_node.__unicode__() == '""'): + print_stdout_case = True + # Special-case print followed by sys.stdout.write("") + # self.warning(node, "Mixing print statements and writes to stdout may result in unexpected formatting") + semicolon_node.next_sibling.remove() + semicolon_node.remove() + + #Because we must match a print_stmt followed by another statement, we + # must match the simple_stmt containing print_stmt instead of print + # itself. Because we may have caught multiple print_stms, iterate over + # each child. + for n in node.children: + if n == Name(u"print"): + # Special-case print all by itself + n.replace(Call(Name(u"print"), [], + prefix=n.prefix)) + continue + if n.type == syms.print_stmt: + assert n.children[0] == Name(u"print") + args = n.children[1:] + if len(args) == 1 and parend_expr.match(args[0]): + # We don't want to keep sticking parens around an + # already-parenthesised expression. + return - if bare_print: - # Special-case print all by itself - bare_print.replace(Call(Name(u"print"), [], - prefix=bare_print.prefix)) - return - assert node.children[0] == Name(u"print") - args = node.children[1:] - if len(args) == 1 and parend_expr.match(args[0]): - # We don't want to keep sticking parens around an - # already-parenthesised expression. - return + sep = end = file = None + if args and args[-1] == Comma(): + args = args[:-1] + if print_stdout_case: + #This was a special case with sys.stdout and the end must reflect that + end = "" + else: + end = " " + if args and args[0] == pytree.Leaf(token.RIGHTSHIFT, u">>"): + assert len(args) >= 2 + file = args[1].clone() + args = args[3:] # Strip a possible comma after the file expression + # Now synthesize a print(args, sep=..., end=..., file=...) n. + l_args = [arg.clone() for arg in args] + if l_args: + l_args[0].prefix = u"" + if sep is not None or end is not None or file is not None: + if sep is not None: + self.add_kwarg(l_args, u"sep", String(repr(sep))) + if end is not None: + self.add_kwarg(l_args, u"end", String(repr(end))) + if file is not None: + self.add_kwarg(l_args, u"file", file) + n_stmt = Call(Name(u"print"), l_args) + n_stmt.prefix = n.prefix - sep = end = file = None - if args and args[-1] == Comma(): - args = args[:-1] - end = " " - if args and args[0] == pytree.Leaf(token.RIGHTSHIFT, u">>"): - assert len(args) >= 2 - file = args[1].clone() - args = args[3:] # Strip a possible comma after the file expression - # Now synthesize a print(args, sep=..., end=..., file=...) node. - l_args = [arg.clone() for arg in args] - if l_args: - l_args[0].prefix = u"" - if sep is not None or end is not None or file is not None: - if sep is not None: - self.add_kwarg(l_args, u"sep", String(repr(sep))) - if end is not None: - self.add_kwarg(l_args, u"end", String(repr(end))) - if file is not None: - self.add_kwarg(l_args, u"file", file) - n_stmt = Call(Name(u"print"), l_args) - n_stmt.prefix = node.prefix - return n_stmt + # replace print n with corrected n inside simple_stmt + n.replace(n_stmt) + return def add_kwarg(self, l_nodes, s_kwd, n_expr): # XXX All this prefix-setting may lose comments (though rarely)