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> Mathias Rav (@Mortal) <rav@cs.au.dk>
Daniel Fiterman (@dfit99) <fitermandaniel2@gmail.com> Daniel Fiterman (@dfit99) <fitermandaniel2@gmail.com>
Simon Ruggier (@sruggier) Simon Ruggier (@sruggier)
Élie Gouzien (@ElieGouzien)
Note: (@user) means a github user name. Note: (@user) means a github user name.

View File

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

View File

@@ -166,14 +166,6 @@ try:
except NameError: except NameError:
unicode = str 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 # exec function
if is_py3: if is_py3:

View File

@@ -342,7 +342,7 @@ class Script(object):
elif isinstance(node, tree.Import): elif isinstance(node, tree.Import):
import_names = set(node.get_defined_names()) import_names = set(node.get_defined_names())
if node.is_nested(): 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: for n in import_names:
imports.infer_import(context, n) imports.infer_import(context, n)
elif node.type == 'expr_stmt': 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 instance
from jedi.evaluate import imports from jedi.evaluate import imports
from jedi.evaluate import compiled 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.evaluate.imports import ImportName
from jedi.api.keywords import KeywordName from jedi.api.keywords import KeywordName
@@ -538,7 +538,12 @@ class Definition(BaseDefinition):
typ = 'def' typ = 'def'
return typ + ' ' + u(self._name.string_name) return typ + ' ' + u(self._name.string_name)
elif typ == 'param': 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() definition = tree_name.get_definition()
# Remove the prefix, because that's not what we want for get_code # 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' api_type = 'keyword'
def __init__(self, evaluator, name): def __init__(self, evaluator, name):
self.evaluator = evaluator
self.string_name = name self.string_name = name
self.parent_context = evaluator.BUILTINS self.parent_context = evaluator.BUILTINS
def eval(self): def eval(self):
return set() return set()
def infer(self):
return [Keyword(self.evaluator, self.string_name, (0, 0))]
class Keyword(object): class Keyword(object):
api_type = 'keyword' api_type = 'keyword'
@@ -100,9 +104,8 @@ class Keyword(object):
""" For a `parsing.Name` like comparision """ """ For a `parsing.Name` like comparision """
return [self.name] return [self.name]
@property def py__doc__(self, include_call_signature=False):
def docstr(self): return imitate_pydoc(self.name.string_name)
return imitate_pydoc(self.name)
def __repr__(self): def __repr__(self):
return '<%s: %s>' % (type(self).__name__, self.name) return '<%s: %s>' % (type(self).__name__, self.name)
@@ -136,6 +139,6 @@ def imitate_pydoc(string):
return '' return ''
try: try:
return pydoc_topics.topics[label] if pydoc_topics else '' return pydoc_topics.topics[label].strip() if pydoc_topics else ''
except KeyError: except KeyError:
return '' return ''

View File

@@ -194,7 +194,7 @@ class Evaluator(object):
if_stmt = if_stmt.parent if_stmt = if_stmt.parent
if if_stmt.type in ('if_stmt', 'for_stmt'): if if_stmt.type in ('if_stmt', 'for_stmt'):
break break
if if_stmt.is_scope(): if parser_utils.is_scope(if_stmt):
if_stmt = None if_stmt = None
break break
predefined_if_name_dict = context.predefined_names.get(if_stmt) predefined_if_name_dict = context.predefined_names.get(if_stmt)
@@ -337,7 +337,7 @@ class Evaluator(object):
# This is the first global lookup. # This is the first global lookup.
stmt = atom.get_definition() stmt = atom.get_definition()
if stmt.type == 'comp_for': 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': if stmt is None or stmt.type != 'expr_stmt':
# We only need to adjust the start_pos for statements, because # We only need to adjust the start_pos for statements, because
# there the name cannot be used. # there the name cannot be used.
@@ -537,7 +537,7 @@ class Evaluator(object):
while True: while True:
node = node.parent node = node.parent
if node.is_scope(): if parser_utils.is_scope(node):
return node return node
elif node.type in ('argument', 'testlist_comp'): elif node.type in ('argument', 'testlist_comp'):
if node.children[1].type == 'comp_for': if node.children[1].type == 'comp_for':
@@ -553,7 +553,7 @@ class Evaluator(object):
return base_context return base_context
is_funcdef = scope_node.type in ('funcdef', 'lambdef') 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) parent_context = from_scope_node(parent_scope, child_is_funcdef=is_funcdef)
if is_funcdef: if is_funcdef:
@@ -586,7 +586,7 @@ class Evaluator(object):
base_node = base_context.tree_node 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 scope_node = node
else: else:
if node.parent.type in ('funcdef', 'classdef'): 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): for result in self.evaluator.execute(bltn_obj, params):
yield result yield result
def is_scope(self):
return True
def get_self_attributes(self): def get_self_attributes(self):
return [] # Instance compatibility return [] # Instance compatibility

View File

@@ -116,8 +116,21 @@ def _load_module(evaluator, path, python_object):
return module 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): def find_syntax_node_name(evaluator, python_object):
try: try:
if not source_findable(python_object):
raise TypeError # Prevents computation of `repr` within inspect.
path = inspect.getsourcefile(python_object) path = inspect.getsourcefile(python_object)
except TypeError: except TypeError:
# The type might not be known (e.g. class_with_dict.__weakref__) # 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 import imports
from jedi.evaluate.param import TreeArguments, create_default_param from jedi.evaluate.param import TreeArguments, create_default_param
from jedi.common import to_list, unite from jedi.common import to_list, unite
from jedi.parser_utils import get_parent_scope
MAX_PARAM_SEARCHES = 20 MAX_PARAM_SEARCHES = 20
@@ -103,7 +104,7 @@ def _search_function_executions(evaluator, module_context, funcdef):
func_string_name = funcdef.name.value func_string_name = funcdef.name.value
compare_node = funcdef compare_node = funcdef
if func_string_name == '__init__': if func_string_name == '__init__':
cls = funcdef.get_parent_scope() cls = get_parent_scope(funcdef)
if isinstance(cls, tree.Class): if isinstance(cls, tree.Class):
func_string_name = cls.name.value func_string_name = cls.name.value
compare_node = cls compare_node = cls

View File

@@ -7,6 +7,7 @@ from abc import abstractmethod
from jedi.parser.tree import search_ancestor from jedi.parser.tree import search_ancestor
from jedi.evaluate import flow_analysis from jedi.evaluate import flow_analysis
from jedi.common import to_list, unite from jedi.common import to_list, unite
from jedi.parser_utils import get_parent_scope
class AbstractNameDefinition(object): class AbstractNameDefinition(object):
@@ -189,7 +190,7 @@ class ParserTreeFilter(AbstractUsedNamesFilter):
if parent.type == 'trailer': if parent.type == 'trailer':
return False return False
base_node = parent if parent.type in ('classdef', 'funcdef') else name 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): def _check_flows(self, names):
for name in sorted(names, key=lambda name: name.start_pos, reverse=True): 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 import helpers
from jedi.evaluate.filters import get_global_filters from jedi.evaluate.filters import get_global_filters
from jedi.evaluate.context import ContextualizedName, ContextualizedNode from jedi.evaluate.context import ContextualizedName, ContextualizedNode
from jedi.parser_utils import is_scope, get_parent_scope
class NameFinder(object): class NameFinder(object):
@@ -106,7 +107,7 @@ class NameFinder(object):
if self._context.predefined_names: if self._context.predefined_names:
# TODO is this ok? node might not always be a tree.Name # TODO is this ok? node might not always be a tree.Name
node = self._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 node = node.parent
if node.type in ("if_stmt", "for_stmt", "comp_for"): if node.type in ("if_stmt", "for_stmt", "comp_for"):
try: try:
@@ -160,7 +161,7 @@ class NameFinder(object):
if base_node.type == 'comp_for': if base_node.type == 'comp_for':
return types return types
while True: 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, n = _check_flow_information(self._name_context, flow_scope,
self._name, self._position) self._name, self._position)
if n is not None: if n is not None:
@@ -298,7 +299,7 @@ def _check_flow_information(context, flow, search_name, pos):
return None return None
result = None result = None
if flow.is_scope(): if is_scope(flow):
# Check for asserts. # Check for asserts.
module_node = flow.get_root_node() module_node = flow.get_root_node()
try: 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): class Status(object):
lookup_table = {} lookup_table = {}
@@ -32,14 +34,14 @@ UNSURE = Status(None, 'unsure')
def _get_flow_scopes(node): def _get_flow_scopes(node):
while True: while True:
node = node.get_parent_scope(include_flows=True) node = get_parent_scope(node, include_flows=True)
if node is None or node.is_scope(): if node is None or is_scope(node):
return return
yield node yield node
def reachability_check(context, context_scope, node, origin_scope=None): 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: if origin_scope is not None:
origin_flow_scopes = list(_get_flow_scopes(origin_scope)) origin_flow_scopes = list(_get_flow_scopes(origin_scope))
node_flow_scopes = list(_get_flow_scopes(node)) node_flow_scopes = list(_get_flow_scopes(node))
@@ -95,7 +97,7 @@ def _break_check(context, context_scope, flow_scope, node):
return reachable return reachable
if context_scope != flow_scope and context_scope != flow_scope.parent: 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) return reachable & _break_check(context, context_scope, flow_scope, node)
else: else:
return reachable return reachable

View File

@@ -3,6 +3,7 @@ from itertools import chain
from contextlib import contextmanager from contextlib import contextmanager
from jedi.parser.python import tree from jedi.parser.python import tree
from jedi.parser_utils import get_parent_scope
def deep_ast_copy(obj): 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 # 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. # node have the parent module and not suite as all the others.
# Therefore it's important to catch that case. # 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 return names

View File

@@ -37,7 +37,7 @@ from jedi.evaluate.filters import AbstractNameDefinition
@memoize_default(default=set()) @memoize_default(default=set())
def infer_import(context, tree_name, is_goto=False): def infer_import(context, tree_name, is_goto=False):
module_context = context.get_root_context() 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) import_path = import_node.get_path_for_name(tree_name)
from_import_name = None from_import_name = None
evaluator = context.evaluator 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 import representation as er
from jedi.evaluate.dynamic import search_params from jedi.evaluate.dynamic import search_params
from jedi.evaluate import iterable from jedi.evaluate import iterable
from jedi.parser_utils import get_parent_scope
class AbstractInstanceContext(Context): class AbstractInstanceContext(Context):
@@ -151,7 +152,7 @@ class AbstractInstanceContext(Context):
def create_instance_context(self, class_context, node): def create_instance_context(self, class_context, node):
if node.parent.type in ('funcdef', 'classdef'): if node.parent.type in ('funcdef', 'classdef'):
node = node.parent node = node.parent
scope = node.get_parent_scope() scope = get_parent_scope(node)
if scope == class_context.tree_node: if scope == class_context.tree_node:
return class_context return class_context
else: else:
@@ -189,13 +190,18 @@ class CompiledInstance(AbstractInstanceContext):
return compiled.CompiledContextName(self, self.class_context.name.string_name) return compiled.CompiledContextName(self, self.class_context.name.string_name)
def create_instance_context(self, class_context, node): 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 return class_context
else: else:
return super(CompiledInstance, self).create_instance_context(class_context, node) return super(CompiledInstance, self).create_instance_context(class_context, node)
class TreeInstance(AbstractInstanceContext): 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 @property
def name(self): def name(self):
return filters.ContextName(self, self.class_context.name.tree_name) return filters.ContextName(self, self.class_context.name.tree_name)
@@ -332,7 +338,7 @@ class InstanceClassFilter(filters.ParserTreeFilter):
while node is not None: while node is not None:
if node == self._parser_scope or node == self.context: if node == self._parser_scope or node == self.context:
return True return True
node = node.get_parent_scope() node = get_parent_scope(node)
return False return False
def _access_possible(self, name): def _access_possible(self, name):

View File

@@ -276,7 +276,7 @@ class FunctionContext(use_metaclass(CachedMetaClass, context.TreeContext)):
def py__class__(self): def py__class__(self):
# This differentiation is only necessary for Python2. Python3 does not # This differentiation is only necessary for Python2. Python3 does not
# use a different method class. # 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' name = 'METHOD_CLASS'
else: else:
name = 'FUNCTION_CLASS' name = 'FUNCTION_CLASS'
@@ -350,8 +350,8 @@ class FunctionExecutionContext(context.TreeContext):
@recursion.execution_recursion_decorator(default=iter([])) @recursion.execution_recursion_decorator(default=iter([]))
def get_yield_values(self): def get_yield_values(self):
for_parents = [(y, tree.search_ancestor(y, ('for_stmt', 'funcdef', for_parents = [(y, tree.search_ancestor(y, 'for_stmt', 'funcdef',
'while_stmt', 'if_stmt'))) 'while_stmt', 'if_stmt'))
for y in self.tree_node.iter_yield_exprs()] for y in self.tree_node.iter_yield_exprs()]
# Calculate if the yields are placed within the same for loop. # Calculate if the yields are placed within the same for loop.
@@ -476,7 +476,7 @@ class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext)):
modules = [] modules = []
for i in self.tree_node.iter_imports(): for i in self.tree_node.iter_imports():
if i.is_star_import(): if i.is_star_import():
name = i.star_import_name() name = i.get_paths()[-1][-1]
new = imports.infer_import(self, name) new = imports.infer_import(self, name)
for module in new: for module in new:
if isinstance(module, ModuleContext): 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: if grammar is None:
grammar = load_grammar() grammar = load_grammar()
if path is not None:
path = os.path.expanduser(path)
if cache and not code and path is not None: if cache and not code and path is not None:
# In this case we do actual caching. We just try to load it. # In this case we do actual caching. We just try to load it.
module_node = load_module(grammar, path) module_node = load_module(grammar, path)

View File

@@ -144,22 +144,9 @@ class Parser(BaseParser):
elif symbol == 'suite' and len(nodes) > 1: elif symbol == 'suite' and len(nodes) > 1:
# suites without an indent in them get discarded. # suites without an indent in them get discarded.
break 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 return index, symbol, nodes
index, symbol, nodes = current_suite(stack) 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) # 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): 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>] [<ImportName: import os@1,0>]
""" """
from itertools import chain
from jedi._compatibility import utf8_repr, unicode from jedi._compatibility import utf8_repr, unicode
from jedi.parser.tree import Node, BaseNode, Leaf, ErrorNode, ErrorLeaf, \ from jedi.parser.tree import Node, BaseNode, Leaf, ErrorNode, ErrorLeaf, \
search_ancestor search_ancestor
@@ -61,18 +59,10 @@ class DocstringMixin(object):
class PythonMixin(object): class PythonMixin(object):
def get_parent_scope(self, include_flows=False): """
""" Some Python specific utitilies.
Returns the underlying scope. """
""" __slots__ = ()
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
def get_definition(self): def get_definition(self):
if self.type in ('newline', 'endmarker'): if self.type in ('newline', 'endmarker'):
@@ -92,10 +82,6 @@ class PythonMixin(object):
break break
return scope 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): def get_name_of_position(self, position):
for c in self.children: for c in self.children:
if isinstance(c, Leaf): if isinstance(c, Leaf):
@@ -269,9 +255,6 @@ class Scope(PythonBaseNode, DocstringMixin):
return scan(self.children) return scan(self.children)
def is_scope(self):
return True
def get_suite(self): def get_suite(self):
""" """
Returns the part that is executed by the function. Returns the part that is executed by the function.
@@ -311,7 +294,7 @@ class Module(Scope):
# the future print statement). # the future print statement).
for imp in self.iter_imports(): for imp in self.iter_imports():
if imp.type == 'import_from' and imp.level == 0: 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] names = [name.value for name in path]
if len(names) == 2 and names[0] == '__future__': if len(names) == 2 and names[0] == '__future__':
yield names[1] yield names[1]
@@ -328,6 +311,10 @@ class Module(Scope):
return False return False
def get_used_names(self): 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: if self._used_names is None:
# Don't directly use self._used_names to eliminate a lookup. # Don't directly use self._used_names to eliminate a lookup.
dct = {} dct = {}
@@ -358,9 +345,15 @@ class ClassOrFunc(Scope):
@property @property
def name(self): def name(self):
"""
Returns the `Name` leaf that defines the function or class name.
"""
return self.children[1] return self.children[1]
def get_decorators(self): def get_decorators(self):
"""
:return list of Decorator:
"""
decorated = self.parent decorated = self.parent
if decorated.type == 'decorated': if decorated.type == 'decorated':
if decorated.children[0].type == 'decorators': if decorated.children[0].type == 'decorators':
@@ -389,6 +382,10 @@ class Class(ClassOrFunc):
super(Class, self).__init__(children) super(Class, self).__init__(children)
def get_super_arglist(self): 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 if self.children[2] != '(': # Has no parentheses
return None return None
else: else:
@@ -452,14 +449,15 @@ class Function(ClassOrFunc):
""" """
Used to store the parsed contents of a python function. Used to store the parsed contents of a python function.
Children: Children::
0. <Keyword: def>
1. <Name> 0. <Keyword: def>
2. parameter list (including open-paren and close-paren <Operator>s) 1. <Name>
3. or 5. <Operator: :> 2. parameter list (including open-paren and close-paren <Operator>s)
4. or 6. Node() representing function body 3. or 5. <Operator: :>
3. -> (if annotation is also present) 4. or 6. Node() representing function body
4. annotation (if present) 3. -> (if annotation is also present)
4. annotation (if present)
""" """
type = 'funcdef' type = 'funcdef'
@@ -514,20 +512,16 @@ class Function(ClassOrFunc):
except IndexError: except IndexError:
return None return None
def _get_paramlist_code(self):
return self.children[2].get_code()
class Lambda(Function): class Lambda(Function):
""" """
Lambdas are basically trimmed functions, so give it the same interface. Lambdas are basically trimmed functions, so give it the same interface.
Children: Children::
0. <Keyword: lambda> 0. <Keyword: lambda>
*. <Param x> for each argument x *. <Param x> for each argument x
-2. <Operator: :> -2. <Operator: :>
-1. Node() representing body -1. Node() representing body
""" """
type = 'lambdef' type = 'lambdef'
__slots__ = () __slots__ = ()
@@ -545,9 +539,6 @@ class Lambda(Function):
""" """
raise AttributeError("lambda is not named.") 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): def _get_param_nodes(self):
return self.children[1:-2] return self.children[1:-2]
@@ -556,7 +547,6 @@ class Lambda(Function):
""" """
Returns `None`, lambdas don't have annotations. Returns `None`, lambdas don't have annotations.
""" """
# lambda functions do not support annotations
return None return None
def __repr__(self): def __repr__(self):
@@ -650,6 +640,10 @@ class WithStmt(Flow):
__slots__ = () __slots__ = ()
def get_defined_names(self): def get_defined_names(self):
"""
Returns the a list of `Name` that the with statement defines. The
defined names are set after `as`.
"""
names = [] names = []
for with_item in self.children[1:-2:2]: for with_item in self.children[1:-2:2]:
# Check with items for 'as' names. # Check with items for 'as' names.
@@ -669,13 +663,18 @@ class Import(PythonBaseNode):
__slots__ = () __slots__ = ()
def get_path_for_name(self, name): 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: try:
# The name may be an alias. If it is, just map it back to the name. # 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: except KeyError:
pass pass
for path in self.paths(): for path in self.get_paths():
if name in path: if name in path:
return path[:path.index(name) + 1] return path[:path.index(name) + 1]
raise ValueError('Name should be defined in the import itself') raise ValueError('Name should be defined in the import itself')
@@ -692,9 +691,14 @@ class ImportFrom(Import):
__slots__ = () __slots__ = ()
def get_defined_names(self): 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()] 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.""" """Mapping from alias to its corresponding name."""
return dict((alias, name) for name, alias in self._as_name_tuples() return dict((alias, name) for name, alias in self._as_name_tuples()
if alias is not None) if alias is not None)
@@ -738,16 +742,12 @@ class ImportFrom(Import):
else: else:
yield as_name.children[::2] # yields x, y -> ``x as y`` yield as_name.children[::2] # yields x, y -> ``x as y``
def star_import_name(self): def get_paths(self):
"""
The last name defined in a star import.
"""
return self.paths()[-1][-1]
def paths(self):
""" """
The import paths defined in an import statement. Typically an array The import paths defined in an import statement. Typically an array
like this: ``[<Name: datetime>, <Name: date>]``. like this: ``[<Name: datetime>, <Name: date>]``.
:return list of list of Name:
""" """
dotted = self.get_from_names() dotted = self.get_from_names()
@@ -762,6 +762,11 @@ class ImportName(Import):
__slots__ = () __slots__ = ()
def get_defined_names(self): 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()] return [alias or path[0] for path, alias in self._dotted_as_names()]
@property @property
@@ -769,7 +774,7 @@ class ImportName(Import):
"""The level parameter of ``__import__``.""" """The level parameter of ``__import__``."""
return 0 # Obviously 0 for imports without from. 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()] return [path for path, alias in self._dotted_as_names()]
def _dotted_as_names(self): def _dotted_as_names(self):
@@ -799,10 +804,13 @@ class ImportName(Import):
import foo.bar import foo.bar
""" """
return [1 for path, alias in self._dotted_as_names() return bool([1 for path, alias in self._dotted_as_names()
if alias is None and len(path) > 1] 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() return dict((alias, path[-1]) for path, alias in self._dotted_as_names()
if alias is not None) if alias is not None)
@@ -880,14 +888,18 @@ class ExprStmt(PythonBaseNode, DocstringMixin):
__slots__ = () __slots__ = ()
def get_defined_names(self): def get_defined_names(self):
"""
Returns a list of `Name` defined before the `=` sign.
"""
names = [] names = []
if self.children[1].type == 'annassign': if self.children[1].type == 'annassign':
names = _defined_names(self.children[0]) names = _defined_names(self.children[0])
return list(chain.from_iterable( return [
_defined_names(self.children[i]) name
for i in range(0, len(self.children) - 2, 2) for i in range(0, len(self.children) - 2, 2)
if '=' in self.children[i + 1].value) if '=' in self.children[i + 1].value
) + names for name in _defined_names(self.children[i])
] + names
def get_rhs(self): def get_rhs(self):
"""Returns the right-hand-side of the equals.""" """Returns the right-hand-side of the equals."""
@@ -925,6 +937,10 @@ class Param(PythonBaseNode):
@property @property
def star_count(self): 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] first = self.children[0]
if first in ('*', '**'): if first in ('*', '**'):
return len(first.value) return len(first.value)
@@ -932,6 +948,10 @@ class Param(PythonBaseNode):
@property @property
def default(self): def default(self):
"""
The default is the test node that appears after the `=`. Is `None` in
case no default is present.
"""
try: try:
return self.children[int(self.children[0] in ('*', '**')) + 2] return self.children[int(self.children[0] in ('*', '**')) + 2]
except IndexError: except IndexError:
@@ -939,6 +959,10 @@ class Param(PythonBaseNode):
@property @property
def annotation(self): def annotation(self):
"""
The default is the test node that appears after `->`. Is `None` in case
no annotation is present.
"""
tfpdef = self._tfpdef() tfpdef = self._tfpdef()
if tfpdef.type == 'tfpdef': if tfpdef.type == 'tfpdef':
assert tfpdef.children[1] == ":" assert tfpdef.children[1] == ":"
@@ -957,6 +981,9 @@ class Param(PythonBaseNode):
@property @property
def name(self): def name(self):
"""
The `Name` leaf of the param.
"""
if self._tfpdef().type == 'tfpdef': if self._tfpdef().type == 'tfpdef':
return self._tfpdef().children[0] return self._tfpdef().children[0]
else: else:
@@ -965,7 +992,7 @@ class Param(PythonBaseNode):
@property @property
def position_index(self): 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) index = self.parent.children.index(self)
try: try:
@@ -979,16 +1006,28 @@ class Param(PythonBaseNode):
def get_parent_function(self): 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 children = self.children
if children[-1] == ',': if children[-1] == ',':
children = 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): def __repr__(self):
default = '' if self.default is None else '=%s' % self.default.get_code() default = '' if self.default is None else '=%s' % self.default.get_code()
@@ -999,8 +1038,8 @@ class CompFor(PythonBaseNode):
type = 'comp_for' type = 'comp_for'
__slots__ = () __slots__ = ()
def is_scope(self):
return True
def get_defined_names(self): def get_defined_names(self):
"""
Returns the a list of `Name` that the comprehension defines.
"""
return _defined_names(self.children[1]) 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 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 Recursively looks at the parents of a node and checks if the type names
match. match.
:param node: The node that is looked at. :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. searched for.
""" """
if not isinstance(node_type_or_types, (list, tuple)):
node_type_or_types = (node_type_or_types,)
while True: while True:
node = node.parent 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 return node

View File

@@ -154,7 +154,12 @@ def get_call_signature(funcdef, width=72, call_string=None):
call_string = '<lambda>' call_string = '<lambda>'
else: else:
call_string = funcdef.name.value 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)) return '\n'.join(textwrap.wrap(code, width))
@@ -217,3 +222,21 @@ def get_following_comment_same_line(node):
comment = comment[:comment.index("\n")] comment = comment[:comment.index("\n")]
return comment 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') 'jedi')
cache_directory = os.path.expanduser(_cache_directory) 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 On Linux, this defaults to ``~/.cache/jedi/``, on OS X to
``~/Library/Caches/Jedi/`` and on Windows to ``%APPDATA%\\Jedi\\Jedi\\``. ``~/Library/Caches/Jedi/`` and on Windows to ``%APPDATA%\\Jedi\\Jedi\\``.

View File

@@ -20,7 +20,22 @@ class TestDocstring(unittest.TestCase):
def func(): def func():
'''Docstring of `func`.''' '''Docstring of `func`.'''
func""").goto_definitions() 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') @unittest.skip('need evaluator class for that')
def test_attribute_docstring(self): def test_attribute_docstring(self):
@@ -28,7 +43,7 @@ class TestDocstring(unittest.TestCase):
x = None x = None
'''Docstring of `x`.''' '''Docstring of `x`.'''
x""").goto_definitions() 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') @unittest.skip('need evaluator class for that')
def test_multiple_docstrings(self): def test_multiple_docstrings(self):
@@ -38,7 +53,7 @@ class TestDocstring(unittest.TestCase):
x = func x = func
'''Docstring of `x`.''' '''Docstring of `x`.'''
x""").goto_definitions() 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`.']) self.assertEqual(docs, ['Original docstring.', 'Docstring of `x`.'])
def test_completion(self): def test_completion(self):
@@ -105,6 +120,10 @@ class TestDocstring(unittest.TestCase):
assert '__init__' in names assert '__init__' in names
assert 'mro' not in names # Exists only for types. 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') @unittest.skipIf(numpydoc_unavailable, 'numpydoc module is unavailable')
def test_numpydoc_docstring(self): def test_numpydoc_docstring(self):
s = dedent(''' 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 os
import sys import sys
@@ -7,6 +12,9 @@ import jedi
from jedi._compatibility import find_module_py33, find_module from jedi._compatibility import find_module_py33, find_module
from ..helpers import cwd_at from ..helpers import cwd_at
from jedi import Script
from jedi._compatibility import is_py26
@pytest.mark.skipif('sys.version_info < (3,3)') @pytest.mark.skipif('sys.version_info < (3,3)')
def test_find_module_py33(): 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 # However for performance reasons not all modules are loaded and the
# docstring is empty in this case. # docstring is empty in this case.
assert completions[0].docstring() == '' 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 pytest
import jedi
from jedi import debug from jedi import debug
from jedi.common import splitlines from jedi.common import splitlines
from jedi import cache from jedi import cache
@@ -386,38 +385,6 @@ def test_node_insertion(differ):
differ.parse(code2, parsers=1, copies=2) 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): def test_whitespace_at_end(differ):
code = dedent('str\n\n') 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 from textwrap import dedent
import jedi
from jedi._compatibility import u
from jedi.parser.python import parse from jedi.parser.python import parse
def test_carriage_return_splitting(): def test_carriage_return_splitting():
source = u(dedent(''' source = dedent('''
@@ -22,50 +20,18 @@ def test_carriage_return_splitting():
class Foo(): class Foo():
pass pass
''')) ''')
source = source.replace('\n', '\r\n') source = source.replace('\n', '\r\n')
module = parse(source) module = parse(source)
assert [n.value for lst in module.get_used_names().values() for n in lst] == ['Foo'] assert [n.value for lst in module.get_used_names().values() for n in lst] == ['Foo']
def test_class_in_docstr(): def check_p(src):
"""
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
module_node = parse(src) module_node = parse(src)
assert src == module_node.get_code() assert src == module_node.get_code()
return module_node 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(): def test_for():
src = dedent("""\ src = dedent("""\
for a in [1,2]: for a in [1,2]:
@@ -74,7 +40,7 @@ def test_for():
for a1 in 1,"": for a1 in 1,"":
a1 a1
""") """)
check_p(src, 1) check_p(src)
def test_class_with_class_var(): def test_class_with_class_var():
@@ -85,7 +51,7 @@ def test_class_with_class_var():
self.foo = 4 self.foo = 4
pass pass
""") """)
check_p(src, 3) check_p(src)
def test_func_with_if(): def test_func_with_if():
@@ -99,7 +65,7 @@ def test_func_with_if():
else: else:
return a return a
""") """)
check_p(src, 1) check_p(src)
def test_decorator(): def test_decorator():
@@ -109,7 +75,7 @@ def test_decorator():
def dec(self, a): def dec(self, a):
return a return a
""") """)
check_p(src, 2) check_p(src)
def test_nested_funcs(): def test_nested_funcs():
@@ -119,25 +85,7 @@ def test_nested_funcs():
return func(*args, **kwargs) return func(*args, **kwargs)
return wrapper return wrapper
""") """)
check_p(src, 3) check_p(src)
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']
def test_multi_line_params(): def test_multi_line_params():
@@ -148,7 +96,7 @@ def test_multi_line_params():
foo = 1 foo = 1
""") """)
check_p(src, 2) check_p(src)
def test_class_func_if(): def test_class_func_if():
@@ -162,7 +110,7 @@ def test_class_func_if():
pass pass
""") """)
check_p(src, 3) check_p(src)
def test_multi_line_for(): def test_multi_line_for():
@@ -173,7 +121,7 @@ def test_multi_line_for():
pass pass
""") """)
check_p(src, 1) check_p(src)
def test_wrong_indentation(): def test_wrong_indentation():
@@ -183,7 +131,7 @@ def test_wrong_indentation():
b b
a a
""") """)
#check_p(src, 1) check_p(src)
src = dedent("""\ src = dedent("""\
def complex(): def complex():
@@ -195,7 +143,7 @@ def test_wrong_indentation():
def other(): def other():
pass pass
""") """)
check_p(src, 3) check_p(src)
def test_strange_parentheses(): def test_strange_parentheses():
@@ -206,7 +154,7 @@ def test_strange_parentheses():
def x(): def x():
pass pass
""") """)
check_p(src, 2) check_p(src)
def test_fake_parentheses(): def test_fake_parentheses():
@@ -224,7 +172,7 @@ def test_fake_parentheses():
def z(): def z():
pass pass
""") """)
check_p(src, 3, 2, 1) check_p(src)
def test_additional_indent(): def test_additional_indent():
@@ -234,45 +182,7 @@ def test_additional_indent():
pass pass
''') ''')
check_p(source, 2) check_p(source)
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
def test_round_trip(): def test_round_trip():
@@ -292,4 +202,4 @@ def test_parentheses_in_string():
import abc import abc
abc.''') abc.''')
check_p(code, 2, 1, 1) check_p(code)

View File

@@ -4,61 +4,21 @@ from textwrap import dedent
import pytest import pytest
import jedi
from jedi._compatibility import u, is_py3 from jedi._compatibility import u, is_py3
from jedi.parser.python import parse, load_grammar from jedi.parser.python import parse, load_grammar
from jedi.parser.python import tree from jedi.parser.python import tree
from jedi.common import splitlines 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(): def test_basic_parsing():
"""github #285""" def compare(string):
s = "from datetime import (\n" \ """Generates the AST object and then regenerates the code."""
" time)" assert parse(string).get_code() == string
for pos in [(2, 1), (2, 4)]: compare('\na #pass\n')
p = parse(s) compare('wblabla* 1\t\n')
stmt = get_statement_of_position(p, pos) compare('def x(a, b:3): pass\n')
assert isinstance(stmt, tree.Import) compare('assert foo\n')
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'
class TestSubscopes(): class TestSubscopes():
@@ -125,33 +85,6 @@ def test_incomplete_list_comprehension():
['error_node', 'error_node', 'newline', 'endmarker'] ['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(): def test_newline_positions():
endmarker = parse('a\n').children[-1] endmarker = parse('a\n').children[-1]
assert endmarker.end_pos == (2, 0) 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 parse
from jedi.parser.python import tree from jedi.parser.python import tree
from jedi.parser_utils import get_doc_with_call_signature, get_call_signature
class TestsFunctionAndLambdaParsing(object): class TestsFunctionAndLambdaParsing(object):
@@ -14,13 +13,11 @@ class TestsFunctionAndLambdaParsing(object):
FIXTURES = [ FIXTURES = [
('def my_function(x, y, z) -> str:\n return x + y * z\n', { ('def my_function(x, y, z) -> str:\n return x + y * z\n', {
'name': 'my_function', 'name': 'my_function',
'call_sig': 'my_function(x, y, z)',
'params': ['x', 'y', 'z'], 'params': ['x', 'y', 'z'],
'annotation': "str", 'annotation': "str",
}), }),
('lambda x, y, z: x + y * z\n', { ('lambda x, y, z: x + y * z\n', {
'name': '<lambda>', 'name': '<lambda>',
'call_sig': '<lambda>(x, y, z)',
'params': ['x', 'y', 'z'], 'params': ['x', 'y', 'z'],
}), }),
] ]
@@ -62,9 +59,3 @@ class TestsFunctionAndLambdaParsing(object):
assert node.annotation is None assert node.annotation is None
else: else:
assert node.annotation.value == expected_annotation 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' 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(): def test_ur_literals():
""" """
Decided to parse `u''` literals regardless of Python version. This makes 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: with open('speed/precedence.py') as f:
line = len(f.read().splitlines()) line = len(f.read().splitlines())
assert jedi.Script(line=line, path='speed/precedence.py').goto_definitions() 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] [tox]
envlist = py26, py27, py33, py34, py35 envlist = py26, py27, py33, py34, py35, py36
[testenv] [testenv]
deps = deps =
pytest>=2.3.5 pytest>=2.3.5