From 4a600695a5ab4e36df911b37ae8ffe002d3e01ad Mon Sep 17 00:00:00 2001 From: aldwinaldwin Date: Thu, 18 Jul 2019 16:22:17 +0800 Subject: [PATCH 1/4] bpo-13272: 2to3 string constants fixer --- Doc/library/2to3.rst | 8 +++ Lib/lib2to3/fixes/fix_string.py | 104 +++++++++++++++++++++++++++++++ Lib/lib2to3/tests/test_fixers.py | 57 +++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 Lib/lib2to3/fixes/fix_string.py diff --git a/Doc/library/2to3.rst b/Doc/library/2to3.rst index fa4b0a9645531c..e703f6242212e1 100644 --- a/Doc/library/2to3.rst +++ b/Doc/library/2to3.rst @@ -404,6 +404,14 @@ and off individually. They are described here in more detail. Renames :exc:`StandardError` to :exc:`Exception`. +.. 2to3fixer:: string + + Renames `lowercase`, `uppercase`, and `letters` with the prefix `ascii_`. + Caution, some user-defined variables with these names might get renamed + also. + + .. versionadded:: 3.9 + .. 2to3fixer:: sys_exc Changes the deprecated :data:`sys.exc_value`, :data:`sys.exc_type`, diff --git a/Lib/lib2to3/fixes/fix_string.py b/Lib/lib2to3/fixes/fix_string.py new file mode 100644 index 00000000000000..bbdf71c38dd648 --- /dev/null +++ b/Lib/lib2to3/fixes/fix_string.py @@ -0,0 +1,104 @@ +"""Fixer for string constants. + +Renames `lowercase`, `uppercase`, and `letters` with the prefix `ascii_`. + +Caution, some user-defined variables with these names might get renamed also. +""" + +# Local imports +from .. import fixer_base +from ..fixer_util import Name, token, find_binding, syms, is_import + + +class FixString(fixer_base.BaseFix): + + BM_compatible = True + constants = ["uppercase","lowercase","letters"] + PATTERN = None + + def compile_pattern(self): + constants = "('"+("'|'".join(self.constants))+"')" + self.PATTERN = """ + power< 'string' + trailer< '.' const=%(constants)s > > + | + const=%(constants)s + """ % dict(constants=constants) + super(FixString, self).compile_pattern() + + + def start_tree(self, tree, filename): + super(FixString, self).start_tree(tree, filename) + + # create list of names imported from 'string' library + self.string_defs = self.constants + self.find_stringdot = False + + # from string import * => use all constants + if find_binding(None, tree, "string"): + return + + # import string => use all constants and only change string.name + if find_binding("string", tree, None): + self.find_stringdot = True + return + + # from string import ... as ... + # see comment of test_from_import_as_with_package: + # 'fail if there is an "from ... import ... as ...' + # find_bindings won't help, so create a local find_imports() + + def find_imports(node): + for child in node.children: + if child.type == syms.simple_stmt: + #recursive + find_imports(child) + elif child.type == syms.import_from: + + # only more testing if it's for string library + if not child.children[1].type == token.NAME or \ + ( child.children[1].type == token.NAME \ + and not child.children[1].value == 'string' ): + continue + + # from string import name (one import) + if child.children[3].type == token.NAME: + name = child.children[3].value + if name in self.constants: + self.string_defs.add(name) + continue + + # from string import name as foo (one import as) + if child.children[3].type == syms.import_as_name: + name = child.children[3].children[0].value + if name in self.constants: + self.string_defs.add(name) + + # from string import something, something_else, ... + # (multiple imports) + if child.children[3].type == syms.import_as_names: + for something in child.children[3].children: + # ... import name as foo, something_else ... + if something.type == syms.import_as_name: + name = something.children[0].value + if name in self.constants: + self.string_defs.add(name) + # ... import name, something_else as bar, ... + else: + if something.type == token.NAME: + name = something.value + if name in self.constants: + self.string_defs.add(name) + + self.string_defs = set() + find_imports(tree) + + + def transform(self, node, results): + if not results.get('const', None) \ + or ( self.find_stringdot and not syms.power ): + return + const = results['const'][0] + if const.value in self.string_defs: + assert const.type == token.NAME + const.replace(Name(("ascii_" + const.value), prefix=node.prefix)) diff --git a/Lib/lib2to3/tests/test_fixers.py b/Lib/lib2to3/tests/test_fixers.py index 3da5dd845c93c6..22af82532b5559 100644 --- a/Lib/lib2to3/tests/test_fixers.py +++ b/Lib/lib2to3/tests/test_fixers.py @@ -3659,6 +3659,63 @@ def test_future(self): def test_run_order(self): self.assert_runs_after('print') + +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" + a += ", 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_import_as_mix(self): + b = "from string import lowercase, uppercase as foo, letters" + a = "from string import ascii_lowercase, ascii_uppercase as foo" + a += ", ascii_letters" + self.check(b, a) + + def test_import_dont_crash(self): + s = "from a.b import lowercase" + self.unchanged(s) + + def test_import_string(self): + b = "import string\nprint string.lowercase" + a = "import string\nprint string.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_replace(self): + b = "from string import lowercase\nprint lowercase" + a = "from string import ascii_lowercase\nprint ascii_lowercase" + self.check(b, a) + + def test_no_import(self): + s = "lowercase = 'foo'\nprint lowercase" + self.unchanged(s) + + def test_nonstring_import(self): + s = "from mystringpackage import uppercase\nprint uppercase" + self.unchanged(s) + + def test_nonstring_import_def(self): + s = "from foo import bar as letters\nprint letters" + self.unchanged(s) + + def test_functions(self): + s = "def letters():\n return 'abc'\nletters()" + self.unchanged(s) + + class Test_itertools(FixerTestCase): fixer = "itertools" From c48fb448e974e2acfe77d0f26fd38da00d62a3d8 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2019 08:35:43 +0000 Subject: [PATCH 2/4] =?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 --- .../NEWS.d/next/Library/2019-07-18-08-35-42.bpo-13272.yiFLVi.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2019-07-18-08-35-42.bpo-13272.yiFLVi.rst diff --git a/Misc/NEWS.d/next/Library/2019-07-18-08-35-42.bpo-13272.yiFLVi.rst b/Misc/NEWS.d/next/Library/2019-07-18-08-35-42.bpo-13272.yiFLVi.rst new file mode 100644 index 00000000000000..9d5926354bd623 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-07-18-08-35-42.bpo-13272.yiFLVi.rst @@ -0,0 +1 @@ +2to3 string constants fixer. Patch by Aldwin Pollefeyt \ No newline at end of file From ee8fb01be3955c7d75fcbcfdc7bb6ec4a75a1742 Mon Sep 17 00:00:00 2001 From: aldwinaldwin Date: Thu, 18 Jul 2019 16:59:38 +0800 Subject: [PATCH 3/4] fix whitespaces --- Lib/lib2to3/fixes/fix_string.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/lib2to3/fixes/fix_string.py b/Lib/lib2to3/fixes/fix_string.py index bbdf71c38dd648..d5fba8fb4d966a 100644 --- a/Lib/lib2to3/fixes/fix_string.py +++ b/Lib/lib2to3/fixes/fix_string.py @@ -22,7 +22,7 @@ def compile_pattern(self): power< 'string' trailer< '.' const=%(constants)s > > | - const=%(constants)s + const=%(constants)s """ % dict(constants=constants) super(FixString, self).compile_pattern() @@ -59,7 +59,7 @@ def find_imports(node): if not child.children[1].type == token.NAME or \ ( child.children[1].type == token.NAME \ and not child.children[1].value == 'string' ): - continue + continue # from string import name (one import) if child.children[3].type == token.NAME: @@ -97,7 +97,7 @@ def find_imports(node): def transform(self, node, results): if not results.get('const', None) \ or ( self.find_stringdot and not syms.power ): - return + return const = results['const'][0] if const.value in self.string_defs: assert const.type == token.NAME From c928c472a2926c8a0782ed5f2134b93b274f7ad2 Mon Sep 17 00:00:00 2001 From: aldwinaldwin Date: Thu, 18 Jul 2019 17:13:28 +0800 Subject: [PATCH 4/4] added :const: to documentation --- Doc/library/2to3.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Doc/library/2to3.rst b/Doc/library/2to3.rst index e703f6242212e1..9ca563a1fb697b 100644 --- a/Doc/library/2to3.rst +++ b/Doc/library/2to3.rst @@ -406,7 +406,10 @@ and off individually. They are described here in more detail. .. 2to3fixer:: string - Renames `lowercase`, `uppercase`, and `letters` with the prefix `ascii_`. + Renames :const:`lowercase`, :const:`uppercase`, and :const:`letters` to + :const:`ascii_lowercase`, :const:`ascii_uppercase` and + :const:`ascii_letters`. + Caution, some user-defined variables with these names might get renamed also.