]> granicus.if.org Git - python/commitdiff
Merged revisions 83852-83853,83857,84042,84216,84274-84276,84375,85388,85478,85506...
authorBenjamin Peterson <benjamin@python.org>
Thu, 14 Oct 2010 23:00:00 +0000 (23:00 +0000)
committerBenjamin Peterson <benjamin@python.org>
Thu, 14 Oct 2010 23:00:00 +0000 (23:00 +0000)
svn+ssh://pythondev@svn.python.org/sandbox/trunk/2to3/lib2to3

........
  r83852 | benjamin.peterson | 2010-08-08 15:45:44 -0500 (Sun, 08 Aug 2010) | 1 line

  wrap with parens
........
  r83853 | benjamin.peterson | 2010-08-08 15:46:31 -0500 (Sun, 08 Aug 2010) | 1 line

  use parens
........
  r83857 | benjamin.peterson | 2010-08-08 15:59:49 -0500 (Sun, 08 Aug 2010) | 1 line

  things which use touch_import should be pre order
........
  r84042 | george.boutsioukis | 2010-08-14 16:10:19 -0500 (Sat, 14 Aug 2010) | 2 lines

  This revision incorporates into the 2to3 tool the new, faster, tree matching algorithm developed during a GSOC project. The algorithm resides in the two added modules, btm_matcher and btm_utils. New code has been added to drive the new matching process in refactor.py and a few minor changes were made in other modules. A BM_compatible flag(False by default) has been added in fixer_base and it is set to True in most of the current fixers.
........
  r84216 | benjamin.peterson | 2010-08-19 16:44:05 -0500 (Thu, 19 Aug 2010) | 1 line

  allow star_expr in testlist_gexp
........
  r84274 | benjamin.peterson | 2010-08-22 18:40:46 -0500 (Sun, 22 Aug 2010) | 1 line

  wrap long line
........
  r84275 | benjamin.peterson | 2010-08-22 18:42:22 -0500 (Sun, 22 Aug 2010) | 1 line

  cleanup
........
  r84276 | benjamin.peterson | 2010-08-22 18:51:01 -0500 (Sun, 22 Aug 2010) | 1 line

  when there's a None value and a traceback, don't call type with it #9661
........
  r84375 | george.boutsioukis | 2010-08-31 08:38:53 -0500 (Tue, 31 Aug 2010) | 3 lines

  Idiomatic code changes & stylistic issues fixed in the BottomMatcher module. Thanks to Benjamin Peterson for taking the time to review the code.
........
  r85388 | benjamin.peterson | 2010-10-12 17:27:44 -0500 (Tue, 12 Oct 2010) | 1 line

  fix urllib fixer with multiple as imports on a line #10069
........
  r85478 | benjamin.peterson | 2010-10-14 08:09:56 -0500 (Thu, 14 Oct 2010) | 1 line

  stop abusing docstrings
........
  r85506 | benjamin.peterson | 2010-10-14 17:45:19 -0500 (Thu, 14 Oct 2010) | 1 line

  kill sibling import
........
  r85507 | benjamin.peterson | 2010-10-14 17:54:15 -0500 (Thu, 14 Oct 2010) | 1 line

  remove trailing whitespace
........
  r85508 | benjamin.peterson | 2010-10-14 17:55:28 -0500 (Thu, 14 Oct 2010) | 1 line

  typo
........

59 files changed:
Lib/lib2to3/Grammar.txt
Lib/lib2to3/btm_matcher.py [new file with mode: 0644]
Lib/lib2to3/btm_utils.py [new file with mode: 0644]
Lib/lib2to3/fixer_base.py
Lib/lib2to3/fixer_util.py
Lib/lib2to3/fixes/fix_apply.py
Lib/lib2to3/fixes/fix_basestring.py
Lib/lib2to3/fixes/fix_buffer.py
Lib/lib2to3/fixes/fix_callable.py
Lib/lib2to3/fixes/fix_dict.py
Lib/lib2to3/fixes/fix_except.py
Lib/lib2to3/fixes/fix_exec.py
Lib/lib2to3/fixes/fix_execfile.py
Lib/lib2to3/fixes/fix_exitfunc.py
Lib/lib2to3/fixes/fix_filter.py
Lib/lib2to3/fixes/fix_funcattrs.py
Lib/lib2to3/fixes/fix_future.py
Lib/lib2to3/fixes/fix_getcwdu.py
Lib/lib2to3/fixes/fix_has_key.py
Lib/lib2to3/fixes/fix_idioms.py
Lib/lib2to3/fixes/fix_import.py
Lib/lib2to3/fixes/fix_imports.py
Lib/lib2to3/fixes/fix_input.py
Lib/lib2to3/fixes/fix_intern.py
Lib/lib2to3/fixes/fix_isinstance.py
Lib/lib2to3/fixes/fix_itertools.py
Lib/lib2to3/fixes/fix_itertools_imports.py
Lib/lib2to3/fixes/fix_long.py
Lib/lib2to3/fixes/fix_map.py
Lib/lib2to3/fixes/fix_metaclass.py
Lib/lib2to3/fixes/fix_methodattrs.py
Lib/lib2to3/fixes/fix_next.py
Lib/lib2to3/fixes/fix_nonzero.py
Lib/lib2to3/fixes/fix_operator.py
Lib/lib2to3/fixes/fix_paren.py
Lib/lib2to3/fixes/fix_print.py
Lib/lib2to3/fixes/fix_raise.py
Lib/lib2to3/fixes/fix_raw_input.py
Lib/lib2to3/fixes/fix_reduce.py
Lib/lib2to3/fixes/fix_renames.py
Lib/lib2to3/fixes/fix_repr.py
Lib/lib2to3/fixes/fix_set_literal.py
Lib/lib2to3/fixes/fix_standarderror.py
Lib/lib2to3/fixes/fix_sys_exc.py
Lib/lib2to3/fixes/fix_throw.py
Lib/lib2to3/fixes/fix_tuple_params.py
Lib/lib2to3/fixes/fix_types.py
Lib/lib2to3/fixes/fix_unicode.py
Lib/lib2to3/fixes/fix_urllib.py
Lib/lib2to3/fixes/fix_xrange.py
Lib/lib2to3/fixes/fix_xreadlines.py
Lib/lib2to3/fixes/fix_zip.py
Lib/lib2to3/patcomp.py
Lib/lib2to3/pygram.py
Lib/lib2to3/pytree.py
Lib/lib2to3/refactor.py
Lib/lib2to3/tests/data/bom.py
Lib/lib2to3/tests/test_fixers.py
Lib/lib2to3/tests/test_pytree.py

index be034e12199ae336311629b1c787b67d54e0aa04..8a96726ecce142fbc91b580564669357d8d2b3eb 100644 (file)
@@ -128,7 +128,7 @@ atom: ('(' [yield_expr|testlist_gexp] ')' |
        '`' testlist1 '`' |
        NAME | NUMBER | STRING+ | '.' '.' '.')
 listmaker: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] )
-testlist_gexp: test ( comp_for | (',' (test|star_expr))* [','] )
+testlist_gexp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] )
 lambdef: 'lambda' [varargslist] ':' test
 trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
 subscriptlist: subscript (',' subscript)* [',']
diff --git a/Lib/lib2to3/btm_matcher.py b/Lib/lib2to3/btm_matcher.py
new file mode 100644 (file)
index 0000000..736ba2b
--- /dev/null
@@ -0,0 +1,168 @@
+"""A bottom-up tree matching algorithm implementation meant to speed
+up 2to3's matching process. After the tree patterns are reduced to
+their rarest linear path, a linear Aho-Corasick automaton is
+created. The linear automaton traverses the linear paths from the
+leaves to the root of the AST and returns a set of nodes for further
+matching. This reduces significantly the number of candidate nodes."""
+
+__author__ = "George Boutsioukis <gboutsioukis@gmail.com>"
+
+import logging
+import itertools
+from collections import defaultdict
+
+from . import pytree
+from .btm_utils import reduce_tree
+
+class BMNode(object):
+    """Class for a node of the Aho-Corasick automaton used in matching"""
+    count = itertools.count()
+    def __init__(self):
+        self.transition_table = {}
+        self.fixers = []
+        self.id = next(BMNode.count)
+        self.content = ''
+
+class BottomMatcher(object):
+    """The main matcher class. After instantiating the patterns should
+    be added using the add_fixer method"""
+
+    def __init__(self):
+        self.match = set()
+        self.root = BMNode()
+        self.nodes = [self.root]
+        self.fixers = []
+        self.logger = logging.getLogger("RefactoringTool")
+
+    def add_fixer(self, fixer):
+        """Reduces a fixer's pattern tree to a linear path and adds it
+        to the matcher(a common Aho-Corasick automaton). The fixer is
+        appended on the matching states and called when they are
+        reached"""
+        self.fixers.append(fixer)
+        tree = reduce_tree(fixer.pattern_tree)
+        linear = tree.get_linear_subpattern()
+        match_nodes = self.add(linear, start=self.root)
+        for match_node in match_nodes:
+            match_node.fixers.append(fixer)
+
+    def add(self, pattern, start):
+        "Recursively adds a linear pattern to the AC automaton"
+        #print("adding pattern", pattern, "to", start)
+        if not pattern:
+            #print("empty pattern")
+            return [start]
+        if isinstance(pattern[0], tuple):
+            #alternatives
+            #print("alternatives")
+            match_nodes = []
+            for alternative in pattern[0]:
+                #add all alternatives, and add the rest of the pattern
+                #to each end node
+                end_nodes = self.add(alternative, start=start)
+                for end in end_nodes:
+                    match_nodes.extend(self.add(pattern[1:], end))
+            return match_nodes
+        else:
+            #single token
+            #not last
+            if pattern[0] not in start.transition_table:
+                #transition did not exist, create new
+                next_node = BMNode()
+                start.transition_table[pattern[0]] = next_node
+            else:
+                #transition exists already, follow
+                next_node = start.transition_table[pattern[0]]
+
+            if pattern[1:]:
+                end_nodes = self.add(pattern[1:], start=next_node)
+            else:
+                end_nodes = [next_node]
+            return end_nodes
+
+    def run(self, leaves):
+        """The main interface with the bottom matcher. The tree is
+        traversed from the bottom using the constructed
+        automaton. Nodes are only checked once as the tree is
+        retraversed. When the automaton fails, we give it one more
+        shot(in case the above tree matches as a whole with the
+        rejected leaf), then we break for the next leaf. There is the
+        special case of multiple arguments(see code comments) where we
+        recheck the nodes
+
+        Args:
+           The leaves of the AST tree to be matched
+
+        Returns:
+           A dictionary of node matches with fixers as the keys
+        """
+        current_ac_node = self.root
+        results = defaultdict(list)
+        for leaf in leaves:
+            current_ast_node = leaf
+            while current_ast_node:
+                current_ast_node.was_checked = True
+                for child in current_ast_node.children:
+                    # multiple statements, recheck
+                    if isinstance(child, pytree.Leaf) and child.value == u";":
+                        current_ast_node.was_checked = False
+                        break
+                if current_ast_node.type == 1:
+                    #name
+                    node_token = current_ast_node.value
+                else:
+                    node_token = current_ast_node.type
+
+                if node_token in current_ac_node.transition_table:
+                    #token matches
+                    current_ac_node = current_ac_node.transition_table[node_token]
+                    for fixer in current_ac_node.fixers:
+                        if not fixer in results:
+                            results[fixer] = []
+                        results[fixer].append(current_ast_node)
+
+                else:
+                    #matching failed, reset automaton
+                    current_ac_node = self.root
+                    if (current_ast_node.parent is not None
+                        and current_ast_node.parent.was_checked):
+                        #the rest of the tree upwards has been checked, next leaf
+                        break
+
+                    #recheck the rejected node once from the root
+                    if node_token in current_ac_node.transition_table:
+                        #token matches
+                        current_ac_node = current_ac_node.transition_table[node_token]
+                        for fixer in current_ac_node.fixers:
+                            if not fixer in results.keys():
+                                results[fixer] = []
+                            results[fixer].append(current_ast_node)
+
+                current_ast_node = current_ast_node.parent
+        return results
+
+    def print_ac(self):
+        "Prints a graphviz diagram of the BM automaton(for debugging)"
+        print("digraph g{")
+        def print_node(node):
+            for subnode_key in node.transition_table.keys():
+                subnode = node.transition_table[subnode_key]
+                print("%d -> %d [label=%s] //%s" %
+                      (node.id, subnode.id, type_repr(subnode_key), str(subnode.fixers)))
+                if subnode_key == 1:
+                    print(subnode.content)
+                print_node(subnode)
+        print_node(self.root)
+        print("}")
+
+# taken from pytree.py for debugging; only used by print_ac
+_type_reprs = {}
+def type_repr(type_num):
+    global _type_reprs
+    if not _type_reprs:
+        from .pygram import python_symbols
+        # printing tokens is possible but not as useful
+        # from .pgen2 import token // token.__dict__.items():
+        for name, val in python_symbols.__dict__.items():
+            if type(val) == int: _type_reprs[val] = name
+    return _type_reprs.setdefault(type_num, type_num)
diff --git a/Lib/lib2to3/btm_utils.py b/Lib/lib2to3/btm_utils.py
new file mode 100644 (file)
index 0000000..2276dc9
--- /dev/null
@@ -0,0 +1,283 @@
+"Utility functions used by the btm_matcher module"
+
+from . import pytree
+from .pgen2 import grammar, token
+from .pygram import pattern_symbols, python_symbols
+
+syms = pattern_symbols
+pysyms = python_symbols
+tokens = grammar.opmap
+token_labels = token
+
+TYPE_ANY = -1
+TYPE_ALTERNATIVES = -2
+TYPE_GROUP = -3
+
+class MinNode(object):
+    """This class serves as an intermediate representation of the
+    pattern tree during the conversion to sets of leaf-to-root
+    subpatterns"""
+
+    def __init__(self, type=None, name=None):
+        self.type = type
+        self.name = name
+        self.children = []
+        self.leaf = False
+        self.parent = None
+        self.alternatives = []
+        self.group = []
+
+    def __repr__(self):
+        return str(self.type) + ' ' + str(self.name)
+
+    def leaf_to_root(self):
+        """Internal method. Returns a characteristic path of the
+        pattern tree. This method must be run for all leaves until the
+        linear subpatterns are merged into a single"""
+        node = self
+        subp = []
+        while node:
+            if node.type == TYPE_ALTERNATIVES:
+                node.alternatives.append(subp)
+                if len(node.alternatives) == len(node.children):
+                    #last alternative
+                    subp = [tuple(node.alternatives)]
+                    node.alternatives = []
+                    node = node.parent
+                    continue
+                else:
+                    node = node.parent
+                    subp = None
+                    break
+
+            if node.type == TYPE_GROUP:
+                node.group.append(subp)
+                #probably should check the number of leaves
+                if len(node.group) == len(node.children):
+                    subp = get_characteristic_subpattern(node.group)
+                    node.group = []
+                    node = node.parent
+                    continue
+                else:
+                    node = node.parent
+                    subp = None
+                    break
+
+            if node.type == token_labels.NAME and node.name:
+                #in case of type=name, use the name instead
+                subp.append(node.name)
+            else:
+                subp.append(node.type)
+
+            node = node.parent
+        return subp
+
+    def get_linear_subpattern(self):
+        """Drives the leaf_to_root method. The reason that
+        leaf_to_root must be run multiple times is because we need to
+        reject 'group' matches; for example the alternative form
+        (a | b c) creates a group [b c] that needs to be matched. Since
+        matching multiple linear patterns overcomes the automaton's
+        capabilities, leaf_to_root merges each group into a single
+        choice based on 'characteristic'ity,
+
+        i.e. (a|b c) -> (a|b) if b more characteristic than c
+
+        Returns: The most 'characteristic'(as defined by
+          get_characteristic_subpattern) path for the compiled pattern
+          tree.
+        """
+
+        for l in self.leaves():
+            subp = l.leaf_to_root()
+            if subp:
+                return subp
+
+    def leaves(self):
+        "Generator that returns the leaves of the tree"
+        for child in self.children:
+            for x in child.leaves():
+                yield x
+        if not self.children:
+            yield self
+
+def reduce_tree(node, parent=None):
+    """
+    Internal function. Reduces a compiled pattern tree to an
+    intermediate representation suitable for feeding the
+    automaton. This also trims off any optional pattern elements(like
+    [a], a*).
+    """
+
+    new_node = None
+    #switch on the node type
+    if node.type == syms.Matcher:
+        #skip
+        node = node.children[0]
+
+    if node.type == syms.Alternatives  :
+        #2 cases
+        if len(node.children) <= 2:
+            #just a single 'Alternative', skip this node
+            new_node = reduce_tree(node.children[0], parent)
+        else:
+            #real alternatives
+            new_node = MinNode(type=TYPE_ALTERNATIVES)
+            #skip odd children('|' tokens)
+            for child in node.children:
+                if node.children.index(child)%2:
+                    continue
+                reduced = reduce_tree(child, new_node)
+                if reduced is not None:
+                    new_node.children.append(reduced)
+    elif node.type == syms.Alternative:
+        if len(node.children) > 1:
+
+            new_node = MinNode(type=TYPE_GROUP)
+            for child in node.children:
+                reduced = reduce_tree(child, new_node)
+                if reduced:
+                    new_node.children.append(reduced)
+            if not new_node.children:
+                # delete the group if all of the children were reduced to None
+                new_node = None
+
+        else:
+            new_node = reduce_tree(node.children[0], parent)
+
+    elif node.type == syms.Unit:
+        if (isinstance(node.children[0], pytree.Leaf) and
+            node.children[0].value == '('):
+            #skip parentheses
+            return reduce_tree(node.children[1], parent)
+        if ((isinstance(node.children[0], pytree.Leaf) and
+               node.children[0].value == '[')
+               or
+               (len(node.children)>1 and
+               hasattr(node.children[1], "value") and
+               node.children[1].value == '[')):
+            #skip whole unit if its optional
+            return None
+
+        leaf = True
+        details_node = None
+        alternatives_node = None
+        has_repeater = False
+        repeater_node = None
+        has_variable_name = False
+
+        for child in node.children:
+            if child.type == syms.Details:
+                leaf = False
+                details_node = child
+            elif child.type == syms.Repeater:
+                has_repeater = True
+                repeater_node = child
+            elif child.type == syms.Alternatives:
+                alternatives_node = child
+            if hasattr(child, 'value') and child.value == '=': # variable name
+                has_variable_name = True
+
+        #skip variable name
+        if has_variable_name:
+            #skip variable name, '='
+            name_leaf = node.children[2]
+            if hasattr(name_leaf, 'value') and name_leaf.value == '(':
+                # skip parenthesis
+                name_leaf = node.children[3]
+        else:
+            name_leaf = node.children[0]
+
+        #set node type
+        if name_leaf.type == token_labels.NAME:
+            #(python) non-name or wildcard
+            if name_leaf.value == 'any':
+                new_node = MinNode(type=TYPE_ANY)
+            else:
+                if hasattr(token_labels, name_leaf.value):
+                    new_node = MinNode(type=getattr(token_labels, name_leaf.value))
+                else:
+                    new_node = MinNode(type=getattr(pysyms, name_leaf.value))
+
+        elif name_leaf.type == token_labels.STRING:
+            #(python) name or character; remove the apostrophes from
+            #the string value
+            name = name_leaf.value.strip("'")
+            if name in tokens:
+                new_node = MinNode(type=tokens[name])
+            else:
+                new_node = MinNode(type=token_labels.NAME, name=name)
+        elif name_leaf.type == syms.Alternatives:
+            new_node = reduce_tree(alternatives_node, parent)
+
+        #handle repeaters
+        if has_repeater:
+            if repeater_node.children[0].value == '*':
+                #reduce to None
+                new_node = None
+            elif repeater_node.children[0].value == '+':
+                #reduce to a single occurence i.e. do nothing
+                pass
+            else:
+                #TODO: handle {min, max} repeaters
+                raise NotImplementedError
+                pass
+
+        #add children
+        if details_node and new_node is not None:
+            for child in details_node.children[1:-1]:
+                #skip '<', '>' markers
+                reduced = reduce_tree(child, new_node)
+                if reduced is not None:
+                    new_node.children.append(reduced)
+    if new_node:
+        new_node.parent = parent
+    return new_node
+
+
+def get_characteristic_subpattern(subpatterns):
+    """Picks the most characteristic from a list of linear patterns
+    Current order used is:
+    names > common_names > common_chars
+    """
+    if not isinstance(subpatterns, list):
+        return subpatterns
+    if len(subpatterns)==1:
+        return subpatterns[0]
+
+    # first pick out the ones containing variable names
+    subpatterns_with_names = []
+    subpatterns_with_common_names = []
+    common_names = ['in', 'for', 'if' , 'not', 'None']
+    subpatterns_with_common_chars = []
+    common_chars = "[]().,:"
+    for subpattern in subpatterns:
+        if any(rec_test(subpattern, lambda x: type(x) is str)):
+            if any(rec_test(subpattern,
+                            lambda x: isinstance(x, str) and x in common_chars)):
+                subpatterns_with_common_chars.append(subpattern)
+            elif any(rec_test(subpattern,
+                              lambda x: isinstance(x, str) and x in common_names)):
+                subpatterns_with_common_names.append(subpattern)
+
+            else:
+                subpatterns_with_names.append(subpattern)
+
+    if subpatterns_with_names:
+        subpatterns = subpatterns_with_names
+    elif subpatterns_with_common_names:
+        subpatterns = subpatterns_with_common_names
+    elif subpatterns_with_common_chars:
+        subpatterns = subpatterns_with_common_chars
+    # of the remaining subpatterns pick out the longest one
+    return max(subpatterns, key=len)
+
+def rec_test(sequence, test_func):
+    """Tests test_func on all items of sequence and items of included
+    sub-iterables"""
+    for x in sequence:
+        if isinstance(x, (list, tuple)):
+            for y in rec_test(x, test_func):
+                yield y
+        else:
+            yield test_func(x)
index 16887aabc2efe29e569204c31aec685f3108c90b..f6421ba3f74fddb5a1ca137a27392e4bf0e5e381 100644 (file)
@@ -24,6 +24,7 @@ class BaseFix(object):
 
     PATTERN = None  # Most subclasses should override with a string literal
     pattern = None  # Compiled pattern, set by compile_pattern()
+    pattern_tree = None # Tree representation of the pattern
     options = None  # Options object passed to initializer
     filename = None # The filename (set by set_filename)
     logger = None   # A logger (set by set_filename)
@@ -36,6 +37,12 @@ class BaseFix(object):
     _accept_type = None # [Advanced and not public] This tells RefactoringTool
                         # which node type to accept when there's not a pattern.
 
+    keep_line_order = False # For the bottom matcher: match with the
+                            # original line order
+    BM_compatible = False # Compatibility with the bottom matching
+                          # module; every fixer should set this
+                          # manually
+
     # Shortcut for access to Python grammar symbols
     syms = pygram.python_symbols
 
@@ -58,7 +65,9 @@ class BaseFix(object):
         self.{pattern,PATTERN} in .match().
         """
         if self.PATTERN is not None:
-            self.pattern = PatternCompiler().compile_pattern(self.PATTERN)
+            PC = PatternCompiler()
+            self.pattern, self.pattern_tree = PC.compile_pattern(self.PATTERN,
+                                                                 with_tree=True)
 
     def set_filename(self, filename):
         """Set the filename, and a logger derived from it.
index 2ad72714c5a79bd96d3681cc0c643f50b321343f..30da8937118340a28d9c6c6126adedfd7e41aa23 100644 (file)
@@ -295,8 +295,8 @@ def touch_import(package, name, node):
     """ Works like `does_tree_import` but adds an import statement
         if it was not imported. """
     def is_import_stmt(node):
-        return node.type == syms.simple_stmt and node.children and \
-               is_import(node.children[0])
+        return (node.type == syms.simple_stmt and node.children and
+                is_import(node.children[0]))
 
     root = find_root(node)
 
@@ -319,8 +319,8 @@ def touch_import(package, name, node):
     # if that also fails, we stick to the beginning of the file
     if insert_pos == 0:
         for idx, node in enumerate(root.children):
-            if node.type == syms.simple_stmt and node.children and \
-               node.children[0].type == token.STRING:
+            if (node.type == syms.simple_stmt and node.children and
+               node.children[0].type == token.STRING):
                 insert_pos = idx + 1
                 break
 
index 6d1c5d9ed450e4a7506b7055b354eafaaf2239b5..a7dc3a046d8f5e392e679c83989c7f924edc59ed 100644 (file)
@@ -12,6 +12,7 @@ from .. import fixer_base
 from ..fixer_util import Call, Comma, parenthesize
 
 class FixApply(fixer_base.BaseFix):
+    BM_compatible = True
 
     PATTERN = """
     power< 'apply'
index 0de6c26e9d5ffaaec0fd9ac935d91e6e1de18b15..a3c9a436492e9c0ec495b7217ee02da91b0b82ed 100644 (file)
@@ -6,6 +6,7 @@ from .. import fixer_base
 from ..fixer_util import Name
 
 class FixBasestring(fixer_base.BaseFix):
+    BM_compatible = True
 
     PATTERN = "'basestring'"
 
index 1c1c4a128bd968fc3be52f41e3c7afaeebf24bd1..c6b092802480f22b57395a86e3f6c7ebf5da5c00 100644 (file)
@@ -9,6 +9,7 @@ from ..fixer_util import Name
 
 
 class FixBuffer(fixer_base.BaseFix):
+    BM_compatible = True
 
     explicit = True # The user must ask for this fixer
 
index ca5d20b3ea6427997935cf37043d7e9955bf6777..df33d614ba1b6ca4bac88b692f83bd7a7e2b1439 100644 (file)
@@ -11,6 +11,9 @@ from lib2to3 import fixer_base
 from lib2to3.fixer_util import Call, Name, String, Attr, touch_import
 
 class FixCallable(fixer_base.BaseFix):
+    BM_compatible = True
+
+    order = "pre"
 
     # Ignore callable(*args) or use of keywords.
     # Either could be a hint that the builtin callable() is not being used.
index 5912cbf6117365331e0361f21e8d3f69daad3601..f681e4d71324bd3d1a848e22ef9460bd20b6486e 100644 (file)
@@ -40,6 +40,8 @@ iter_exempt = fixer_util.consuming_calls | set(["iter"])
 
 
 class FixDict(fixer_base.BaseFix):
+    BM_compatible = True
+
     PATTERN = """
     power< head=any+
          trailer< '.' method=('keys'|'items'|'values'|
index 8118058b7f502da65657a254f63c799decbb4bcc..e324718f64905b5e0aa242610787242ce6dea0c1 100644 (file)
@@ -34,6 +34,7 @@ def find_excepts(nodes):
                 yield (n, nodes[i+2])
 
 class FixExcept(fixer_base.BaseFix):
+    BM_compatible = True
 
     PATTERN = """
     try_stmt< 'try' ':' (simple_stmt | suite)
index 121431c0c272c88da2a8332c91289cb7bd132afc..50e185445453bdfe3db1abf31834607ac9d71f2f 100644 (file)
@@ -16,6 +16,7 @@ from ..fixer_util import Comma, Name, Call
 
 
 class FixExec(fixer_base.BaseFix):
+    BM_compatible = True
 
     PATTERN = """
     exec_stmt< 'exec' a=any 'in' b=any [',' c=any] >
index 38d6abc9b358cb7921e790e9aac636c7b078c74f..2f29d3b281a901bd038da2c401d59fdcf4124e22 100644 (file)
@@ -13,6 +13,7 @@ from ..fixer_util import (Comma, Name, Call, LParen, RParen, Dot, Node,
 
 
 class FixExecfile(fixer_base.BaseFix):
+    BM_compatible = True
 
     PATTERN = """
     power< 'execfile' trailer< '(' arglist< filename=any [',' globals=any [',' locals=any ] ] > ')' > >
index 28d30b8f79d378a6a57e655f4ae77bb0d94120e3..89fb3db53367a97fd5614a04571cbcbd57ff3c5a 100644 (file)
@@ -9,6 +9,8 @@ from lib2to3.fixer_util import Name, Attr, Call, Comma, Newline, syms
 
 
 class FixExitfunc(fixer_base.BaseFix):
+    keep_line_order = True
+    BM_compatible = True
 
     PATTERN = """
               (
index 0e92b1452119afc007d83529ef1d4889986cf2a0..18ee2ffc0627113d4d54a46d02294b30bd8421eb 100644 (file)
@@ -19,6 +19,7 @@ from .. import fixer_base
 from ..fixer_util import Name, Call, ListComp, in_special_context
 
 class FixFilter(fixer_base.ConditionalFix):
+    BM_compatible = True
 
     PATTERN = """
     filter_lambda=power<
index 5d1ba8cfa8aa07cfe7ba7cedff7a10a5d2db1248..9e45c028574f3d37ea6ee0a61e5a1afc3380a8bc 100644 (file)
@@ -7,6 +7,8 @@ from ..fixer_util import Name
 
 
 class FixFuncattrs(fixer_base.BaseFix):
+    BM_compatible = True
+
     PATTERN = """
     power< any+ trailer< '.' attr=('func_closure' | 'func_doc' | 'func_globals'
                                   | 'func_name' | 'func_defaults' | 'func_code'
index 861e13c62c775a62a34fbee691319b5e178d5654..fbcb86af0791338e4edebd2f68bd49cd7a9160c2 100644 (file)
@@ -9,6 +9,8 @@ from .. import fixer_base
 from ..fixer_util import BlankLine
 
 class FixFuture(fixer_base.BaseFix):
+    BM_compatible = True
+
     PATTERN = """import_from< 'from' module_name="__future__" 'import' any >"""
 
     # This should be run last -- some things check for the import
index a5b5aa377873f8f48d37fe00d2020d8349b2ee00..82233c8993b35e2e6f0915731f80e0cf5341fc7d 100644 (file)
@@ -8,6 +8,7 @@ from .. import fixer_base
 from ..fixer_util import Name
 
 class FixGetcwdu(fixer_base.BaseFix):
+    BM_compatible = True
 
     PATTERN = """
               power< 'os' trailer< dot='.' name='getcwdu' > any* >
index 0915a363bd7210c874ab54be6dd260b7a5778d7c..bead4cb51cde860bd4803eeeccc1122a87a30d6d 100644 (file)
@@ -37,6 +37,7 @@ from ..fixer_util import Name, parenthesize
 
 
 class FixHasKey(fixer_base.BaseFix):
+    BM_compatible = True
 
     PATTERN = """
     anchor=power<
index c07b77e55a5d5bb2785747d1afc55907b2838efe..37b6eefa513473507ff586611604cdab3bb34bcf 100644 (file)
@@ -35,7 +35,6 @@ CMP = "(n='!=' | '==' | 'is' | n=comp_op< 'is' 'not' >)"
 TYPE = "power< 'type' trailer< '(' x=any ')' > >"
 
 class FixIdioms(fixer_base.BaseFix):
-
     explicit = True # The user must ask for this fixer
 
     PATTERN = r"""
index d35fa0caf8185af6890072c85089c52239725a63..201e811e619a6d90ec3cbec594cf219c5d35153d 100644 (file)
@@ -36,6 +36,7 @@ def traverse_imports(names):
 
 
 class FixImport(fixer_base.BaseFix):
+    BM_compatible = True
 
     PATTERN = """
     import_from< 'from' imp=any 'import' ['('] any [')'] >
index e1ad667f7bfab9f72e5d2a2f925fe4612e9f87c0..93c9e6787b164b75045ff321a5df2dba536d6e89 100644 (file)
@@ -84,6 +84,8 @@ def build_pattern(mapping=MAPPING):
 
 class FixImports(fixer_base.BaseFix):
 
+    BM_compatible = True
+    keep_line_order = True
     # This is overridden in fix_imports2.
     mapping = MAPPING
 
index 291cadf220b05f20de9732c3f502a642b3bbdbe2..fbf4c72f5c9d8bb3f05da8b25d5bb3fe59d113bf 100644 (file)
@@ -11,7 +11,7 @@ context = patcomp.compile_pattern("power< 'eval' trailer< '(' any ')' > >")
 
 
 class FixInput(fixer_base.BaseFix):
-
+    BM_compatible = True
     PATTERN = """
               power< 'input' args=trailer< '(' [any] ')' > >
               """
index 67f98efbed37aeaa8a3dc262b98e451c0319db15..e7bb5052b4b5d9571da6b4b40941ddd27288a488 100644 (file)
@@ -12,6 +12,8 @@ from ..fixer_util import Name, Attr, touch_import
 
 
 class FixIntern(fixer_base.BaseFix):
+    BM_compatible = True
+    order = "pre"
 
     PATTERN = """
     power< 'intern'
index c720a837e2e42b36feacf2ff73b9e840aed432d5..4b04c8fd0c665a423e482f40de898fdcaa4ee305 100644 (file)
@@ -14,7 +14,7 @@ from ..fixer_util import token
 
 
 class FixIsinstance(fixer_base.BaseFix):
-
+    BM_compatible = True
     PATTERN = """
     power<
         'isinstance'
index 77b3a015f2795b3bf3e9a41c11d07cd0ab3b26d8..5391a37b255dcfd6497daed2015836b3e7786786 100644 (file)
@@ -12,6 +12,7 @@ from .. import fixer_base
 from ..fixer_util import Name
 
 class FixItertools(fixer_base.BaseFix):
+    BM_compatible = True
     it_funcs = "('imap'|'ifilter'|'izip'|'ifilterfalse')"
     PATTERN = """
               power< it='itertools'
index 72f4fe4044bf2085725dbcac567541b135a5ba2b..c1deb038e9590591cd8f467ec6f6a891707157db 100644 (file)
@@ -6,6 +6,7 @@ from lib2to3.fixer_util import BlankLine, syms, token
 
 
 class FixItertoolsImports(fixer_base.BaseFix):
+    BM_compatible = True
     PATTERN = """
               import_from< 'from' 'itertools' 'import' imports=any >
               """ %(locals())
index 6f3661cbab5bed11dc1ed884d637865d8cca3f03..5dddde0d08d7e98b2f5b3856874bbd612939e79b 100644 (file)
@@ -10,7 +10,7 @@ from lib2to3.fixer_util import is_probably_builtin
 
 
 class FixLong(fixer_base.BaseFix):
-
+    BM_compatible = True
     PATTERN = "'long'"
 
     def transform(self, node, results):
index 6d269573add0f950eaf756cf0600505e10b57854..7a7d0dbc66c0b75416643bb0404ab5777303ca3a 100644 (file)
@@ -26,6 +26,7 @@ from ..fixer_util import Name, Call, ListComp, in_special_context
 from ..pygram import python_symbols as syms
 
 class FixMap(fixer_base.ConditionalFix):
+    BM_compatible = True
 
     PATTERN = """
     map_none=power<
index 789cb168bbc36a8486bf36ccc3f54ab14cb3acb6..f403fd6d535970158f111590c2c8e1f1648e8478 100644 (file)
@@ -143,6 +143,7 @@ def fixup_indent(suite):
 
 
 class FixMetaclass(fixer_base.BaseFix):
+    BM_compatible = True
 
     PATTERN = """
     classdef<any*>
index 5e94a9c526a003126b53248cbaf6ab52db6a18cf..f3c1ecfec15393526e1cbbb6bb2ba590a87dac33 100644 (file)
@@ -13,6 +13,7 @@ MAP = {
     }
 
 class FixMethodattrs(fixer_base.BaseFix):
+    BM_compatible = True
     PATTERN = """
     power< any+ trailer< '.' attr=('im_func' | 'im_self' | 'im_class') > any* >
     """
index 9514798f4ce8d9795213e616ccdb21b93b3fb431..f021a9bd70a4f2802f488ec4e92977a08990af68 100644 (file)
@@ -15,6 +15,7 @@ bind_warning = "Calls to builtin next() possibly shadowed by global binding"
 
 
 class FixNext(fixer_base.BaseFix):
+    BM_compatible = True
     PATTERN = """
     power< base=any+ trailer< '.' attr='next' > trailer< '(' ')' > >
     |
index 17be377013b29c8a593b564df541c2c75da16b1f..ba83478f818ad46c08fb08f7cc8c83b838d1bfe1 100644 (file)
@@ -6,6 +6,7 @@ from .. import fixer_base
 from ..fixer_util import Name, syms
 
 class FixNonzero(fixer_base.BaseFix):
+    BM_compatible = True
     PATTERN = """
     classdef< 'class' any+ ':'
               suite< any*
index da3d579ae23a4ced876700aff871daf3f3e667a3..7bf2c0dd22c644301a799f2a60cdbaac7a8ea1f9 100644 (file)
@@ -14,7 +14,16 @@ from lib2to3 import fixer_base
 from lib2to3.fixer_util import Call, Name, String, touch_import
 
 
+def invocation(s):
+    def dec(f):
+        f.invocation = s
+        return f
+    return dec
+
+
 class FixOperator(fixer_base.BaseFix):
+    BM_compatible = True
+    order = "pre"
 
     methods = """
               method=('isCallable'|'sequenceIncludes'
@@ -34,34 +43,34 @@ class FixOperator(fixer_base.BaseFix):
         if method is not None:
             return method(node, results)
 
+    @invocation("operator.contains(%s)")
     def _sequenceIncludes(self, node, results):
-        """operator.contains(%s)"""
         return self._handle_rename(node, results, u"contains")
 
+    @invocation("hasattr(%s, '__call__')")
     def _isCallable(self, node, results):
-        """hasattr(%s, '__call__')"""
         obj = results["obj"]
         args = [obj.clone(), String(u", "), String(u"'__call__'")]
         return Call(Name(u"hasattr"), args, prefix=node.prefix)
 
+    @invocation("operator.mul(%s)")
     def _repeat(self, node, results):
-        """operator.mul(%s)"""
         return self._handle_rename(node, results, u"mul")
 
+    @invocation("operator.imul(%s)")
     def _irepeat(self, node, results):
-        """operator.imul(%s)"""
         return self._handle_rename(node, results, u"imul")
 
+    @invocation("isinstance(%s, collections.Sequence)")
     def _isSequenceType(self, node, results):
-        """isinstance(%s, collections.Sequence)"""
         return self._handle_type2abc(node, results, u"collections", u"Sequence")
 
+    @invocation("isinstance(%s, collections.Mapping)")
     def _isMappingType(self, node, results):
-        """isinstance(%s, collections.Mapping)"""
         return self._handle_type2abc(node, results, u"collections", u"Mapping")
 
+    @invocation("isinstance(%s, numbers.Number)")
     def _isNumberType(self, node, results):
-        """isinstance(%s, numbers.Number)"""
         return self._handle_type2abc(node, results, u"numbers", u"Number")
 
     def _handle_rename(self, node, results, name):
@@ -82,6 +91,6 @@ class FixOperator(fixer_base.BaseFix):
                 return method
             else:
                 sub = (unicode(results["obj"]),)
-                invocation_str = unicode(method.__doc__) % sub
+                invocation_str = unicode(method.invocation) % sub
                 self.warning(node, u"You should use '%s' here." % invocation_str)
         return None
index eeb0d4070a07411b0b097a68d61bf6429d55f954..8650cd9078af4dcff243f9958248532f67096d0e 100644 (file)
@@ -10,6 +10,8 @@ from ..fixer_util import LParen, RParen
 
 # XXX This doesn't support nested for loops like [x for x in 1, 2 for x in 1, 2]
 class FixParen(fixer_base.BaseFix):
+    BM_compatible = True
+
     PATTERN = """
         atom< ('[' | '(')
             (listmaker< any
index 6cad8ce59aa4bdc400c2d906dd9f8cad3da3aabb..98786b3ecdc5ec7a6d6025a66d6134cd374808f2 100644 (file)
@@ -28,6 +28,8 @@ parend_expr = patcomp.compile_pattern(
 
 class FixPrint(fixer_base.BaseFix):
 
+    BM_compatible = True
+
     PATTERN = """
               simple_stmt< any* bare='print' any* > | print_stmt
               """
index 24e123c730af38b57a6a32ded409bc23bc1d817d..b958ba0129de71e89d583d311a1a1d0cec7e384b 100644 (file)
@@ -4,6 +4,7 @@ raise         -> raise
 raise E       -> raise E
 raise E, V    -> raise E(V)
 raise E, V, T -> raise E(V).with_traceback(T)
+raise E, None, T -> raise E.with_traceback(T)
 
 raise (((E, E'), E''), E'''), V -> raise E(V)
 raise "foo", V, T               -> warns about string exceptions
@@ -29,6 +30,7 @@ from ..fixer_util import Name, Call, Attr, ArgList, is_tuple
 
 class FixRaise(fixer_base.BaseFix):
 
+    BM_compatible = True
     PATTERN = """
     raise_stmt< 'raise' exc=any [',' val=any [',' tb=any]] >
     """
@@ -37,8 +39,9 @@ class FixRaise(fixer_base.BaseFix):
         syms = self.syms
 
         exc = results["exc"].clone()
-        if exc.type is token.STRING:
-            self.cannot_convert(node, "Python 3 does not support string exceptions")
+        if exc.type == token.STRING:
+            msg = "Python 3 does not support string exceptions"
+            self.cannot_convert(node, msg)
             return
 
         # Python 2 supports
@@ -52,7 +55,7 @@ class FixRaise(fixer_base.BaseFix):
                 # exc.children[1:-1] is the unparenthesized tuple
                 # exc.children[1].children[0] is the first element of the tuple
                 exc = exc.children[1].children[0].clone()
-            exc.prefix = " "
+            exc.prefix = u" "
 
         if "val" not in results:
             # One-argument raise
@@ -71,7 +74,12 @@ class FixRaise(fixer_base.BaseFix):
             tb = results["tb"].clone()
             tb.prefix = u""
 
-            e = Call(exc, args)
+            e = exc
+            # If there's a traceback and None is passed as the value, then don't
+            # add a call, since the user probably just wants to add a
+            # traceback. See issue #9661.
+            if val.type != token.NAME or val.value != u"None":
+                e = Call(exc, args)
             with_tb = Attr(e, Name(u'with_traceback')) + [ArgList([tb])]
             new = pytree.Node(syms.simple_stmt, [Name(u"raise")] + with_tb)
             new.prefix = node.prefix
index ba045d634126b229b221fa8c134ba934703c3c39..3a73b81864e2fb3240d5e33dc4776ba49f8fd7c9 100644 (file)
@@ -7,6 +7,7 @@ from ..fixer_util import Name
 
 class FixRawInput(fixer_base.BaseFix):
 
+    BM_compatible = True
     PATTERN = """
               power< name='raw_input' trailer< '(' [any] ')' > any* >
               """
index 194866d6a06f9bb711fe435fb30afb3755865712..6bd785c1cd909c905d69856630e57899ae7f4cd8 100644 (file)
@@ -14,6 +14,9 @@ from lib2to3.fixer_util import touch_import
 
 class FixReduce(fixer_base.BaseFix):
 
+    BM_compatible = True
+    order = "pre"
+
     PATTERN = """
     power< 'reduce'
         trailer< '('
index a45345a9811e18ea1ebf0129f43b2470d2d6e00b..4bcce8c4ebdd6e3cb8bffee1e38ef2c3e992b0a9 100644 (file)
@@ -40,6 +40,7 @@ def build_pattern():
 
 
 class FixRenames(fixer_base.BaseFix):
+    BM_compatible = True
     PATTERN = "|".join(build_pattern())
 
     order = "pre" # Pre-order tree traversal
index 4b37e732597f53918020e9f7ad1d3a99c5d4a046..f34365647f0b1cdb0db3ef4b1bae38ea9396749e 100644 (file)
@@ -10,6 +10,7 @@ from ..fixer_util import Call, Name, parenthesize
 
 class FixRepr(fixer_base.BaseFix):
 
+    BM_compatible = True
     PATTERN = """
               atom < '`' expr=any '`' >
               """
index cfc522cb46e97696cf7f733dbe1d14513b9170e7..d3d38ec4e0ab6623130b6ead0f4de67d4a1a2331 100644 (file)
@@ -11,6 +11,7 @@ from lib2to3.fixer_util import token, syms
 
 class FixSetLiteral(fixer_base.BaseFix):
 
+    BM_compatible = True
     explicit = True
 
     PATTERN = """power< 'set' trailer< '('
index a5789ba55bf7266a05689cfaef0c49a270e8bb68..6cad51116d0361b98b913c317c19e447cb959574 100644 (file)
@@ -9,7 +9,7 @@ from ..fixer_util import Name
 
 
 class FixStandarderror(fixer_base.BaseFix):
-
+    BM_compatible = True
     PATTERN = """
               'StandardError'
               """
index 348929bb6ed959e9fec23db6031b645d37e70eb6..2ecca2b53543cc2666cf3e213bd6db50081b62dc 100644 (file)
@@ -14,6 +14,7 @@ from ..fixer_util import Attr, Call, Name, Number, Subscript, Node, syms
 class FixSysExc(fixer_base.BaseFix):
     # This order matches the ordering of sys.exc_info().
     exc_info = [u"exc_type", u"exc_value", u"exc_traceback"]
+    BM_compatible = True
     PATTERN = """
               power< 'sys' trailer< dot='.' attribute=(%s) > >
               """ % '|'.join("'%s'" % e for e in exc_info)
index 63271cf1d3ff097b3a5a745e1fcdd762e8744067..1468d89a450671d9c48ed70fee0a45d2663ce4ef 100644 (file)
@@ -14,7 +14,7 @@ from .. import fixer_base
 from ..fixer_util import Name, Call, ArgList, Attr, is_tuple
 
 class FixThrow(fixer_base.BaseFix):
-
+    BM_compatible = True
     PATTERN = """
     power< any trailer< '.' 'throw' >
            trailer< '(' args=arglist< exc=any ',' val=any [',' tb=any] > ')' >
index 647eec0cfc9320dba83efce75f41de8800020e9f..6361717dc54d7987dfd8cf8484ca4cc1a66fe9ed 100644 (file)
@@ -29,6 +29,10 @@ def is_docstring(stmt):
            stmt.children[0].type == token.STRING
 
 class FixTupleParams(fixer_base.BaseFix):
+    run_order = 4 #use a lower order since lambda is part of other
+                  #patterns
+    BM_compatible = True
+
     PATTERN = """
               funcdef< 'def' any parameters< '(' args=any ')' >
                        ['->' any] ':' suite=any+ >
index 2fbbab87923bff1e7091b2d572d28edf8abce4c6..fc9d4959276a716970d020dab580cb613e0f6217 100644 (file)
@@ -52,7 +52,7 @@ _TYPE_MAPPING = {
 _pats = ["power< 'types' trailer< '.' name='%s' > >" % t for t in _TYPE_MAPPING]
 
 class FixTypes(fixer_base.BaseFix):
-
+    BM_compatible = True
     PATTERN = '|'.join(_pats)
 
     def transform(self, node, results):
index cd3b89cb1747ee279a16524b93ca5918fb066b6a..6c89576540c242935f91424752ec3cc618e09b5f 100644 (file)
@@ -10,7 +10,7 @@ _mapping = {u"unichr" : u"chr", u"unicode" : u"str"}
 _literal_re = re.compile(ur"[uU][rR]?[\'\"]")
 
 class FixUnicode(fixer_base.BaseFix):
-
+    BM_compatible = True
     PATTERN = "STRING | 'unicode' | 'unichr'"
 
     def transform(self, node, results):
index 1b3ef3f8d90cf13990b72aecc04ede4ffe4c467b..f91fe2a03542017e83162623ed8a58bb3bb5f45e 100644 (file)
@@ -8,7 +8,7 @@
 from lib2to3.fixes.fix_imports import alternates, FixImports
 from lib2to3 import fixer_base
 from lib2to3.fixer_util import (Name, Comma, FromImport, Newline,
-                                find_indentation)
+                                find_indentation, Node, syms)
 
 MAPPING = {"urllib":  [
                 ("urllib.request",
@@ -121,26 +121,37 @@ class FixUrllib(FixImports):
             mod_dict = {}
             members = results["members"]
             for member in members:
-                member = member.value
                 # we only care about the actual members
-                if member != u",":
+                if member.type == syms.import_as_name:
+                    as_name = member.children[2].value
+                    member_name = member.children[0].value
+                else:
+                    member_name = member.value
+                    as_name = None
+                if member_name != u",":
                     for change in MAPPING[mod_member.value]:
-                        if member in change[1]:
-                            if change[0] in mod_dict:
-                                mod_dict[change[0]].append(member)
-                            else:
-                                mod_dict[change[0]] = [member]
+                        if member_name in change[1]:
+                            if change[0] not in mod_dict:
                                 modules.append(change[0])
+                            mod_dict.setdefault(change[0], []).append(member)
 
             new_nodes = []
             indentation = find_indentation(node)
             first = True
+            def handle_name(name, prefix):
+                if name.type == syms.import_as_name:
+                    kids = [Name(name.children[0].value, prefix=prefix),
+                            name.children[1].clone(),
+                            name.children[2].clone()]
+                    return [Node(syms.import_as_name, kids)]
+                return [Name(name.value, prefix=prefix)]
             for module in modules:
                 elts = mod_dict[module]
                 names = []
                 for elt in elts[:-1]:
-                    names.extend([Name(elt, prefix=pref), Comma()])
-                names.append(Name(elts[-1], prefix=pref))
+                    names.extend(handle_name(elt, pref))
+                    names.append(Comma())
+                names.extend(handle_name(elts[-1], pref))
                 new = FromImport(module, names)
                 if not first or node.parent.prefix.endswith(indentation):
                     new.prefix = indentation
index 2eb106504ec6baa82af8ecf022bda5948ca43223..f1436724b454427407914fc129d915309c791971 100644 (file)
@@ -10,7 +10,7 @@ from .. import patcomp
 
 
 class FixXrange(fixer_base.BaseFix):
-
+    BM_compatible = True
     PATTERN = """
               power<
                  (name='range'|name='xrange') trailer< '(' args=any ')' >
index ee4b356d3d5dc9f9b620839e1cf5ff466a84ecb2..f50b9a27558fafad0f9bc69ae8c0f7f9d119a3e5 100644 (file)
@@ -9,6 +9,7 @@ from ..fixer_util import Name
 
 
 class FixXreadlines(fixer_base.BaseFix):
+    BM_compatible = True
     PATTERN = """
     power< call=any+ trailer< '.' 'xreadlines' > trailer< '(' ')' > >
     |
index 6736a7edc2607e45995e1b491fdd0438afd177fa..c5d7b66d6739f8edd996e19e388ad6406772ccc0 100644 (file)
@@ -13,6 +13,7 @@ from ..fixer_util import Name, Call, in_special_context
 
 class FixZip(fixer_base.ConditionalFix):
 
+    BM_compatible = True
     PATTERN = """
     power< 'zip' args=trailer< '(' [any] ')' >
     >
index daa67fa87be348198448cf324806fd3cfb92fbaf..84fee5b2410e7fef1cb9f3dde8c6b7c5c0a75eeb 100644 (file)
@@ -52,14 +52,17 @@ class PatternCompiler(object):
         self.pysyms = pygram.python_symbols
         self.driver = driver.Driver(self.grammar, convert=pattern_convert)
 
-    def compile_pattern(self, input, debug=False):
+    def compile_pattern(self, input, debug=False, with_tree=False):
         """Compiles a pattern string to a nested pytree.*Pattern object."""
         tokens = tokenize_wrapper(input)
         try:
             root = self.driver.parse_tokens(tokens, debug=debug)
-        except parse.ParseError, e:
+        except parse.ParseError as e:
             raise PatternSyntaxError(str(e))
-        return self.compile_node(root)
+        if with_tree:
+            return self.compile_node(root), root
+        else:
+            return self.compile_node(root)
 
     def compile_node(self, node):
         """Compiles a node, recursively.
index 6cdb3a44e85f31e162debdc691cd5810389a3024..621ff24c95471b967333f27c1055b8fd6ff2a4ba 100644 (file)
@@ -13,6 +13,8 @@ from . import pytree
 
 # The grammar file
 _GRAMMAR_FILE = os.path.join(os.path.dirname(__file__), "Grammar.txt")
+_PATTERN_GRAMMAR_FILE = os.path.join(os.path.dirname(__file__),
+                                     "PatternGrammar.txt")
 
 
 class Symbols(object):
@@ -33,3 +35,6 @@ python_symbols = Symbols(python_grammar)
 
 python_grammar_no_print_statement = python_grammar.copy()
 del python_grammar_no_print_statement.keywords["print"]
+
+pattern_grammar = driver.load_grammar(_PATTERN_GRAMMAR_FILE)
+pattern_symbols = Symbols(pattern_grammar)
index 4c87ea027941ed1cc5d066d1c3756364d64dc5f7..b24283e0eead78144326ab54b055270a819c81f0 100644 (file)
@@ -16,7 +16,6 @@ import sys
 import warnings
 from StringIO import StringIO
 
-
 HUGE = 0x7FFFFFFF  # maximum repeat count, default max
 
 _type_reprs = {}
@@ -30,7 +29,6 @@ def type_repr(type_num):
             if type(val) == int: _type_reprs[val] = name
     return _type_reprs.setdefault(type_num, type_num)
 
-
 class Base(object):
 
     """
@@ -47,6 +45,7 @@ class Base(object):
     parent = None  # Parent node pointer, or None
     children = ()  # Tuple of subnodes
     was_changed = False
+    was_checked = False
 
     def __new__(cls, *args, **kwds):
         """Constructor that prevents Base from being instantiated."""
@@ -213,6 +212,16 @@ class Base(object):
                     return None
                 return self.parent.children[i-1]
 
+    def leaves(self):
+        for child in self.children:
+            for x in child.leaves():
+                yield x
+
+    def depth(self):
+        if self.parent is None:
+            return 0
+        return 1 + self.parent.depth()
+
     def get_suffix(self):
         """
         Return the string immediately following the invocant node. This is
@@ -227,12 +236,14 @@ class Base(object):
         def __str__(self):
             return unicode(self).encode("ascii")
 
-
 class Node(Base):
 
     """Concrete implementation for interior nodes."""
 
-    def __init__(self, type, children, context=None, prefix=None):
+    def __init__(self,type, children,
+                 context=None,
+                 prefix=None,
+                 fixers_applied=None):
         """
         Initializer.
 
@@ -249,6 +260,10 @@ class Node(Base):
             ch.parent = self
         if prefix is not None:
             self.prefix = prefix
+        if fixers_applied:
+            self.fixers_applied = fixers_applied[:]
+        else:
+            self.fixers_applied = None
 
     def __repr__(self):
         """Return a canonical string representation."""
@@ -273,7 +288,8 @@ class Node(Base):
 
     def clone(self):
         """Return a cloned (deep) copy of self."""
-        return Node(self.type, [ch.clone() for ch in self.children])
+        return Node(self.type, [ch.clone() for ch in self.children],
+                    fixers_applied=self.fixers_applied)
 
     def post_order(self):
         """Return a post-order iterator for the tree."""
@@ -341,7 +357,10 @@ class Leaf(Base):
     lineno = 0    # Line where this token starts in the input
     column = 0    # Column where this token tarts in the input
 
-    def __init__(self, type, value, context=None, prefix=None):
+    def __init__(self, type, value,
+                 context=None,
+                 prefix=None,
+                 fixers_applied=[]):
         """
         Initializer.
 
@@ -355,6 +374,7 @@ class Leaf(Base):
         self.value = value
         if prefix is not None:
             self._prefix = prefix
+        self.fixers_applied = fixers_applied[:]
 
     def __repr__(self):
         """Return a canonical string representation."""
@@ -380,7 +400,11 @@ class Leaf(Base):
     def clone(self):
         """Return a cloned (deep) copy of self."""
         return Leaf(self.type, self.value,
-                    (self.prefix, (self.lineno, self.column)))
+                    (self.prefix, (self.lineno, self.column)),
+                    fixers_applied=self.fixers_applied)
+
+    def leaves(self):
+        yield self
 
     def post_order(self):
         """Return a post-order iterator for the tree."""
index bca02038971cc489f00a6c96cf11982a366e9015..a51eb716b493846bdab02c716db9e9b2cb9c9a58 100644 (file)
@@ -24,7 +24,10 @@ from itertools import chain
 
 # Local imports
 from .pgen2 import driver, tokenize, token
+from .fixer_util import find_root
 from . import pytree, pygram
+from . import btm_utils as bu
+from . import btm_matcher as bm
 
 
 def get_all_fix_names(fixer_pkg, remove_prefix=True):
@@ -201,11 +204,28 @@ class RefactoringTool(object):
                                     logger=self.logger)
         self.pre_order, self.post_order = self.get_fixers()
 
-        self.pre_order_heads = _get_headnode_dict(self.pre_order)
-        self.post_order_heads = _get_headnode_dict(self.post_order)
 
         self.files = []  # List of files that were or should be modified
 
+        self.BM = bm.BottomMatcher()
+        self.bmi_pre_order = [] # Bottom Matcher incompatible fixers
+        self.bmi_post_order = []
+
+        for fixer in chain(self.post_order, self.pre_order):
+            if fixer.BM_compatible:
+                self.BM.add_fixer(fixer)
+                # remove fixers that will be handled by the bottom-up
+                # matcher
+            elif fixer in self.pre_order:
+                self.bmi_pre_order.append(fixer)
+            elif fixer in self.post_order:
+                self.bmi_post_order.append(fixer)
+
+        self.bmi_pre_order_heads = _get_headnode_dict(self.bmi_pre_order)
+        self.bmi_post_order_heads = _get_headnode_dict(self.bmi_post_order)
+
+
+
     def get_fixers(self):
         """Inspects the options to load the requested patterns and handlers.
 
@@ -268,6 +288,7 @@ class RefactoringTool(object):
 
     def refactor(self, items, write=False, doctests_only=False):
         """Refactor a list of files and directories."""
+
         for dir_or_file in items:
             if os.path.isdir(dir_or_file):
                 self.refactor_dir(dir_or_file, write, doctests_only)
@@ -299,7 +320,7 @@ class RefactoringTool(object):
         """
         try:
             f = open(filename, "rb")
-        except IOError, err:
+        except IOError as err:
             self.log_error("Can't open %s: %s", filename, err)
             return None, None
         try:
@@ -348,7 +369,7 @@ class RefactoringTool(object):
             self.driver.grammar = pygram.python_grammar_no_print_statement
         try:
             tree = self.driver.parse_string(data)
-        except Exception, err:
+        except Exception as err:
             self.log_error("Can't parse %s: %s: %s",
                            name, err.__class__.__name__, err)
             return
@@ -378,6 +399,10 @@ class RefactoringTool(object):
     def refactor_tree(self, tree, name):
         """Refactors a parse tree (modifying the tree in place).
 
+        For compatible patterns the bottom matcher module is
+        used. Otherwise the tree is traversed node-to-node for
+        matches.
+
         Args:
             tree: a pytree.Node instance representing the root of the tree
                   to be refactored.
@@ -386,11 +411,65 @@ class RefactoringTool(object):
         Returns:
             True if the tree was modified, False otherwise.
         """
+
         for fixer in chain(self.pre_order, self.post_order):
             fixer.start_tree(tree, name)
 
-        self.traverse_by(self.pre_order_heads, tree.pre_order())
-        self.traverse_by(self.post_order_heads, tree.post_order())
+        #use traditional matching for the incompatible fixers
+        self.traverse_by(self.bmi_pre_order_heads, tree.pre_order())
+        self.traverse_by(self.bmi_post_order_heads, tree.post_order())
+
+        # obtain a set of candidate nodes
+        match_set = self.BM.run(tree.leaves())
+
+        while any(match_set.values()):
+            for fixer in self.BM.fixers:
+                if fixer in match_set and match_set[fixer]:
+                    #sort by depth; apply fixers from bottom(of the AST) to top
+                    match_set[fixer].sort(key=pytree.Base.depth, reverse=True)
+
+                    if fixer.keep_line_order:
+                        #some fixers(eg fix_imports) must be applied
+                        #with the original file's line order
+                        match_set[fixer].sort(key=pytree.Base.get_lineno)
+
+                    for node in list(match_set[fixer]):
+                        if node in match_set[fixer]:
+                            match_set[fixer].remove(node)
+
+                        try:
+                            find_root(node)
+                        except AssertionError:
+                            # this node has been cut off from a
+                            # previous transformation ; skip
+                            continue
+
+                        if node.fixers_applied and fixer in node.fixers_applied:
+                            # do not apply the same fixer again
+                            continue
+
+                        results = fixer.match(node)
+
+                        if results:
+                            new = fixer.transform(node, results)
+                            if new is not None:
+                                node.replace(new)
+                                #new.fixers_applied.append(fixer)
+                                for node in new.post_order():
+                                    # do not apply the fixer again to
+                                    # this or any subnode
+                                    if not node.fixers_applied:
+                                        node.fixers_applied = []
+                                    node.fixers_applied.append(fixer)
+
+                                # update the original match set for
+                                # the added code
+                                new_matches = self.BM.run(new.leaves())
+                                for fxr in new_matches:
+                                    if not fxr in match_set:
+                                        match_set[fxr]=[]
+
+                                    match_set[fxr].extend(new_matches[fxr])
 
         for fixer in chain(self.pre_order, self.post_order):
             fixer.finish_tree(tree, name)
@@ -448,12 +527,12 @@ class RefactoringTool(object):
         """
         try:
             f = _open_with_encoding(filename, "w", encoding=encoding)
-        except os.error, err:
+        except os.error as err:
             self.log_error("Can't create %s: %s", filename, err)
             return
         try:
             f.write(_to_system_newlines(new_text))
-        except os.error, err:
+        except os.error as err:
             self.log_error("Can't write %s: %s", filename, err)
         finally:
             f.close()
@@ -516,7 +595,7 @@ class RefactoringTool(object):
         """
         try:
             tree = self.parse_block(block, lineno, indent)
-        except Exception, err:
+        except Exception as err:
             if self.logger.isEnabledFor(logging.DEBUG):
                 for line in block:
                     self.log_debug("Source: %s", line.rstrip(u"\n"))
index ecb782a30d4b982a1e060fb5fe9aaaa1d7ff3b9f..9bc3975a42f6018c7167492794e04cb0085792ed 100644 (file)
@@ -1,3 +1,2 @@
 # coding: utf-8
 print "BOM BOOM!"
-
index 36f5ac9ef6253a1cc619584dfd9d52608ea9a59a..18589d32cf49a9b739562b59f0ece57f0c705401 100644 (file)
@@ -868,6 +868,11 @@ class Test_raise(FixerTestCase):
                     raise Exception(5).with_traceback(6) # foo"""
         self.check(b, a)
 
+    def test_None_value(self):
+        b = """raise Exception(5), None, tb"""
+        a = """raise Exception(5).with_traceback(tb)"""
+        self.check(b, a)
+
     def test_tuple_value(self):
         b = """raise Exception, (5, 6, 7)"""
         a = """raise Exception(5, 6, 7)"""
@@ -1812,6 +1817,9 @@ class Test_urllib(FixerTestCase):
                     b = "from %s import %s as foo_bar" % (old, member)
                     a = "from %s import %s as foo_bar" % (new, member)
                     self.check(b, a)
+                    b = "from %s import %s as blah, %s" % (old, member, member)
+                    a = "from %s import %s as blah, %s" % (new, member, member)
+                    self.check(b, a)
 
     def test_star(self):
         for old in self.modules:
index 53695f5083eb7c29e614aa516f752803e88d2fb3..ac7d9006aa9b7410c73f63d2351a4bf4e546721a 100644 (file)
@@ -178,6 +178,27 @@ class TestNodes(support.TestCase):
         self.assertEqual(str(n1), "foo**bar")
         self.assertTrue(isinstance(n1.children, list))
 
+    def test_leaves(self):
+        l1 = pytree.Leaf(100, "foo")
+        l2 = pytree.Leaf(100, "bar")
+        l3 = pytree.Leaf(100, "fooey")
+        n2 = pytree.Node(1000, [l1, l2])
+        n3 = pytree.Node(1000, [l3])
+        n1 = pytree.Node(1000, [n2, n3])
+
+        self.assertEqual(list(n1.leaves()), [l1, l2, l3])
+
+    def test_depth(self):
+        l1 = pytree.Leaf(100, "foo")
+        l2 = pytree.Leaf(100, "bar")
+        n2 = pytree.Node(1000, [l1, l2])
+        n3 = pytree.Node(1000, [])
+        n1 = pytree.Node(1000, [n2, n3])
+
+        self.assertEqual(l1.depth(), 2)
+        self.assertEqual(n3.depth(), 1)
+        self.assertEqual(n1.depth(), 0)
+
     def test_post_order(self):
         l1 = pytree.Leaf(100, "foo")
         l2 = pytree.Leaf(100, "bar")