diff --git a/docs/fixers.rst b/docs/fixers.rst index 0f8f993..d72b3ed 100644 --- a/docs/fixers.rst +++ b/docs/fixers.rst @@ -181,6 +181,12 @@ version of ``six`` is installed. .. seealso:: :func:`six.with_metaclass` +.. attribute:: nonzero + + Changes all definitions of :func:`__nonzero__ ` to + :func:`__bool__ `, adding an alias that maintains PY2 + compatibility. + .. attribute:: raise_six Changes ``raise E, V, T`` to ``six.reraise(E, V, T)``. diff --git a/libmodernize/fixes/__init__.py b/libmodernize/fixes/__init__.py index 6458426..7369a2c 100644 --- a/libmodernize/fixes/__init__.py +++ b/libmodernize/fixes/__init__.py @@ -42,6 +42,7 @@ "libmodernize.fixes.fix_int_long_tuple", "libmodernize.fixes.fix_map", "libmodernize.fixes.fix_metaclass", + "libmodernize.fixes.fix_nonzero", "libmodernize.fixes.fix_raise_six", "libmodernize.fixes.fix_unicode", "libmodernize.fixes.fix_unicode_type", diff --git a/libmodernize/fixes/fix_nonzero.py b/libmodernize/fixes/fix_nonzero.py new file mode 100644 index 0000000..30d01f3 --- /dev/null +++ b/libmodernize/fixes/fix_nonzero.py @@ -0,0 +1,72 @@ +"""Fixer for `__nonzero__` -> `__bool__`, adding an alias for PY2 compatibility. + +Based on Lib/lib2to3/fixes/fix_nonzero.py. + +""" +# This is a derived work of Lib/lib2to3/fixes/fix_nonzero.py. That file +# is under the copyright of the Python Software Foundation and licensed +# under the Python Software Foundation License 2. +# +# Copyright notice: +# +# 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +# 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 +# Python Software Foundation; All Rights Reserved. + +# Author: Collin Winter, James Bowman + +from __future__ import absolute_import +from lib2to3 import fixer_base +from lib2to3.fixer_util import Assign, Dot, find_indentation, Leaf, Name, Newline, Node, syms +from lib2to3.pygram import token +import libmodernize + + +class FixNonzero(fixer_base.BaseFix): + BM_compatible = True + + PATTERN = """ + classdef< 'class' any+ ':' + suite< any* + funcdef< 'def' name='__nonzero__' + parameters< '(' NAME ')' > any+ > + any* > > + """ + + def transform(self, node, results): + # Start by making the same transformation as lib2to3.fix_nonzero, purely + # renaming the method: + + name = results["name"] + bool_funcdef = name.parent + new_name = Name("__bool__", prefix=name.prefix) + name.replace(new_name) + + # Then, import six and add a conditional PY2-enabled function alias: + + libmodernize.touch_import(None, u'six', node) + + suite = bool_funcdef.parent + bool_funcdef_suite = bool_funcdef.children[-1] + old_dedent = bool_funcdef_suite.children[-1] + new_dedent = Leaf(token.DEDENT, '', prefix='\n' + find_indentation(bool_funcdef)) + bool_funcdef_suite.children[-1].replace(new_dedent) + + reassignment = Node(syms.suite, [ + Newline(), + Leaf(token.INDENT, + find_indentation(bool_funcdef.children[-1].children[1])), + Node(syms.simple_stmt, + [Assign(Name('__nonzero__'), Name('__bool__')), + Newline()]), old_dedent + ]) + + if_stmt = Node(syms.if_stmt, [ + Name('if'), + Node(syms.power, + [Name(' six'), + Node(syms.trailer, [Dot(), Name('PY2')])]), + Leaf(token.COLON, ':'), reassignment + ]) + + suite.insert_child(suite.children.index(bool_funcdef) + 1, if_stmt) diff --git a/tests/test_fix_nonzero.py b/tests/test_fix_nonzero.py new file mode 100644 index 0000000..0ccba6f --- /dev/null +++ b/tests/test_fix_nonzero.py @@ -0,0 +1,111 @@ +from __future__ import absolute_import + +from utils import check_on_input + + +NONZERO_DEF = ("""\ +class Foo: + def other(self): + return false + + def __nonzero__(self): + return true + + def another(self): + return false +""", """\ +from __future__ import absolute_import +import six +class Foo: + def other(self): + return false + + def __bool__(self): + return true + + if six.PY2: + __nonzero__ = __bool__ + + def another(self): + return false +""") + +NONZERO_DEF_2_SPACE_INDENT = ("""\ +class Foo: + def other(self): + return false + + def __nonzero__(self): + return true + + def another(self): + return false +""", """\ +from __future__ import absolute_import +import six +class Foo: + def other(self): + return false + + def __bool__(self): + return true + + if six.PY2: + __nonzero__ = __bool__ + + def another(self): + return false +""") + +MULTI_CLASS_NONZERO_DEF = ("""\ +class Foo: + def __nonzero__(self): + return true + + +class Bar: + pass +""", """\ +from __future__ import absolute_import +import six +class Foo: + def __bool__(self): + return true + + if six.PY2: + __nonzero__ = __bool__ + + +class Bar: + pass +""") + +INNER_CLASS_NONZERO_DEF = ("""\ +class Baz: + class Foo: + def __nonzero__(self): + return true +""", """\ +from __future__ import absolute_import +import six +class Baz: + class Foo: + def __bool__(self): + return true + + if six.PY2: + __nonzero__ = __bool__ +""") + + +def test_nonzero_def(): + check_on_input(*NONZERO_DEF) + +def test_nonzero_def_2_space_indent(): + check_on_input(*NONZERO_DEF_2_SPACE_INDENT) + +def test_multi_class_nonzero_def(): + check_on_input(*MULTI_CLASS_NONZERO_DEF) + +def test_inner_class_nonzero_def(): + check_on_input(*INNER_CLASS_NONZERO_DEF)