diff --git a/Doc/library/2to3.rst b/Doc/library/2to3.rst --- a/Doc/library/2to3.rst +++ b/Doc/library/2to3.rst @@ -168,16 +168,26 @@ and off individually. They are describe converted respectively to :meth:`dict.items`, :meth:`dict.keys` and :meth:`dict.values`. It also wraps existing usages of :meth:`dict.items`, :meth:`dict.keys`, and :meth:`dict.values` in a call to :class:`list`. .. 2to3fixer:: except Converts ``except X, T`` to ``except X as T``. +.. 2to3fixer:: exceptions + + Removes :mod:`exceptions` imports and usage. For example, this block:: + + raise exceptions.ValueError('Spam eggs') + + is changed to:: + + raise ValueError('Spam eggs') + .. 2to3fixer:: exec Converts the ``exec`` statement to the :func:`exec` function. .. 2to3fixer:: execfile Removes usage of :func:`execfile`. The argument to :func:`execfile` is wrapped in calls to :func:`open`, :func:`compile`, and :func:`exec`. diff --git a/Lib/lib2to3/fixes/fix_exceptions.py b/Lib/lib2to3/fixes/fix_exceptions.py new file mode 100644 --- /dev/null +++ b/Lib/lib2to3/fixes/fix_exceptions.py @@ -0,0 +1,79 @@ +"""Fixer that simply removes "exceptions" imports and usage.""" + +# Author: Benjamin Peterson + +from ..fixer_base import BaseFix +from ..fixer_util import syms +from ..pgen2 import token + + +class FixExceptions(BaseFix): + + PATTERN = """ + ( + (import=import_name < 'import' + ('exceptions' + | + names=dotted_as_names< + (any ',')* 'exceptions' (',' any)* + >) + > + | + import=import_from < 'from' 'exceptions' 'import' any+ > + ) + + | + usage=power< mod='exceptions' trailer< dot='.' exc=NAME > any* > + ) + """ + + def transform(self, node, results): + if "usage" in results: + return self.transform_usage(node, results) + + import_stmt = results["import"] + containing_stmt = import_stmt.parent + names = results.get("names") + if names is not None: # Handle a from import + to_remove = [] + children = names.children + # Go over the imports, skipping over commas + for i in range(0, len(children), 2): + name = names.children[i] + if name.value == "exceptions": + to_remove.append(name) + if i != len(children) - 1: + # We are in the middle of a sequence of imports + # separated by commas; we want to remove the comma + # after the "exceptions" import. + to_remove.append(children[i + 1]) + else: + # Remove the comma before the "exceptions" import. + to_remove.append(children[i - 1]) + for node in to_remove: + node.remove() + else: # Handle a simple (or not so simple) import + if containing_stmt is not None: + kids = containing_stmt.children + my_index = kids.index(import_stmt) + after_me = kids[my_index + 1] + # Remove an extra newline that will + if after_me.type in (token.NEWLINE, token.SEMI): + after_me.remove() + if len(kids) != 1 and kids[0] is import_stmt: + # The exceptions import was at the beginning of a set of + # semicolon separated imports. We want to make the next + # import hug the side. + kids[1].prefix = "" + import_stmt.remove() + + def transform_usage(self, node, results): + """Change everything that starts with the name "exceptions" with a dot + after it.""" + mod = results["mod"] + dot = results["dot"] + exc = results["exc"] + + exc.prefix = mod.prefix + mod.remove() + dot.remove() diff --git a/Lib/lib2to3/tests/test_fixers.py b/Lib/lib2to3/tests/test_fixers.py --- a/Lib/lib2to3/tests/test_fixers.py +++ b/Lib/lib2to3/tests/test_fixers.py @@ -4593,8 +4593,143 @@ class Test_exitfunc(FixerTestCase): msg = ("Can't find sys import; Please add an atexit import at the " "top of your file.") self.warns(b, a, msg) def test_unchanged(self): s = """f(sys.exitfunc)""" self.unchanged(s) + + +class Test_exceptions(FixerTestCase): + + fixer = "exceptions" + + def test_1(self): + b = """import exceptions\n""" + a = "" + self.check(b, a) + + def test_2(self): + b = """ + import apples # Foo + import exceptions + """ + a = """ + import apples # Foo + """ + self.check(b, a) + + def test_3(self): + b = """ + import apples # Foo + import exceptions + import fruit + """ + a = """ + import apples # Foo + import fruit + """ + self.check(b, a) + + def test_4(self): + b = """import cherries, exceptions, apples""" + a = """import cherries, apples""" + self.check(b, a) + + def test_5(self): + b = """import apples, exceptions""" + a = """import apples""" + self.check(b, a) + + def test_6(self): + b = """from exceptions import ValueError\n""" + a = "" + self.check(b, a) + + def test_7(self): + b = """from exceptions import (ValueError, Exception)\n""" + a = "" + self.check(b, a) + + def test_8(self): + b = """from exceptions import *\n""" + a = "" + self.check(b, a) + + def test_9(self): + b = """ + import fruit + from exceptions import ValueError, SystemExit + import sys + """ + a = """ + import fruit + import sys + """ + self.check(b, a) + + def test_10(self): + b = """import exceptions; import apples""" + a = """import apples""" + self.check(b, a) + + def test_11(self): + b = """import fruit; import exceptions; import apples""" + a = """import fruit; import apples""" + self.check(b, a) + + def test_usage_1(self): + b = """exceptions.ValueError""" + a = """ValueError""" + self.check(b, a) + + def test_usage_2(self): + b = """exceptions.ValueError("Error, Error!")""" + a = """ValueError("Error, Error!")""" + self.check(b, a) + + def test_usage_3(self): + b = """something(); exceptions.ValueError""" + a = """something(); ValueError""" + self.check(b, a) + + def test_usage_4(self): + b = """raise exceptions.ValueError, "error" """ + a = """raise ValueError, "error" """ + self.check(b, a) + + def test_usage_5(self): + b = """f(exceptions.ValueError)""" + a = """f(ValueError)""" + self.check(b, a) + + def test_usage_6(self): + b = """f(); exceptions.ValueError""" + a = """f(); ValueError""" + self.check(b, a) + + def test_usage_7(self): + b = """f(x= exceptions.ValueError('error'))""" + a = """f(x= ValueError('error'))""" + self.check(b, a) + + def test_usage_8(self): + b = """raise exceptions.ValueError("error") """ + a = """raise ValueError("error") """ + self.check(b, a) + + def test_unchanged_1(self): + s = """from . import exceptions""" + self.unchanged(s) + + def test_unchanged_2(self): + s = """import pack.exceptions""" + self.unchanged(s) + + def test_unchanged_3(self): + s = """from foo import bar, exceptions""" + self.unchanged(s) + + def test_unchanged_4(self): + s = """from foo import bar, exceptions, baz""" + self.unchanged(s)