def norm_arglist(trailer_node): """ normilize argument list node, e.g. Node(trailer, [Leaf(7, '('), Leaf(2, '3'), Leaf(8, ')')]) to Node(trailer, [Leaf(7, '('), Node(arglist, [Node(argument, [Leaf(2, '1')])]), Leaf(8, ')')]) """ if trailer_node.type != python_symbols.trailer or len(trailer_node.children) < 2 or len(trailer_node.children) > 3: logger.warning("node type is not trailer, or len(children) < 2 or len(children) > 3") return second_node = trailer_node.children[1] # add arglist node if needed if second_node.type != python_symbols.arglist: if second_node.type == token.RPAR: arglist_node = Node(python_symbols.arglist, []) trailer_node.insert_child(1, arglist_node) else: _second_node = second_node.clone() arglist_node = Node(python_symbols.arglist, [_second_node]) second_node.remove() trailer_node.insert_child(1, arglist_node) else: arglist_node = second_node # add argument node if needed for i in range(len(arglist_node.children)): node = arglist_node.children[i] # skip "," node if node.type == token.COMMA: continue if node.type != python_symbols.argument: _node = node.clone() arg_node = Node(python_symbols.argument, [_node]) node.replace(arg_node)
def finish_tree(self, tree: Node, filename: str) -> None: # TODO: what about name clash between dotted-path imports and # introspected locals? imports_dict = get_import_lines(self.threadlocals.strategy_to_names) insert_pos = _find_import_pos(tree) def _sort_key(val): left, right = val left = left or "" return (left, right) sorted_tuples = sorted( imports_dict.items(), key=_sort_key, reverse=True, # because we insert last nodes first ) for i, (left, right) in enumerate(sorted_tuples): if left: import_node = _make_from_import_node( left=left, right=sorted(right), trailing_nl=i == 0 and insert_pos == 0, ) tree.insert_child(insert_pos, import_node) else: for j, name in enumerate(right): import_node = _make_bare_import_node( name=name, trailing_nl=i == 0 and j == 0 and insert_pos == 0, ) tree.insert_child(insert_pos, import_node)
def replace_decorators(node, capture, filename): """ Replaces usage of ``@tornado.<etc>`` with ``@salt.ext.tornado.<etc>`` """ indent = find_indentation(node) decorator = _get_decorator(node) decorator.remove() decorated = Node( SYMBOL.decorated, [ Node( SYMBOL.decorator, [ Leaf(TOKEN.AT, "@"), Name("salt.ext.{}".format(get_decorator_name(decorator))), Leaf(TOKEN.NEWLINE, "\n"), ], ) ], prefix=decorator.prefix, ) node.replace(decorated) decorated.append_child(node) if indent is not None: node.prefix = indent else: node.prefix = ""
def _create_simple_stmt_node_and_insert_behind(code, node): if node is None or node.type != python_symbols.simple_stmt: return simple_stmt_node = Node(python_symbols.simple_stmt, [utils.newline_node(node)]) _node = utils.code_repr(code).children[0].children[0] _node.parent = None simple_stmt_node.insert_child(0, _node) simple_stmt_node.prefix = utils.get_indent(node) utils.insert_node_behind(node, simple_stmt_node)
def add_future(node, symbol): root = fixer_util.find_root(node) for idx, node in enumerate(root.children): if (node.type == syms.simple_stmt and len(node.children) > 0 and node.children[0].type == token.STRING): # skip over docstring continue names = _check_future_import(node) if not names: # not a future statement; need to insert before this break if symbol in names: # already imported return import_ = fixer_util.FromImport("__future__", [Leaf(token.NAME, symbol, prefix=" ")]) # Place after any comments or whitespace. (copyright, shebang etc.) import_.prefix = node.prefix node.prefix = "" children = [import_, fixer_util.Newline()] root.insert_child(idx, Node(syms.simple_stmt, children))
def build_new_name_node(*, old_node, attach: bool, new_name: str, old_name: str = None): # build new node from new_name if '.' in new_name: children = [] for part in dotted_parts(new_name): if part == '.': children.append(Dot()) else: children.append(Name(part)) else: children = [Name(new_name)] # attach to the new node subimports from the old module if attach and type(old_node) is Node: original_name_size = len(dotted_parts(old_name)) for part in old_node.children[original_name_size:]: if part.value == '.': children.append(Dot()) else: children.append(Name(part.value)) return Node( type=syms.dotted_name, children=children, prefix=old_node.prefix, )
def rename_transform(node: LN, capture: Capture, filename: Filename) -> None: log.debug(f"{filename} [{list(capture)}]: {node}") # If two keys reference the same underlying object, do not modify it twice visited: List[LN] = [] for _key, value in capture.items(): log.debug(f"{_key}: {value}") if value in visited: continue visited.append(value) if isinstance(value, Leaf) and value.type == TOKEN.NAME: if value.value == old_name and value.parent is not None: value.replace(Name(new_name, prefix=value.prefix)) break elif isinstance(value, Node): if type_repr(value.type) == "dotted_name": dp_old = dotted_parts(old_name) dp_new = dotted_parts(new_name) parts = zip(dp_old, dp_new, value.children) for old, new, leaf in parts: if old != leaf.value: break if old != new: leaf.replace(Name(new, prefix=leaf.prefix)) if len(dp_new) < len(dp_old): # if new path is shorter, remove excess children del value.children[len(dp_new) : len(dp_old)] elif len(dp_new) > len(dp_old): # if new path is longer, add new children children = [ Name(new) for new in dp_new[len(dp_old) : len(dp_new)] ] value.children[len(dp_old) : len(dp_old)] = children elif type_repr(value.type) == "power": # We don't actually need the '.' so just skip it dp_old = old_name.split(".") dp_new = new_name.split(".") for old, new, leaf in zip(dp_old, dp_new, value.children): if isinstance(leaf, Node): name_leaf = leaf.children[1] else: name_leaf = leaf if old != name_leaf.value: break name_leaf.replace(Name(new, prefix=name_leaf.prefix)) if len(dp_new) < len(dp_old): # if new path is shorter, remove excess children del value.children[len(dp_new) : len(dp_old)] elif len(dp_new) > len(dp_old): # if new path is longer, add new trailers in the middle for i in range(len(dp_old), len(dp_new)): value.insert_child( i, Node(SYMBOL.trailer, [Dot(), Name(dp_new[i])]) )
def _make_from_import_node(left: str, right: Sequence[str], trailing_nl: bool = False) -> Node: assert right # non-empty name_leaves = [Leaf(token.NAME, right[0], prefix=" ")] name_leaves.extend( Leaf(token.NAME, name, prefix=", ") for name in right[1:]) children = [ Leaf(token.NAME, "from"), Leaf(token.NAME, left, prefix=" "), Leaf(token.NAME, "import", prefix=" "), Node(syms.import_as_names, name_leaves), Newline(), ] if trailing_nl: children.append(Newline()) return Node(syms.import_from, children)
def process_format_format( node: LN, capture: Capture, filename: Filename # noqa: U100 ) -> Optional[LN]: formatstring = capture["formatstr"] if formatstring.type != token.STRING: print( "Not formatting {filename}:{node.get_lineno()} because indirect format" ) return None num_specifiers = len(RE_FORMAT_SPECIFIER.findall(formatstring.value)) understood = RE_UNDERSTOOD_FORMAT.findall(formatstring.value) if len(understood) != num_specifiers: print( f"Not formatting {filename}:{node.get_lineno()} because complex format specifiers:\n {formatstring.value.strip()}" ) return None # Basic {} formatstring.value = re.sub(r"{}", "%s", formatstring.value) # {:.3f} and friends formatstring.value = re.sub(r"(?<!{){:(\d*\.\d+f)}", r"%\1", formatstring.value) # {:>12} formatstring.value = re.sub(r"(?<!{){:>(\d+)}", r"%\1s", formatstring.value) # if RE_NONPLAIN_FORMAT.search(formatstring.value): # return None # print_node(node, capture=capture) # breakpoint() if isinstance(capture["arglist"], Leaf): arguments = [capture["arglist"]] else: arguments = list(capture["arglist"].children) # Clear the parents of moved nodes for child in arguments: child.remove() formatstring.remove() # Rewrite the format-string specifier formatstring.value = formatstring.value.replace("{}", "%s") # breakpoint() # Build the arglist capture["formatblock"].replace( Node(python_symbols.arglist, [formatstring, Comma()] + arguments)) # RE_NONPLAIN_FORMAT # print_node(node, capture=capture) # breakpoint() return None
def ListNode(*args): parts = [] for arg in args: if parts: parts.append(Space()) arg = arg.clone() arg.prefix = arg.prefix.lstrip() parts.append(arg) parts.append(Comma()) parts.pop() return Node(SYMBOL.atom, [LBrace(), *parts, RBrace()])
def process_root(node: LN, capture: Capture, filename: Filename) -> Optional[LN]: print(filename) # Find any imports close to the root level imports = [ x for x in get_children( node, python_symbols.import_from, recursive=True, recurse_depth=1) if x.children[1].type == token.NAME and x.children[1].value == "__future__" ] if len(imports) <= 1 and FLOAT_MODE: # Do nothing if one or no imports and only in float-mode return elif len(imports) == 0: # We're in update mode - add missing __future__ imports import_insert_point = find_future_import_insert_point(node) insert = driver.parse_string( "from __future__ import absolute_import, division, print_function\n" ) node.children.insert(import_insert_point, insert) return # Extract the list of __future__ imports from all of the import statements future_names = set() for imp in imports: if imp.children[3].type == token.NAME: future_names.add(imp.children[3].value) elif imp.children[3].type == python_symbols.import_as_names: future_names.update( {x.value for x in get_children(imp.children[3], token.NAME)}) # If we're not purely floating, update the list of names if not FLOAT_MODE: # If present, make all the basics present if future_names: future_names |= {"absolute_import", "division", "print_function"} # Remove anything already mandatory future_names -= {"generators", "nested_scopes", "with_statement"} # Update the first import instance with all the names if len(future_names) == 1: imports[0].children[3] = Name(list(future_names)[0]) else: # Make the first one a multi-import commad = join_comma( [Name(x, prefix=" ") for x in sorted(future_names)]) names = Node(python_symbols.import_as_names, commad) imports[0].children[3] = names # Remove the other imports for imp in imports[1:]: assert imp.parent.type == python_symbols.simple_stmt imp.parent.parent.children.remove(imp.parent)
def TupleNode(*args): parts = [] for arg in args: if parts: parts.append(Space()) arg = arg.clone() arg.prefix = arg.prefix.lstrip() parts.append(arg) parts.append(Comma()) if len(parts) != 2: parts.pop() return Node(SYMBOL.atom, [LParen(), *parts, RParen()])
def process_percent_format( node: LN, capture: Capture, filename: Filename # noqa: U100 ) -> Optional[LN]: # if capture["formatstr"] has more than one format point, don't # expand it for now # if capture["formatstr"]: # print_node(node, capture=capture) specifiers = None if capture["formatstr"].type == token.STRING: if RE_MAPPINGKEY.search(capture["formatstr"].value): print( f"Not formatting {filename}:{node.get_lineno()} as appears to have mapping key" ) return None specifiers = RE_SPECIFIERS.findall(capture["formatstr"].value) try: # Access precise chain of lookups for the inline-tuple case if capture["vars"].children[0].value == "(": inner = capture["vars"].children[1] if isinstance(inner, Node): format_args = inner.children elif isinstance(inner, Leaf): format_args = [inner] else: raise RuntimeError("Unknown type") else: # It's not this specific case, treat it as one item raise IndexError except IndexError: # Not a tuple, if we have more than one specifier then we can't # reliably rewrite this. if specifiers and len(specifiers) > 1: print( f"Not formatting {filename}:{node.get_lineno()} because unsafe rewriting indirect tuple" ) return None # We aren't something with a sub-tuple, assume single-argument format_args = [capture["vars"]] # Don't rewrite if # Prepare these node for transplant for child in format_args: child.parent = None capture["formatstr"].parent = None capture["call"].children[1] = Node( python_symbols.arglist, [capture["formatstr"], Comma()] + format_args) return None
def _make_bare_import_node(name: str, trailing_nl: bool = False) -> Node: assert name # non-empty children = [ Leaf(token.NAME, "import"), Leaf(token.NAME, name, prefix=" "), Newline(), ] if trailing_nl: children.append(Newline()) return Node( syms.import_name, children, )
def convert_regex_to_path_modifier(node: Node, capture: Capture, filename: Filename) -> None: # Replace the import if is_import(node): name_leafs = [ Name("path", prefix=" "), Comma(), Name("re_path", prefix=" "), ] node.replace([FromImport("django.url", name_leafs=name_leafs)]) # And function calls from url to path, re_path if capture and "function_arguments" in capture: function_node: Node = next(node.leaves()) args = capture.get("function_arguments") regex_leaf: Leaf = next(args[0].leaves()) converted = update_regex_to_path(regex_leaf.value) if converted == regex_leaf.value: function_node.replace(Name("re_path", prefix=function_node.prefix)) else: function_node.replace(Name("path", prefix=function_node.prefix)) regex_leaf.value = update_regex_to_path(regex_leaf.value)
def modify_dict_literal(node: LN, capture: Capture, filename: Filename) -> Optional[LN]: toks = iter(capture.get("body")) items = [] prefix = "" while True: try: tok = next(toks) if tok.type == TOKEN.DOUBLESTAR: body = next(toks).clone() body.prefix = prefix + tok.prefix + body.prefix items.append(body) else: colon = next(toks) value = next(toks).clone() value.prefix = colon.prefix + value.prefix if items and isinstance(items[-1], list): items[-1].append(TupleNode(tok, value)) else: items.append([TupleNode(tok, value)]) comma = next(toks) prefix = comma.prefix except StopIteration: break listitems = [] for item in items: if listitems: listitems.extend([Space(), Plus(), Space()]) if isinstance(item, list): listitems.append(ListNode(*item)) else: call = Node(SYMBOL.test, [*Attr(item, Name("items")), ArgList([])]) listitems.append(call) args = listitems if len(listitems) > 1: args = [Node(SYMBOL.arith_expr, args)] args.append(String(node.children[-1].prefix)) return Call(Name("dict"), args, prefix=node.prefix)
def add_marker(node, capture, filename): """Add ``MARKER`` to the functions.""" indent = _get_indent(node) decorated = Node( SYMBOL.decorated, [ Node( SYMBOL.decorator, [Leaf(TOKEN.AT, "@"), Name(MARKER), Leaf(TOKEN.NEWLINE, "\n")], ) ], prefix=node.prefix, ) node.replace(decorated) decorated.append_child(node) if indent is not None: node.prefix = indent else: node.prefix = ""
def __call__(self, node: LN, capture: Capture, filename: Filename) -> None: old_node = capture['module_name'] new_node = Node( type=syms.dotted_as_name, children=[ build_new_name_node( old_node=old_node, new_name=self.new_name, attach=False, ), Name('as', prefix=' '), old_node.clone(), ], ) old_node.replace(new_node)
def get_trailing_text_node(node: Node) -> Leaf: """ Find the dedent subnode containing any trailing text. If there are none, return the first. """ # trails = [] first = None for tail in reversed(list(node.leaves())): if not tail.type == token.DEDENT: break if first is None: first = tail if tail.prefix: return tail return first
def _modify_power(self, node): prefix = node.children[0].prefix # remove old prefix parts = dotted_parts(self.old_name) for _ in range((len(parts) + 1) // 2): node.children.pop(0) # add new prefix head = Name(self.new_name.split('.', maxsplit=1)[0], prefix=prefix) children = [] for part in dotted_parts(self.new_name)[2::2]: children.append( Node( type=syms.trailer, children=[Dot(), Name(part)], )) node.children = [head] + children + node.children
def assertStr(self, node, string): if isinstance(node, (tuple, list)): node = Node(fixer_util.syms.simple_stmt, node) self.assertEqual(str(node), string)
def sort_imports(root, capture, filename): statement_nodes = get_top_import_nodes(root) module_imports = [] # * Go through all top-of-file imports. # * Index them by module name. # * Do inline sorting of imported names (`import b, c, a` --> `import a, b, c`) for i, stmt in enumerate(statement_nodes): first_name = None imp = stmt.children[0] if imp.type == syms.import_name: module_node = imp.children[1] if module_node.type == TOKEN.NAME: # 'import os' module = module_node.value elif module_node.type == syms.dotted_name: # 'import x.y' module = str(module_node) elif module_node.type == syms.dotted_as_name: # 'import os as OS' module = module_node.children[0].value elif module_node.type == syms.dotted_as_names: # 'import os, io' module = _sort_imported_names(imp.children[1]) else: raise ValueError(f"Unknown import format: {imp}") elif imp.type == syms.import_from: module_node = imp.children[1] if module_node.type == syms.dotted_name: # 'from x.y import z' module = ''.join(c.value for c in module_node.children) else: module = module_node.value names = [n for n in imp.children[3:] if n.type != TOKEN.LPAR] if names[0].type == TOKEN.NAME: # 'from x import y' first_name = names[0].value elif names[0].type == syms.import_as_name: # 'from x import y as z' first_name = names[0].children[0].value elif names[0].type == syms.import_as_names: # 'from x import y, z' # 'from x import y as a, z as b' first_name = _sort_imported_names(names[0]) else: raise ValueError(f"Unknown import format: {imp}") else: # top-of-module docstring. float to top. module = '' module = module.strip() root_module_name = module.split('.')[0] # do 'from ...' imports after 'import ...' imports. from_ = 1 if first_name is not None else 0 if root_module_name == '': # module docstring group = Groups.DOCSTRING elif root_module_name == '__future__': # special case; must come first group = Groups.FUTURE elif root_module_name in STDLIB_MODULES: # stdlib modules group = Groups.STDLIB elif root_module_name not in cfg['first_party_modules']: # third party modules group = Groups.THIRD_PARTY else: # first party modules group = Groups.FIRST_PARTY # note: the `i` here is for a weird edge case where you try to sort # two of the same exact import. # turns out, Node instances aren't comparable, so we get an error if # the sort ever has to compare them. # So we insert a unique integer before them, thus preventing us ever having to # compare the node instances. module_imports.append((group, from_, module.lower(), first_name and first_name.lower(), i, stmt)) # Now sort the various lines we've encountered. module_imports.sort() # Now, clear out all the statements from the parse tree for n in statement_nodes: n.remove() # Then repopulate the tree with the sorted nodes, cleaning up whitespace as we go. last_group = 0 last_root_module_name = None for i, (group, from_, module_lower, first_name_lower, _, stmt_node) in enumerate(module_imports): assert len(stmt_node.children) == 2 root_module_name = module_lower.split('.')[0] import_node = stmt_node.children[0] newline_node = stmt_node.children[1] prefix = strip_prefix(import_node.prefix) if i != 0: if last_group != group: # add a space between groups. prefix = f'\n{prefix}' elif (last_root_module_name != root_module_name and group == Groups.FIRST_PARTY): # also add a space between different first-party projects. prefix = f'\n{prefix}' new_stmt = Node( syms.simple_stmt, [import_node.clone(), newline_node.clone()]) new_stmt.prefix = prefix root.insert_child(i, new_stmt) last_group = group last_root_module_name = root_module_name
def makeStatement(): return Node( python_symbols.stmt, [] )
def makeImport(): return Node( python_symbols.import_stmt, [] )
def encapsulate_transform( node: LN, capture: Capture, filename: Filename ) -> None: if "attr_assignment" in capture: leaf = capture["attr_name"] leaf.replace(Name(new_name, prefix=leaf.prefix)) if make_property: # TODO: capture and use type annotation from original assignment class_node = get_class(node) suite = find_first(class_node, SYMBOL.suite) assert isinstance(suite, Node) indent_node = find_first(suite, TOKEN.INDENT) assert isinstance(indent_node, Leaf) indent = indent_node.value getter = Node( SYMBOL.decorated, [ Node( SYMBOL.decorator, [ Leaf(TOKEN.AT, "@"), Name("property"), Leaf(TOKEN.NEWLINE, "\n"), ], ), Node( SYMBOL.funcdef, [ Name("def", indent), Name(old_name, prefix=" "), Node( SYMBOL.parameters, [LParen(), Name("self"), RParen()], ), Leaf(TOKEN.COLON, ":"), Node( SYMBOL.suite, [ Newline(), Leaf(TOKEN.INDENT, indent.value + " "), Node( SYMBOL.simple_stmt, [ Node( SYMBOL.return_stmt, [ Name("return"), Node( SYMBOL.power, Attr( Name("self"), Name(new_name), ), prefix=" ", ), ], ), Newline(), ], ), Leaf(TOKEN.DEDENT, "\n" + indent), ], ), ], prefix=indent, ), ], ) setter = Node( SYMBOL.decorated, [ Node( SYMBOL.decorator, [ Leaf(TOKEN.AT, "@"), Node( SYMBOL.dotted_name, [Name(old_name), Dot(), Name("setter")], ), Leaf(TOKEN.NEWLINE, "\n"), ], ), Node( SYMBOL.funcdef, [ Name("def", indent), Name(old_name, prefix=" "), Node( SYMBOL.parameters, [ LParen(), Node( SYMBOL.typedargslist, [ Name("self"), Comma(), Name("value", prefix=" "), ], ), RParen(), ], ), Leaf(TOKEN.COLON, ":"), Node( SYMBOL.suite, [ Newline(), Leaf(TOKEN.INDENT, indent + " "), Node( SYMBOL.simple_stmt, [ Node( SYMBOL.expr_stmt, [ Node( SYMBOL.power, Attr( Name("self"), Name(new_name), ), ), Leaf( TOKEN.EQUAL, "=", prefix=" ", ), Name("value", prefix=" "), ], ), Newline(), ], ), Leaf(TOKEN.DEDENT, "\n" + indent), ], ), ], prefix=indent, ), ], ) suite.insert_child(-1, getter) suite.insert_child(-1, setter) prev = find_previous(getter, TOKEN.DEDENT, recursive=True) curr = find_last(setter, TOKEN.DEDENT, recursive=True) assert isinstance(prev, Leaf) and isinstance(curr, Leaf) prev.prefix, curr.prefix = curr.prefix, prev.prefix prev.value, curr.value = curr.value, prev.value
def makeTrailer(): return Node( python_symbols.trailer, [] )
def _remove_with_dygraph_guard(node: LN, capture: Capture, filename: Filename): # index of with_node, with_node will be replaced with simple_stmt node with_node = capture['with'] parent = with_node.parent idx = None for i, child in enumerate(parent.children): if child is with_node: idx = i break # create simple_stmt node for "paddle.disable_static" arg_list_nodes = capture['arg_list'] simple_stmt_disable_static = Node(python_symbols.simple_stmt, [utils.newline_node(node)]) _node = utils.code_repr('paddle.disable_static' + str(arg_list_nodes)).children[0].children[0] _node.parent = None simple_stmt_disable_static.insert_child(0, _node) simple_stmt_disable_static.prefix = with_node.prefix # create simple_stmt node for "paddle.enable_static" simple_stmt_enable_static = Node(python_symbols.simple_stmt, [utils.newline_node(node)]) simple_stmt_enable_static _node = utils.code_repr( 'paddle.enable_static()').children[0].children[0] _node.parent = None simple_stmt_enable_static.insert_child(0, _node) simple_stmt_enable_static.prefix = utils.get_indent(with_node) suite_node = capture['suite'] # remove first newline for node in suite_node.children: if not isinstance(node, Leaf): continue if node.type == token.NEWLINE: node.remove() break # remove first indent node, and add indent prefix to sibling node. indent = None for node in suite_node.children: if not isinstance(node, Leaf): continue if node.type == token.INDENT: indent = node.value if node.next_sibling is not None: node.next_sibling.prefix = node.prefix + indent node.remove() break # transfer post leading dedent node prefix to sibling of with node leaves = [leaf for leaf in suite_node.leaves()] # visit all leaves in reversed order last_dedent_leaf_idx = len(leaves) for leaf in leaves[::-1]: if leaf.type == token.DEDENT: with_node.next_sibling.prefix = leaf.prefix + with_node.next_sibling.prefix leaf.prefix = "" else: break # remove dedenet node corresponding to with node for node in suite_node.children[::-1]: if not isinstance(node, Leaf): continue if node.type == token.DEDENT: node.remove() break # unindent all code in suite for node in suite_node.leaves(): if node.type == token.INDENT: node.value = utils.dec_indent(node.value) else: node.prefix = utils.dec_indent(node.prefix) with_node.remove() parent.insert_child(idx, simple_stmt_disable_static) idx += 1 for node in suite_node.children: parent.insert_child(idx, node) idx += 1 parent.insert_child(idx, simple_stmt_enable_static)
def method_to_property_modifier(node: Node, capture: Capture, filename: Filename) -> None: node.children = node.children[:-1]