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