| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 |
- import re
- import textwrap
- from ast import literal_eval
- from inspect import cleandoc
- from weakref import WeakKeyDictionary
- from parso.python import tree
- from parso.cache import parser_cache
- from parso import split_lines
- _EXECUTE_NODES = {'funcdef', 'classdef', 'import_from', 'import_name', 'test',
- 'or_test', 'and_test', 'not_test', 'comparison', 'expr',
- 'xor_expr', 'and_expr', 'shift_expr', 'arith_expr',
- 'atom_expr', 'term', 'factor', 'power', 'atom'}
- _FLOW_KEYWORDS = (
- 'try', 'except', 'finally', 'else', 'if', 'elif', 'with', 'for', 'while'
- )
- def get_executable_nodes(node, last_added=False):
- """
- For static analysis.
- """
- result = []
- typ = node.type
- if typ == 'name':
- next_leaf = node.get_next_leaf()
- if last_added is False and node.parent.type != 'param' and next_leaf != '=':
- result.append(node)
- elif typ == 'expr_stmt':
- # I think inferring the statement (and possibly returned arrays),
- # should be enough for static analysis.
- result.append(node)
- for child in node.children:
- result += get_executable_nodes(child, last_added=True)
- elif typ == 'decorator':
- # decorator
- if node.children[-2] == ')':
- node = node.children[-3]
- if node != '(':
- result += get_executable_nodes(node)
- else:
- try:
- children = node.children
- except AttributeError:
- pass
- else:
- if node.type in _EXECUTE_NODES and not last_added:
- result.append(node)
- for child in children:
- result += get_executable_nodes(child, last_added)
- return result
- def get_sync_comp_fors(comp_for):
- yield comp_for
- last = comp_for.children[-1]
- while True:
- if last.type == 'comp_for':
- yield last.children[1] # Ignore the async.
- elif last.type == 'sync_comp_for':
- yield last
- elif not last.type == 'comp_if':
- break
- last = last.children[-1]
- def for_stmt_defines_one_name(for_stmt):
- """
- Returns True if only one name is returned: ``for x in y``.
- Returns False if the for loop is more complicated: ``for x, z in y``.
- :returns: bool
- """
- return for_stmt.children[1].type == 'name'
- def get_flow_branch_keyword(flow_node, node):
- start_pos = node.start_pos
- if not (flow_node.start_pos < start_pos <= flow_node.end_pos):
- raise ValueError('The node is not part of the flow.')
- keyword = None
- for i, child in enumerate(flow_node.children):
- if start_pos < child.start_pos:
- return keyword
- first_leaf = child.get_first_leaf()
- if first_leaf in _FLOW_KEYWORDS:
- keyword = first_leaf
- return None
- def clean_scope_docstring(scope_node):
- """ Returns a cleaned version of the docstring token. """
- node = scope_node.get_doc_node()
- if node is not None:
- # TODO We have to check next leaves until there are no new
- # leaves anymore that might be part of the docstring. A
- # docstring can also look like this: ``'foo' 'bar'
- # Returns a literal cleaned version of the ``Token``.
- return cleandoc(safe_literal_eval(node.value))
- return ''
- def find_statement_documentation(tree_node):
- if tree_node.type == 'expr_stmt':
- tree_node = tree_node.parent # simple_stmt
- maybe_string = tree_node.get_next_sibling()
- if maybe_string is not None:
- if maybe_string.type == 'simple_stmt':
- maybe_string = maybe_string.children[0]
- if maybe_string.type == 'string':
- return cleandoc(safe_literal_eval(maybe_string.value))
- return ''
- def safe_literal_eval(value):
- first_two = value[:2].lower()
- if first_two[0] == 'f' or first_two in ('fr', 'rf'):
- # literal_eval is not able to resovle f literals. We have to do that
- # manually, but that's right now not implemented.
- return ''
- return literal_eval(value)
- def get_signature(funcdef, width=72, call_string=None,
- omit_first_param=False, omit_return_annotation=False):
- """
- Generate a string signature of a function.
- :param width: Fold lines if a line is longer than this value.
- :type width: int
- :arg func_name: Override function name when given.
- :type func_name: str
- :rtype: str
- """
- # Lambdas have no name.
- if call_string is None:
- if funcdef.type == 'lambdef':
- call_string = '<lambda>'
- else:
- call_string = funcdef.name.value
- params = funcdef.get_params()
- if omit_first_param:
- params = params[1:]
- p = '(' + ''.join(param.get_code() for param in params).strip() + ')'
- # TODO this is pretty bad, we should probably just normalize.
- p = re.sub(r'\s+', ' ', p)
- if funcdef.annotation and not omit_return_annotation:
- rtype = " ->" + funcdef.annotation.get_code()
- else:
- rtype = ""
- code = call_string + p + rtype
- return '\n'.join(textwrap.wrap(code, width))
- def move(node, line_offset):
- """
- Move the `Node` start_pos.
- """
- try:
- children = node.children
- except AttributeError:
- node.line += line_offset
- else:
- for c in children:
- move(c, line_offset)
- def get_following_comment_same_line(node):
- """
- returns (as string) any comment that appears on the same line,
- after the node, including the #
- """
- try:
- if node.type == 'for_stmt':
- whitespace = node.children[5].get_first_leaf().prefix
- elif node.type == 'with_stmt':
- whitespace = node.children[3].get_first_leaf().prefix
- elif node.type == 'funcdef':
- # actually on the next line
- whitespace = node.children[4].get_first_leaf().get_next_leaf().prefix
- else:
- whitespace = node.get_last_leaf().get_next_leaf().prefix
- except AttributeError:
- return None
- except ValueError:
- # TODO in some particular cases, the tree doesn't seem to be linked
- # correctly
- return None
- if "#" not in whitespace:
- return None
- comment = whitespace[whitespace.index("#"):]
- if "\r" in comment:
- comment = comment[:comment.index("\r")]
- if "\n" in comment:
- comment = comment[:comment.index("\n")]
- return comment
- def is_scope(node):
- t = node.type
- if t == 'comp_for':
- # Starting with Python 3.8, async is outside of the statement.
- return node.children[1].type != 'sync_comp_for'
- return t in ('file_input', 'classdef', 'funcdef', 'lambdef', 'sync_comp_for')
- def _get_parent_scope_cache(func):
- cache = WeakKeyDictionary()
- def wrapper(parso_cache_node, node, include_flows=False):
- if parso_cache_node is None:
- return func(node, include_flows)
- try:
- for_module = cache[parso_cache_node]
- except KeyError:
- for_module = cache[parso_cache_node] = {}
- try:
- return for_module[node]
- except KeyError:
- result = for_module[node] = func(node, include_flows)
- return result
- return wrapper
- def get_parent_scope(node, include_flows=False):
- """
- Returns the underlying scope.
- """
- scope = node.parent
- if scope is None:
- return None # It's a module already.
- while True:
- if is_scope(scope):
- if scope.type in ('classdef', 'funcdef', 'lambdef'):
- index = scope.children.index(':')
- if scope.children[index].start_pos >= node.start_pos:
- if node.parent.type == 'param' and node.parent.name == node:
- pass
- elif node.parent.type == 'tfpdef' and node.parent.children[0] == node:
- pass
- else:
- scope = scope.parent
- continue
- return scope
- elif include_flows and isinstance(scope, tree.Flow):
- # The cursor might be on `if foo`, so the parent scope will not be
- # the if, but the parent of the if.
- if not (scope.type == 'if_stmt'
- and any(n.start_pos <= node.start_pos < n.end_pos
- for n in scope.get_test_nodes())):
- return scope
- scope = scope.parent
- get_cached_parent_scope = _get_parent_scope_cache(get_parent_scope)
- def get_cached_code_lines(grammar, path):
- """
- Basically access the cached code lines in parso. This is not the nicest way
- to do this, but we avoid splitting all the lines again.
- """
- return get_parso_cache_node(grammar, path).lines
- def get_parso_cache_node(grammar, path):
- """
- This is of course not public. But as long as I control parso, this
- shouldn't be a problem. ~ Dave
- The reason for this is mostly caching. This is obviously also a sign of a
- broken caching architecture.
- """
- return parser_cache[grammar._hashed][path]
- def cut_value_at_position(leaf, position):
- """
- Cuts of the value of the leaf at position
- """
- lines = split_lines(leaf.value, keepends=True)[:position[0] - leaf.line + 1]
- column = position[1]
- if leaf.line == position[0]:
- column -= leaf.column
- if not lines:
- return ''
- lines[-1] = lines[-1][:column]
- return ''.join(lines)
- def expr_is_dotted(node):
- """
- Checks if a path looks like `name` or `name.foo.bar` and not `name()`.
- """
- if node.type == 'atom':
- if len(node.children) == 3 and node.children[0] == '(':
- return expr_is_dotted(node.children[1])
- return False
- if node.type == 'atom_expr':
- children = node.children
- if children[0] == 'await':
- return False
- if not expr_is_dotted(children[0]):
- return False
- # Check trailers
- return all(c.children[0] == '.' for c in children[1:])
- return node.type == 'name'
- def _function_is_x_method(decorator_checker):
- def wrapper(function_node):
- """
- This is a heuristic. It will not hold ALL the times, but it will be
- correct pretty much for anyone that doesn't try to beat it.
- staticmethod/classmethod are builtins and unless overwritten, this will
- be correct.
- """
- for decorator in function_node.get_decorators():
- dotted_name = decorator.children[1]
- if decorator_checker(dotted_name.get_code()):
- return True
- return False
- return wrapper
- function_is_staticmethod = _function_is_x_method(lambda m: m == "staticmethod")
- function_is_classmethod = _function_is_x_method(lambda m: m == "classmethod")
- function_is_property = _function_is_x_method(
- lambda m: m == "property"
- or m == "cached_property"
- or (m.endswith(".setter"))
- )
|