def _new_type_check_with_import(package, name, root, insert_pos): # type: (Optional[str], str, Node, int) -> None """ Inserts a new TYPE_CHECKING block containing a new import statement for package and name Parameters ----------- package : Optional[str] name : str root : Node insert_pos : int """ # [Grammar] # if_stmt: 'if' namedexpr_test ':' suite ('elif' namedexpr_test ':' suite)* ['else' ':' suite] type_check_node = Node(syms.if_stmt, [Leaf(token.NAME, 'if'), Leaf(token.NAME, 'TYPE_CHECKING', prefix=" "), Leaf(token.COLON, ':'), # [Grammar] # suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT Node(syms.suite, [Leaf(token.NEWLINE, '\n'), Leaf(token.INDENT, ' '), Node(syms.simple_stmt, [_generate_import_node(package, name), Newline()]), Leaf(token.DEDENT, '')])]) # We can just hardcode the correct insert position since we just created the typing block root.insert_child(insert_pos, type_check_node) # Make sure to import TYPE_CHECKING just before using import_type_checking = [_generate_import_node('typing', 'TYPE_CHECKING'), Newline()] root.insert_child(insert_pos, Node(syms.simple_stmt, import_type_checking))
def transform(self, node, results): signame = results['signame'] if 'emitter' in results: emitter = results.get("emitter") emitter = Name(''.join(map(str, emitter))) if 'sigarg' in results: args = results.get("args").clone() args.children = args.children[2:] if args.children: args.children[0].prefix = '' res = Node(syms.power, [emitter, Name('.'), Name(signame), Name('.'), Name('emit')] + [ArgList([args])]) else: res = Node(syms.power, [emitter, Name('.'), Name(signame), Name('.'), Name('emit()')]) else: sender = results.get("sender").clone() method = results.get("method") if isinstance(method, list): method = method[0] method = method.clone() sender.prefix = node.prefix slot = results.get("slot").clone() slot.prefix = "" res = Node(syms.power, [sender, Name('.'), Name(signame), Name('.'), method] + [ArgList([slot])]) return res
def _generate_import_node(package, name, prefix=""): def DottedName(name, prefix=""): split = name.rsplit('.') if len(split) > 1: # Reconstruct the dotted name as a list of leaves leftmost_name = Leaf(token.NAME, split[0]) children = [leftmost_name] for entry in split[1:]: next_name = [Leaf(token.DOT, '.'), Leaf(token.NAME, entry)] children.extend(next_name) return Node(syms.dotted_name, children, prefix=prefix) return Leaf(token.NAME, name, prefix=prefix) if not package: import_ = Node(syms.import_name, [ Leaf(token.NAME, "import", prefix=prefix), DottedName(name, prefix=" ") ]) else: import_ = Node(syms.import_from, [ Leaf(token.NAME, "from", prefix=prefix), DottedName(package, prefix=" "), Leaf(token.NAME, "import", prefix=" "), Leaf(token.NAME, name, prefix=" "), ]) return import_
def RaisesRegexOp(context, designator, exceptionClass, expected_regex, indent, kws, arglist, node): expected_regex.prefix = "" arglist = [a.clone() for a in arglist.children] del arglist[2:4] # remove pattern and comma arglist = Node(syms.arglist, arglist) with_stmt = RaisesOp(context, exceptionClass, indent, kws, arglist, node) with_stmt.insert_child(2, Name('as', prefix=" ")) with_stmt.insert_child(3, Name(designator, prefix=" ")) # if this is already part of a with statement we need to insert re.search # after the last leaf with content if node.parent.type == syms.with_stmt: parent_with = node.parent for leaf in reversed(list(parent_with.leaves())): if leaf.value.strip(): break i = leaf.parent.children.index(leaf) leaf.parent.insert_child(i + 1, Newline()) leaf.parent.insert_child( i + 2, Name('assert %s.match(%s)' % (designator, expected_regex), prefix=indent)) return with_stmt else: return Node(syms.suite, [ with_stmt, Newline(), Name('assert %s.match(%s)' % (designator, expected_regex), prefix=indent) ])
def transform(self, node: LN, capture: Capture) -> None: full_name = [] trailer: List[LN] = [] for i, n in enumerate(node.children): if n.type == token.NAME: full_name.append(n.value) elif n.type == syms.trailer: if n.children[0].type != token.DOT: trailer = node.children[i:] break full_name.append(n.children[1].value) else: trailer = node.children[i:] try: new_name = self.renames.get_new_name(".".join(full_name)) except NameRemovedError as exc: self.warn(node, str(exc)) return if new_name: new_node = Node(syms.power, AttrChain(new_name), prefix=node.prefix) for n in trailer: new_node.append_child(n) node.replace(new_node)
def RaisesOp(context, exceptionClass, indent, kws, arglist, node): with_item = Call(Name(context), [exceptionClass]) with_item.prefix = " " args = [] arglist = [a.clone() for a in arglist.children[4:]] if arglist: arglist[0].prefix = "" func = None # :fixme: this uses hardcoded parameter names, which may change if 'callableObj' in kws: func = kws['callableObj'] elif 'callable_obj' in kws: func = kws['callable_obj'] elif kws['args']: # any arguments assigned to `*args` func = kws['args'][0] else: func = None if func is None: # Context manager return Node(syms.with_stmt, [with_item]) if func.type == syms.lambdef: suite = func.children[-1].clone() else: suite = Call(func, arglist) suite.prefix = indent + (4 * " ") return Node( syms.with_stmt, [Name('with'), with_item, Name(':'), Newline(), suite])
def RaisesOp(context, exceptionClass, indent, kws, arglist): with_item = Call(Name(context), [exceptionClass]) with_item.prefix = " " args = [] arglist = [a.clone() for a in arglist.children[4:]] if arglist: arglist[0].prefix="" func = None # :fixme: this uses hardcoded parameter names, which may change if 'callableObj' in kws: func = kws['callableObj'] elif 'callable_obj' in kws: func = kws['callable_obj'] elif kws['args']: # any arguments assigned to `*args` func = kws['args'][0] else: raise NotImplementedError('with %s is not implemented' % context) if func is unittest.case._sentinel: # with self.assertRaises(SomeException): return Node(syms.with_stmt, [with_item]) suite = Call(func, arglist) suite.prefix = indent + (4 * " ") return Node(syms.with_stmt, [Name('with'), with_item, Name(':'), Newline(), 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 efter 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 transform(self, node, results): # before before = [b.clone() for b in results['before']] # tr | translate tr = results['tr'] new_tr = [(tr[0] if isinstance(tr, list) else tr).clone()] # message message = results['message'].clone() for ch in (message.pre_order() if not isinstance(message, Leaf) else [message]): if isinstance(ch, Leaf): for i in range(N_ARGS): # %1 -> {0}, ... ch.value = ch.value.replace("%{0}".format(i+1), "{%d}" % i) new_tr += [ArgList([message])] # format def format_args(): for key in sorted(results): if key.startswith('arg'): arg = results[key] if isinstance(arg, list): for a in arg: yield a.clone() else: yield arg.clone() yield Comma() yield String(' ') # Skip last Comma, String new_format = [Dot(), Name('format'), ArgList(list(format_args())[:-2])] # rest rest = [r.clone() for r in results['rest']] # new node new = Node(syms.power, before + new_tr + new_format + rest) new.prefix = node.prefix new.parent = node.parent return new
def transform(self, node, results): signal = results.get("signal").value signal = re.sub('^["\']([^(]+)(?:\(.*\))?["\']$', '\\1', signal) if 'emitter' in results: emitter = results.get("emitter").clone() emitter.prefix = node.prefix args = results.get("args").clone() args.children = args.children[2:] if args.children: args.children[0].prefix = '' res = Node( syms.power, [emitter, Name('.'), Name(signal), Name('.'), Name('emit')] + [ArgList([args])]) else: sender = results.get("sender").clone() method = results.get("method") if isinstance(method, list): method = method[0] method = method.clone() sender.prefix = node.prefix slot = results.get("slot").clone() slot.prefix = "" res = Node( syms.power, [sender, Name('.'), Name(signal), Name('.'), method] + [ArgList([slot])]) return res
def ImportAsName(name, as_name, prefix=None): new_name = Name(name) new_as = Name(u"as", prefix=u" ") new_as_name = Name(as_name, prefix=u" ") new_node = Node(syms.import_as_name, [new_name, new_as, new_as_name]) if prefix is not None: new_node.prefix = prefix return new_node
def transform(self, node, results): def process_arg(arg): if isinstance(arg, Leaf) and arg.type == token.COMMA: return elif isinstance(arg, Node) and arg.type == syms.argument: # keyword argument name, equal, value = arg.children assert name.type == token.NAME # what is the symbol for 1? assert equal.type == token.EQUAL # what is the symbol for 1? value = value.clone() value.prefix = " " kwargs[name.value] = value else: assert not kwargs, 'all positional args are assumed to come first' posargs.append(arg.clone()) method = results['method'][0].value # map (deprecated) aliases to original to avoid analysing # the decorator function method = _method_aliases.get(method, method) posargs = [] kwargs = {} # This is either a "arglist" or a single argument if results['arglist'].type == syms.arglist: for arg in results['arglist'].children: process_arg(arg) else: process_arg(results['arglist']) try: test_func = getattr(unittest.TestCase, method) except AttributeError: raise RuntimeError("Your unittest package does not support '%s'. " "consider updating the package" % method) required_args, argsdict = utils.resolve_func_args(test_func, posargs, kwargs) if method.startswith(('assertRaises', 'assertWarns')): n_stmt = _method_map[method](*required_args, indent=find_indentation(node), kws=argsdict, arglist=results['arglist']) else: n_stmt = Node(syms.assert_stmt, [Name('assert'), _method_map[method](*required_args, kws=argsdict)]) if argsdict.get('msg', None) is not None: n_stmt.children.extend((Name(','), argsdict['msg'])) n_stmt.prefix = node.prefix return n_stmt
def RaisesRegexOp(context, designator, exceptionClass, expected_regex, indent, kws, arglist): arglist = [a.clone() for a in arglist.children] del arglist[2:4] # remove pattern and comma arglist = Node(syms.arglist, arglist) with_stmt = RaisesOp(context, exceptionClass, indent, kws, arglist) with_stmt.insert_child(2, Name('as', prefix=" ")) with_stmt.insert_child(3, Name(designator, prefix=" ")) return Node(syms.suite, [with_stmt, Newline(), Name('assert re.search(pattern, %s.value)' % designator, prefix=indent) ])
def add_import(import_name, node): suite = get_parent_of_type(node, syms.suite) test_case = suite while test_case.parent.type != syms.file_input: test_case = test_case.parent file_input = test_case.parent if not does_tree_import(None, import_name, node): import_stmt = Node(syms.simple_stmt, [ Node( syms.import_name, [Name('import'), Name(import_name, prefix=' ')]), Newline(), ]) insert_import(import_stmt, test_case, file_input)
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)]
def future_import2(feature, node): """ An alternative to future_import() which might not work ... """ root = find_root(node) if does_tree_import(u"__future__", feature, node): return 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: insert_pos = idx + 1 break for thing_after in root.children[insert_pos:]: if thing_after.type == token.NEWLINE: insert_pos += 1 continue prefix = thing_after.prefix thing_after.prefix = u"" break else: prefix = u"" import_ = FromImport(u"__future__", [Leaf(token.NAME, feature, prefix=u" ")]) children = [import_, Newline()] root.insert_child(insert_pos, Node(syms.simple_stmt, children, prefix=prefix))
def Import(name_leafs): for leaf in name_leafs: # Pull the leaves out of their old tree leaf.remove() def add_commas(leafs): yield leafs[0] for a in leafs[1:]: yield Comma() yield a children = [Leaf(token.NAME, u'import'), Node(syms.dotted_as_names, list(add_commas(name_leafs)))] imp = Node(syms.import_name, children) return imp
def transform(self, node, results): FIXME name, val, trc = ( results.get(u"name"), results.get(u"val"), results.get(u"trc"), ) chain = results.get(u"chain") if chain is not None: self.warning( node, u"explicit exception chaining is not supported in Python 2") chain.prev_sibling.remove() chain.remove() if trc is not None: val = val[0] if val else Leaf(token.NAME, u"None") val.prefix = trc.prefix = u" " kids = [ Leaf(token.NAME, u"raise"), name.clone(), Comma(), val.clone(), Comma(), trc.clone(), ] raise_stmt = Node(syms.raise_stmt, kids) node.replace(raise_stmt)
def transform(self, node, results): meta_results = has_metaclass(node) if not meta_results: return for meta in meta_results: meta.remove() target = Leaf(token.NAME, u"__metaclass__") equal = Leaf(token.EQUAL, u"=", prefix=u" ") # meta is the last item in what was returned by has_metaclass(): name name = meta name.prefix = u" " stmt_node = Node(syms.atom, [target, equal, name]) suitify(node) for item in node.children: if item.type == syms.suite: for stmt in item.children: if stmt.type == token.INDENT: # Insert, in reverse order, the statement, a newline, # and an indent right after the first indented line loc = item.children.index(stmt) + 1 # Keep consistent indentation form ident = Leaf(token.INDENT, stmt.value) item.insert_child(loc, ident) item.insert_child(loc, Newline()) item.insert_child(loc, stmt_node) break
def match(self, node): u""" Since the tree needs to be fixed once and only once if and only if it matches, we can start discarding matches after the first. """ if node.type == self.syms.term: matched = False skip = False children = [] for child in node.children: if skip: skip = False continue if match_division(child) and not is_floaty(child): matched = True # Strip any leading space for the first number: children[0].prefix = u'' children = [ wrap_in_fn_call( "old_div", children + [Comma(), child.next_sibling.clone()], prefix=node.prefix) ] skip = True else: children.append(child.clone()) if matched: return Node(node.type, children, fixers_applied=node.fixers_applied) return False
def RaisesOp(context, exceptionClass, indent, kws, arglist, node): exceptionClass.prefix = "" args = [exceptionClass] # Add match keyword arg to with statement if an expected regex was provided. # In py27 the keyword is `expected_regexp`, in py3 is `expected_regex` if 'expected_regex' in kws or 'expected_regexp' in kws: expected_regex = kws.get('expected_regex', kws.get('expected_regexp')).clone() expected_regex.prefix = '' args.append(String(', ')) args.append(KeywordArg(Name('match'), expected_regex)) with_item = Call(Name(context), args) with_item.prefix = " " args = [] arglist = [a.clone() for a in arglist.children[4:]] if arglist: arglist[0].prefix = "" func = None # :fixme: this uses hardcoded parameter names, which may change if 'callableObj' in kws: func = kws['callableObj'] elif 'callable_obj' in kws: func = kws['callable_obj'] elif kws['args']: # any arguments assigned to `*args` func = kws['args'][0] else: func = None if func is None: # Context manager return Node(syms.with_stmt, [with_item]) if func.type == syms.lambdef: suite = func.children[-1].clone() else: # TODO: Newlines within arguments are not handled yet. # If argment prefix contains a newline, all whitespace around this # ought to be replaced by indent plus 4+1+len(func) spaces. suite = Call(func, arglist) suite.prefix = indent + (4 * " ") return Node( syms.with_stmt, [Name('with'), with_item, Name(':'), Newline(), suite])
def ImportAsName(name: str, nick: Optional[str], prefix: Optional[str] = None) -> Node: if not nick: return Name(name, prefix=prefix) return Node( syms.import_as_name, [Name(name), Name("as", prefix=" "), Name(nick, prefix=" ")], prefix=prefix, )
def transform(self, node, result): if len(node.children) == 3 and node.children[-1].type == token.NUMBER: userdata_id = int(node.children[-1].value) replacement = self.subfun(userdata_id) if replacement: new = Node(python_symbols.subscriptlist, [Leaf(token.NAME, replacement)]) return new return None
def CompOp(op, left, right, kws): op = Name(op, prefix=" ") left = parenthesize_expression(left) right = parenthesize_expression(right) left.prefix = "" if '\n' not in right.prefix: right.prefix = " " return Node(syms.comparison, (left, op, right), prefix=" ")
def UnaryOp(prefix, postfix, value, kws): kids = [] if prefix: kids.append(Name(prefix, prefix=" ")) value.prefix = " " kids.append(value) if postfix: kids.append(Name(postfix, prefix=" ")) return Node(syms.test, kids, prefix=" ")
def FromImport( package: str, imports: List[Tuple[str, Optional[str]]], prefix: Optional[str] ) -> Node: children = [] for name, nick in imports: children.append(ImportAsName(name, nick, prefix=" ")) children.append(Comma()) children.pop() import_as_names = Node(syms.import_as_names, children) return Node( syms.import_from, [ Name("from", prefix=prefix), DottedName(package, prefix=" "), Name("import", prefix=" "), import_as_names, ], )
def RaisesRegexOp(context, designator, exceptionClass, expected_regex, indent, kws, arglist, node): arglist = [a.clone() for a in arglist.children] pattern = arglist[2] del arglist[2:4] # remove pattern and comma arglist = Node(syms.arglist, arglist) with_stmt = RaisesOp(context, exceptionClass, indent, kws, arglist, node) # if this is already part of a with statement we need to insert re.search # after the last leaf with content if node.parent.type == syms.with_stmt: parent_with = node.parent for leaf in reversed(list(parent_with.leaves())): if leaf.value.strip(): break i = leaf.parent.children.index(leaf) return with_stmt else: return Node(syms.suite, [with_stmt])
def DottedName(full_name: str, prefix: Optional[str]) -> Node: names = [name.strip() for name in full_name.split(".")] dotted_name = [] for name in names: dotted_name.append(Name(name)) dotted_name.append(Dot()) dotted_name.pop() return Node(syms.dotted_name, dotted_name, prefix=prefix)
def DottedName(name, prefix=""): split = name.rsplit('.') if len(split) > 1: # Reconstruct the dotted name as a list of leaves leftmost_name = Leaf(token.NAME, split[0]) children = [leftmost_name] for entry in split[1:]: next_name = [Leaf(token.DOT, '.'), Leaf(token.NAME, entry)] children.extend(next_name) return Node(syms.dotted_name, children, prefix=prefix) return Leaf(token.NAME, name, prefix=prefix)
def UnaryOp(prefix, postfix, value, kws): if prefix or postfix: value = parenthesize_expression(value) kids = [] if prefix: kids.append(Name(prefix, prefix=" ")) value.prefix = " " kids.append(value) if postfix: kids.append(Name(postfix, prefix=" ")) return Node(syms.test, kids, prefix=" ")
def match(self, node): u""" Since the tree needs to be fixed once and only once if and only if it matches, we can start discarding matches after the first. """ if node.type == self.syms.term: matched = False skip = False children = [] for child in node.children: if skip: skip = False continue if match_division(child) and not is_floaty(child): matched = True # Strip any leading space for the first number: children[0].prefix = u'' children = [ wrap_in_fn_call( "old_div", children + [Comma(), child.next_sibling.clone()], prefix=node.prefix) ] skip = True else: children.append(child.clone()) if matched: # In Python 2.6, `Node` does not have the fixers_applied attribute # https://github.com/python/cpython/blob/8493c0cd66cfc181ac1517268a74f077e9998701/Lib/lib2to3/pytree.py#L235 if hasattr(Node, "fixers_applied"): return Node(node.type, children, fixers_applied=node.fixers_applied) else: return Node(node.type, children) return False
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 efter 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 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 # !%@#! oneliners 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 transform(self, node, results): if not has_metaclass(node): return # pragma: no cover 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, u")")) node.insert_child(2, arglist) node.insert_child(2, Leaf(token.LPAR, u"(")) else: raise ValueError("Unexpected class definition") # pragma: no cover touch_import(None, u"six", node) metaclass = last_metaclass.children[0].children[2].clone() metaclass.prefix = u"" arguments = [metaclass] if arglist.children: bases = arglist.clone() bases.prefix = u" " arguments.extend([Comma(), bases]) arglist.replace(Call(Name(u"six.with_metaclass", prefix=arglist.prefix), arguments)) fixup_indent(suite) # check for empty suite if not suite.children: # one-liner that was just __metaclass__ suite.remove() pass_leaf = Leaf(text_type, u"pass") pass_leaf.prefix = last_metaclass.prefix node.append_child(pass_leaf) node.append_child(Leaf(token.NEWLINE, u"\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, u"pass") suite.insert_child(-1, pass_leaf) suite.insert_child(-1, Leaf(token.NEWLINE, u"\n"))
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, u')')) node.insert_child(2, arglist) node.insert_child(2, Leaf(token.LPAR, u'(')) else: raise ValueError("Unexpected class definition") touch_import(None, u'six', node) metaclass = last_metaclass.children[0].children[2].clone() metaclass.prefix = u'' arguments = [metaclass] if arglist.children: if len(arglist.children) == 1: base = arglist.children[0].clone() base.prefix = u' ' else: # Unfortunately six.with_metaclass() only allows one base # class, so we have to dynamically generate a base class if # there is more than one. bases = parenthesize(arglist.clone()) bases.prefix = u' ' base = Call(Name('type'), [ String("'NewBase'"), Comma(), bases, Comma(), Node( syms.atom, [Leaf(token.LBRACE, u'{'), Leaf(token.RBRACE, u'}')], prefix=u' ' ) ], prefix=u' ') arguments.extend([Comma(), base]) arglist.replace(Call( Name(u'six.with_metaclass', prefix=arglist.prefix), arguments )) fixup_indent(suite) # check for empty suite if not suite.children: # one-liner that was just __metaclass_ suite.remove() pass_leaf = Leaf(text_type, u'pass') pass_leaf.prefix = orig_meta_prefix node.append_child(pass_leaf) node.append_child(Leaf(token.NEWLINE, u'\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, u'pass') suite.insert_child(-1, pass_leaf) suite.insert_child(-1, Leaf(token.NEWLINE, u'\n'))
def transform(self, node, results): def process_arg(arg): if isinstance(arg, Leaf) and arg.type == token.COMMA: return elif isinstance(arg, Node) and arg.type == syms.argument: # keyword argument name, equal, value = arg.children assert name.type == token.NAME # what is the symbol for 1? assert equal.type == token.EQUAL # what is the symbol for 1? value = value.clone() kwargs[name.value] = value if '\n' in arg.prefix: value.prefix = arg.prefix else: value.prefix = arg.prefix.strip() + " " else: assert not kwargs, 'all positional args are assumed to come first' posargs.append(arg.clone()) method = results['method'][0].value # map (deprecated) aliases to original to avoid analysing # the decorator function method = _method_aliases.get(method, method) posargs = [] kwargs = {} # This is either a "arglist" or a single argument if results['arglist'].type == syms.arglist: for arg in results['arglist'].children: process_arg(arg) else: process_arg(results['arglist']) try: test_func = getattr(unittest.TestCase, method) except AttributeError: raise RuntimeError("Your unittest package does not support '%s'. " "consider updating the package" % method) required_args, argsdict = utils.resolve_func_args(test_func, posargs, kwargs) if method.startswith(('assertRaises', 'assertWarns')): n_stmt = _method_map[method](*required_args, indent=find_indentation(node), kws=argsdict, arglist=results['arglist'], node=node) else: n_stmt = Node(syms.assert_stmt, [Name('assert'), _method_map[method](*required_args, kws=argsdict)]) if argsdict.get('msg', None) is not None: n_stmt.children.extend((Name(','), argsdict['msg'])) def fix_line_wrapping(x): for c in x.children: # no need to worry about wrapping of "[", "{" and "(" if c.type in [token.LSQB, token.LBRACE, token.LPAR]: break if c.prefix.startswith('\n'): c.prefix = c.prefix.replace('\n', ' \\\n') fix_line_wrapping(c) fix_line_wrapping(n_stmt) # the prefix should be set only after fixing line wrapping because it can contain a '\n' n_stmt.prefix = node.prefix # add necessary imports if 'Raises' in method or 'Warns' in method: add_import('pytest', node) if 'Regex' in method: add_import('re', node) return n_stmt
def transform(self, node, results): if 'name' in results: # This matched an import statement. Fix that up: name = results["name"] name.replace(Name(self.DECORATOR_NAME, prefix=name.prefix)) if 'rename' in results: # The import statement use import as self._add_pattern("'%s'" % results['rename'].value) if 'interface_rename' in results: self._add_pattern("'%s' trailer< '.' '%s' > " % ( results['interface_rename'].value, self.FUNCTION_NAME)) if 'statement' in results: # This matched a class that has an <FUNCTION_NAME>(IFoo) statement. # We must convert that statement to a class decorator # and put it before the class definition. statement = results['statement'] if not isinstance(statement, list): statement = [statement] # Make a copy for insertion before the class: statement = [x.clone() for x in statement] # Get rid of leading whitespace: statement[0].prefix = '' # Rename function to decorator: if statement[-1].children: func = statement[-1].children[-1] else: func = statement[-1] if func.value == self.FUNCTION_NAME: func.value = self.DECORATOR_NAME interface = results['interface'] if not isinstance(interface, list): interface = [interface] interface = [x.clone() for x in interface] # Create the decorator: decorator = Node(syms.decorator, [Leaf(50, '@'),] + statement + [Leaf(7, '(')] + interface + [Leaf(8, ')')]) # Take the current class constructor prefix, and stick it into # the decorator, to set the decorators indentation. nodeprefix = node.prefix decorator.prefix = nodeprefix # Preserve only the indent: if '\n' in nodeprefix: nodeprefix = nodeprefix[nodeprefix.rfind('\n')+1:] # Then find the last line of the previous node and use that as # indentation, and add that to the class constructors prefix. previous = node.prev_sibling if previous is None: prefix = '' else: prefix = str(previous) if '\n' in prefix: prefix = prefix[prefix.rfind('\n')+1:] prefix = prefix + nodeprefix if not prefix or prefix[0] != '\n': prefix = '\n' + prefix node.prefix = prefix new_node = Node(syms.decorated, [decorator, node.clone()]) # Look for the actual function calls in the new node and remove it. for node in new_node.post_order(): for pattern in self.function_patterns: if pattern.match(node, results): parent = node.parent previous = node.prev_sibling # Remove the node node.remove() if not str(parent).strip(): # This is an empty class. Stick in a pass if (len(parent.children) < 3 or ' ' in parent.children[2].value): # This class had no body whitespace. parent.insert_child(2, Leaf(0, ' pass')) else: # This class had body whitespace already. parent.insert_child(2, Leaf(0, 'pass')) parent.insert_child(3, Leaf(0, '\n')) elif (prefix and isinstance(previous, Leaf) and '\n' not in previous.value and previous.value.strip() == ''): # This is just whitespace, remove it: previous.remove() return new_node