forked from VimPlug/jedi
Merge branch 'dev'
This commit is contained in:
@@ -43,6 +43,7 @@ bstaint (@bstaint)
|
||||
Mathias Rav (@Mortal) <rav@cs.au.dk>
|
||||
Daniel Fiterman (@dfit99) <fitermandaniel2@gmail.com>
|
||||
Simon Ruggier (@sruggier)
|
||||
Élie Gouzien (@ElieGouzien)
|
||||
|
||||
|
||||
Note: (@user) means a github user name.
|
||||
|
||||
@@ -22,7 +22,7 @@ All nodes and leaves have these methods/properties:
|
||||
|
||||
|
||||
Python Parser Tree
|
||||
-----------------
|
||||
------------------
|
||||
|
||||
.. automodule:: jedi.parser.python.tree
|
||||
:members:
|
||||
|
||||
@@ -166,14 +166,6 @@ try:
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
if is_py3:
|
||||
u = lambda s: s
|
||||
else:
|
||||
u = lambda s: s.decode('utf-8')
|
||||
|
||||
u.__doc__ = """
|
||||
Decode a raw string into unicode object. Do nothing in Python 3.
|
||||
"""
|
||||
|
||||
# exec function
|
||||
if is_py3:
|
||||
|
||||
@@ -342,7 +342,7 @@ class Script(object):
|
||||
elif isinstance(node, tree.Import):
|
||||
import_names = set(node.get_defined_names())
|
||||
if node.is_nested():
|
||||
import_names |= set(path[-1] for path in node.paths())
|
||||
import_names |= set(path[-1] for path in node.get_paths())
|
||||
for n in import_names:
|
||||
imports.infer_import(context, n)
|
||||
elif node.type == 'expr_stmt':
|
||||
|
||||
@@ -15,7 +15,7 @@ from jedi.evaluate import representation as er
|
||||
from jedi.evaluate import instance
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate.filters import ParamName, TreeNameDefinition
|
||||
from jedi.evaluate.filters import ParamName
|
||||
from jedi.evaluate.imports import ImportName
|
||||
from jedi.api.keywords import KeywordName
|
||||
|
||||
@@ -538,7 +538,12 @@ class Definition(BaseDefinition):
|
||||
typ = 'def'
|
||||
return typ + ' ' + u(self._name.string_name)
|
||||
elif typ == 'param':
|
||||
return typ + ' ' + tree_name.get_definition().get_description()
|
||||
code = tree_name.get_definition().get_code(
|
||||
include_prefix=False,
|
||||
include_comma=False
|
||||
)
|
||||
return typ + ' ' + code
|
||||
|
||||
|
||||
definition = tree_name.get_definition()
|
||||
# Remove the prefix, because that's not what we want for get_code
|
||||
|
||||
@@ -76,12 +76,16 @@ class KeywordName(AbstractNameDefinition):
|
||||
api_type = 'keyword'
|
||||
|
||||
def __init__(self, evaluator, name):
|
||||
self.evaluator = evaluator
|
||||
self.string_name = name
|
||||
self.parent_context = evaluator.BUILTINS
|
||||
|
||||
def eval(self):
|
||||
return set()
|
||||
|
||||
def infer(self):
|
||||
return [Keyword(self.evaluator, self.string_name, (0, 0))]
|
||||
|
||||
|
||||
class Keyword(object):
|
||||
api_type = 'keyword'
|
||||
@@ -100,9 +104,8 @@ class Keyword(object):
|
||||
""" For a `parsing.Name` like comparision """
|
||||
return [self.name]
|
||||
|
||||
@property
|
||||
def docstr(self):
|
||||
return imitate_pydoc(self.name)
|
||||
def py__doc__(self, include_call_signature=False):
|
||||
return imitate_pydoc(self.name.string_name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (type(self).__name__, self.name)
|
||||
@@ -136,6 +139,6 @@ def imitate_pydoc(string):
|
||||
return ''
|
||||
|
||||
try:
|
||||
return pydoc_topics.topics[label] if pydoc_topics else ''
|
||||
return pydoc_topics.topics[label].strip() if pydoc_topics else ''
|
||||
except KeyError:
|
||||
return ''
|
||||
|
||||
@@ -194,7 +194,7 @@ class Evaluator(object):
|
||||
if_stmt = if_stmt.parent
|
||||
if if_stmt.type in ('if_stmt', 'for_stmt'):
|
||||
break
|
||||
if if_stmt.is_scope():
|
||||
if parser_utils.is_scope(if_stmt):
|
||||
if_stmt = None
|
||||
break
|
||||
predefined_if_name_dict = context.predefined_names.get(if_stmt)
|
||||
@@ -337,7 +337,7 @@ class Evaluator(object):
|
||||
# This is the first global lookup.
|
||||
stmt = atom.get_definition()
|
||||
if stmt.type == 'comp_for':
|
||||
stmt = tree.search_ancestor(stmt, ('expr_stmt', 'lambdef', 'funcdef', 'classdef'))
|
||||
stmt = tree.search_ancestor(stmt, 'expr_stmt', 'lambdef', 'funcdef', 'classdef')
|
||||
if stmt is None or stmt.type != 'expr_stmt':
|
||||
# We only need to adjust the start_pos for statements, because
|
||||
# there the name cannot be used.
|
||||
@@ -537,7 +537,7 @@ class Evaluator(object):
|
||||
while True:
|
||||
node = node.parent
|
||||
|
||||
if node.is_scope():
|
||||
if parser_utils.is_scope(node):
|
||||
return node
|
||||
elif node.type in ('argument', 'testlist_comp'):
|
||||
if node.children[1].type == 'comp_for':
|
||||
@@ -553,7 +553,7 @@ class Evaluator(object):
|
||||
return base_context
|
||||
|
||||
is_funcdef = scope_node.type in ('funcdef', 'lambdef')
|
||||
parent_scope = scope_node.get_parent_scope()
|
||||
parent_scope = parser_utils.get_parent_scope(scope_node)
|
||||
parent_context = from_scope_node(parent_scope, child_is_funcdef=is_funcdef)
|
||||
|
||||
if is_funcdef:
|
||||
@@ -586,7 +586,7 @@ class Evaluator(object):
|
||||
|
||||
base_node = base_context.tree_node
|
||||
|
||||
if node_is_context and node.is_scope():
|
||||
if node_is_context and parser_utils.is_scope(node):
|
||||
scope_node = node
|
||||
else:
|
||||
if node.parent.type in ('funcdef', 'classdef'):
|
||||
|
||||
@@ -222,9 +222,6 @@ class CompiledObject(Context):
|
||||
for result in self.evaluator.execute(bltn_obj, params):
|
||||
yield result
|
||||
|
||||
def is_scope(self):
|
||||
return True
|
||||
|
||||
def get_self_attributes(self):
|
||||
return [] # Instance compatibility
|
||||
|
||||
|
||||
@@ -116,8 +116,21 @@ def _load_module(evaluator, path, python_object):
|
||||
return module
|
||||
|
||||
|
||||
def source_findable(python_object):
|
||||
"""Check if inspect.getfile has a chance to find the source."""
|
||||
return (inspect.ismodule(python_object) or
|
||||
inspect.isclass(python_object) or
|
||||
inspect.ismethod(python_object) or
|
||||
inspect.isfunction(python_object) or
|
||||
inspect.istraceback(python_object) or
|
||||
inspect.isframe(python_object) or
|
||||
inspect.iscode(python_object))
|
||||
|
||||
|
||||
def find_syntax_node_name(evaluator, python_object):
|
||||
try:
|
||||
if not source_findable(python_object):
|
||||
raise TypeError # Prevents computation of `repr` within inspect.
|
||||
path = inspect.getsourcefile(python_object)
|
||||
except TypeError:
|
||||
# The type might not be known (e.g. class_with_dict.__weakref__)
|
||||
|
||||
@@ -24,6 +24,7 @@ from jedi.evaluate.cache import memoize_default
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate.param import TreeArguments, create_default_param
|
||||
from jedi.common import to_list, unite
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
|
||||
|
||||
MAX_PARAM_SEARCHES = 20
|
||||
@@ -103,7 +104,7 @@ def _search_function_executions(evaluator, module_context, funcdef):
|
||||
func_string_name = funcdef.name.value
|
||||
compare_node = funcdef
|
||||
if func_string_name == '__init__':
|
||||
cls = funcdef.get_parent_scope()
|
||||
cls = get_parent_scope(funcdef)
|
||||
if isinstance(cls, tree.Class):
|
||||
func_string_name = cls.name.value
|
||||
compare_node = cls
|
||||
|
||||
@@ -7,6 +7,7 @@ from abc import abstractmethod
|
||||
from jedi.parser.tree import search_ancestor
|
||||
from jedi.evaluate import flow_analysis
|
||||
from jedi.common import to_list, unite
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
|
||||
|
||||
class AbstractNameDefinition(object):
|
||||
@@ -189,7 +190,7 @@ class ParserTreeFilter(AbstractUsedNamesFilter):
|
||||
if parent.type == 'trailer':
|
||||
return False
|
||||
base_node = parent if parent.type in ('classdef', 'funcdef') else name
|
||||
return base_node.get_parent_scope() == self._parser_scope
|
||||
return get_parent_scope(base_node) == self._parser_scope
|
||||
|
||||
def _check_flows(self, names):
|
||||
for name in sorted(names, key=lambda name: name.start_pos, reverse=True):
|
||||
|
||||
@@ -32,6 +32,7 @@ from jedi.evaluate import param
|
||||
from jedi.evaluate import helpers
|
||||
from jedi.evaluate.filters import get_global_filters
|
||||
from jedi.evaluate.context import ContextualizedName, ContextualizedNode
|
||||
from jedi.parser_utils import is_scope, get_parent_scope
|
||||
|
||||
|
||||
class NameFinder(object):
|
||||
@@ -106,7 +107,7 @@ class NameFinder(object):
|
||||
if self._context.predefined_names:
|
||||
# TODO is this ok? node might not always be a tree.Name
|
||||
node = self._name
|
||||
while node is not None and not node.is_scope():
|
||||
while node is not None and not is_scope(node):
|
||||
node = node.parent
|
||||
if node.type in ("if_stmt", "for_stmt", "comp_for"):
|
||||
try:
|
||||
@@ -160,7 +161,7 @@ class NameFinder(object):
|
||||
if base_node.type == 'comp_for':
|
||||
return types
|
||||
while True:
|
||||
flow_scope = flow_scope.get_parent_scope(include_flows=True)
|
||||
flow_scope = get_parent_scope(flow_scope, include_flows=True)
|
||||
n = _check_flow_information(self._name_context, flow_scope,
|
||||
self._name, self._position)
|
||||
if n is not None:
|
||||
@@ -298,7 +299,7 @@ def _check_flow_information(context, flow, search_name, pos):
|
||||
return None
|
||||
|
||||
result = None
|
||||
if flow.is_scope():
|
||||
if is_scope(flow):
|
||||
# Check for asserts.
|
||||
module_node = flow.get_root_node()
|
||||
try:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from jedi.parser_utils import get_flow_branch_keyword
|
||||
from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope
|
||||
|
||||
|
||||
class Status(object):
|
||||
lookup_table = {}
|
||||
|
||||
@@ -32,14 +34,14 @@ UNSURE = Status(None, 'unsure')
|
||||
|
||||
def _get_flow_scopes(node):
|
||||
while True:
|
||||
node = node.get_parent_scope(include_flows=True)
|
||||
if node is None or node.is_scope():
|
||||
node = get_parent_scope(node, include_flows=True)
|
||||
if node is None or is_scope(node):
|
||||
return
|
||||
yield node
|
||||
|
||||
|
||||
def reachability_check(context, context_scope, node, origin_scope=None):
|
||||
first_flow_scope = node.get_parent_scope(include_flows=True)
|
||||
first_flow_scope = get_parent_scope(node, include_flows=True)
|
||||
if origin_scope is not None:
|
||||
origin_flow_scopes = list(_get_flow_scopes(origin_scope))
|
||||
node_flow_scopes = list(_get_flow_scopes(node))
|
||||
@@ -95,7 +97,7 @@ def _break_check(context, context_scope, flow_scope, node):
|
||||
return reachable
|
||||
|
||||
if context_scope != flow_scope and context_scope != flow_scope.parent:
|
||||
flow_scope = flow_scope.get_parent_scope(include_flows=True)
|
||||
flow_scope = get_parent_scope(flow_scope, include_flows=True)
|
||||
return reachable & _break_check(context, context_scope, flow_scope, node)
|
||||
else:
|
||||
return reachable
|
||||
|
||||
@@ -3,6 +3,7 @@ from itertools import chain
|
||||
from contextlib import contextmanager
|
||||
|
||||
from jedi.parser.python import tree
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
|
||||
|
||||
def deep_ast_copy(obj):
|
||||
@@ -143,7 +144,7 @@ def get_module_names(module, all_scopes):
|
||||
# parent_scope. There's None as a parent, because nodes in the module
|
||||
# node have the parent module and not suite as all the others.
|
||||
# Therefore it's important to catch that case.
|
||||
names = [n for n in names if n.get_parent_scope().parent in (module, None)]
|
||||
names = [n for n in names if get_parent_scope(n).parent in (module, None)]
|
||||
return names
|
||||
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ from jedi.evaluate.filters import AbstractNameDefinition
|
||||
@memoize_default(default=set())
|
||||
def infer_import(context, tree_name, is_goto=False):
|
||||
module_context = context.get_root_context()
|
||||
import_node = search_ancestor(tree_name, ('import_name', 'import_from'))
|
||||
import_node = search_ancestor(tree_name, 'import_name', 'import_from')
|
||||
import_path = import_node.get_path_for_name(tree_name)
|
||||
from_import_name = None
|
||||
evaluator = context.evaluator
|
||||
|
||||
@@ -11,6 +11,7 @@ from jedi.cache import memoize_method
|
||||
from jedi.evaluate import representation as er
|
||||
from jedi.evaluate.dynamic import search_params
|
||||
from jedi.evaluate import iterable
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
|
||||
|
||||
class AbstractInstanceContext(Context):
|
||||
@@ -151,7 +152,7 @@ class AbstractInstanceContext(Context):
|
||||
def create_instance_context(self, class_context, node):
|
||||
if node.parent.type in ('funcdef', 'classdef'):
|
||||
node = node.parent
|
||||
scope = node.get_parent_scope()
|
||||
scope = get_parent_scope(node)
|
||||
if scope == class_context.tree_node:
|
||||
return class_context
|
||||
else:
|
||||
@@ -189,13 +190,18 @@ class CompiledInstance(AbstractInstanceContext):
|
||||
return compiled.CompiledContextName(self, self.class_context.name.string_name)
|
||||
|
||||
def create_instance_context(self, class_context, node):
|
||||
if node.get_parent_scope().type == 'classdef':
|
||||
if get_parent_scope(node).type == 'classdef':
|
||||
return class_context
|
||||
else:
|
||||
return super(CompiledInstance, self).create_instance_context(class_context, node)
|
||||
|
||||
|
||||
class TreeInstance(AbstractInstanceContext):
|
||||
def __init__(self, evaluator, parent_context, class_context, var_args):
|
||||
super(TreeInstance, self).__init__(evaluator, parent_context,
|
||||
class_context, var_args)
|
||||
self.tree_node = class_context.tree_node
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return filters.ContextName(self, self.class_context.name.tree_name)
|
||||
@@ -332,7 +338,7 @@ class InstanceClassFilter(filters.ParserTreeFilter):
|
||||
while node is not None:
|
||||
if node == self._parser_scope or node == self.context:
|
||||
return True
|
||||
node = node.get_parent_scope()
|
||||
node = get_parent_scope(node)
|
||||
return False
|
||||
|
||||
def _access_possible(self, name):
|
||||
|
||||
@@ -276,7 +276,7 @@ class FunctionContext(use_metaclass(CachedMetaClass, context.TreeContext)):
|
||||
def py__class__(self):
|
||||
# This differentiation is only necessary for Python2. Python3 does not
|
||||
# use a different method class.
|
||||
if isinstance(self.tree_node.get_parent_scope(), tree.Class):
|
||||
if isinstance(parser_utils.get_parent_scope(self.tree_node), tree.Class):
|
||||
name = 'METHOD_CLASS'
|
||||
else:
|
||||
name = 'FUNCTION_CLASS'
|
||||
@@ -350,8 +350,8 @@ class FunctionExecutionContext(context.TreeContext):
|
||||
|
||||
@recursion.execution_recursion_decorator(default=iter([]))
|
||||
def get_yield_values(self):
|
||||
for_parents = [(y, tree.search_ancestor(y, ('for_stmt', 'funcdef',
|
||||
'while_stmt', 'if_stmt')))
|
||||
for_parents = [(y, tree.search_ancestor(y, 'for_stmt', 'funcdef',
|
||||
'while_stmt', 'if_stmt'))
|
||||
for y in self.tree_node.iter_yield_exprs()]
|
||||
|
||||
# Calculate if the yields are placed within the same for loop.
|
||||
@@ -476,7 +476,7 @@ class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext)):
|
||||
modules = []
|
||||
for i in self.tree_node.iter_imports():
|
||||
if i.is_star_import():
|
||||
name = i.star_import_name()
|
||||
name = i.get_paths()[-1][-1]
|
||||
new = imports.infer_import(self, name)
|
||||
for module in new:
|
||||
if isinstance(module, ModuleContext):
|
||||
|
||||
@@ -74,9 +74,6 @@ def parse(code=None, path=None, grammar=None, error_recovery=True,
|
||||
if grammar is None:
|
||||
grammar = load_grammar()
|
||||
|
||||
if path is not None:
|
||||
path = os.path.expanduser(path)
|
||||
|
||||
if cache and not code and path is not None:
|
||||
# In this case we do actual caching. We just try to load it.
|
||||
module_node = load_module(grammar, path)
|
||||
|
||||
@@ -144,22 +144,9 @@ class Parser(BaseParser):
|
||||
elif symbol == 'suite' and len(nodes) > 1:
|
||||
# suites without an indent in them get discarded.
|
||||
break
|
||||
elif symbol == 'simple_stmt' and len(nodes) > 1:
|
||||
# simple_stmt can just be turned into a PythonNode, if
|
||||
# there are enough statements. Ignore the rest after that.
|
||||
break
|
||||
return index, symbol, nodes
|
||||
|
||||
index, symbol, nodes = current_suite(stack)
|
||||
if symbol == 'simple_stmt':
|
||||
index -= 2
|
||||
(_, _, (type_, suite_nodes)) = stack[index]
|
||||
symbol = grammar.number2symbol[type_]
|
||||
suite_nodes.append(tree.PythonNode(symbol, list(nodes)))
|
||||
# Remove
|
||||
nodes[:] = []
|
||||
nodes = suite_nodes
|
||||
stack[index]
|
||||
|
||||
# print('err', token.tok_name[typ], repr(value), start_pos, len(stack), index)
|
||||
if self._stack_removal(grammar, stack, arcs, index + 1, value, start_pos):
|
||||
|
||||
@@ -25,8 +25,6 @@ Any subclasses of :class:`Scope`, including :class:`Module` has an attribute
|
||||
[<ImportName: import os@1,0>]
|
||||
"""
|
||||
|
||||
from itertools import chain
|
||||
|
||||
from jedi._compatibility import utf8_repr, unicode
|
||||
from jedi.parser.tree import Node, BaseNode, Leaf, ErrorNode, ErrorLeaf, \
|
||||
search_ancestor
|
||||
@@ -61,18 +59,10 @@ class DocstringMixin(object):
|
||||
|
||||
|
||||
class PythonMixin(object):
|
||||
def get_parent_scope(self, include_flows=False):
|
||||
"""
|
||||
Returns the underlying scope.
|
||||
"""
|
||||
scope = self.parent
|
||||
while scope is not None:
|
||||
if include_flows and isinstance(scope, Flow):
|
||||
return scope
|
||||
if scope.is_scope():
|
||||
break
|
||||
scope = scope.parent
|
||||
return scope
|
||||
"""
|
||||
Some Python specific utitilies.
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def get_definition(self):
|
||||
if self.type in ('newline', 'endmarker'):
|
||||
@@ -92,10 +82,6 @@ class PythonMixin(object):
|
||||
break
|
||||
return scope
|
||||
|
||||
def is_scope(self):
|
||||
# Default is not being a scope. Just inherit from Scope.
|
||||
return False
|
||||
|
||||
def get_name_of_position(self, position):
|
||||
for c in self.children:
|
||||
if isinstance(c, Leaf):
|
||||
@@ -269,9 +255,6 @@ class Scope(PythonBaseNode, DocstringMixin):
|
||||
|
||||
return scan(self.children)
|
||||
|
||||
def is_scope(self):
|
||||
return True
|
||||
|
||||
def get_suite(self):
|
||||
"""
|
||||
Returns the part that is executed by the function.
|
||||
@@ -311,7 +294,7 @@ class Module(Scope):
|
||||
# the future print statement).
|
||||
for imp in self.iter_imports():
|
||||
if imp.type == 'import_from' and imp.level == 0:
|
||||
for path in imp.paths():
|
||||
for path in imp.get_paths():
|
||||
names = [name.value for name in path]
|
||||
if len(names) == 2 and names[0] == '__future__':
|
||||
yield names[1]
|
||||
@@ -328,6 +311,10 @@ class Module(Scope):
|
||||
return False
|
||||
|
||||
def get_used_names(self):
|
||||
"""
|
||||
Returns all the `Name` leafs that exist in this module. Tihs includes
|
||||
both definitions and references of names.
|
||||
"""
|
||||
if self._used_names is None:
|
||||
# Don't directly use self._used_names to eliminate a lookup.
|
||||
dct = {}
|
||||
@@ -358,9 +345,15 @@ class ClassOrFunc(Scope):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
Returns the `Name` leaf that defines the function or class name.
|
||||
"""
|
||||
return self.children[1]
|
||||
|
||||
def get_decorators(self):
|
||||
"""
|
||||
:return list of Decorator:
|
||||
"""
|
||||
decorated = self.parent
|
||||
if decorated.type == 'decorated':
|
||||
if decorated.children[0].type == 'decorators':
|
||||
@@ -389,6 +382,10 @@ class Class(ClassOrFunc):
|
||||
super(Class, self).__init__(children)
|
||||
|
||||
def get_super_arglist(self):
|
||||
"""
|
||||
Returns the `arglist` node that defines the super classes. It returns
|
||||
None if there are no arguments.
|
||||
"""
|
||||
if self.children[2] != '(': # Has no parentheses
|
||||
return None
|
||||
else:
|
||||
@@ -452,14 +449,15 @@ class Function(ClassOrFunc):
|
||||
"""
|
||||
Used to store the parsed contents of a python function.
|
||||
|
||||
Children:
|
||||
0. <Keyword: def>
|
||||
1. <Name>
|
||||
2. parameter list (including open-paren and close-paren <Operator>s)
|
||||
3. or 5. <Operator: :>
|
||||
4. or 6. Node() representing function body
|
||||
3. -> (if annotation is also present)
|
||||
4. annotation (if present)
|
||||
Children::
|
||||
|
||||
0. <Keyword: def>
|
||||
1. <Name>
|
||||
2. parameter list (including open-paren and close-paren <Operator>s)
|
||||
3. or 5. <Operator: :>
|
||||
4. or 6. Node() representing function body
|
||||
3. -> (if annotation is also present)
|
||||
4. annotation (if present)
|
||||
"""
|
||||
type = 'funcdef'
|
||||
|
||||
@@ -514,20 +512,16 @@ class Function(ClassOrFunc):
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def _get_paramlist_code(self):
|
||||
return self.children[2].get_code()
|
||||
|
||||
|
||||
class Lambda(Function):
|
||||
"""
|
||||
Lambdas are basically trimmed functions, so give it the same interface.
|
||||
|
||||
Children:
|
||||
Children::
|
||||
|
||||
0. <Keyword: lambda>
|
||||
*. <Param x> for each argument x
|
||||
-2. <Operator: :>
|
||||
-1. Node() representing body
|
||||
0. <Keyword: lambda>
|
||||
*. <Param x> for each argument x
|
||||
-2. <Operator: :>
|
||||
-1. Node() representing body
|
||||
"""
|
||||
type = 'lambdef'
|
||||
__slots__ = ()
|
||||
@@ -545,9 +539,6 @@ class Lambda(Function):
|
||||
"""
|
||||
raise AttributeError("lambda is not named.")
|
||||
|
||||
def _get_paramlist_code(self):
|
||||
return '(' + ''.join(param.get_code() for param in self.params).strip() + ')'
|
||||
|
||||
def _get_param_nodes(self):
|
||||
return self.children[1:-2]
|
||||
|
||||
@@ -556,7 +547,6 @@ class Lambda(Function):
|
||||
"""
|
||||
Returns `None`, lambdas don't have annotations.
|
||||
"""
|
||||
# lambda functions do not support annotations
|
||||
return None
|
||||
|
||||
def __repr__(self):
|
||||
@@ -650,6 +640,10 @@ class WithStmt(Flow):
|
||||
__slots__ = ()
|
||||
|
||||
def get_defined_names(self):
|
||||
"""
|
||||
Returns the a list of `Name` that the with statement defines. The
|
||||
defined names are set after `as`.
|
||||
"""
|
||||
names = []
|
||||
for with_item in self.children[1:-2:2]:
|
||||
# Check with items for 'as' names.
|
||||
@@ -669,13 +663,18 @@ class Import(PythonBaseNode):
|
||||
__slots__ = ()
|
||||
|
||||
def get_path_for_name(self, name):
|
||||
"""
|
||||
The path is the list of names that leads to the searched name.
|
||||
|
||||
:return list of Name:
|
||||
"""
|
||||
try:
|
||||
# The name may be an alias. If it is, just map it back to the name.
|
||||
name = self.aliases()[name]
|
||||
name = self._aliases()[name]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
for path in self.paths():
|
||||
for path in self.get_paths():
|
||||
if name in path:
|
||||
return path[:path.index(name) + 1]
|
||||
raise ValueError('Name should be defined in the import itself')
|
||||
@@ -692,9 +691,14 @@ class ImportFrom(Import):
|
||||
__slots__ = ()
|
||||
|
||||
def get_defined_names(self):
|
||||
"""
|
||||
Returns the a list of `Name` that the import defines. The
|
||||
defined names are set after `import` or in case an alias - `as` - is
|
||||
present that name is returned.
|
||||
"""
|
||||
return [alias or name for name, alias in self._as_name_tuples()]
|
||||
|
||||
def aliases(self):
|
||||
def _aliases(self):
|
||||
"""Mapping from alias to its corresponding name."""
|
||||
return dict((alias, name) for name, alias in self._as_name_tuples()
|
||||
if alias is not None)
|
||||
@@ -738,16 +742,12 @@ class ImportFrom(Import):
|
||||
else:
|
||||
yield as_name.children[::2] # yields x, y -> ``x as y``
|
||||
|
||||
def star_import_name(self):
|
||||
"""
|
||||
The last name defined in a star import.
|
||||
"""
|
||||
return self.paths()[-1][-1]
|
||||
|
||||
def paths(self):
|
||||
def get_paths(self):
|
||||
"""
|
||||
The import paths defined in an import statement. Typically an array
|
||||
like this: ``[<Name: datetime>, <Name: date>]``.
|
||||
|
||||
:return list of list of Name:
|
||||
"""
|
||||
dotted = self.get_from_names()
|
||||
|
||||
@@ -762,6 +762,11 @@ class ImportName(Import):
|
||||
__slots__ = ()
|
||||
|
||||
def get_defined_names(self):
|
||||
"""
|
||||
Returns the a list of `Name` that the import defines. The defined names
|
||||
is always the first name after `import` or in case an alias - `as` - is
|
||||
present that name is returned.
|
||||
"""
|
||||
return [alias or path[0] for path, alias in self._dotted_as_names()]
|
||||
|
||||
@property
|
||||
@@ -769,7 +774,7 @@ class ImportName(Import):
|
||||
"""The level parameter of ``__import__``."""
|
||||
return 0 # Obviously 0 for imports without from.
|
||||
|
||||
def paths(self):
|
||||
def get_paths(self):
|
||||
return [path for path, alias in self._dotted_as_names()]
|
||||
|
||||
def _dotted_as_names(self):
|
||||
@@ -799,10 +804,13 @@ class ImportName(Import):
|
||||
|
||||
import foo.bar
|
||||
"""
|
||||
return [1 for path, alias in self._dotted_as_names()
|
||||
if alias is None and len(path) > 1]
|
||||
return bool([1 for path, alias in self._dotted_as_names()
|
||||
if alias is None and len(path) > 1])
|
||||
|
||||
def aliases(self):
|
||||
def _aliases(self):
|
||||
"""
|
||||
:return list of Name: Returns all the alias
|
||||
"""
|
||||
return dict((alias, path[-1]) for path, alias in self._dotted_as_names()
|
||||
if alias is not None)
|
||||
|
||||
@@ -880,14 +888,18 @@ class ExprStmt(PythonBaseNode, DocstringMixin):
|
||||
__slots__ = ()
|
||||
|
||||
def get_defined_names(self):
|
||||
"""
|
||||
Returns a list of `Name` defined before the `=` sign.
|
||||
"""
|
||||
names = []
|
||||
if self.children[1].type == 'annassign':
|
||||
names = _defined_names(self.children[0])
|
||||
return list(chain.from_iterable(
|
||||
_defined_names(self.children[i])
|
||||
return [
|
||||
name
|
||||
for i in range(0, len(self.children) - 2, 2)
|
||||
if '=' in self.children[i + 1].value)
|
||||
) + names
|
||||
if '=' in self.children[i + 1].value
|
||||
for name in _defined_names(self.children[i])
|
||||
] + names
|
||||
|
||||
def get_rhs(self):
|
||||
"""Returns the right-hand-side of the equals."""
|
||||
@@ -925,6 +937,10 @@ class Param(PythonBaseNode):
|
||||
|
||||
@property
|
||||
def star_count(self):
|
||||
"""
|
||||
Is `0` in case of `foo`, `1` in case of `*foo` or `2` in case of
|
||||
`**foo`.
|
||||
"""
|
||||
first = self.children[0]
|
||||
if first in ('*', '**'):
|
||||
return len(first.value)
|
||||
@@ -932,6 +948,10 @@ class Param(PythonBaseNode):
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
"""
|
||||
The default is the test node that appears after the `=`. Is `None` in
|
||||
case no default is present.
|
||||
"""
|
||||
try:
|
||||
return self.children[int(self.children[0] in ('*', '**')) + 2]
|
||||
except IndexError:
|
||||
@@ -939,6 +959,10 @@ class Param(PythonBaseNode):
|
||||
|
||||
@property
|
||||
def annotation(self):
|
||||
"""
|
||||
The default is the test node that appears after `->`. Is `None` in case
|
||||
no annotation is present.
|
||||
"""
|
||||
tfpdef = self._tfpdef()
|
||||
if tfpdef.type == 'tfpdef':
|
||||
assert tfpdef.children[1] == ":"
|
||||
@@ -957,6 +981,9 @@ class Param(PythonBaseNode):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
The `Name` leaf of the param.
|
||||
"""
|
||||
if self._tfpdef().type == 'tfpdef':
|
||||
return self._tfpdef().children[0]
|
||||
else:
|
||||
@@ -965,7 +992,7 @@ class Param(PythonBaseNode):
|
||||
@property
|
||||
def position_index(self):
|
||||
"""
|
||||
Returns the positional index of a paramter.
|
||||
Property for the positional index of a paramter.
|
||||
"""
|
||||
index = self.parent.children.index(self)
|
||||
try:
|
||||
@@ -979,16 +1006,28 @@ class Param(PythonBaseNode):
|
||||
|
||||
def get_parent_function(self):
|
||||
"""
|
||||
Returns the function/lambda a paramter is defined in.
|
||||
Returns the function/lambda of a parameter.
|
||||
"""
|
||||
return search_ancestor(self, ('funcdef', 'lambdef'))
|
||||
return search_ancestor(self, 'funcdef', 'lambdef')
|
||||
|
||||
def get_code(self, normalized=False, include_prefix=True, include_comma=True):
|
||||
"""
|
||||
Like all the other get_code functions, but includes the param
|
||||
`include_comma`.
|
||||
|
||||
:param include_comma bool: If enabled includes the comma in the string output.
|
||||
"""
|
||||
if include_comma:
|
||||
return super(Param, self).get_code(normalized, include_prefix)
|
||||
|
||||
def get_description(self):
|
||||
# TODO Remove?
|
||||
children = self.children
|
||||
if children[-1] == ',':
|
||||
children = children[:-1]
|
||||
return self._get_code_for_children(children, False, False)
|
||||
return self._get_code_for_children(
|
||||
children,
|
||||
normalized=False,
|
||||
include_prefix=include_prefix
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
default = '' if self.default is None else '=%s' % self.default.get_code()
|
||||
@@ -999,8 +1038,8 @@ class CompFor(PythonBaseNode):
|
||||
type = 'comp_for'
|
||||
__slots__ = ()
|
||||
|
||||
def is_scope(self):
|
||||
return True
|
||||
|
||||
def get_defined_names(self):
|
||||
"""
|
||||
Returns the a list of `Name` that the comprehension defines.
|
||||
"""
|
||||
return _defined_names(self.children[1])
|
||||
|
||||
@@ -2,21 +2,18 @@ from abc import abstractmethod, abstractproperty
|
||||
from jedi._compatibility import utf8_repr, encoding, is_py3
|
||||
|
||||
|
||||
def search_ancestor(node, node_type_or_types):
|
||||
def search_ancestor(node, *node_types):
|
||||
"""
|
||||
Recursively looks at the parents of a node and checks if the type names
|
||||
match.
|
||||
|
||||
:param node: The node that is looked at.
|
||||
:param node_type_or_types: A tuple or a string of type names that are
|
||||
:param node_types: A tuple or a string of type names that are
|
||||
searched for.
|
||||
"""
|
||||
if not isinstance(node_type_or_types, (list, tuple)):
|
||||
node_type_or_types = (node_type_or_types,)
|
||||
|
||||
while True:
|
||||
node = node.parent
|
||||
if node is None or node.type in node_type_or_types:
|
||||
if node is None or node.type in node_types:
|
||||
return node
|
||||
|
||||
|
||||
|
||||
@@ -154,7 +154,12 @@ def get_call_signature(funcdef, width=72, call_string=None):
|
||||
call_string = '<lambda>'
|
||||
else:
|
||||
call_string = funcdef.name.value
|
||||
code = call_string + funcdef._get_paramlist_code()
|
||||
if funcdef.type == 'lambdef':
|
||||
p = '(' + ''.join(param.get_code() for param in funcdef.params).strip() + ')'
|
||||
else:
|
||||
p = funcdef.children[2].get_code()
|
||||
code = call_string + p
|
||||
|
||||
return '\n'.join(textwrap.wrap(code, width))
|
||||
|
||||
|
||||
@@ -217,3 +222,21 @@ def get_following_comment_same_line(node):
|
||||
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
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ else:
|
||||
'jedi')
|
||||
cache_directory = os.path.expanduser(_cache_directory)
|
||||
"""
|
||||
The path where all the caches can be found.
|
||||
The path where the cache is stored.
|
||||
|
||||
On Linux, this defaults to ``~/.cache/jedi/``, on OS X to
|
||||
``~/Library/Caches/Jedi/`` and on Windows to ``%APPDATA%\\Jedi\\Jedi\\``.
|
||||
|
||||
@@ -20,7 +20,22 @@ class TestDocstring(unittest.TestCase):
|
||||
def func():
|
||||
'''Docstring of `func`.'''
|
||||
func""").goto_definitions()
|
||||
self.assertEqual(defs[0].raw_doc, 'Docstring of `func`.')
|
||||
self.assertEqual(defs[0].docstring(), 'func()\n\nDocstring of `func`.')
|
||||
|
||||
def test_class_doc(self):
|
||||
defs = jedi.Script("""
|
||||
class TestClass():
|
||||
'''Docstring of `TestClass`.'''
|
||||
TestClass""").goto_definitions()
|
||||
self.assertEqual(defs[0].docstring(), 'Docstring of `TestClass`.')
|
||||
|
||||
def test_instance_doc(self):
|
||||
defs = jedi.Script("""
|
||||
class TestClass():
|
||||
'''Docstring of `TestClass`.'''
|
||||
tc = TestClass()
|
||||
tc""").goto_definitions()
|
||||
self.assertEqual(defs[0].docstring(), 'Docstring of `TestClass`.')
|
||||
|
||||
@unittest.skip('need evaluator class for that')
|
||||
def test_attribute_docstring(self):
|
||||
@@ -28,7 +43,7 @@ class TestDocstring(unittest.TestCase):
|
||||
x = None
|
||||
'''Docstring of `x`.'''
|
||||
x""").goto_definitions()
|
||||
self.assertEqual(defs[0].raw_doc, 'Docstring of `x`.')
|
||||
self.assertEqual(defs[0].docstring(), 'Docstring of `x`.')
|
||||
|
||||
@unittest.skip('need evaluator class for that')
|
||||
def test_multiple_docstrings(self):
|
||||
@@ -38,7 +53,7 @@ class TestDocstring(unittest.TestCase):
|
||||
x = func
|
||||
'''Docstring of `x`.'''
|
||||
x""").goto_definitions()
|
||||
docs = [d.raw_doc for d in defs]
|
||||
docs = [d.docstring() for d in defs]
|
||||
self.assertEqual(docs, ['Original docstring.', 'Docstring of `x`.'])
|
||||
|
||||
def test_completion(self):
|
||||
@@ -105,6 +120,10 @@ class TestDocstring(unittest.TestCase):
|
||||
assert '__init__' in names
|
||||
assert 'mro' not in names # Exists only for types.
|
||||
|
||||
def test_docstring_keyword(self):
|
||||
completions = jedi.Script('assert').completions()
|
||||
self.assertIn('assert', completions[0].docstring())
|
||||
|
||||
@unittest.skipIf(numpydoc_unavailable, 'numpydoc module is unavailable')
|
||||
def test_numpydoc_docstring(self):
|
||||
s = dedent('''
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
"""
|
||||
Tests of various import related things that could not be tested with "Black Box
|
||||
Tests".
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
@@ -7,6 +12,9 @@ import jedi
|
||||
from jedi._compatibility import find_module_py33, find_module
|
||||
from ..helpers import cwd_at
|
||||
|
||||
from jedi import Script
|
||||
from jedi._compatibility import is_py26
|
||||
|
||||
|
||||
@pytest.mark.skipif('sys.version_info < (3,3)')
|
||||
def test_find_module_py33():
|
||||
@@ -127,3 +135,85 @@ def test_import_completion_docstring():
|
||||
# However for performance reasons not all modules are loaded and the
|
||||
# docstring is empty in this case.
|
||||
assert completions[0].docstring() == ''
|
||||
|
||||
|
||||
def test_goto_definition_on_import():
|
||||
assert Script("import sys_blabla", 1, 8).goto_definitions() == []
|
||||
assert len(Script("import sys", 1, 8).goto_definitions()) == 1
|
||||
|
||||
|
||||
@cwd_at('jedi')
|
||||
def test_complete_on_empty_import():
|
||||
assert Script("from datetime import").completions()[0].name == 'import'
|
||||
# should just list the files in the directory
|
||||
assert 10 < len(Script("from .", path='whatever.py').completions()) < 30
|
||||
|
||||
# Global import
|
||||
assert len(Script("from . import", 1, 5, 'whatever.py').completions()) > 30
|
||||
# relative import
|
||||
assert 10 < len(Script("from . import", 1, 6, 'whatever.py').completions()) < 30
|
||||
|
||||
# Global import
|
||||
assert len(Script("from . import classes", 1, 5, 'whatever.py').completions()) > 30
|
||||
# relative import
|
||||
assert 10 < len(Script("from . import classes", 1, 6, 'whatever.py').completions()) < 30
|
||||
|
||||
wanted = set(['ImportError', 'import', 'ImportWarning'])
|
||||
assert set([c.name for c in Script("import").completions()]) == wanted
|
||||
if not is_py26: # python 2.6 doesn't always come with a library `import*`.
|
||||
assert len(Script("import import", path='').completions()) > 0
|
||||
|
||||
# 111
|
||||
assert Script("from datetime import").completions()[0].name == 'import'
|
||||
assert Script("from datetime import ").completions()
|
||||
|
||||
|
||||
def test_imports_on_global_namespace_without_path():
|
||||
"""If the path is None, there shouldn't be any import problem"""
|
||||
completions = Script("import operator").completions()
|
||||
assert [c.name for c in completions] == ['operator']
|
||||
completions = Script("import operator", path='example.py').completions()
|
||||
assert [c.name for c in completions] == ['operator']
|
||||
|
||||
# the first one has a path the second doesn't
|
||||
completions = Script("import keyword", path='example.py').completions()
|
||||
assert [c.name for c in completions] == ['keyword']
|
||||
completions = Script("import keyword").completions()
|
||||
assert [c.name for c in completions] == ['keyword']
|
||||
|
||||
|
||||
def test_named_import():
|
||||
"""named import - jedi-vim issue #8"""
|
||||
s = "import time as dt"
|
||||
assert len(Script(s, 1, 15, '/').goto_definitions()) == 1
|
||||
assert len(Script(s, 1, 10, '/').goto_definitions()) == 1
|
||||
|
||||
|
||||
@pytest.mark.skipif('True', reason='The nested import stuff is still very messy.')
|
||||
def test_goto_following_on_imports():
|
||||
s = "import multiprocessing.dummy; multiprocessing.dummy"
|
||||
g = Script(s).goto_assignments()
|
||||
assert len(g) == 1
|
||||
assert (g[0].line, g[0].column) != (0, 0)
|
||||
|
||||
|
||||
def test_after_from():
|
||||
def check(source, result, column=None):
|
||||
completions = Script(source, column=column).completions()
|
||||
assert [c.name for c in completions] == result
|
||||
|
||||
check('\nfrom os. ', ['path'])
|
||||
check('\nfrom os ', ['import'])
|
||||
check('from os ', ['import'])
|
||||
check('\nfrom os import whatever', ['import'], len('from os im'))
|
||||
|
||||
check('from os\\\n', ['import'])
|
||||
check('from os \\\n', ['import'])
|
||||
|
||||
|
||||
def test_path_issues():
|
||||
"""
|
||||
See pull request #684 for details.
|
||||
"""
|
||||
source = '''from datetime import '''
|
||||
assert jedi.Script(source).completions()
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
"""
|
||||
Tests of various import related things that could not be tested with "Black Box
|
||||
Tests".
|
||||
"""
|
||||
from jedi import Script
|
||||
from .helpers import cwd_at
|
||||
from jedi._compatibility import is_py26
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_goto_definition_on_import():
|
||||
assert Script("import sys_blabla", 1, 8).goto_definitions() == []
|
||||
assert len(Script("import sys", 1, 8).goto_definitions()) == 1
|
||||
|
||||
|
||||
@cwd_at('jedi')
|
||||
def test_complete_on_empty_import():
|
||||
assert Script("from datetime import").completions()[0].name == 'import'
|
||||
# should just list the files in the directory
|
||||
assert 10 < len(Script("from .", path='whatever.py').completions()) < 30
|
||||
|
||||
# Global import
|
||||
assert len(Script("from . import", 1, 5, 'whatever.py').completions()) > 30
|
||||
# relative import
|
||||
assert 10 < len(Script("from . import", 1, 6, 'whatever.py').completions()) < 30
|
||||
|
||||
# Global import
|
||||
assert len(Script("from . import classes", 1, 5, 'whatever.py').completions()) > 30
|
||||
# relative import
|
||||
assert 10 < len(Script("from . import classes", 1, 6, 'whatever.py').completions()) < 30
|
||||
|
||||
wanted = set(['ImportError', 'import', 'ImportWarning'])
|
||||
assert set([c.name for c in Script("import").completions()]) == wanted
|
||||
if not is_py26: # python 2.6 doesn't always come with a library `import*`.
|
||||
assert len(Script("import import", path='').completions()) > 0
|
||||
|
||||
# 111
|
||||
assert Script("from datetime import").completions()[0].name == 'import'
|
||||
assert Script("from datetime import ").completions()
|
||||
|
||||
|
||||
def test_imports_on_global_namespace_without_path():
|
||||
"""If the path is None, there shouldn't be any import problem"""
|
||||
completions = Script("import operator").completions()
|
||||
assert [c.name for c in completions] == ['operator']
|
||||
completions = Script("import operator", path='example.py').completions()
|
||||
assert [c.name for c in completions] == ['operator']
|
||||
|
||||
# the first one has a path the second doesn't
|
||||
completions = Script("import keyword", path='example.py').completions()
|
||||
assert [c.name for c in completions] == ['keyword']
|
||||
completions = Script("import keyword").completions()
|
||||
assert [c.name for c in completions] == ['keyword']
|
||||
|
||||
|
||||
def test_named_import():
|
||||
"""named import - jedi-vim issue #8"""
|
||||
s = "import time as dt"
|
||||
assert len(Script(s, 1, 15, '/').goto_definitions()) == 1
|
||||
assert len(Script(s, 1, 10, '/').goto_definitions()) == 1
|
||||
|
||||
|
||||
@pytest.mark.skipif('True', reason='The nested import stuff is still very messy.')
|
||||
def test_goto_following_on_imports():
|
||||
s = "import multiprocessing.dummy; multiprocessing.dummy"
|
||||
g = Script(s).goto_assignments()
|
||||
assert len(g) == 1
|
||||
assert (g[0].line, g[0].column) != (0, 0)
|
||||
|
||||
|
||||
def test_after_from():
|
||||
def check(source, result, column=None):
|
||||
completions = Script(source, column=column).completions()
|
||||
assert [c.name for c in completions] == result
|
||||
|
||||
check('\nfrom os. ', ['path'])
|
||||
check('\nfrom os ', ['import'])
|
||||
check('from os ', ['import'])
|
||||
check('\nfrom os import whatever', ['import'], len('from os im'))
|
||||
|
||||
check('from os\\\n', ['import'])
|
||||
check('from os \\\n', ['import'])
|
||||
@@ -1,61 +0,0 @@
|
||||
"""
|
||||
Test the Jedi "System" which means for example to test if imports are
|
||||
correctly used.
|
||||
"""
|
||||
|
||||
import os
|
||||
import inspect
|
||||
|
||||
import jedi
|
||||
|
||||
|
||||
def test_settings_module():
|
||||
"""
|
||||
jedi.settings and jedi.cache.settings must be the same module.
|
||||
"""
|
||||
from jedi import cache
|
||||
from jedi import settings
|
||||
assert cache.settings is settings
|
||||
|
||||
|
||||
def test_no_duplicate_modules():
|
||||
"""
|
||||
Make sure that import hack works as expected.
|
||||
|
||||
Jedi does an import hack (see: jedi/__init__.py) to have submodules
|
||||
with circular dependencies. The modules in this circular dependency
|
||||
"loop" must be imported by ``import <module>`` rather than normal
|
||||
``from jedi import <module>`` (or ``from . jedi ...``). This test
|
||||
make sure that this is satisfied.
|
||||
|
||||
See also:
|
||||
|
||||
- `#160 <https://github.com/davidhalter/jedi/issues/160>`_
|
||||
- `#161 <https://github.com/davidhalter/jedi/issues/161>`_
|
||||
"""
|
||||
import sys
|
||||
jedipath = os.path.dirname(os.path.abspath(jedi.__file__))
|
||||
|
||||
def is_submodule(m):
|
||||
try:
|
||||
filepath = m.__file__
|
||||
except AttributeError:
|
||||
return False
|
||||
return os.path.abspath(filepath).startswith(jedipath)
|
||||
|
||||
modules = list(filter(is_submodule, sys.modules.values()))
|
||||
top_modules = [m for m in modules if not m.__name__.startswith('jedi.')]
|
||||
for m in modules:
|
||||
if m is jedi:
|
||||
# py.test automatically improts `jedi.*` when --doctest-modules
|
||||
# is given. So this test cannot succeeds.
|
||||
continue
|
||||
for tm in top_modules:
|
||||
try:
|
||||
imported = getattr(m, tm.__name__)
|
||||
except AttributeError:
|
||||
continue
|
||||
if inspect.ismodule(imported):
|
||||
# module could have a function with the same name, e.g.
|
||||
# `keywords.keywords`.
|
||||
assert imported is tm
|
||||
@@ -1,13 +0,0 @@
|
||||
from jedi._compatibility import u
|
||||
from jedi.parser.python import parse
|
||||
|
||||
|
||||
def test_basic_parsing():
|
||||
def compare(string):
|
||||
"""Generates the AST object and then regenerates the code."""
|
||||
assert parse(string).get_code() == string
|
||||
|
||||
compare(u('\na #pass\n'))
|
||||
compare(u('wblabla* 1\t\n'))
|
||||
compare(u('def x(a, b:3): pass\n'))
|
||||
compare(u('assert foo\n'))
|
||||
@@ -2,7 +2,6 @@ from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
|
||||
import jedi
|
||||
from jedi import debug
|
||||
from jedi.common import splitlines
|
||||
from jedi import cache
|
||||
@@ -386,38 +385,6 @@ def test_node_insertion(differ):
|
||||
differ.parse(code2, parsers=1, copies=2)
|
||||
|
||||
|
||||
def test_add_to_end():
|
||||
"""
|
||||
fast_parser doesn't parse everything again. It just updates with the
|
||||
help of caches, this is an example that didn't work.
|
||||
"""
|
||||
|
||||
a = dedent("""\
|
||||
class Abc():
|
||||
def abc(self):
|
||||
self.x = 3
|
||||
|
||||
class Two(Abc):
|
||||
def g(self):
|
||||
self
|
||||
""") # ^ here is the first completion
|
||||
|
||||
b = " def h(self):\n" \
|
||||
" self."
|
||||
|
||||
def complete(code, line=None, column=None):
|
||||
script = jedi.Script(code, line, column, 'example.py')
|
||||
assert script.completions()
|
||||
_assert_valid_graph(script._get_module())
|
||||
|
||||
complete(a, 7, 12)
|
||||
complete(a + b)
|
||||
|
||||
a = a[:-1] + '.\n'
|
||||
complete(a, 7, 13)
|
||||
complete(a + b)
|
||||
|
||||
|
||||
def test_whitespace_at_end(differ):
|
||||
code = dedent('str\n\n')
|
||||
|
||||
|
||||
@@ -8,13 +8,11 @@ However the tests might still be relevant for the parser.
|
||||
|
||||
from textwrap import dedent
|
||||
|
||||
import jedi
|
||||
from jedi._compatibility import u
|
||||
from jedi.parser.python import parse
|
||||
|
||||
|
||||
def test_carriage_return_splitting():
|
||||
source = u(dedent('''
|
||||
source = dedent('''
|
||||
|
||||
|
||||
|
||||
@@ -22,50 +20,18 @@ def test_carriage_return_splitting():
|
||||
|
||||
class Foo():
|
||||
pass
|
||||
'''))
|
||||
''')
|
||||
source = source.replace('\n', '\r\n')
|
||||
module = parse(source)
|
||||
assert [n.value for lst in module.get_used_names().values() for n in lst] == ['Foo']
|
||||
|
||||
|
||||
def test_class_in_docstr():
|
||||
"""
|
||||
Regression test for a problem with classes in docstrings.
|
||||
"""
|
||||
a = '"\nclasses\n"'
|
||||
jedi.Script(a, 1, 0)._get_module()
|
||||
|
||||
b = a + '\nimport os'
|
||||
assert jedi.Script(b, 4, 8).goto_assignments()
|
||||
|
||||
|
||||
def check_p(src, number_parsers_used, number_of_splits=None, number_of_misses=0):
|
||||
if number_of_splits is None:
|
||||
number_of_splits = number_parsers_used
|
||||
|
||||
def check_p(src):
|
||||
module_node = parse(src)
|
||||
|
||||
assert src == module_node.get_code()
|
||||
return module_node
|
||||
|
||||
|
||||
def test_if():
|
||||
src = dedent('''\
|
||||
def func():
|
||||
x = 3
|
||||
if x:
|
||||
def y():
|
||||
return x
|
||||
return y()
|
||||
|
||||
func()
|
||||
''')
|
||||
|
||||
# Two parsers needed, one for pass and one for the function.
|
||||
check_p(src, 2)
|
||||
assert [d.name for d in jedi.Script(src, 8, 6).goto_definitions()] == ['int']
|
||||
|
||||
|
||||
def test_for():
|
||||
src = dedent("""\
|
||||
for a in [1,2]:
|
||||
@@ -74,7 +40,7 @@ def test_for():
|
||||
for a1 in 1,"":
|
||||
a1
|
||||
""")
|
||||
check_p(src, 1)
|
||||
check_p(src)
|
||||
|
||||
|
||||
def test_class_with_class_var():
|
||||
@@ -85,7 +51,7 @@ def test_class_with_class_var():
|
||||
self.foo = 4
|
||||
pass
|
||||
""")
|
||||
check_p(src, 3)
|
||||
check_p(src)
|
||||
|
||||
|
||||
def test_func_with_if():
|
||||
@@ -99,7 +65,7 @@ def test_func_with_if():
|
||||
else:
|
||||
return a
|
||||
""")
|
||||
check_p(src, 1)
|
||||
check_p(src)
|
||||
|
||||
|
||||
def test_decorator():
|
||||
@@ -109,7 +75,7 @@ def test_decorator():
|
||||
def dec(self, a):
|
||||
return a
|
||||
""")
|
||||
check_p(src, 2)
|
||||
check_p(src)
|
||||
|
||||
|
||||
def test_nested_funcs():
|
||||
@@ -119,25 +85,7 @@ def test_nested_funcs():
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
""")
|
||||
check_p(src, 3)
|
||||
|
||||
|
||||
def test_class_and_if():
|
||||
src = dedent("""\
|
||||
class V:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
if 1:
|
||||
c = 3
|
||||
|
||||
def a_func():
|
||||
return 1
|
||||
|
||||
# COMMENT
|
||||
a_func()""")
|
||||
check_p(src, 5, 5)
|
||||
assert [d.name for d in jedi.Script(src).goto_definitions()] == ['int']
|
||||
check_p(src)
|
||||
|
||||
|
||||
def test_multi_line_params():
|
||||
@@ -148,7 +96,7 @@ def test_multi_line_params():
|
||||
|
||||
foo = 1
|
||||
""")
|
||||
check_p(src, 2)
|
||||
check_p(src)
|
||||
|
||||
|
||||
def test_class_func_if():
|
||||
@@ -162,7 +110,7 @@ def test_class_func_if():
|
||||
|
||||
pass
|
||||
""")
|
||||
check_p(src, 3)
|
||||
check_p(src)
|
||||
|
||||
|
||||
def test_multi_line_for():
|
||||
@@ -173,7 +121,7 @@ def test_multi_line_for():
|
||||
|
||||
pass
|
||||
""")
|
||||
check_p(src, 1)
|
||||
check_p(src)
|
||||
|
||||
|
||||
def test_wrong_indentation():
|
||||
@@ -183,7 +131,7 @@ def test_wrong_indentation():
|
||||
b
|
||||
a
|
||||
""")
|
||||
#check_p(src, 1)
|
||||
check_p(src)
|
||||
|
||||
src = dedent("""\
|
||||
def complex():
|
||||
@@ -195,7 +143,7 @@ def test_wrong_indentation():
|
||||
def other():
|
||||
pass
|
||||
""")
|
||||
check_p(src, 3)
|
||||
check_p(src)
|
||||
|
||||
|
||||
def test_strange_parentheses():
|
||||
@@ -206,7 +154,7 @@ def test_strange_parentheses():
|
||||
def x():
|
||||
pass
|
||||
""")
|
||||
check_p(src, 2)
|
||||
check_p(src)
|
||||
|
||||
|
||||
def test_fake_parentheses():
|
||||
@@ -224,7 +172,7 @@ def test_fake_parentheses():
|
||||
def z():
|
||||
pass
|
||||
""")
|
||||
check_p(src, 3, 2, 1)
|
||||
check_p(src)
|
||||
|
||||
|
||||
def test_additional_indent():
|
||||
@@ -234,45 +182,7 @@ def test_additional_indent():
|
||||
pass
|
||||
''')
|
||||
|
||||
check_p(source, 2)
|
||||
|
||||
|
||||
def test_incomplete_function():
|
||||
source = '''return ImportErr'''
|
||||
|
||||
script = jedi.Script(dedent(source), 1, 3)
|
||||
assert script.completions()
|
||||
|
||||
|
||||
def test_string_literals():
|
||||
"""Simplified case of jedi-vim#377."""
|
||||
source = dedent("""
|
||||
x = ur'''
|
||||
|
||||
def foo():
|
||||
pass
|
||||
""")
|
||||
|
||||
script = jedi.Script(dedent(source))
|
||||
assert script._get_module().tree_node.end_pos == (6, 0)
|
||||
assert script.completions()
|
||||
|
||||
|
||||
def test_decorator_string_issue():
|
||||
"""
|
||||
Test case from #589
|
||||
"""
|
||||
source = dedent('''\
|
||||
"""
|
||||
@"""
|
||||
def bla():
|
||||
pass
|
||||
|
||||
bla.''')
|
||||
|
||||
s = jedi.Script(source)
|
||||
assert s.completions()
|
||||
assert s._get_module().tree_node.get_code() == source
|
||||
check_p(source)
|
||||
|
||||
|
||||
def test_round_trip():
|
||||
@@ -292,4 +202,4 @@ def test_parentheses_in_string():
|
||||
import abc
|
||||
|
||||
abc.''')
|
||||
check_p(code, 2, 1, 1)
|
||||
check_p(code)
|
||||
|
||||
@@ -4,61 +4,21 @@ from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
|
||||
import jedi
|
||||
from jedi._compatibility import u, is_py3
|
||||
from jedi.parser.python import parse, load_grammar
|
||||
from jedi.parser.python import tree
|
||||
from jedi.common import splitlines
|
||||
from jedi.parser_utils import get_statement_of_position, \
|
||||
clean_scope_docstring, safe_literal_eval
|
||||
|
||||
|
||||
def test_user_statement_on_import():
|
||||
"""github #285"""
|
||||
s = "from datetime import (\n" \
|
||||
" time)"
|
||||
def test_basic_parsing():
|
||||
def compare(string):
|
||||
"""Generates the AST object and then regenerates the code."""
|
||||
assert parse(string).get_code() == string
|
||||
|
||||
for pos in [(2, 1), (2, 4)]:
|
||||
p = parse(s)
|
||||
stmt = get_statement_of_position(p, pos)
|
||||
assert isinstance(stmt, tree.Import)
|
||||
assert [n.value for n in stmt.get_defined_names()] == ['time']
|
||||
|
||||
|
||||
class TestCallAndName():
|
||||
def get_call(self, source):
|
||||
# Get the simple_stmt and then the first one.
|
||||
simple_stmt = parse(source).children[0]
|
||||
return simple_stmt.children[0]
|
||||
|
||||
def test_name_and_call_positions(self):
|
||||
name = self.get_call('name\nsomething_else')
|
||||
assert name.value == 'name'
|
||||
assert name.start_pos == (1, 0)
|
||||
assert name.end_pos == (1, 4)
|
||||
|
||||
leaf = self.get_call('1.0\n')
|
||||
assert leaf.value == '1.0'
|
||||
assert safe_literal_eval(leaf.value) == 1.0
|
||||
assert leaf.start_pos == (1, 0)
|
||||
assert leaf.end_pos == (1, 3)
|
||||
|
||||
def test_call_type(self):
|
||||
call = self.get_call('hello')
|
||||
assert isinstance(call, tree.Name)
|
||||
|
||||
def test_literal_type(self):
|
||||
literal = self.get_call('1.0')
|
||||
assert isinstance(literal, tree.Literal)
|
||||
assert type(safe_literal_eval(literal.value)) == float
|
||||
|
||||
literal = self.get_call('1')
|
||||
assert isinstance(literal, tree.Literal)
|
||||
assert type(safe_literal_eval(literal.value)) == int
|
||||
|
||||
literal = self.get_call('"hello"')
|
||||
assert isinstance(literal, tree.Literal)
|
||||
assert safe_literal_eval(literal.value) == 'hello'
|
||||
compare('\na #pass\n')
|
||||
compare('wblabla* 1\t\n')
|
||||
compare('def x(a, b:3): pass\n')
|
||||
compare('assert foo\n')
|
||||
|
||||
|
||||
class TestSubscopes():
|
||||
@@ -125,33 +85,6 @@ def test_incomplete_list_comprehension():
|
||||
['error_node', 'error_node', 'newline', 'endmarker']
|
||||
|
||||
|
||||
def test_hex_values_in_docstring():
|
||||
source = r'''
|
||||
def foo(object):
|
||||
"""
|
||||
\xff
|
||||
"""
|
||||
return 1
|
||||
'''
|
||||
|
||||
doc = clean_scope_docstring(next(parse(source).iter_funcdefs()))
|
||||
if is_py3:
|
||||
assert doc == '\xff'
|
||||
else:
|
||||
assert doc == u('<EFBFBD>')
|
||||
|
||||
|
||||
def test_error_correction_with():
|
||||
source = """
|
||||
with open() as f:
|
||||
try:
|
||||
f."""
|
||||
comps = jedi.Script(source).completions()
|
||||
assert len(comps) > 30
|
||||
# `open` completions have a closed attribute.
|
||||
assert [1 for c in comps if c.name == 'closed']
|
||||
|
||||
|
||||
def test_newline_positions():
|
||||
endmarker = parse('a\n').children[-1]
|
||||
assert endmarker.end_pos == (2, 0)
|
||||
|
||||
@@ -6,7 +6,6 @@ import pytest
|
||||
|
||||
from jedi.parser.python import parse
|
||||
from jedi.parser.python import tree
|
||||
from jedi.parser_utils import get_doc_with_call_signature, get_call_signature
|
||||
|
||||
|
||||
class TestsFunctionAndLambdaParsing(object):
|
||||
@@ -14,13 +13,11 @@ class TestsFunctionAndLambdaParsing(object):
|
||||
FIXTURES = [
|
||||
('def my_function(x, y, z) -> str:\n return x + y * z\n', {
|
||||
'name': 'my_function',
|
||||
'call_sig': 'my_function(x, y, z)',
|
||||
'params': ['x', 'y', 'z'],
|
||||
'annotation': "str",
|
||||
}),
|
||||
('lambda x, y, z: x + y * z\n', {
|
||||
'name': '<lambda>',
|
||||
'call_sig': '<lambda>(x, y, z)',
|
||||
'params': ['x', 'y', 'z'],
|
||||
}),
|
||||
]
|
||||
@@ -62,9 +59,3 @@ class TestsFunctionAndLambdaParsing(object):
|
||||
assert node.annotation is None
|
||||
else:
|
||||
assert node.annotation.value == expected_annotation
|
||||
|
||||
def test_get_call_signature(self, node, expected):
|
||||
assert get_call_signature(node) == expected['call_sig']
|
||||
|
||||
def test_doc(self, node, expected):
|
||||
assert get_doc_with_call_signature(node) == (expected['call_sig'] + '\n\n')
|
||||
|
||||
@@ -147,12 +147,6 @@ class TokenTest(unittest.TestCase):
|
||||
assert safe_literal_eval(string_tok.value) == 'test'
|
||||
|
||||
|
||||
def test_tokenizer_with_string_literal_backslash():
|
||||
import jedi
|
||||
c = jedi.Script("statement = u'foo\\\n'; statement").goto_definitions()
|
||||
assert c[0]._name._context.obj == 'foo'
|
||||
|
||||
|
||||
def test_ur_literals():
|
||||
"""
|
||||
Decided to parse `u''` literals regardless of Python version. This makes
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import jedi
|
||||
|
||||
|
||||
def test_form_feed_characters():
|
||||
s = "\f\nclass Test(object):\n pass"
|
||||
jedi.Script(s, line=2, column=18).call_signatures()
|
||||
87
test/test_parso_integration/test_basic.py
Normal file
87
test/test_parso_integration/test_basic.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from textwrap import dedent
|
||||
|
||||
from jedi.parser.python import parse
|
||||
import jedi
|
||||
|
||||
|
||||
def test_form_feed_characters():
|
||||
s = "\f\nclass Test(object):\n pass"
|
||||
jedi.Script(s, line=2, column=18).call_signatures()
|
||||
|
||||
|
||||
def check_p(src):
|
||||
module_node = parse(src)
|
||||
assert src == module_node.get_code()
|
||||
return module_node
|
||||
|
||||
|
||||
def test_if():
|
||||
src = dedent('''\
|
||||
def func():
|
||||
x = 3
|
||||
if x:
|
||||
def y():
|
||||
return x
|
||||
return y()
|
||||
|
||||
func()
|
||||
''')
|
||||
|
||||
# Two parsers needed, one for pass and one for the function.
|
||||
check_p(src)
|
||||
assert [d.name for d in jedi.Script(src, 8, 6).goto_definitions()] == ['int']
|
||||
|
||||
|
||||
def test_class_and_if():
|
||||
src = dedent("""\
|
||||
class V:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
if 1:
|
||||
c = 3
|
||||
|
||||
def a_func():
|
||||
return 1
|
||||
|
||||
# COMMENT
|
||||
a_func()""")
|
||||
check_p(src)
|
||||
assert [d.name for d in jedi.Script(src).goto_definitions()] == ['int']
|
||||
|
||||
|
||||
def test_add_to_end():
|
||||
"""
|
||||
The diff parser doesn't parse everything again. It just updates with the
|
||||
help of caches, this is an example that didn't work.
|
||||
"""
|
||||
|
||||
a = dedent("""\
|
||||
class Abc():
|
||||
def abc(self):
|
||||
self.x = 3
|
||||
|
||||
class Two(Abc):
|
||||
def g(self):
|
||||
self
|
||||
""") # ^ here is the first completion
|
||||
|
||||
b = " def h(self):\n" \
|
||||
" self."
|
||||
|
||||
def complete(code, line=None, column=None):
|
||||
script = jedi.Script(code, line, column, 'example.py')
|
||||
assert script.completions()
|
||||
|
||||
complete(a, 7, 12)
|
||||
complete(a + b)
|
||||
|
||||
a = a[:-1] + '.\n'
|
||||
complete(a, 7, 13)
|
||||
complete(a + b)
|
||||
|
||||
|
||||
def test_tokenizer_with_string_literal_backslash():
|
||||
import jedi
|
||||
c = jedi.Script("statement = u'foo\\\n'; statement").goto_definitions()
|
||||
assert c[0]._name._context.obj == 'foo'
|
||||
52
test/test_parso_integration/test_error_correction.py
Normal file
52
test/test_parso_integration/test_error_correction.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from textwrap import dedent
|
||||
|
||||
import jedi
|
||||
|
||||
|
||||
def test_error_correction_with():
|
||||
source = """
|
||||
with open() as f:
|
||||
try:
|
||||
f."""
|
||||
comps = jedi.Script(source).completions()
|
||||
assert len(comps) > 30
|
||||
# `open` completions have a closed attribute.
|
||||
assert [1 for c in comps if c.name == 'closed']
|
||||
|
||||
|
||||
def test_string_literals():
|
||||
"""Simplified case of jedi-vim#377."""
|
||||
source = dedent("""
|
||||
x = ur'''
|
||||
|
||||
def foo():
|
||||
pass
|
||||
""")
|
||||
|
||||
script = jedi.Script(dedent(source))
|
||||
assert script._get_module().tree_node.end_pos == (6, 0)
|
||||
assert script.completions()
|
||||
|
||||
|
||||
def test_incomplete_function():
|
||||
source = '''return ImportErr'''
|
||||
|
||||
script = jedi.Script(dedent(source), 1, 3)
|
||||
assert script.completions()
|
||||
|
||||
|
||||
def test_decorator_string_issue():
|
||||
"""
|
||||
Test case from #589
|
||||
"""
|
||||
source = dedent('''\
|
||||
"""
|
||||
@"""
|
||||
def bla():
|
||||
pass
|
||||
|
||||
bla.''')
|
||||
|
||||
s = jedi.Script(source)
|
||||
assert s.completions()
|
||||
assert s._get_module().tree_node.get_code() == source
|
||||
84
test/test_parso_integration/test_parser_utils.py
Normal file
84
test/test_parso_integration/test_parser_utils.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from jedi._compatibility import u, is_py3
|
||||
from jedi import parser_utils
|
||||
from jedi.parser.python import parse
|
||||
from jedi.parser.python import tree
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestCallAndName():
|
||||
def get_call(self, source):
|
||||
# Get the simple_stmt and then the first one.
|
||||
simple_stmt = parse(source).children[0]
|
||||
return simple_stmt.children[0]
|
||||
|
||||
def test_name_and_call_positions(self):
|
||||
name = self.get_call('name\nsomething_else')
|
||||
assert name.value == 'name'
|
||||
assert name.start_pos == (1, 0)
|
||||
assert name.end_pos == (1, 4)
|
||||
|
||||
leaf = self.get_call('1.0\n')
|
||||
assert leaf.value == '1.0'
|
||||
assert parser_utils.safe_literal_eval(leaf.value) == 1.0
|
||||
assert leaf.start_pos == (1, 0)
|
||||
assert leaf.end_pos == (1, 3)
|
||||
|
||||
def test_call_type(self):
|
||||
call = self.get_call('hello')
|
||||
assert isinstance(call, tree.Name)
|
||||
|
||||
def test_literal_type(self):
|
||||
literal = self.get_call('1.0')
|
||||
assert isinstance(literal, tree.Literal)
|
||||
assert type(parser_utils.safe_literal_eval(literal.value)) == float
|
||||
|
||||
literal = self.get_call('1')
|
||||
assert isinstance(literal, tree.Literal)
|
||||
assert type(parser_utils.safe_literal_eval(literal.value)) == int
|
||||
|
||||
literal = self.get_call('"hello"')
|
||||
assert isinstance(literal, tree.Literal)
|
||||
assert parser_utils.safe_literal_eval(literal.value) == 'hello'
|
||||
|
||||
|
||||
def test_user_statement_on_import():
|
||||
"""github #285"""
|
||||
s = "from datetime import (\n" \
|
||||
" time)"
|
||||
|
||||
for pos in [(2, 1), (2, 4)]:
|
||||
p = parse(s)
|
||||
stmt = parser_utils.get_statement_of_position(p, pos)
|
||||
assert isinstance(stmt, tree.Import)
|
||||
assert [n.value for n in stmt.get_defined_names()] == ['time']
|
||||
|
||||
|
||||
def test_hex_values_in_docstring():
|
||||
source = r'''
|
||||
def foo(object):
|
||||
"""
|
||||
\xff
|
||||
"""
|
||||
return 1
|
||||
'''
|
||||
|
||||
doc = parser_utils.clean_scope_docstring(next(parse(source).iter_funcdefs()))
|
||||
if is_py3:
|
||||
assert doc == '\xff'
|
||||
else:
|
||||
assert doc == u('<EFBFBD>')
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'code,call_signature', [
|
||||
('def my_function(x, y, z) -> str:\n return', 'my_function(x, y, z)'),
|
||||
('lambda x, y, z: x + y * z\n', '<lambda>(x, y, z)')
|
||||
])
|
||||
def test_get_call_signature(code, call_signature):
|
||||
node = parse(code).children[0]
|
||||
if node.type == 'simple_stmt':
|
||||
node = node.children[0]
|
||||
assert parser_utils.get_call_signature(node) == call_signature
|
||||
|
||||
assert parser_utils.get_doc_with_call_signature(node) == (call_signature + '\n\n')
|
||||
@@ -51,3 +51,19 @@ class TestSpeed(TestCase):
|
||||
with open('speed/precedence.py') as f:
|
||||
line = len(f.read().splitlines())
|
||||
assert jedi.Script(line=line, path='speed/precedence.py').goto_definitions()
|
||||
|
||||
@_check_speed(0.1)
|
||||
def test_no_repr_computation(self):
|
||||
"""
|
||||
For Interpreter completion aquisition of sourcefile can trigger
|
||||
unwanted computation of repr(). Exemple : big pandas data.
|
||||
See issue #919.
|
||||
"""
|
||||
class SlowRepr():
|
||||
"class to test what happens if __repr__ is very slow."
|
||||
def some_method(self):
|
||||
pass
|
||||
def __repr__(self):
|
||||
time.sleep(0.2)
|
||||
test = SlowRepr()
|
||||
jedi.Interpreter('test.som', [locals()]).completions()
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import jedi
|
||||
|
||||
def test_path_issues():
|
||||
"""
|
||||
See pull request #684 for details.
|
||||
"""
|
||||
source = '''from datetime import '''
|
||||
assert jedi.Script(source).completions()
|
||||
Reference in New Issue
Block a user