OLD | NEW |
(Empty) | |
| 1 # coding: utf-8 |
| 2 """Fixer for __metaclass__ = X -> (future.utils.with_metaclass(X)) methods. |
| 3 |
| 4 The various forms of classef (inherits nothing, inherits once, inherints |
| 5 many) don't parse the same in the CST so we look at ALL classes for |
| 6 a __metaclass__ and if we find one normalize the inherits to all be |
| 7 an arglist. |
| 8 |
| 9 For one-liner classes ('class X: pass') there is no indent/dedent so |
| 10 we normalize those into having a suite. |
| 11 |
| 12 Moving the __metaclass__ into the classdef can also cause the class |
| 13 body to be empty so there is some special casing for that as well. |
| 14 |
| 15 This fixer also tries very hard to keep original indenting and spacing |
| 16 in all those corner cases. |
| 17 """ |
| 18 # This is a derived work of Lib/lib2to3/fixes/fix_metaclass.py under the |
| 19 # copyright of the Python Software Foundation, licensed under the Python |
| 20 # Software Foundation License 2. |
| 21 # |
| 22 # Copyright notice: |
| 23 # |
| 24 # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, |
| 25 # 2011, 2012, 2013 Python Software Foundation. All rights reserved. |
| 26 # |
| 27 # Full license text: http://docs.python.org/3.4/license.html |
| 28 |
| 29 # Author: Jack Diederich, Daniel Neuhäuser |
| 30 |
| 31 # Local imports |
| 32 from lib2to3 import fixer_base |
| 33 from lib2to3.pygram import token |
| 34 from lib2to3.fixer_util import Name, syms, Node, Leaf, touch_import, Call, \ |
| 35 String, Comma, parenthesize |
| 36 |
| 37 |
| 38 def has_metaclass(parent): |
| 39 """ we have to check the cls_node without changing it. |
| 40 There are two possiblities: |
| 41 1) clsdef => suite => simple_stmt => expr_stmt => Leaf('__meta') |
| 42 2) clsdef => simple_stmt => expr_stmt => Leaf('__meta') |
| 43 """ |
| 44 for node in parent.children: |
| 45 if node.type == syms.suite: |
| 46 return has_metaclass(node) |
| 47 elif node.type == syms.simple_stmt and node.children: |
| 48 expr_node = node.children[0] |
| 49 if expr_node.type == syms.expr_stmt and expr_node.children: |
| 50 left_side = expr_node.children[0] |
| 51 if isinstance(left_side, Leaf) and \ |
| 52 left_side.value == '__metaclass__': |
| 53 return True |
| 54 return False |
| 55 |
| 56 |
| 57 def fixup_parse_tree(cls_node): |
| 58 """ one-line classes don't get a suite in the parse tree so we add |
| 59 one to normalize the tree |
| 60 """ |
| 61 for node in cls_node.children: |
| 62 if node.type == syms.suite: |
| 63 # already in the preferred format, do nothing |
| 64 return |
| 65 |
| 66 # !%@#! oneliners have no suite node, we have to fake one up |
| 67 for i, node in enumerate(cls_node.children): |
| 68 if node.type == token.COLON: |
| 69 break |
| 70 else: |
| 71 raise ValueError("No class suite and no ':'!") |
| 72 |
| 73 # move everything into a suite node |
| 74 suite = Node(syms.suite, []) |
| 75 while cls_node.children[i+1:]: |
| 76 move_node = cls_node.children[i+1] |
| 77 suite.append_child(move_node.clone()) |
| 78 move_node.remove() |
| 79 cls_node.append_child(suite) |
| 80 node = suite |
| 81 |
| 82 |
| 83 def fixup_simple_stmt(parent, i, stmt_node): |
| 84 """ if there is a semi-colon all the parts count as part of the same |
| 85 simple_stmt. We just want the __metaclass__ part so we move |
| 86 everything efter the semi-colon into its own simple_stmt node |
| 87 """ |
| 88 for semi_ind, node in enumerate(stmt_node.children): |
| 89 if node.type == token.SEMI: # *sigh* |
| 90 break |
| 91 else: |
| 92 return |
| 93 |
| 94 node.remove() # kill the semicolon |
| 95 new_expr = Node(syms.expr_stmt, []) |
| 96 new_stmt = Node(syms.simple_stmt, [new_expr]) |
| 97 while stmt_node.children[semi_ind:]: |
| 98 move_node = stmt_node.children[semi_ind] |
| 99 new_expr.append_child(move_node.clone()) |
| 100 move_node.remove() |
| 101 parent.insert_child(i, new_stmt) |
| 102 new_leaf1 = new_stmt.children[0].children[0] |
| 103 old_leaf1 = stmt_node.children[0].children[0] |
| 104 new_leaf1.prefix = old_leaf1.prefix |
| 105 |
| 106 |
| 107 def remove_trailing_newline(node): |
| 108 if node.children and node.children[-1].type == token.NEWLINE: |
| 109 node.children[-1].remove() |
| 110 |
| 111 |
| 112 def find_metas(cls_node): |
| 113 # find the suite node (Mmm, sweet nodes) |
| 114 for node in cls_node.children: |
| 115 if node.type == syms.suite: |
| 116 break |
| 117 else: |
| 118 raise ValueError("No class suite!") |
| 119 |
| 120 # look for simple_stmt[ expr_stmt[ Leaf('__metaclass__') ] ] |
| 121 for i, simple_node in list(enumerate(node.children)): |
| 122 if simple_node.type == syms.simple_stmt and simple_node.children: |
| 123 expr_node = simple_node.children[0] |
| 124 if expr_node.type == syms.expr_stmt and expr_node.children: |
| 125 # Check if the expr_node is a simple assignment. |
| 126 left_node = expr_node.children[0] |
| 127 if isinstance(left_node, Leaf) and \ |
| 128 left_node.value == u'__metaclass__': |
| 129 # We found a assignment to __metaclass__. |
| 130 fixup_simple_stmt(node, i, simple_node) |
| 131 remove_trailing_newline(simple_node) |
| 132 yield (node, i, simple_node) |
| 133 |
| 134 |
| 135 def fixup_indent(suite): |
| 136 """ If an INDENT is followed by a thing with a prefix then nuke the prefix |
| 137 Otherwise we get in trouble when removing __metaclass__ at suite start |
| 138 """ |
| 139 kids = suite.children[::-1] |
| 140 # find the first indent |
| 141 while kids: |
| 142 node = kids.pop() |
| 143 if node.type == token.INDENT: |
| 144 break |
| 145 |
| 146 # find the first Leaf |
| 147 while kids: |
| 148 node = kids.pop() |
| 149 if isinstance(node, Leaf) and node.type != token.DEDENT: |
| 150 if node.prefix: |
| 151 node.prefix = u'' |
| 152 return |
| 153 else: |
| 154 kids.extend(node.children[::-1]) |
| 155 |
| 156 |
| 157 class FixMetaclass(fixer_base.BaseFix): |
| 158 BM_compatible = True |
| 159 |
| 160 PATTERN = """ |
| 161 classdef<any*> |
| 162 """ |
| 163 |
| 164 def transform(self, node, results): |
| 165 if not has_metaclass(node): |
| 166 return |
| 167 |
| 168 fixup_parse_tree(node) |
| 169 |
| 170 # find metaclasses, keep the last one |
| 171 last_metaclass = None |
| 172 for suite, i, stmt in find_metas(node): |
| 173 last_metaclass = stmt |
| 174 stmt.remove() |
| 175 |
| 176 text_type = node.children[0].type # always Leaf(nnn, 'class') |
| 177 |
| 178 # figure out what kind of classdef we have |
| 179 if len(node.children) == 7: |
| 180 # Node(classdef, ['class', 'name', '(', arglist, ')', ':', suite]) |
| 181 # 0 1 2 3 4 5 6 |
| 182 if node.children[3].type == syms.arglist: |
| 183 arglist = node.children[3] |
| 184 # Node(classdef, ['class', 'name', '(', 'Parent', ')', ':', suite]) |
| 185 else: |
| 186 parent = node.children[3].clone() |
| 187 arglist = Node(syms.arglist, [parent]) |
| 188 node.set_child(3, arglist) |
| 189 elif len(node.children) == 6: |
| 190 # Node(classdef, ['class', 'name', '(', ')', ':', suite]) |
| 191 # 0 1 2 3 4 5 |
| 192 arglist = Node(syms.arglist, []) |
| 193 node.insert_child(3, arglist) |
| 194 elif len(node.children) == 4: |
| 195 # Node(classdef, ['class', 'name', ':', suite]) |
| 196 # 0 1 2 3 |
| 197 arglist = Node(syms.arglist, []) |
| 198 node.insert_child(2, Leaf(token.RPAR, u')')) |
| 199 node.insert_child(2, arglist) |
| 200 node.insert_child(2, Leaf(token.LPAR, u'(')) |
| 201 else: |
| 202 raise ValueError("Unexpected class definition") |
| 203 |
| 204 # now stick the metaclass in the arglist |
| 205 meta_txt = last_metaclass.children[0].children[0] |
| 206 meta_txt.value = 'metaclass' |
| 207 orig_meta_prefix = meta_txt.prefix |
| 208 |
| 209 # Was: touch_import(None, u'future.utils', node) |
| 210 touch_import(u'future.utils', u'with_metaclass', node) |
| 211 |
| 212 metaclass = last_metaclass.children[0].children[2].clone() |
| 213 metaclass.prefix = u'' |
| 214 |
| 215 arguments = [metaclass] |
| 216 |
| 217 if arglist.children: |
| 218 if len(arglist.children) == 1: |
| 219 base = arglist.children[0].clone() |
| 220 base.prefix = u' ' |
| 221 else: |
| 222 # Unfortunately six.with_metaclass() only allows one base |
| 223 # class, so we have to dynamically generate a base class if |
| 224 # there is more than one. |
| 225 bases = parenthesize(arglist.clone()) |
| 226 bases.prefix = u' ' |
| 227 base = Call(Name('type'), [ |
| 228 String("'NewBase'"), |
| 229 Comma(), |
| 230 bases, |
| 231 Comma(), |
| 232 Node( |
| 233 syms.atom, |
| 234 [Leaf(token.LBRACE, u'{'), Leaf(token.RBRACE, u'}')], |
| 235 prefix=u' ' |
| 236 ) |
| 237 ], prefix=u' ') |
| 238 arguments.extend([Comma(), base]) |
| 239 |
| 240 arglist.replace(Call( |
| 241 Name(u'with_metaclass', prefix=arglist.prefix), |
| 242 arguments |
| 243 )) |
| 244 |
| 245 fixup_indent(suite) |
| 246 |
| 247 # check for empty suite |
| 248 if not suite.children: |
| 249 # one-liner that was just __metaclass_ |
| 250 suite.remove() |
| 251 pass_leaf = Leaf(text_type, u'pass') |
| 252 pass_leaf.prefix = orig_meta_prefix |
| 253 node.append_child(pass_leaf) |
| 254 node.append_child(Leaf(token.NEWLINE, u'\n')) |
| 255 |
| 256 elif len(suite.children) > 1 and \ |
| 257 (suite.children[-2].type == token.INDENT and |
| 258 suite.children[-1].type == token.DEDENT): |
| 259 # there was only one line in the class body and it was __metaclass__ |
| 260 pass_leaf = Leaf(text_type, u'pass') |
| 261 suite.insert_child(-1, pass_leaf) |
| 262 suite.insert_child(-1, Leaf(token.NEWLINE, u'\n')) |
OLD | NEW |