diff -r a36d469f31c1 Lib/lib2to3/fixes/fix_string.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/lib2to3/fixes/fix_string.py Tue Sep 09 16:06:34 2014 -0400 @@ -0,0 +1,41 @@ +"""Fixer for string constants. + +This prefixes lowercase, uppercase, and letters with ascii_ +""" + +# Local imports +from .. import fixer_base +from ..fixer_util import Name, token, find_binding, syms + + +class FixString(fixer_base.BaseFix): + BM_compatible = True + constants = "('uppercase'|'lowercase'|'letters')" + PATTERN = """ + const=%(constants)s + """ % (locals()) + + # Needs to be run before fix_string_imports + run_order = 4 + + def start_tree(self, tree, filename): + super(FixString, self).start_tree(tree, filename) + warning = "%s appears to be user-defined, not renaming to %s" + + # check whether the name has been defined by the user + # currently only detects top level definitions - see tests + self.user_defs = set() + for name in ["uppercase","lowercase","letters"]: + binding = find_binding(name, tree) + if binding: + if (binding.type == syms.import_from + and binding.children[1].value == "string"): + continue + self.warning(binding, warning % (name, "ascii_" + name)) + self.user_defs.add(name) + + def transform(self, node, results): + const = results['const'][0] + if const.value not in self.user_defs: + assert const.type == token.NAME + const.replace(Name("ascii_" + const.value, prefix=node.prefix)) diff -r a36d469f31c1 Lib/lib2to3/fixes/fix_string_imports.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/lib2to3/fixes/fix_string_imports.py Tue Sep 09 16:06:34 2014 -0400 @@ -0,0 +1,45 @@ +""" Remove duplicate imports which may be created by FixString """ + +# Local imports +from lib2to3 import fixer_base +from lib2to3.fixer_util import syms, token + + +class FixStringImports(fixer_base.BaseFix): + BM_compatible = True + PATTERN = """ + import_from< 'from' 'string' 'import' imports=any > + """ % (locals()) + + def transform(self, node, results): + imports = results['imports'] + children = imports.children[:] or [imports] + + if children[0].type == token.STAR: + return + + found = set() + prev = None + + for child in children: + if child.type == token.COMMA: + prev = child + continue + + if child.type == token.NAME: + name_node = child + else: + assert child.type == syms.import_as_name + name_node = child.children[0] + + if name_node.value in found: + child.value = None + child.remove() + if prev and prev.type == token.COMMA: + prev.value = None + prev.remove() + + found.add(name_node.value) + prev = child + + diff -r a36d469f31c1 Lib/lib2to3/tests/test_fixers.py --- a/Lib/lib2to3/tests/test_fixers.py Sun Aug 03 15:26:32 2014 -0400 +++ b/Lib/lib2to3/tests/test_fixers.py Tue Sep 09 16:06:34 2014 -0400 @@ -3647,6 +3647,88 @@ def test_run_order(self): self.assert_runs_after('print') + +class Test_string_imports(FixerTestCase): + fixer = "string_imports" + + def test_simple(self): + s = "from string import lowercase, uppercase, letters" + self.unchanged(s) + + def test_duplicate(self): + b = "from string import ascii_lowercase, foo, ascii_lowercase" + a = "from string import ascii_lowercase, foo" + self.check(b, a) + + def test_as(self): + b = "from string import uppercase as foo, uppercase as bar" + a = "from string import uppercase as foo" + self.check(b, a) + + def test_star(self): + s = "from string import *" + self.unchanged(s) + + +class Test_string(FixerTestCase): + fixer = "string" + + def test_import(self): + b = "from string import lowercase, uppercase, letters" + a = "from string import ascii_lowercase, ascii_uppercase, ascii_letters" + self.check(b, a) + + def test_import_as(self): + b = "from string import uppercase as foo" + a = "from string import ascii_uppercase as foo" + self.check(b, a) + + def test_replace(self): + b = "from string import lowercase\nprint lowercase" + a = "from string import ascii_lowercase\nprint ascii_lowercase" + self.check(b,a) + + def test_star(self): + b = "from string import *\nprint letters" + a = "from string import *\nprint ascii_letters" + self.check(b,a) + + def test_qualified(self): + b = "print string.lowercase" + a = "print string.ascii_lowercase" + self.check(b,a) + + def test_user_def(self): + s = "lowercase = 'foo'\nprint lowercase" + self.warns_unchanged(s, "lowercase appears to be user-defined, not renaming to ascii_lowercase") + + def test_nonstring_import(self): + s = "from mystringpackage import uppercase" + self.warns_unchanged(s, "uppercase appears to be user-defined, not renaming to ascii_uppercase") + + def test_import_def(self): + s = "from foo import bar as letters\nprint letters" + self.warns_unchanged(s, "letters appears to be user-defined, not renaming to ascii_letters") + + def test_functions(self): + s = "def letters():\n return 'abc'\nletters()" + self.warns_unchanged(s, "letters appears to be user-defined, not renaming to ascii_letters") + + # lowercase shouldn't get changed here but it currently does + def XXX_test_class(self): + s = """ +class Foo: + def __init__(self): + self.lowercase = "blargh" + +f = Foo() +f.__init__() +print f.lowercase + +""" + self.warns_unchanged(s, "lowercase appears to be user-defined, not renaming to ascii_lowercase") + + class Test_itertools(FixerTestCase): fixer = "itertools"