123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- import re
- import textwrap
- from inspect import cleandoc
- from parso.python import tree
- from parso.cache import parser_cache
- from jedi._compatibility import literal_eval, force_unicode
- _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 evaluating 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_comp_fors(comp_for):
- yield comp_for
- last = comp_for.children[-1]
- while True:
- if last.type == '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 0
- def get_statement_of_position(node, pos):
- for c in node.children:
- if c.start_pos <= pos <= c.end_pos:
- if c.type not in ('decorated', 'simple_stmt', 'suite',
- 'async_stmt', 'async_funcdef') \
- and not isinstance(c, (tree.Flow, tree.ClassOrFunc)):
- return c
- else:
- try:
- return get_statement_of_position(c, pos)
- except AttributeError:
- pass # Must be a non-scope
- 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``.
- cleaned = cleandoc(safe_literal_eval(node.value))
- # Since we want the docstr output to be always unicode, just
- # force it.
- return force_unicode(cleaned)
- 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 ''
- try:
- return literal_eval(value)
- except SyntaxError:
- # It's possible to create syntax errors with literals like rb'' in
- # Python 2. This should not be possible and in that case just return an
- # empty string.
- # Before Python 3.3 there was a more strict definition in which order
- # you could define literals.
- return ''
- def get_call_signature(funcdef, width=72, call_string=None):
- """
- Generate call signature of this 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
- if funcdef.type == 'lambdef':
- p = '(' + ''.join(param.get_code() for param in funcdef.get_params()).strip() + ')'
- else:
- p = funcdef.children[2].get_code()
- p = re.sub(r'\s+', ' ', p)
- if funcdef.annotation:
- rtype = " ->" + funcdef.annotation.get_code()
- else:
- rtype = ""
- code = call_string + p + rtype
- return '\n'.join(textwrap.wrap(code, width))
- def get_doc_with_call_signature(scope_node):
- """
- Return a document string including call signature.
- """
- call_signature = None
- if scope_node.type == 'classdef':
- for funcdef in scope_node.iter_funcdefs():
- if funcdef.name.value == '__init__':
- call_signature = \
- get_call_signature(funcdef, call_string=scope_node.name.value)
- elif scope_node.type in ('funcdef', 'lambdef'):
- call_signature = get_call_signature(scope_node)
- doc = clean_scope_docstring(scope_node)
- if call_signature is None:
- return doc
- if not doc:
- return call_signature
- return '%s\n\n%s' % (call_signature, doc)
- 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):
- return node.type in ('file_input', 'classdef', 'funcdef', 'lambdef', 'comp_for')
- def get_parent_scope(node, include_flows=False):
- """
- Returns the underlying scope.
- """
- scope = node.parent
- while scope is not None:
- if include_flows and isinstance(scope, tree.Flow):
- return scope
- if is_scope(scope):
- break
- scope = scope.parent
- return 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 parser_cache[grammar._hashed][path].lines
|