1
0
forked from VimPlug/jedi

Merge branch 'dev'

This commit is contained in:
Dave Halter
2017-08-12 22:46:24 +02:00
43 changed files with 580 additions and 542 deletions

View File

@@ -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.

View File

@@ -22,7 +22,7 @@ All nodes and leaves have these methods/properties:
Python Parser Tree
-----------------
------------------
.. automodule:: jedi.parser.python.tree
:members:

View File

@@ -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:

View File

@@ -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':

View File

@@ -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

View File

@@ -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 ''

View File

@@ -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'):

View File

@@ -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

View File

@@ -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__)

View File

@@ -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

View File

@@ -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):

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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):

View File

@@ -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)

View File

@@ -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):

View File

@@ -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])

View File

@@ -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

View File

@@ -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

View File

@@ -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\\``.

View File

@@ -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('''

View File

@@ -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()

View File

@@ -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'])

View File

@@ -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

View File

@@ -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'))

View File

@@ -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')

View File

@@ -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)

View File

@@ -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)

View File

@@ -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')

View File

@@ -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

View File

@@ -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()

View 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'

View 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

View 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')

View File

@@ -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()

View File

@@ -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()

View File

@@ -1,5 +1,5 @@
[tox]
envlist = py26, py27, py33, py34, py35
envlist = py26, py27, py33, py34, py35, py36
[testenv]
deps =
pytest>=2.3.5