Spaces:
Build error
Build error
"""Fixer for __metaclass__ = X -> (metaclass=X) methods. | |
The various forms of classef (inherits nothing, inherits once, inherits | |
many) don't parse the same in the CST so we look at ALL classes for | |
a __metaclass__ and if we find one normalize the inherits to all be | |
an arglist. | |
For one-liner classes ('class X: pass') there is no indent/dedent so | |
we normalize those into having a suite. | |
Moving the __metaclass__ into the classdef can also cause the class | |
body to be empty so there is some special casing for that as well. | |
This fixer also tries very hard to keep original indenting and spacing | |
in all those corner cases. | |
""" | |
# Author: Jack Diederich | |
# Local imports | |
from .. import fixer_base | |
from ..pygram import token | |
from ..fixer_util import syms, Node, Leaf | |
def has_metaclass(parent): | |
""" we have to check the cls_node without changing it. | |
There are two possibilities: | |
1) clsdef => suite => simple_stmt => expr_stmt => Leaf('__meta') | |
2) clsdef => simple_stmt => expr_stmt => Leaf('__meta') | |
""" | |
for node in parent.children: | |
if node.type == syms.suite: | |
return has_metaclass(node) | |
elif node.type == syms.simple_stmt and node.children: | |
expr_node = node.children[0] | |
if expr_node.type == syms.expr_stmt and expr_node.children: | |
left_side = expr_node.children[0] | |
if isinstance(left_side, Leaf) and \ | |
left_side.value == '__metaclass__': | |
return True | |
return False | |
def fixup_parse_tree(cls_node): | |
""" one-line classes don't get a suite in the parse tree so we add | |
one to normalize the tree | |
""" | |
for node in cls_node.children: | |
if node.type == syms.suite: | |
# already in the preferred format, do nothing | |
return | |
# !%@#! one-liners have no suite node, we have to fake one up | |
for i, node in enumerate(cls_node.children): | |
if node.type == token.COLON: | |
break | |
else: | |
raise ValueError("No class suite and no ':'!") | |
# move everything into a suite node | |
suite = Node(syms.suite, []) | |
while cls_node.children[i+1:]: | |
move_node = cls_node.children[i+1] | |
suite.append_child(move_node.clone()) | |
move_node.remove() | |
cls_node.append_child(suite) | |
node = suite | |
def fixup_simple_stmt(parent, i, stmt_node): | |
""" if there is a semi-colon all the parts count as part of the same | |
simple_stmt. We just want the __metaclass__ part so we move | |
everything after the semi-colon into its own simple_stmt node | |
""" | |
for semi_ind, node in enumerate(stmt_node.children): | |
if node.type == token.SEMI: # *sigh* | |
break | |
else: | |
return | |
node.remove() # kill the semicolon | |
new_expr = Node(syms.expr_stmt, []) | |
new_stmt = Node(syms.simple_stmt, [new_expr]) | |
while stmt_node.children[semi_ind:]: | |
move_node = stmt_node.children[semi_ind] | |
new_expr.append_child(move_node.clone()) | |
move_node.remove() | |
parent.insert_child(i, new_stmt) | |
new_leaf1 = new_stmt.children[0].children[0] | |
old_leaf1 = stmt_node.children[0].children[0] | |
new_leaf1.prefix = old_leaf1.prefix | |
def remove_trailing_newline(node): | |
if node.children and node.children[-1].type == token.NEWLINE: | |
node.children[-1].remove() | |
def find_metas(cls_node): | |
# find the suite node (Mmm, sweet nodes) | |
for node in cls_node.children: | |
if node.type == syms.suite: | |
break | |
else: | |
raise ValueError("No class suite!") | |
# look for simple_stmt[ expr_stmt[ Leaf('__metaclass__') ] ] | |
for i, simple_node in list(enumerate(node.children)): | |
if simple_node.type == syms.simple_stmt and simple_node.children: | |
expr_node = simple_node.children[0] | |
if expr_node.type == syms.expr_stmt and expr_node.children: | |
# Check if the expr_node is a simple assignment. | |
left_node = expr_node.children[0] | |
if isinstance(left_node, Leaf) and \ | |
left_node.value == '__metaclass__': | |
# We found an assignment to __metaclass__. | |
fixup_simple_stmt(node, i, simple_node) | |
remove_trailing_newline(simple_node) | |
yield (node, i, simple_node) | |
def fixup_indent(suite): | |
""" If an INDENT is followed by a thing with a prefix then nuke the prefix | |
Otherwise we get in trouble when removing __metaclass__ at suite start | |
""" | |
kids = suite.children[::-1] | |
# find the first indent | |
while kids: | |
node = kids.pop() | |
if node.type == token.INDENT: | |
break | |
# find the first Leaf | |
while kids: | |
node = kids.pop() | |
if isinstance(node, Leaf) and node.type != token.DEDENT: | |
if node.prefix: | |
node.prefix = '' | |
return | |
else: | |
kids.extend(node.children[::-1]) | |
class FixMetaclass(fixer_base.BaseFix): | |
BM_compatible = True | |
PATTERN = """ | |
classdef<any*> | |
""" | |
def transform(self, node, results): | |
if not has_metaclass(node): | |
return | |
fixup_parse_tree(node) | |
# find metaclasses, keep the last one | |
last_metaclass = None | |
for suite, i, stmt in find_metas(node): | |
last_metaclass = stmt | |
stmt.remove() | |
text_type = node.children[0].type # always Leaf(nnn, 'class') | |
# figure out what kind of classdef we have | |
if len(node.children) == 7: | |
# Node(classdef, ['class', 'name', '(', arglist, ')', ':', suite]) | |
# 0 1 2 3 4 5 6 | |
if node.children[3].type == syms.arglist: | |
arglist = node.children[3] | |
# Node(classdef, ['class', 'name', '(', 'Parent', ')', ':', suite]) | |
else: | |
parent = node.children[3].clone() | |
arglist = Node(syms.arglist, [parent]) | |
node.set_child(3, arglist) | |
elif len(node.children) == 6: | |
# Node(classdef, ['class', 'name', '(', ')', ':', suite]) | |
# 0 1 2 3 4 5 | |
arglist = Node(syms.arglist, []) | |
node.insert_child(3, arglist) | |
elif len(node.children) == 4: | |
# Node(classdef, ['class', 'name', ':', suite]) | |
# 0 1 2 3 | |
arglist = Node(syms.arglist, []) | |
node.insert_child(2, Leaf(token.RPAR, ')')) | |
node.insert_child(2, arglist) | |
node.insert_child(2, Leaf(token.LPAR, '(')) | |
else: | |
raise ValueError("Unexpected class definition") | |
# now stick the metaclass in the arglist | |
meta_txt = last_metaclass.children[0].children[0] | |
meta_txt.value = 'metaclass' | |
orig_meta_prefix = meta_txt.prefix | |
if arglist.children: | |
arglist.append_child(Leaf(token.COMMA, ',')) | |
meta_txt.prefix = ' ' | |
else: | |
meta_txt.prefix = '' | |
# compact the expression "metaclass = Meta" -> "metaclass=Meta" | |
expr_stmt = last_metaclass.children[0] | |
assert expr_stmt.type == syms.expr_stmt | |
expr_stmt.children[1].prefix = '' | |
expr_stmt.children[2].prefix = '' | |
arglist.append_child(last_metaclass) | |
fixup_indent(suite) | |
# check for empty suite | |
if not suite.children: | |
# one-liner that was just __metaclass_ | |
suite.remove() | |
pass_leaf = Leaf(text_type, 'pass') | |
pass_leaf.prefix = orig_meta_prefix | |
node.append_child(pass_leaf) | |
node.append_child(Leaf(token.NEWLINE, '\n')) | |
elif len(suite.children) > 1 and \ | |
(suite.children[-2].type == token.INDENT and | |
suite.children[-1].type == token.DEDENT): | |
# there was only one line in the class body and it was __metaclass__ | |
pass_leaf = Leaf(text_type, 'pass') | |
suite.insert_child(-1, pass_leaf) | |
suite.insert_child(-1, Leaf(token.NEWLINE, '\n')) | |