def find_definition_root_nodes(attribute, graph, inclusive=False): """Find the root of a function definition. If the function has decorator(s) above it, return the first node that defines all of them. Args: attribute (str): The name of a Python attribute. e.g. "tests", "help", etc. graph (:class:`parso.python.tree.PythonBaseNode`): The base node to check from. Usually this represents a Python module. inclusive (bool, optional): If True, include `graph` as the first item to be iterated over. If False, only iterate over `graph`'s children, recursively. Default is False. """ items = node_seek.iter_nested_children(graph) if inclusive: items = itertools.chain([graph], items) nodes = [] for child in items: if isinstance(child, tree.Function) and child.name == attribute: nodes.append(child) elif _is_decorator_wrapper(child) and find_definition_nodes( attribute, node_seek.iter_nested_children(child)): nodes.append(child) return nodes
def _flatten_node(node): """Remove all prefix / whitespace information from a parso node + its children. :class:`TestsAdapter` is responsible for setting + appending whitespace for all of the parso nodes that eventually get written to disk. But existing data may already have whitespace prefix data. So this function sets that all back to zero, to make it easier to append a prefix to existing nodes. Args: node (:class:`parso.python.tree.PythonBaseNode`): A parso object that presumably has child nodes. These children may have whitespace - every child's whitespace will be removed. Returns: :class:`parso.python.tree.PythonBaseNode`: A copy of `node` whose children have no defined whitespace. """ node = copy.deepcopy(node) if hasattr(node, "prefix"): node.prefix = "" for child in node_seek.iter_nested_children(node): if hasattr(child, "prefix"): child.prefix = "" break return node
def _kill_suffix(node): """Remove any trailing newlines from `node`.""" for child in reversed(list(node_seek.iter_nested_children(node))): if isinstance(child, tree.Newline): child.value = "" return
def find_assignment_nodes(attribute, graph, inclusive=False): """Get a parso node each time a certain Python attribute is declared and given a value. Args: attribute (str): The name of a Python attribute. e.g. "tests", "help", etc. graph (:class:`parso.python.tree.PythonBaseNode`): The root node that will be checked. Usually, this is a :class:`parso.python.tree.Module` but it doesn't have to be. inclusive (bool, optional): If True, include `graph` as the first item to be iterated over. If False, only iterate over `graph`'s children, recursively. Default is False. Returns: set[:class:`parso.python.tree.PythonBaseNode`]: The found assignments, if any. """ nodes = [] items = node_seek.iter_nested_children(graph) if inclusive: items = itertools.chain([graph], items) for child in items: if isinstance(child, tree.ExprStmt): for name in child.get_defined_names(): if name.value == attribute: column = name.start_pos[1] if column == 0: nodes.append(child) return nodes
def _get_tail_children(nodes): """Get the tail namespaces of a from-import. In other words, from "from foo import bar, thing", get the "bar" and "thing". Args: nodes (iter[:class:`parso.python.tree.PythonBaseNode`]): All parso objects that represent a Python from-import's tail. It may contain commas and other syntax-specific parts. But this will all be filtered out before being returned. Returns: list[:class:`parso.python.tree.PythonBaseNode`]: The found tail nodes. """ children = [] for node in nodes: if isinstance(node, tree.Name): children.append(node) else: for child in node_seek.iter_nested_children(node): if isinstance(child, tree.Name) and not _is_alias_name(child): children.append(child) return children
def _get_inner_list_entries(node): """Find the literal "requires" entries of a node. Args: node (:class:`parso.python.tree.PythonBaseNode`): A parso object that (we assume) has 0+ child list parso objects. Each child found is returned. Returns: list[:class:`parso.python.tree.PythonNode`]: The found key / value "requires" attribute entries, if any. """ entries = [] for child in node_seek.iter_nested_children(node): if not _is_list_root_definition(child): continue if any(child_ for child_ in child.children if isinstance(child_, tree.PythonNode)): continue entries.append(child) return entries
def _get_entries(node): """Get the existing "requires" assignment parso nodes. Args: node (:class:`parso.python.tree.ExprStmt`): A parso assignment node that defines "requires". e.g. Imagine "requires = ["foo-1"]", but as a single Python node. Returns: list[:class:`parso.python.tree.String`]: The existing requirements in `node`. """ root_entries = _get_inner_list_entries(node) if not root_entries: return [] entries = [] for item in root_entries: for child in node_seek.iter_nested_children(item): if not isinstance(child, tree.Operator): entries.append(child) return entries
def _get_inner_children(node): children = [] for child in node_seek.iter_nested_children(node): if not _is_alias_name(child): children.append(child) return children
def get_imports(graph, partial=False, namespaces=frozenset(), aliases=False): """Find every import in `graph`. Args: graph (:class:`parso.python.tree.Module`): Some parso node that may have Python imports iside of it. partial (bool, optional): If True, allow namespace replacement even if the user doesn't provide 100% of the namespace. If False, they must describe the entire import namespace before it can be renamed. Default is False. namespaces (set[str], optional): If `partial` is False, these dot-separated Python import namespaces are used to figure out if it's okay to replace an import or if any part of an import statement is missing. If no namespaces are given then `partial` must be set to True. Default: set(). aliases (bool, optional): If True and replacing a namespace would cause Python statements to fail, auto-add an import alias to ensure backwards compatibility If False, don't add aliases. Default is False. Returns: :class:`.BaseAdapter`: Every import in `graph`, returned as a class that can easily query its data or replace it. """ imports = set() processed = set( ) # This variable makes sure nodes are only ever parsed once # We reverse the nested children because parso nests its nodes # in ways that makes it hard to choose the correct adapter class # without starting from the bottom-level nodes and working our way # up each node's list of parents. # for child in reversed(list(node_seek.iter_nested_children(graph))): parents = node_seek.iter_parents(child) if any(parent for parent in parents if parent in processed): # Make sure that if a parent of `child` was parsed to not try to parse `child` continue adapter = import_registry.get_import_data(child, partial=partial, namespaces=namespaces, aliases=aliases) if adapter: imports.add(adapter) processed.add(child) return imports
def is_valid(node): """bool: Check if `node` is compatible with this class.""" if not isinstance(node, tree.ImportName): return False for child in node_seek.iter_nested_children(node): if isinstance(child, tree.Operator) and child.value == ",": return False return True
def test_nested(self): """Return all children of a node.""" graph = parso.parse("something = 8") self.assertEqual( [ graph.children[0], graph.children[0].children[0], graph.children[0].children[1], graph.children[0].children[2], graph.children[1], ], list(node_seek.iter_nested_children(graph)), )
def _replace_namespace(nodes, old_parts, new_parts, partial=False): """Change `nodes` into an import resembling the Python namespace defined by `new_parts`. If there's no common namespace between `nodes` and `old_parts` then this function does nothing. Args: node (:class:`parso.python.tree.ImportFrom`): A parso object that represents a Python import. old_parts (list[str]): The namespace that is expected to be all or part of the namespace that `node` defines. new_parts (list[str]): The namespace to replace `node` with. e.g. ["foo", "bar"]. """ def _is_fully_defined_by(names, parts): for index, name in enumerate(names): try: part = parts[index] except IndexError: return False if part != name: return False return True names = [ child for node in nodes for child in itertools.chain([node], node_seek.iter_nested_children(node)) if isinstance(child, tree.Name) ] prefix = node_seek.get_node_with_first_prefix(names[0]).prefix start, end = import_helper.get_replacement_indices(names, old_parts) if start == -1 or end == -1: # There's nothing to replace, in this case return if not partial and not _is_fully_defined_by( [name.value for name in names], old_parts ): return new_nodes = import_helper.make_replacement_nodes(new_parts, prefix) names[0].parent.children[start : end + 1] = new_nodes
def _get_str_root(node): """Find the first node that could be considered the root of a "help" attribute. Args: node (:class:`parso.python.tree.ExprStmt`): A node that defines a "help" attribute and should contain a string help URL or file path. Returns: :class:`parso.python.tree.PythonNode` or NoneType: The URL / file-path node, if any. """ for child in node_seek.iter_nested_children(node): if isinstance(child, tree.String): return child return None
def _get_list_root(node): """Find the first node that could be considered the root of a "help" attribute. Args: node (:class:`parso.python.tree.ExprStmt`): A node that defines a "help" attribute and should contain a list of list of strings. Returns: :class:`parso.python.tree.PythonNode` or NoneType: The top of a list of list of strings, if any. """ for child in node_seek.iter_nested_children(node): if _is_list_root_definition(child): return child return None
def _apply_formatting(node): """Get a copy of `node` that has "human-readable" whitespace. Args: node (:class:`parso.python.Tree.PythonBaseNode`): A parso object that represents the top-level "list of list of strings" that defines a Rez help attribute. Returns: :class:`parso.python.Tree.PythonBaseNode`: The copy of `node` that has nice newlines. """ def _needs_space(node): if isinstance(node, tree.Operator) and node.value in ("[", "]"): return False if isinstance(node, tree.String): return False return True # pragma: no cover def _iter_inner_entries(node): for child in node_seek.iter_nested_children(node): if isinstance(child, tree.PythonNode) and child.type == "atom": yield child node = copy.deepcopy(node) for child in node_seek.iter_nested_children(node): if isinstance(child, tree.Operator) and child.value == ",": child.prefix = "" elif hasattr(child, "prefix") and _needs_space(child): child.prefix = " " # pragma: no cover for child in _iter_inner_entries(node): opening_brace = child.children[0] opening_brace.prefix = "\n " node.children[-1].prefix = "\n" return node
def _get_dict_maker_root(node): """Find the inner parso PythonNode that defines a Python dict. Args: node (:class:`parso.python.tree.PythonBaseNode`): A parso node that may or may not have a "dictorsetmaker" node as one of its children. Usually, this will actually be an "atom" type :class:`parso.python.tree.PythonNode` Returns: :class:`parso.python.tree.PythonNode` or NoneType: Get the inner dict, if any exists in `node`. """ for child in node_seek.iter_nested_children(node): if isinstance(child, tree.PythonNode) and child.type == "dictorsetmaker": # If this happens, it means that `node` is a non-empty dict return child return None
def _is_empty_dict(node): """Check if the given parso `node` represents an initialized-but-empty assignment. Args: node (:class:`parso.python.tree.ExprStmt`): The assignment node to check. Returns: bool: Return False if `node` is a non-empty dict. Otherwise, return True. """ def _is_close(node): if not isinstance(node, tree.Operator): return False # pragma: no cover return node.value == ")" def _is_open(node): if not isinstance(node, tree.Operator): return False # pragma: no cover return node.value == "(" for child in node_seek.iter_nested_children(node): if (isinstance(child, tree.PythonNode) and child.type == "atom" and len(child.children) == 2 and child.children[0].value == "{" and child.children[-1].value == "}"): # If this happens, it means that `node` is an empty dict return True if isinstance(child, tree.Name) and child.value == "dict": # Check if `child` is `dict()` maybe_open = child.get_next_leaf() if _is_open(maybe_open) and _is_close(maybe_open.get_next_leaf()): return True return False # pragma: no cover
def test_empty(self): """Return no children because the node has no children.""" graph = parso.parse("something = 8") name_node = graph.get_first_leaf() self.assertEqual([], list(node_seek.iter_nested_children(name_node)))
def _iter_inner_entries(node): for child in node_seek.iter_nested_children(node): if isinstance(child, tree.PythonNode) and child.type == "atom": yield child