mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 22:14:27 +08:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60415033b4 | ||
|
|
a06d760f45 | ||
|
|
b7687fcfb7 | ||
|
|
0ec86d5034 | ||
|
|
cef23f44cd | ||
|
|
e889a4923e | ||
|
|
114aba462c | ||
|
|
26c7cec7b5 | ||
|
|
3e3a33ab79 | ||
|
|
7f386e0e68 | ||
|
|
82d970d2b8 | ||
|
|
3ed9e836cc | ||
|
|
f984e8d6ef | ||
|
|
670cf4d394 | ||
|
|
e85fba844c | ||
|
|
ee5557ddf6 | ||
|
|
42f72b219b | ||
|
|
374721b789 | ||
|
|
01cec186ae | ||
|
|
3fb89f9f9b | ||
|
|
a0b4e76c1a | ||
|
|
97bf83aa03 | ||
|
|
ca7658cab7 | ||
|
|
dd78f4cfbf | ||
|
|
08019075c3 | ||
|
|
943617a94f | ||
|
|
d579c0ad57 | ||
|
|
76c6104415 | ||
|
|
ef9d803ce3 | ||
|
|
a26cb42d07 | ||
|
|
6b9b2836ba | ||
|
|
abdb8de89d | ||
|
|
ac492ef598 | ||
|
|
947bfe7b78 | ||
|
|
be6c90d135 | ||
|
|
8cb059deda | ||
|
|
0f4da5c1cf | ||
|
|
de138e9114 | ||
|
|
15bb9b29a2 | ||
|
|
4c132d94b9 | ||
|
|
925fc89447 | ||
|
|
cb95dbc707 | ||
|
|
1e3b6a201d | ||
|
|
3829ef4785 | ||
|
|
b382f06be0 | ||
|
|
94faceb57c | ||
|
|
a9ff58683e | ||
|
|
fafd6b2ac6 | ||
|
|
344a03e6b2 | ||
|
|
265abe1d08 | ||
|
|
ebdae87821 | ||
|
|
56ec79d62a | ||
|
|
c413b486fb | ||
|
|
cb0a0d228a | ||
|
|
3ae4a154f9 | ||
|
|
aa2dc6be09 | ||
|
|
a62ba86d7b |
@@ -51,5 +51,6 @@ Maksim Novikov (@m-novikov) <mnovikov.work@gmail.com>
|
||||
Tobias Rzepka (@TobiasRzepka)
|
||||
micbou (@micbou)
|
||||
Dima Gerasimov (@karlicoss) <karlicoss@gmail.com>
|
||||
Max Woerner Chase (@mwchase) <max.chase@gmail.com>
|
||||
|
||||
Note: (@user) means a github user name.
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
Changelog
|
||||
---------
|
||||
|
||||
0.14.1 (2019-07-13)
|
||||
+++++++++++++++++++
|
||||
|
||||
- CallSignature.index should now be working a lot better
|
||||
- A couple of smaller bugfixes
|
||||
|
||||
0.14.0 (2019-06-20)
|
||||
+++++++++++++++++++
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ Acknowledgements
|
||||
|
||||
|
||||
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
|
||||
.. _youcompleteme: https://valloric.github.io/YouCompleteMe/
|
||||
.. _youcompleteme: https://github.com/ycm-core/YouCompleteMe
|
||||
.. _deoplete-jedi: https://github.com/zchee/deoplete-jedi
|
||||
.. _completor.vim: https://github.com/maralla/completor.vim
|
||||
.. _Jedi.el: https://github.com/tkf/emacs-jedi
|
||||
|
||||
13
conftest.py
13
conftest.py
@@ -105,6 +105,11 @@ def Script(environment):
|
||||
return partial(jedi.Script, environment=environment)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def names(environment):
|
||||
return partial(jedi.names, environment=environment)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def has_typing(environment):
|
||||
if environment.version_info >= (3, 5, 0):
|
||||
@@ -127,3 +132,11 @@ def skip_python2(environment):
|
||||
# This if is just needed to avoid that tests ever skip way more than
|
||||
# they should for all Python versions.
|
||||
pytest.skip()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def skip_pre_python38(environment):
|
||||
if environment.version_info < (3, 8):
|
||||
# This if is just needed to avoid that tests ever skip way more than
|
||||
# they should for all Python versions.
|
||||
pytest.skip()
|
||||
|
||||
@@ -33,7 +33,7 @@ As you see Jedi is pretty simple and allows you to concentrate on writing a
|
||||
good text editor, while still having very good IDE features for Python.
|
||||
"""
|
||||
|
||||
__version__ = '0.14.0'
|
||||
__version__ = '0.14.1'
|
||||
|
||||
from jedi.api import Script, Interpreter, set_debug_function, \
|
||||
preload_module, names
|
||||
|
||||
@@ -361,9 +361,9 @@ except NameError:
|
||||
FileNotFoundError = IOError
|
||||
|
||||
try:
|
||||
NotADirectoryError = NotADirectoryError
|
||||
IsADirectoryError = IsADirectoryError
|
||||
except NameError:
|
||||
NotADirectoryError = IOError
|
||||
IsADirectoryError = IOError
|
||||
|
||||
try:
|
||||
PermissionError = PermissionError
|
||||
|
||||
@@ -365,19 +365,18 @@ class Script(object):
|
||||
|
||||
:rtype: list of :class:`classes.CallSignature`
|
||||
"""
|
||||
call_signature_details = \
|
||||
helpers.get_call_signature_details(self._module_node, self._pos)
|
||||
if call_signature_details is None:
|
||||
call_details = helpers.get_call_signature_details(self._module_node, self._pos)
|
||||
if call_details is None:
|
||||
return []
|
||||
|
||||
context = self._evaluator.create_context(
|
||||
self._get_module(),
|
||||
call_signature_details.bracket_leaf
|
||||
call_details.bracket_leaf
|
||||
)
|
||||
definitions = helpers.cache_call_signatures(
|
||||
self._evaluator,
|
||||
context,
|
||||
call_signature_details.bracket_leaf,
|
||||
call_details.bracket_leaf,
|
||||
self._code_lines,
|
||||
self._pos
|
||||
)
|
||||
@@ -385,10 +384,7 @@ class Script(object):
|
||||
|
||||
# TODO here we use stubs instead of the actual contexts. We should use
|
||||
# the signatures from stubs, but the actual contexts, probably?!
|
||||
return [classes.CallSignature(self._evaluator, signature,
|
||||
call_signature_details.bracket_leaf.start_pos,
|
||||
call_signature_details.call_index,
|
||||
call_signature_details.keyword_name_str)
|
||||
return [classes.CallSignature(self._evaluator, signature, call_details)
|
||||
for signature in definitions.get_signatures()]
|
||||
|
||||
def _analysis(self):
|
||||
|
||||
@@ -358,9 +358,10 @@ class BaseDefinition(object):
|
||||
return Definition(self._evaluator, context.name)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s full_name=%r, description=%r>" % (
|
||||
return "<%s %sname=%r, description=%r>" % (
|
||||
self.__class__.__name__,
|
||||
self.full_name,
|
||||
'full_' if self.full_name else '',
|
||||
self.full_name or self.name,
|
||||
self.description,
|
||||
)
|
||||
|
||||
@@ -529,17 +530,13 @@ class Definition(BaseDefinition):
|
||||
"""
|
||||
typ = self.type
|
||||
tree_name = self._name.tree_name
|
||||
if typ == 'param':
|
||||
return typ + ' ' + self._name.to_string()
|
||||
if typ in ('function', 'class', 'module', 'instance') or tree_name is None:
|
||||
if typ == 'function':
|
||||
# For the description we want a short and a pythonic way.
|
||||
typ = 'def'
|
||||
return typ + ' ' + self._name.string_name
|
||||
elif typ == 'param':
|
||||
code = search_ancestor(tree_name, 'param').get_code(
|
||||
include_prefix=False,
|
||||
include_comma=False
|
||||
)
|
||||
return typ + ' ' + code
|
||||
|
||||
definition = tree_name.get_definition() or tree_name
|
||||
# Remove the prefix, because that's not what we want for get_code
|
||||
@@ -607,11 +604,9 @@ class CallSignature(Definition):
|
||||
It knows what functions you are currently in. e.g. `isinstance(` would
|
||||
return the `isinstance` function. without `(` it would return nothing.
|
||||
"""
|
||||
def __init__(self, evaluator, signature, bracket_start_pos, index, key_name_str):
|
||||
def __init__(self, evaluator, signature, call_details):
|
||||
super(CallSignature, self).__init__(evaluator, signature.name)
|
||||
self._index = index
|
||||
self._key_name_str = key_name_str
|
||||
self._bracket_start_pos = bracket_start_pos
|
||||
self._call_details = call_details
|
||||
self._signature = signature
|
||||
|
||||
@property
|
||||
@@ -620,26 +615,7 @@ class CallSignature(Definition):
|
||||
The Param index of the current call.
|
||||
Returns None if the index cannot be found in the curent call.
|
||||
"""
|
||||
if self._key_name_str is not None:
|
||||
for i, param in enumerate(self.params):
|
||||
if self._key_name_str == param.name:
|
||||
return i
|
||||
if self.params:
|
||||
param_name = self.params[-1]._name
|
||||
if param_name.tree_name is not None:
|
||||
if param_name.tree_name.get_definition().star_count == 2:
|
||||
return i
|
||||
return None
|
||||
|
||||
if self._index >= len(self.params):
|
||||
for i, param in enumerate(self.params):
|
||||
tree_name = param._name.tree_name
|
||||
if tree_name is not None:
|
||||
# *args case
|
||||
if tree_name.get_definition().star_count == 1:
|
||||
return i
|
||||
return None
|
||||
return self._index
|
||||
return self._call_details.calculate_index(self._signature.get_param_names())
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
@@ -651,7 +627,7 @@ class CallSignature(Definition):
|
||||
The indent of the bracket that is responsible for the last function
|
||||
call.
|
||||
"""
|
||||
return self._bracket_start_pos
|
||||
return self._call_details.bracket_leaf.start_pos
|
||||
|
||||
@property
|
||||
def _params_str(self):
|
||||
@@ -662,7 +638,7 @@ class CallSignature(Definition):
|
||||
return '<%s: %s index=%r params=[%s]>' % (
|
||||
type(self).__name__,
|
||||
self._name.string_name,
|
||||
self._index,
|
||||
self.index,
|
||||
self._params_str,
|
||||
)
|
||||
|
||||
|
||||
@@ -171,7 +171,10 @@ class Completion:
|
||||
elif type_ == 'for_stmt':
|
||||
allowed_transitions.append('else')
|
||||
|
||||
completion_names = list(self._get_keyword_completion_names(allowed_transitions))
|
||||
completion_names = []
|
||||
current_line = self._code_lines[self._position[0] - 1][:self._position[1]]
|
||||
if not current_line or current_line[-1] in ' \t.;':
|
||||
completion_names += self._get_keyword_completion_names(allowed_transitions)
|
||||
|
||||
if any(t in allowed_transitions for t in (PythonTokenTypes.NAME,
|
||||
PythonTokenTypes.INDENT)):
|
||||
|
||||
@@ -8,7 +8,7 @@ from textwrap import dedent
|
||||
from parso.python.parser import Parser
|
||||
from parso.python import tree
|
||||
|
||||
from jedi._compatibility import u
|
||||
from jedi._compatibility import u, Parameter
|
||||
from jedi.evaluate.base_context import NO_CONTEXTS
|
||||
from jedi.evaluate.syntax_tree import eval_atom
|
||||
from jedi.evaluate.helpers import evaluate_call_of_leaf
|
||||
@@ -159,10 +159,139 @@ def evaluate_goto_definition(evaluator, context, leaf):
|
||||
return definitions
|
||||
|
||||
|
||||
CallSignatureDetails = namedtuple(
|
||||
'CallSignatureDetails',
|
||||
class CallDetails(object):
|
||||
def __init__(self, bracket_leaf, children, position):
|
||||
['bracket_leaf', 'call_index', 'keyword_name_str']
|
||||
)
|
||||
self.bracket_leaf = bracket_leaf
|
||||
self._children = children
|
||||
self._position = position
|
||||
|
||||
@property
|
||||
def index(self):
|
||||
return _get_index_and_key(self._children, self._position)[0]
|
||||
|
||||
@property
|
||||
def keyword_name_str(self):
|
||||
return _get_index_and_key(self._children, self._position)[1]
|
||||
|
||||
def calculate_index(self, param_names):
|
||||
positional_count = 0
|
||||
used_names = set()
|
||||
star_count = -1
|
||||
args = list(_iter_arguments(self._children, self._position))
|
||||
if not args:
|
||||
if param_names:
|
||||
return 0
|
||||
else:
|
||||
return None
|
||||
|
||||
is_kwarg = False
|
||||
for i, (star_count, key_start, had_equal) in enumerate(args):
|
||||
is_kwarg |= had_equal | (star_count == 2)
|
||||
if star_count:
|
||||
pass # For now do nothing, we don't know what's in there here.
|
||||
else:
|
||||
if i + 1 != len(args): # Not last
|
||||
if had_equal:
|
||||
used_names.add(key_start)
|
||||
else:
|
||||
positional_count += 1
|
||||
|
||||
for i, param_name in enumerate(param_names):
|
||||
kind = param_name.get_kind()
|
||||
|
||||
if not is_kwarg:
|
||||
if kind == Parameter.VAR_POSITIONAL:
|
||||
return i
|
||||
if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
|
||||
if i == positional_count:
|
||||
return i
|
||||
|
||||
if key_start is not None and not star_count == 1 or star_count == 2:
|
||||
if param_name.string_name not in used_names \
|
||||
and (kind == Parameter.KEYWORD_ONLY
|
||||
or kind == Parameter.POSITIONAL_OR_KEYWORD
|
||||
and positional_count <= i):
|
||||
if star_count:
|
||||
return i
|
||||
if had_equal:
|
||||
if param_name.string_name == key_start:
|
||||
return i
|
||||
else:
|
||||
if param_name.string_name.startswith(key_start):
|
||||
return i
|
||||
|
||||
if kind == Parameter.VAR_KEYWORD:
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
def _iter_arguments(nodes, position):
|
||||
def remove_after_pos(name):
|
||||
if name.type != 'name':
|
||||
return None
|
||||
return name.value[:position[1] - name.start_pos[1]]
|
||||
|
||||
# Returns Generator[Tuple[star_count, Optional[key_start: str], had_equal]]
|
||||
nodes_before = [c for c in nodes if c.start_pos < position]
|
||||
if nodes_before[-1].type == 'arglist':
|
||||
for x in _iter_arguments(nodes_before[-1].children, position):
|
||||
yield x # Python 2 :(
|
||||
return
|
||||
|
||||
previous_node_yielded = False
|
||||
stars_seen = 0
|
||||
for i, node in enumerate(nodes_before):
|
||||
if node.type == 'argument':
|
||||
previous_node_yielded = True
|
||||
first = node.children[0]
|
||||
second = node.children[1]
|
||||
if second == '=':
|
||||
if second.start_pos < position:
|
||||
yield 0, first.value, True
|
||||
else:
|
||||
yield 0, remove_after_pos(first), False
|
||||
elif first in ('*', '**'):
|
||||
yield len(first.value), remove_after_pos(second), False
|
||||
else:
|
||||
# Must be a Comprehension
|
||||
first_leaf = node.get_first_leaf()
|
||||
if first_leaf.type == 'name' and first_leaf.start_pos >= position:
|
||||
yield 0, remove_after_pos(first_leaf), False
|
||||
else:
|
||||
yield 0, None, False
|
||||
stars_seen = 0
|
||||
elif node.type in ('testlist', 'testlist_star_expr'): # testlist is Python 2
|
||||
for n in node.children[::2]:
|
||||
if n.type == 'star_expr':
|
||||
stars_seen = 1
|
||||
n = n.children[1]
|
||||
yield stars_seen, remove_after_pos(n), False
|
||||
stars_seen = 0
|
||||
# The count of children is even if there's a comma at the end.
|
||||
previous_node_yielded = bool(len(node.children) % 2)
|
||||
elif isinstance(node, tree.PythonLeaf) and node.value == ',':
|
||||
if not previous_node_yielded:
|
||||
yield stars_seen, '', False
|
||||
stars_seen = 0
|
||||
previous_node_yielded = False
|
||||
elif isinstance(node, tree.PythonLeaf) and node.value in ('*', '**'):
|
||||
stars_seen = len(node.value)
|
||||
elif node == '=' and nodes_before[-1]:
|
||||
previous_node_yielded = True
|
||||
before = nodes_before[i - 1]
|
||||
if before.type == 'name':
|
||||
yield 0, before.value, True
|
||||
else:
|
||||
yield 0, None, False
|
||||
# Just ignore the star that is probably a syntax error.
|
||||
stars_seen = 0
|
||||
|
||||
if not previous_node_yielded:
|
||||
if nodes_before[-1].type == 'name':
|
||||
yield stars_seen, remove_after_pos(nodes_before[-1]), False
|
||||
else:
|
||||
yield stars_seen, '', False
|
||||
|
||||
|
||||
def _get_index_and_key(nodes, position):
|
||||
@@ -171,11 +300,10 @@ def _get_index_and_key(nodes, position):
|
||||
"""
|
||||
nodes_before = [c for c in nodes if c.start_pos < position]
|
||||
if nodes_before[-1].type == 'arglist':
|
||||
nodes_before = [c for c in nodes_before[-1].children if c.start_pos < position]
|
||||
return _get_index_and_key(nodes_before[-1].children, position)
|
||||
|
||||
key_str = None
|
||||
|
||||
if nodes_before:
|
||||
last = nodes_before[-1]
|
||||
if last.type == 'argument' and last.children[1] == '=' \
|
||||
and last.children[1].end_pos <= position:
|
||||
@@ -187,7 +315,7 @@ def _get_index_and_key(nodes, position):
|
||||
return nodes_before.count(','), key_str
|
||||
|
||||
|
||||
def _get_call_signature_details_from_error_node(node, position):
|
||||
def _get_call_signature_details_from_error_node(node, additional_children, position):
|
||||
for index, element in reversed(list(enumerate(node.children))):
|
||||
# `index > 0` means that it's a trailer and not an atom.
|
||||
if element == '(' and element.end_pos <= position and index > 0:
|
||||
@@ -198,10 +326,7 @@ def _get_call_signature_details_from_error_node(node, position):
|
||||
if name is None:
|
||||
continue
|
||||
if name.type == 'name' or name.parent.type in ('trailer', 'atom'):
|
||||
return CallSignatureDetails(
|
||||
element,
|
||||
*_get_index_and_key(children, position)
|
||||
)
|
||||
return CallDetails(element, children + additional_children, position)
|
||||
|
||||
|
||||
def get_call_signature_details(module, position):
|
||||
@@ -213,6 +338,7 @@ def get_call_signature_details(module, position):
|
||||
return None
|
||||
|
||||
if leaf == ')':
|
||||
# TODO is this ok?
|
||||
if leaf.end_pos == position:
|
||||
leaf = leaf.get_next_leaf()
|
||||
|
||||
@@ -225,18 +351,25 @@ def get_call_signature_details(module, position):
|
||||
# makes it feel strange to have a call signature.
|
||||
return None
|
||||
|
||||
for n in node.children[::-1]:
|
||||
if n.start_pos < position and n.type == 'error_node':
|
||||
result = _get_call_signature_details_from_error_node(n, position)
|
||||
additional_children = []
|
||||
for n in reversed(node.children):
|
||||
if n.start_pos < position:
|
||||
if n.type == 'error_node':
|
||||
result = _get_call_signature_details_from_error_node(
|
||||
n, additional_children, position
|
||||
)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
additional_children[0:0] = n.children
|
||||
continue
|
||||
additional_children.insert(0, n)
|
||||
|
||||
if node.type == 'trailer' and node.children[0] == '(':
|
||||
leaf = node.get_previous_leaf()
|
||||
if leaf is None:
|
||||
return None
|
||||
return CallSignatureDetails(
|
||||
node.children[0], *_get_index_and_key(node.children, position))
|
||||
return CallDetails(node.children[0], node.children, position)
|
||||
|
||||
node = node.parent
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import json
|
||||
|
||||
from jedi._compatibility import FileNotFoundError, NotADirectoryError, PermissionError
|
||||
from jedi._compatibility import FileNotFoundError, PermissionError, IsADirectoryError
|
||||
from jedi.api.environment import SameEnvironment, \
|
||||
get_cached_default_environment
|
||||
from jedi.api.exceptions import WrongVersion
|
||||
@@ -153,7 +153,7 @@ def _is_django_path(directory):
|
||||
try:
|
||||
with open(os.path.join(directory, 'manage.py'), 'rb') as f:
|
||||
return b"DJANGO_SETTINGS_MODULE" in f.read()
|
||||
except (FileNotFoundError, NotADirectoryError, PermissionError):
|
||||
except (FileNotFoundError, IsADirectoryError, PermissionError):
|
||||
return False
|
||||
|
||||
return False
|
||||
@@ -169,7 +169,7 @@ def get_default_project(path=None):
|
||||
for dir in traverse_parents(check, include_current=True):
|
||||
try:
|
||||
return Project.load(dir)
|
||||
except (FileNotFoundError, NotADirectoryError, PermissionError):
|
||||
except (FileNotFoundError, IsADirectoryError, PermissionError):
|
||||
pass
|
||||
|
||||
if first_no_init_file is None:
|
||||
|
||||
@@ -318,11 +318,11 @@ class Evaluator(object):
|
||||
return [TreeNameDefinition(context, name)]
|
||||
elif type_ == 'param':
|
||||
return [ParamName(context, name)]
|
||||
elif type_ in ('funcdef', 'classdef'):
|
||||
return [TreeNameDefinition(context, name)]
|
||||
elif type_ in ('import_from', 'import_name'):
|
||||
module_names = imports.infer_import(context, name, is_goto=True)
|
||||
return module_names
|
||||
else:
|
||||
return [TreeNameDefinition(context, name)]
|
||||
else:
|
||||
contexts = self._follow_error_node_imports_if_possible(context, name)
|
||||
if contexts is not None:
|
||||
|
||||
@@ -241,7 +241,7 @@ class _ContextWrapperBase(HelperContextMixin):
|
||||
return cls(*args, **kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
assert name != '_wrapped_context'
|
||||
assert name != '_wrapped_context', 'Problem with _get_wrapped_context'
|
||||
return getattr(self._wrapped_context, name)
|
||||
|
||||
|
||||
|
||||
@@ -412,7 +412,7 @@ class DirectObjectAccess(object):
|
||||
name=p.name,
|
||||
has_default=p.default is not p.empty,
|
||||
default=self._create_access_path(p.default),
|
||||
default_string=str(p.default),
|
||||
default_string=repr(p.default),
|
||||
has_annotation=p.annotation is not p.empty,
|
||||
annotation=self._create_access_path(p.annotation),
|
||||
annotation_string=str(p.default),
|
||||
@@ -448,6 +448,17 @@ class DirectObjectAccess(object):
|
||||
# the signature. In that case we just want a simple escape for now.
|
||||
raise ValueError
|
||||
|
||||
def get_return_annotation(self):
|
||||
try:
|
||||
o = self._obj.__annotations__.get('return')
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
if o is None:
|
||||
return None
|
||||
|
||||
return self._create_access_path(o)
|
||||
|
||||
def negate(self):
|
||||
return self._create_access_path(-self._obj)
|
||||
|
||||
|
||||
@@ -46,6 +46,11 @@ class CompiledObject(Context):
|
||||
self.access_handle = access_handle
|
||||
|
||||
def py__call__(self, arguments):
|
||||
return_annotation = self.access_handle.get_return_annotation()
|
||||
if return_annotation is not None:
|
||||
# TODO the return annotation may also be a string.
|
||||
return create_from_access_path(self.evaluator, return_annotation).execute_annotation()
|
||||
|
||||
try:
|
||||
self.access_handle.getattr_paths(u'__call__')
|
||||
except AttributeError:
|
||||
@@ -122,6 +127,9 @@ class CompiledObject(Context):
|
||||
signature_params = self.access_handle.get_signature_params()
|
||||
except ValueError: # Has no signature
|
||||
params_str, ret = self._parse_function_doc()
|
||||
if not params_str:
|
||||
tokens = []
|
||||
else:
|
||||
tokens = params_str.split(',')
|
||||
if self.access_handle.ismethoddescriptor():
|
||||
tokens.insert(0, 'self')
|
||||
@@ -276,7 +284,11 @@ class CompiledName(AbstractNameDefinition):
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
return next(iter(self.infer())).api_type
|
||||
api = self.infer()
|
||||
# If we can't find the type, assume it is an instance variable
|
||||
if not api:
|
||||
return "instance"
|
||||
return next(iter(api)).api_type
|
||||
|
||||
@underscore_memoization
|
||||
def infer(self):
|
||||
@@ -285,9 +297,7 @@ class CompiledName(AbstractNameDefinition):
|
||||
)])
|
||||
|
||||
|
||||
class SignatureParamName(AbstractNameDefinition, ParamNameInterface):
|
||||
api_type = u'param'
|
||||
|
||||
class SignatureParamName(ParamNameInterface, AbstractNameDefinition):
|
||||
def __init__(self, compiled_obj, signature_param):
|
||||
self.parent_context = compiled_obj.parent_context
|
||||
self._signature_param = signature_param
|
||||
@@ -297,7 +307,7 @@ class SignatureParamName(AbstractNameDefinition, ParamNameInterface):
|
||||
return self._signature_param.name
|
||||
|
||||
def to_string(self):
|
||||
s = self.string_name
|
||||
s = self._kind_string() + self.string_name
|
||||
if self._signature_param.has_annotation:
|
||||
s += ': ' + self._signature_param.annotation_string
|
||||
if self._signature_param.has_default:
|
||||
@@ -322,9 +332,7 @@ class SignatureParamName(AbstractNameDefinition, ParamNameInterface):
|
||||
return contexts
|
||||
|
||||
|
||||
class UnresolvableParamName(AbstractNameDefinition, ParamNameInterface):
|
||||
api_type = u'param'
|
||||
|
||||
class UnresolvableParamName(ParamNameInterface, AbstractNameDefinition):
|
||||
def __init__(self, compiled_obj, name, default):
|
||||
self.parent_context = compiled_obj.parent_context
|
||||
self.string_name = name
|
||||
|
||||
@@ -4,6 +4,7 @@ Used only for REPL Completion.
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
|
||||
from jedi.parser_utils import get_cached_code_lines
|
||||
|
||||
@@ -131,6 +132,9 @@ def _load_module(evaluator, path):
|
||||
|
||||
def _get_object_to_check(python_object):
|
||||
"""Check if inspect.getfile has a chance to find the source."""
|
||||
if sys.version_info[0] > 2:
|
||||
python_object = inspect.unwrap(python_object)
|
||||
|
||||
if (inspect.ismodule(python_object) or
|
||||
inspect.isclass(python_object) or
|
||||
inspect.ismethod(python_object) or
|
||||
@@ -146,10 +150,7 @@ def _get_object_to_check(python_object):
|
||||
raise TypeError # Prevents computation of `repr` within inspect.
|
||||
|
||||
|
||||
def _find_syntax_node_name(evaluator, access_handle):
|
||||
# TODO accessing this is bad, but it probably doesn't matter that much,
|
||||
# because we're working with interpreteters only here.
|
||||
python_object = access_handle.access._obj
|
||||
def _find_syntax_node_name(evaluator, python_object):
|
||||
try:
|
||||
python_object = _get_object_to_check(python_object)
|
||||
path = inspect.getsourcefile(python_object)
|
||||
@@ -224,10 +225,12 @@ def _create(evaluator, access_handle, parent_context, *args):
|
||||
parent_context=parent_context and parent_context.compiled_object
|
||||
)
|
||||
|
||||
result = _find_syntax_node_name(evaluator, access_handle)
|
||||
# TODO accessing this is bad, but it probably doesn't matter that much,
|
||||
# because we're working with interpreteters only here.
|
||||
python_object = access_handle.access._obj
|
||||
result = _find_syntax_node_name(evaluator, python_object)
|
||||
if result is None:
|
||||
# TODO Care about generics from stuff like `[1]` and don't return like this.
|
||||
python_object = access_handle.access._obj
|
||||
if type(python_object) in (dict, list, tuple):
|
||||
return ContextSet({compiled_object})
|
||||
|
||||
|
||||
@@ -64,8 +64,9 @@ class FunctionMixin(object):
|
||||
origin_scope=origin_scope
|
||||
)
|
||||
else:
|
||||
scope = self.py__class__()
|
||||
for filter in scope.get_filters(search_global=False, origin_scope=origin_scope):
|
||||
cls = self.py__class__()
|
||||
for instance in cls.execute_evaluated():
|
||||
for filter in instance.get_filters(search_global=False, origin_scope=origin_scope):
|
||||
yield filter
|
||||
|
||||
def py__get__(self, instance, class_context):
|
||||
|
||||
@@ -462,12 +462,13 @@ class InstanceClassFilter(AbstractFilter):
|
||||
origin_scope=origin_scope,
|
||||
is_instance=True,
|
||||
))
|
||||
assert isinstance(self._class_filter, ClassFilter), self._class_filter
|
||||
|
||||
def get(self, name):
|
||||
return self._convert(self._class_filter.get(name))
|
||||
return self._convert(self._class_filter.get(name, from_instance=True))
|
||||
|
||||
def values(self):
|
||||
return self._convert(self._class_filter.values())
|
||||
return self._convert(self._class_filter.values(from_instance=True))
|
||||
|
||||
def _convert(self, names):
|
||||
return [LazyInstanceClassName(self._instance, self._class_context, n) for n in names]
|
||||
@@ -503,7 +504,7 @@ class SelfAttributeFilter(ClassFilter):
|
||||
if trailer.type == 'trailer' \
|
||||
and len(trailer.parent.children) == 2 \
|
||||
and trailer.children[0] == '.':
|
||||
if name.is_definition() and self._access_possible(name):
|
||||
if name.is_definition() and self._access_possible(name, from_instance=True):
|
||||
# TODO filter non-self assignments.
|
||||
yield name
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ from jedi.evaluate import compiled
|
||||
from jedi.evaluate.lazy_context import LazyKnownContexts
|
||||
from jedi.evaluate.filters import ParserTreeFilter
|
||||
from jedi.evaluate.names import TreeNameDefinition, ContextName
|
||||
from jedi.evaluate.arguments import unpack_arglist
|
||||
from jedi.evaluate.arguments import unpack_arglist, ValuesArguments
|
||||
from jedi.evaluate.base_context import ContextSet, iterator_to_context_set, \
|
||||
NO_CONTEXTS
|
||||
from jedi.evaluate.context.function import FunctionAndClassBase
|
||||
@@ -109,13 +109,27 @@ class ClassFilter(ParserTreeFilter):
|
||||
node = get_cached_parent_scope(self._used_names, node)
|
||||
return False
|
||||
|
||||
def _access_possible(self, name):
|
||||
def _access_possible(self, name, from_instance=False):
|
||||
# Filter for ClassVar variables
|
||||
# TODO this is not properly done, yet. It just checks for the string
|
||||
# ClassVar in the annotation, which can be quite imprecise. If we
|
||||
# wanted to do this correct, we would have to resolve the ClassVar.
|
||||
if not from_instance:
|
||||
expr_stmt = name.get_definition()
|
||||
if expr_stmt is not None and expr_stmt.type == 'expr_stmt':
|
||||
annassign = expr_stmt.children[1]
|
||||
if annassign.type == 'annassign':
|
||||
# TODO this is not proper matching
|
||||
if 'ClassVar' not in annassign.children[1].get_code():
|
||||
return False
|
||||
|
||||
# Filter for name mangling of private variables like __foo
|
||||
return not name.value.startswith('__') or name.value.endswith('__') \
|
||||
or self._equals_origin_scope()
|
||||
|
||||
def _filter(self, names):
|
||||
def _filter(self, names, from_instance=False):
|
||||
names = super(ClassFilter, self)._filter(names)
|
||||
return [name for name in names if self._access_possible(name)]
|
||||
return [name for name in names if self._access_possible(name, from_instance)]
|
||||
|
||||
|
||||
class ClassMixin(object):
|
||||
@@ -198,8 +212,14 @@ class ClassMixin(object):
|
||||
if not is_instance:
|
||||
from jedi.evaluate.compiled import builtin_from_name
|
||||
type_ = builtin_from_name(self.evaluator, u'type')
|
||||
assert isinstance(type_, ClassContext)
|
||||
if type_ != self:
|
||||
yield next(type_.get_filters())
|
||||
for instance in type_.py__call__(ValuesArguments([])):
|
||||
instance_filters = instance.get_filters()
|
||||
# Filter out self filters
|
||||
next(instance_filters)
|
||||
next(instance_filters)
|
||||
yield next(instance_filters)
|
||||
|
||||
|
||||
class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase)):
|
||||
|
||||
@@ -145,13 +145,18 @@ class ModuleMixin(SubModuleDictMixin):
|
||||
# to push the star imports into Evaluator.module_cache, if we reenable this.
|
||||
@evaluator_method_cache([])
|
||||
def star_imports(self):
|
||||
from jedi.evaluate.imports import infer_import
|
||||
from jedi.evaluate.imports import Importer
|
||||
|
||||
modules = []
|
||||
for i in self.tree_node.iter_imports():
|
||||
if i.is_star_import():
|
||||
name = i.get_paths()[-1][-1]
|
||||
new = infer_import(self, name)
|
||||
new = Importer(
|
||||
self.evaluator,
|
||||
import_path=i.get_paths()[-1],
|
||||
module_context=self,
|
||||
level=i.level
|
||||
).follow()
|
||||
|
||||
for module in new:
|
||||
if isinstance(module, ModuleContext):
|
||||
modules += module.star_imports()
|
||||
|
||||
@@ -74,20 +74,22 @@ class AbstractUsedNamesFilter(AbstractFilter):
|
||||
self._used_names = self._module_node.get_used_names()
|
||||
self.context = context
|
||||
|
||||
def get(self, name):
|
||||
def get(self, name, **filter_kwargs):
|
||||
return self._convert_names(self._filter(
|
||||
_get_definition_names(self._used_names, name)
|
||||
_get_definition_names(self._used_names, name),
|
||||
**filter_kwargs
|
||||
))
|
||||
|
||||
def _convert_names(self, names):
|
||||
return [self.name_class(self.context, name) for name in names]
|
||||
|
||||
def values(self):
|
||||
def values(self, **filter_kwargs):
|
||||
return self._convert_names(
|
||||
name
|
||||
for name_key in self._used_names
|
||||
for name in self._filter(
|
||||
_get_definition_names(self._used_names, name_key)
|
||||
_get_definition_names(self._used_names, name_key),
|
||||
**filter_kwargs
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ from jedi.evaluate.filters import FilterWrapper
|
||||
from jedi.evaluate.names import NameWrapper, AbstractTreeName, \
|
||||
AbstractNameDefinition, ContextName
|
||||
from jedi.evaluate.helpers import is_string
|
||||
from jedi.evaluate.context.klass import ClassMixin
|
||||
from jedi.evaluate.context.klass import ClassMixin, ClassFilter
|
||||
|
||||
_PROXY_CLASS_TYPES = 'Tuple Generic Protocol Callable Type'.split()
|
||||
_TYPE_ALIAS_TYPES = {
|
||||
@@ -54,12 +54,15 @@ class _BaseTypingContext(Context):
|
||||
return self._tree_name
|
||||
|
||||
def get_filters(self, *args, **kwargs):
|
||||
# TODO this is obviously wrong.
|
||||
class EmptyFilter():
|
||||
def get(self, name):
|
||||
# TODO this is obviously wrong. Is it though?
|
||||
class EmptyFilter(ClassFilter):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get(self, name, **kwargs):
|
||||
return []
|
||||
|
||||
def values(self):
|
||||
def values(self, **kwargs):
|
||||
return []
|
||||
|
||||
yield EmptyFilter()
|
||||
@@ -112,6 +115,8 @@ class TypingModuleName(NameWrapper):
|
||||
yield builtin_from_name(evaluator, u'True')
|
||||
elif name == 'overload':
|
||||
yield OverloadFunction.create_cached(evaluator, self.parent_context, self.tree_name)
|
||||
elif name == 'NewType':
|
||||
yield NewTypeFunction.create_cached(evaluator, self.parent_context, self.tree_name)
|
||||
elif name == 'cast':
|
||||
# TODO implement cast
|
||||
yield CastFunction.create_cached(evaluator, self.parent_context, self.tree_name)
|
||||
@@ -456,6 +461,32 @@ class OverloadFunction(_BaseTypingContext):
|
||||
return func_context_set
|
||||
|
||||
|
||||
class NewTypeFunction(_BaseTypingContext):
|
||||
def py__call__(self, arguments):
|
||||
ordered_args = arguments.unpack()
|
||||
next(ordered_args, (None, None))
|
||||
_, second_arg = next(ordered_args, (None, None))
|
||||
if second_arg is None:
|
||||
return NO_CONTEXTS
|
||||
return ContextSet(
|
||||
NewType(
|
||||
self.evaluator,
|
||||
contextualized_node.context,
|
||||
contextualized_node.node,
|
||||
second_arg.infer(),
|
||||
) for contextualized_node in arguments.get_calling_nodes())
|
||||
|
||||
|
||||
class NewType(Context):
|
||||
def __init__(self, evaluator, parent_context, tree_node, type_context_set):
|
||||
super(NewType, self).__init__(evaluator, parent_context)
|
||||
self._type_context_set = type_context_set
|
||||
self.tree_node = tree_node
|
||||
|
||||
def py__call__(self, arguments):
|
||||
return self._type_context_set.execute_annotation()
|
||||
|
||||
|
||||
class CastFunction(_BaseTypingContext):
|
||||
@repack_with_argument_clinic('type, object, /')
|
||||
def py__call__(self, type_context_set, object_context_set):
|
||||
|
||||
@@ -151,6 +151,16 @@ class TreeNameDefinition(AbstractTreeName):
|
||||
|
||||
|
||||
class ParamNameInterface(object):
|
||||
api_type = u'param'
|
||||
|
||||
def _kind_string(self):
|
||||
kind = self.get_kind()
|
||||
if kind == Parameter.VAR_POSITIONAL: # *args
|
||||
return '*'
|
||||
if kind == Parameter.VAR_KEYWORD: # **kwargs
|
||||
return '**'
|
||||
return ''
|
||||
|
||||
def get_kind(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -158,9 +168,7 @@ class ParamNameInterface(object):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ParamName(AbstractTreeName, ParamNameInterface):
|
||||
api_type = u'param'
|
||||
|
||||
class ParamName(ParamNameInterface, AbstractTreeName):
|
||||
def __init__(self, parent_context, tree_name):
|
||||
self.parent_context = parent_context
|
||||
self.tree_name = tree_name
|
||||
@@ -168,6 +176,15 @@ class ParamName(AbstractTreeName, ParamNameInterface):
|
||||
def _get_param_node(self):
|
||||
return search_ancestor(self.tree_name, 'param')
|
||||
|
||||
@property
|
||||
def string_name(self):
|
||||
name = self.tree_name.value
|
||||
if name.startswith('__'):
|
||||
# Params starting with __ are an equivalent to positional only
|
||||
# variables in typeshed.
|
||||
name = name[2:]
|
||||
return name
|
||||
|
||||
def get_kind(self):
|
||||
tree_param = self._get_param_node()
|
||||
if tree_param.star_count == 1: # *args
|
||||
@@ -175,17 +192,29 @@ class ParamName(AbstractTreeName, ParamNameInterface):
|
||||
if tree_param.star_count == 2: # **kwargs
|
||||
return Parameter.VAR_KEYWORD
|
||||
|
||||
# Params starting with __ are an equivalent to positional only
|
||||
# variables in typeshed.
|
||||
if tree_param.name.value.startswith('__'):
|
||||
return Parameter.POSITIONAL_ONLY
|
||||
|
||||
parent = tree_param.parent
|
||||
param_appeared = False
|
||||
for p in parent.children:
|
||||
if param_appeared:
|
||||
if p == '/':
|
||||
return Parameter.POSITIONAL_ONLY
|
||||
else:
|
||||
if p == '*':
|
||||
return Parameter.KEYWORD_ONLY
|
||||
if p.type == 'param':
|
||||
if p.star_count:
|
||||
return Parameter.KEYWORD_ONLY
|
||||
if p == tree_param:
|
||||
break
|
||||
param_appeared = True
|
||||
return Parameter.POSITIONAL_OR_KEYWORD
|
||||
|
||||
def to_string(self):
|
||||
output = self.string_name
|
||||
output = self._kind_string() + self.string_name
|
||||
param_node = self._get_param_node()
|
||||
if param_node.annotation is not None:
|
||||
output += ': ' + param_node.annotation.get_code(include_prefix=False)
|
||||
|
||||
@@ -25,7 +25,9 @@ class AbstractSignature(object):
|
||||
yield '/'
|
||||
is_positional = False
|
||||
|
||||
if kind == Parameter.KEYWORD_ONLY and not is_kw_only:
|
||||
if kind == Parameter.VAR_POSITIONAL:
|
||||
is_kw_only = True
|
||||
elif kind == Parameter.KEYWORD_ONLY and not is_kw_only:
|
||||
yield '*'
|
||||
is_kw_only = True
|
||||
|
||||
|
||||
@@ -19,10 +19,9 @@ from jedi.evaluate.arguments import ValuesArguments, \
|
||||
repack_with_argument_clinic, AbstractArguments, TreeArgumentsWrapper
|
||||
from jedi.evaluate import analysis
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate.context.instance import \
|
||||
AbstractInstanceContext, BoundMethod, InstanceArguments
|
||||
from jedi.evaluate.context.instance import BoundMethod, InstanceArguments
|
||||
from jedi.evaluate.base_context import ContextualizedNode, \
|
||||
NO_CONTEXTS, ContextSet, ContextWrapper
|
||||
NO_CONTEXTS, ContextSet, ContextWrapper, LazyContextWrapper
|
||||
from jedi.evaluate.context import ClassContext, ModuleContext, \
|
||||
FunctionExecutionContext
|
||||
from jedi.evaluate.context import iterable
|
||||
@@ -230,20 +229,38 @@ def builtins_type(objects, bases, dicts):
|
||||
return objects.py__class__()
|
||||
|
||||
|
||||
class SuperInstance(AbstractInstanceContext):
|
||||
class SuperInstance(LazyContextWrapper):
|
||||
"""To be used like the object ``super`` returns."""
|
||||
def __init__(self, evaluator, cls):
|
||||
su = cls.py_mro()[1]
|
||||
super().__init__(evaluator, su and su[0] or self)
|
||||
def __init__(self, evaluator, instance):
|
||||
self.evaluator = evaluator
|
||||
self._instance = instance # Corresponds to super().__self__
|
||||
|
||||
def _get_bases(self):
|
||||
return self._instance.py__class__().py__bases__()
|
||||
|
||||
def _get_wrapped_context(self):
|
||||
objs = self._get_bases()[0].infer().execute_evaluated()
|
||||
if not objs:
|
||||
# This is just a fallback and will only be used, if it's not
|
||||
# possible to find a class
|
||||
return self._instance
|
||||
return next(iter(objs))
|
||||
|
||||
def get_filters(self, search_global=False, until_position=None, origin_scope=None):
|
||||
for b in self._get_bases():
|
||||
for obj in b.infer().execute_evaluated():
|
||||
for f in obj.get_filters():
|
||||
yield f
|
||||
|
||||
|
||||
@argument_clinic('[type[, obj]], /', want_context=True)
|
||||
def builtins_super(types, objects, context):
|
||||
# TODO make this able to detect multiple inheritance super
|
||||
if isinstance(context, FunctionExecutionContext):
|
||||
if isinstance(context.var_args, InstanceArguments):
|
||||
su = context.var_args.instance.py__class__().py__bases__()
|
||||
return su[0].infer().execute_evaluated()
|
||||
instance = context.var_args.instance
|
||||
# TODO if a class is given it doesn't have to be the direct super
|
||||
# class, it can be an anecestor from long ago.
|
||||
return ContextSet({SuperInstance(instance.evaluator, instance)})
|
||||
|
||||
return NO_CONTEXTS
|
||||
|
||||
@@ -582,4 +599,8 @@ _implemented = {
|
||||
# being used instead. This only matters for 3.7+.
|
||||
'_alias': lambda obj, arguments: NO_CONTEXTS,
|
||||
},
|
||||
'dataclasses': {
|
||||
# For now this works at least better than Jedi trying to understand it.
|
||||
'dataclass': lambda obj, arguments: NO_CONTEXTS,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
parso>=0.3.0
|
||||
parso>=0.5.0
|
||||
|
||||
4
setup.py
4
setup.py
@@ -37,7 +37,8 @@ setup(name='jedi',
|
||||
install_requires=install_requires,
|
||||
extras_require={
|
||||
'testing': [
|
||||
'pytest>=3.1.0',
|
||||
# Pytest 5 doesn't support Python 2 and Python 3.4 anymore.
|
||||
'pytest>=3.1.0,<5.0.0',
|
||||
# docopt for sith doctests
|
||||
'docopt',
|
||||
# coloroma for colored debug output
|
||||
@@ -60,6 +61,7 @@ setup(name='jedi',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Topic :: Text Editors :: Integrated Development Environments (IDE)',
|
||||
'Topic :: Utilities',
|
||||
|
||||
@@ -18,8 +18,10 @@ int(str)
|
||||
str..
|
||||
#? []
|
||||
a(0):.
|
||||
#? 2 ['and', 'or', 'if', 'is', 'in', 'not']
|
||||
#? 2 []
|
||||
0x0
|
||||
#? []
|
||||
1j
|
||||
#? ['and', 'or', 'if', 'is', 'in', 'not']
|
||||
1j
|
||||
x = None()
|
||||
|
||||
@@ -310,11 +310,14 @@ follow_statement(1)
|
||||
# class decorators should just be ignored
|
||||
@should_ignore
|
||||
class A():
|
||||
x = 3
|
||||
def ret(self):
|
||||
return 1
|
||||
|
||||
#? int()
|
||||
A().ret()
|
||||
#? int()
|
||||
A().x
|
||||
|
||||
|
||||
# -----------------
|
||||
|
||||
@@ -109,6 +109,23 @@ class X():
|
||||
#! 3 []
|
||||
X(foo=x)
|
||||
|
||||
|
||||
# Multiple inheritance
|
||||
class Foo:
|
||||
def foo(self):
|
||||
print("foo")
|
||||
class Bar:
|
||||
def bar(self):
|
||||
print("bar")
|
||||
class Baz(Foo, Bar):
|
||||
def baz(self):
|
||||
#! ['def foo']
|
||||
super().foo
|
||||
#! ['def bar']
|
||||
super().bar
|
||||
#! ['instance Foo']
|
||||
super()
|
||||
|
||||
# -----------------
|
||||
# imports
|
||||
# -----------------
|
||||
@@ -203,9 +220,10 @@ for key, value in [(1,2)]:
|
||||
#! ['for key, value in [(1,2)]: key']
|
||||
key
|
||||
|
||||
for i in []:
|
||||
#! ['for i in []: i']
|
||||
i
|
||||
#! 4 ['for y in [1]: y']
|
||||
for y in [1]:
|
||||
#! ['for y in [1]: y']
|
||||
y
|
||||
|
||||
# -----------------
|
||||
# decorator
|
||||
|
||||
@@ -256,6 +256,31 @@ for key in x.keys():
|
||||
for value in x.values():
|
||||
#? int()
|
||||
value
|
||||
|
||||
WrappingType = typing.NewType('WrappingType', str) # Chosen arbitrarily
|
||||
y = WrappingType(0) # Per https://github.com/davidhalter/jedi/issues/1015#issuecomment-355795929
|
||||
#? str()
|
||||
y
|
||||
|
||||
def testnewtype(y):
|
||||
"""
|
||||
:type y: WrappingType
|
||||
"""
|
||||
#? str()
|
||||
y
|
||||
#? ["upper"]
|
||||
y.u
|
||||
|
||||
WrappingType2 = typing.NewType()
|
||||
|
||||
def testnewtype2(y):
|
||||
"""
|
||||
:type y: WrappingType2
|
||||
"""
|
||||
#?
|
||||
y
|
||||
#? []
|
||||
y.
|
||||
# python >= 3.4
|
||||
|
||||
class TestDefaultDict(typing.DefaultDict[str, int]):
|
||||
@@ -295,7 +320,6 @@ import typing as t
|
||||
def union2(x: t.Union[int, str]):
|
||||
#? int() str()
|
||||
x
|
||||
|
||||
from typing import Union
|
||||
def union3(x: Union[int, str]):
|
||||
#? int() str()
|
||||
|
||||
@@ -38,12 +38,16 @@ for char in NOT_DEFINED:
|
||||
char
|
||||
|
||||
|
||||
# -------------------------
|
||||
# instance/class vars
|
||||
# -------------------------
|
||||
|
||||
class Foo():
|
||||
bar: int
|
||||
baz: typing.ClassVar[str]
|
||||
|
||||
|
||||
#? int()
|
||||
#?
|
||||
Foo.bar
|
||||
#? int()
|
||||
Foo().bar
|
||||
@@ -51,3 +55,61 @@ Foo().bar
|
||||
Foo.baz
|
||||
#? str()
|
||||
Foo().baz
|
||||
|
||||
class VarClass:
|
||||
var_instance1: int = 1
|
||||
var_instance2: float
|
||||
var_class1: typing.ClassVar[str] = 1
|
||||
var_class2: typing.ClassVar[bytes]
|
||||
|
||||
def __init__(self):
|
||||
#? int()
|
||||
d.var_instance1
|
||||
#? float()
|
||||
d.var_instance2
|
||||
#? str()
|
||||
d.var_class1
|
||||
#? bytes()
|
||||
d.var_class2
|
||||
#? []
|
||||
d.int
|
||||
#? ['var_class1', 'var_class2', 'var_instance1', 'var_instance2']
|
||||
self.var_
|
||||
|
||||
|
||||
#? ['var_class1', 'var_class2']
|
||||
VarClass.var_
|
||||
#?
|
||||
VarClass.var_instance1
|
||||
#?
|
||||
VarClass.var_instance2
|
||||
#? str()
|
||||
VarClass.var_class1
|
||||
#? bytes()
|
||||
VarClass.var_class2
|
||||
#? []
|
||||
VarClass.int
|
||||
|
||||
d = VarClass()
|
||||
#? ['var_class1', 'var_class2', 'var_instance1', 'var_instance2']
|
||||
d.var_
|
||||
#? int()
|
||||
d.var_instance1
|
||||
#? float()
|
||||
d.var_instance2
|
||||
#? str()
|
||||
d.var_class1
|
||||
#? bytes()
|
||||
d.var_class2
|
||||
#? []
|
||||
d.int
|
||||
|
||||
|
||||
|
||||
import dataclasses
|
||||
@dataclasses.dataclass
|
||||
class DC:
|
||||
name: int = 1
|
||||
|
||||
#? int()
|
||||
DC().name
|
||||
|
||||
@@ -73,7 +73,7 @@ def _check_number(Script, source, result='float'):
|
||||
|
||||
def test_completion_on_number_literals(Script):
|
||||
# No completions on an int literal (is a float).
|
||||
assert [c.name for c in Script('1.').completions()] \
|
||||
assert [c.name for c in Script('1. ').completions()] \
|
||||
== ['and', 'if', 'in', 'is', 'not', 'or']
|
||||
|
||||
# Multiple points after an int literal basically mean that there's a float
|
||||
@@ -109,9 +109,12 @@ def test_completion_on_complex_literals(Script):
|
||||
_check_number(Script, '1j.', 'complex')
|
||||
_check_number(Script, '44.j.', 'complex')
|
||||
_check_number(Script, '4.0j.', 'complex')
|
||||
# No dot no completion - I thought, but 4j is actually a literall after
|
||||
# No dot no completion - I thought, but 4j is actually a literal after
|
||||
# which a keyword like or is allowed. Good times, haha!
|
||||
assert ({c.name for c in Script('4j').completions()} ==
|
||||
# However this has been disabled again, because it apparently annoyed
|
||||
# users. So no completion after j without a space :)
|
||||
assert not Script('4j').completions()
|
||||
assert ({c.name for c in Script('4j ').completions()} ==
|
||||
{'if', 'and', 'in', 'is', 'not', 'or'})
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
import inspect
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from ..helpers import TestCase
|
||||
from jedi import cache
|
||||
from jedi.parser_utils import get_call_signature
|
||||
from jedi import Interpreter
|
||||
|
||||
|
||||
def assert_signature(Script, source, expected_name, expected_index=0, line=None, column=None):
|
||||
@@ -43,7 +44,7 @@ class TestCallSignatures(TestCase):
|
||||
run(s1, 'sorted', 0, 7)
|
||||
run(s1, 'sorted', 1, 9)
|
||||
run(s1, 'sorted', 1, 10)
|
||||
run(s1, 'sorted', 1, 11)
|
||||
run(s1, 'sorted', None, 11)
|
||||
run(s1, 'bool', 0, 15)
|
||||
|
||||
s2 = "abs(), "
|
||||
@@ -272,6 +273,13 @@ def test_int_params(Script):
|
||||
assert sig2.params[0].name == 'x'
|
||||
|
||||
|
||||
def test_pow_params(Script):
|
||||
# See Github #1357.
|
||||
for sig in Script('pow(').call_signatures():
|
||||
param_names = [p.name for p in sig.params]
|
||||
assert param_names in (['x', 'y'], ['x', 'y', 'z'])
|
||||
|
||||
|
||||
def test_param_name(Script):
|
||||
sigs = Script('open(something,').call_signatures()
|
||||
for sig in sigs:
|
||||
@@ -394,6 +402,148 @@ def test_keyword_argument_index(Script, environment):
|
||||
assert get(both + 'foo(a, b, c').index == 0
|
||||
|
||||
|
||||
code1 = 'def f(u, /, v=3, *, abc, abd, xyz): pass'
|
||||
code2 = 'def g(u, /, v=3, *, abc, abd, xyz, **kwargs): pass'
|
||||
code3 = 'def h(u, /, v, *args, x=1, y): pass'
|
||||
code4 = 'def i(u, /, v, *args, x=1, y, **kwargs): pass'
|
||||
|
||||
|
||||
_calls = [
|
||||
# No *args, **kwargs
|
||||
(code1, 'f(', 0),
|
||||
(code1, 'f(a', 0),
|
||||
(code1, 'f(a,', 1),
|
||||
(code1, 'f(a,b', 1),
|
||||
(code1, 'f(a,b,', 2),
|
||||
(code1, 'f(a,b,c', None),
|
||||
(code1, 'f(a,b,a', 2),
|
||||
(code1, 'f(a,b,a=', None),
|
||||
(code1, 'f(a,b,abc', 2),
|
||||
(code1, 'f(a,b,abc=(', 2),
|
||||
(code1, 'f(a,b,abc=(f,1,2,3', 2),
|
||||
(code1, 'f(a,b,abd', 3),
|
||||
(code1, 'f(a,b,x', 4),
|
||||
(code1, 'f(a,b,xy', 4),
|
||||
(code1, 'f(a,b,xyz=', 4),
|
||||
(code1, 'f(a,b,xy=', None),
|
||||
(code1, 'f(u=', (0, None)),
|
||||
(code1, 'f(v=', 1),
|
||||
|
||||
# **kwargs
|
||||
(code2, 'g(a,b,a', 2),
|
||||
(code2, 'g(a,b,abc', 2),
|
||||
(code2, 'g(a,b,abd', 3),
|
||||
(code2, 'g(a,b,arr', 5),
|
||||
(code2, 'g(a,b,xy', 4),
|
||||
(code2, 'g(a,b,xyz=', 4),
|
||||
(code2, 'g(a,b,xy=', 5),
|
||||
(code2, 'g(a,b,abc=1,abd=4,', 4),
|
||||
(code2, 'g(a,b,abc=1,xyz=3,abd=4,', 5),
|
||||
(code2, 'g(a,b,abc=1,abd=4,lala', 5),
|
||||
(code2, 'g(a,b,abc=1,abd=4,lala=', 5),
|
||||
(code2, 'g(a,b,abc=1,abd=4,abd=', 5),
|
||||
(code2, 'g(a,b,kw', 5),
|
||||
(code2, 'g(a,b,kwargs=', 5),
|
||||
(code2, 'g(u=', (0, 5)),
|
||||
(code2, 'g(v=', 1),
|
||||
|
||||
# *args
|
||||
(code3, 'h(a,b,c', 2),
|
||||
(code3, 'h(a,b,c,', 2),
|
||||
(code3, 'h(a,b,c,d', 2),
|
||||
(code3, 'h(a,b,c,d[', 2),
|
||||
(code3, 'h(a,b,c,(3,', 2),
|
||||
(code3, 'h(a,b,c,(3,)', 2),
|
||||
(code3, 'h(a,b,args=', None),
|
||||
(code3, 'h(u,v=', 1),
|
||||
(code3, 'h(u=', (0, None)),
|
||||
(code3, 'h(u,*xxx', 1),
|
||||
(code3, 'h(u,*xxx,*yyy', 1),
|
||||
(code3, 'h(u,*[]', 1),
|
||||
(code3, 'h(u,*', 1),
|
||||
(code3, 'h(u,*, *', 1),
|
||||
(code3, 'h(u,1,**', 3),
|
||||
(code3, 'h(u,**y', 1),
|
||||
(code3, 'h(u,x=2,**', 1),
|
||||
(code3, 'h(u,x=2,**y', 1),
|
||||
(code3, 'h(u,v=2,**y', 3),
|
||||
(code3, 'h(u,x=2,**vv', 1),
|
||||
|
||||
# *args, **kwargs
|
||||
(code4, 'i(a,b,c,d', 2),
|
||||
(code4, 'i(a,b,c,d,e', 2),
|
||||
(code4, 'i(a,b,c,d,e=', 5),
|
||||
(code4, 'i(a,b,c,d,e=3', 5),
|
||||
(code4, 'i(a,b,c,d=,x=', 3),
|
||||
(code4, 'i(a,b,c,d=5,x=4', 3),
|
||||
(code4, 'i(a,b,c,d=5,x=4,y', 4),
|
||||
(code4, 'i(a,b,c,d=5,x=4,y=3,', 5),
|
||||
(code4, 'i(a,b,c,d=5,y=4,x=3,', 5),
|
||||
(code4, 'i(a,b,c,d=4,', 3),
|
||||
(code4, 'i(a,b,c,x=1,d=,', 4),
|
||||
|
||||
# Error nodes
|
||||
(code4, 'i(1, [a,b', 1),
|
||||
(code4, 'i(1, [a,b=,', 2),
|
||||
(code4, 'i(1, [a?b,', 2),
|
||||
(code4, 'i(1, [a?b,*', 2),
|
||||
(code4, 'i(?b,*r,c', 1),
|
||||
(code4, 'i(?*', 0),
|
||||
(code4, 'i(?**', (0, 1)),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('ending', ['', ')'])
|
||||
@pytest.mark.parametrize('code, call, expected_index', _calls)
|
||||
def test_signature_index(skip_python2, Script, environment, code, call, expected_index, ending):
|
||||
if isinstance(expected_index, tuple):
|
||||
expected_index = expected_index[environment.version_info > (3, 8)]
|
||||
if environment.version_info < (3, 8):
|
||||
code = code.replace('/,', '')
|
||||
|
||||
sig, = Script(code + '\n' + call + ending, column=len(call)).call_signatures()
|
||||
index = sig.index
|
||||
assert expected_index == index
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Python 2 doesn't support __signature__")
|
||||
@pytest.mark.parametrize('code', ['foo', 'instance.foo'])
|
||||
def test_arg_defaults(Script, environment, code):
|
||||
def foo(arg="bla", arg1=1):
|
||||
pass
|
||||
|
||||
class Klass:
|
||||
def foo(self, arg="bla", arg1=1):
|
||||
pass
|
||||
|
||||
instance = Klass()
|
||||
|
||||
src = dedent("""
|
||||
def foo2(arg="bla", arg1=1):
|
||||
pass
|
||||
|
||||
class Klass2:
|
||||
def foo2(self, arg="bla", arg1=1):
|
||||
pass
|
||||
|
||||
instance = Klass2()
|
||||
""")
|
||||
|
||||
executed_locals = dict()
|
||||
exec(src, None, executed_locals)
|
||||
locals_ = locals()
|
||||
|
||||
def iter_scripts():
|
||||
yield Interpreter(code + '(', namespaces=[locals_])
|
||||
yield Script(src + code + "2(")
|
||||
yield Interpreter(code + '2(', namespaces=[executed_locals])
|
||||
|
||||
for script in iter_scripts():
|
||||
signatures = script.call_signatures()
|
||||
assert signatures[0].params[0].description in ('param arg="bla"', "param arg='bla'")
|
||||
assert signatures[0].params[1].description == 'param arg1=1'
|
||||
|
||||
|
||||
def test_bracket_start(Script):
|
||||
def bracket_start(src):
|
||||
signatures = Script(src).call_signatures()
|
||||
|
||||
@@ -7,19 +7,16 @@ from inspect import cleandoc
|
||||
import pytest
|
||||
|
||||
import jedi
|
||||
from jedi import __doc__ as jedi_doc, names
|
||||
from ..helpers import TestCase
|
||||
from jedi import __doc__ as jedi_doc
|
||||
from jedi.evaluate.compiled import CompiledContextName
|
||||
|
||||
|
||||
def test_is_keyword(Script):
|
||||
#results = Script('import ', 1, 1, None).goto_definitions()
|
||||
#assert len(results) == 1 and results[0].is_keyword is True
|
||||
results = Script('str', 1, 1, None).goto_definitions()
|
||||
assert len(results) == 1 and results[0].is_keyword is False
|
||||
|
||||
|
||||
def test_basedefinition_type(Script, environment):
|
||||
def test_basedefinition_type(Script, names):
|
||||
def make_definitions():
|
||||
"""
|
||||
Return a list of definitions for parametrized tests.
|
||||
@@ -44,7 +41,7 @@ def test_basedefinition_type(Script, environment):
|
||||
""")
|
||||
|
||||
definitions = []
|
||||
definitions += names(source, environment=environment)
|
||||
definitions += names(source)
|
||||
|
||||
source += dedent("""
|
||||
variable = sys or C or x or f or g or g() or h""")
|
||||
@@ -102,8 +99,8 @@ def test_function_call_signature_in_doc(Script):
|
||||
assert "f(x, y=1, z='a')" in str(doc)
|
||||
|
||||
|
||||
def test_param_docstring():
|
||||
param = jedi.names("def test(parameter): pass", all_scopes=True)[1]
|
||||
def test_param_docstring(names):
|
||||
param = names("def test(parameter): pass", all_scopes=True)[1]
|
||||
assert param.name == 'parameter'
|
||||
assert param.docstring() == ''
|
||||
|
||||
@@ -140,28 +137,32 @@ def test_completion_docstring(Script, jedi_path):
|
||||
|
||||
docstr('abcd=3;abcd', '')
|
||||
docstr('"hello"\nabcd=3\nabcd', '')
|
||||
docstr(dedent('''
|
||||
docstr(
|
||||
dedent('''
|
||||
def x():
|
||||
"hello"
|
||||
0
|
||||
x'''),
|
||||
'hello'
|
||||
)
|
||||
docstr(dedent('''
|
||||
docstr(
|
||||
dedent('''
|
||||
def x():
|
||||
"hello";0
|
||||
x'''),
|
||||
'hello'
|
||||
)
|
||||
# Shouldn't work with a tuple.
|
||||
docstr(dedent('''
|
||||
docstr(
|
||||
dedent('''
|
||||
def x():
|
||||
"hello",0
|
||||
x'''),
|
||||
''
|
||||
)
|
||||
# Should also not work if we rename something.
|
||||
docstr(dedent('''
|
||||
docstr(
|
||||
dedent('''
|
||||
def x():
|
||||
"hello"
|
||||
y = x
|
||||
@@ -216,62 +217,47 @@ def test_param_endings(Script):
|
||||
assert [p.description for p in sig.params] == ['param a', 'param b=5', 'param c=""']
|
||||
|
||||
|
||||
class TestIsDefinition(TestCase):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, environment):
|
||||
self.environment = environment
|
||||
|
||||
def _def(self, source, index=-1):
|
||||
return names(
|
||||
dedent(source),
|
||||
@pytest.mark.parametrize(
|
||||
'code, index, name, is_definition', [
|
||||
('name', 0, 'name', False),
|
||||
('a = f(x)', 0, 'a', True),
|
||||
('a = f(x)', 1, 'f', False),
|
||||
('a = f(x)', 2, 'x', False),
|
||||
]
|
||||
)
|
||||
def test_is_definition(names, code, index, name, is_definition):
|
||||
d = names(
|
||||
dedent(code),
|
||||
references=True,
|
||||
all_scopes=True,
|
||||
environment=self.environment
|
||||
)[index]
|
||||
assert d.name == name
|
||||
assert d.is_definition() == is_definition
|
||||
|
||||
def _bool_is_definitions(self, source):
|
||||
ns = names(dedent(source), references=True, all_scopes=True)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'code, expected', (
|
||||
('import x as a', [False, True]),
|
||||
('from x import y', [False, True]),
|
||||
('from x.z import y', [False, False, True]),
|
||||
)
|
||||
)
|
||||
def test_is_definition_import(names, code, expected):
|
||||
ns = names(dedent(code), references=True, all_scopes=True)
|
||||
# Assure that names are definitely sorted.
|
||||
ns = sorted(ns, key=lambda name: (name.line, name.column))
|
||||
return [name.is_definition() for name in ns]
|
||||
|
||||
def test_name(self):
|
||||
d = self._def('name')
|
||||
assert d.name == 'name'
|
||||
assert not d.is_definition()
|
||||
|
||||
def test_stmt(self):
|
||||
src = 'a = f(x)'
|
||||
d = self._def(src, 0)
|
||||
assert d.name == 'a'
|
||||
assert d.is_definition()
|
||||
d = self._def(src, 1)
|
||||
assert d.name == 'f'
|
||||
assert not d.is_definition()
|
||||
d = self._def(src)
|
||||
assert d.name == 'x'
|
||||
assert not d.is_definition()
|
||||
|
||||
def test_import(self):
|
||||
assert self._bool_is_definitions('import x as a') == [False, True]
|
||||
assert self._bool_is_definitions('from x import y') == [False, True]
|
||||
assert self._bool_is_definitions('from x.z import y') == [False, False, True]
|
||||
assert [name.is_definition() for name in ns] == expected
|
||||
|
||||
|
||||
class TestParent(TestCase):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, Script):
|
||||
self.Script = Script
|
||||
|
||||
def _parent(self, source, line=None, column=None):
|
||||
def_, = self.Script(dedent(source), line, column).goto_assignments()
|
||||
def test_parent(Script):
|
||||
def _parent(source, line=None, column=None):
|
||||
def_, = Script(dedent(source), line, column).goto_assignments()
|
||||
return def_.parent()
|
||||
|
||||
def test_parent(self):
|
||||
parent = self._parent('foo=1\nfoo')
|
||||
parent = _parent('foo=1\nfoo')
|
||||
assert parent.type == 'module'
|
||||
|
||||
parent = self._parent('''
|
||||
parent = _parent('''
|
||||
def spam():
|
||||
if 1:
|
||||
y=1
|
||||
@@ -279,10 +265,11 @@ class TestParent(TestCase):
|
||||
assert parent.name == 'spam'
|
||||
assert parent.parent().type == 'module'
|
||||
|
||||
def test_on_function(self):
|
||||
parent = self._parent('''\
|
||||
def spam():
|
||||
pass''', 1, len('def spam'))
|
||||
|
||||
def test_parent_on_function(Script):
|
||||
code = 'def spam():\n pass'
|
||||
def_, = Script(code, line=1, column=len('def spam')).goto_assignments()
|
||||
parent = def_.parent()
|
||||
assert parent.name == ''
|
||||
assert parent.type == 'module'
|
||||
|
||||
@@ -331,8 +318,8 @@ different as an implementation.
|
||||
"""
|
||||
|
||||
|
||||
def test_goto_assignment_repetition(environment):
|
||||
defs = names('a = 1; a', references=True, definitions=False, environment=environment)
|
||||
def test_goto_assignment_repetition(names):
|
||||
defs = names('a = 1; a', references=True, definitions=False)
|
||||
# Repeat on the same variable. Shouldn't change once we're on a
|
||||
# definition.
|
||||
for _ in range(3):
|
||||
@@ -341,34 +328,34 @@ def test_goto_assignment_repetition(environment):
|
||||
assert ass[0].description == 'a = 1'
|
||||
|
||||
|
||||
def test_goto_assignments_named_params(environment):
|
||||
def test_goto_assignments_named_params(names):
|
||||
src = """\
|
||||
def foo(a=1, bar=2):
|
||||
pass
|
||||
foo(bar=1)
|
||||
"""
|
||||
bar = names(dedent(src), references=True, environment=environment)[-1]
|
||||
bar = names(dedent(src), references=True)[-1]
|
||||
param = bar.goto_assignments()[0]
|
||||
assert (param.line, param.column) == (1, 13)
|
||||
assert param.type == 'param'
|
||||
|
||||
|
||||
def test_class_call(environment):
|
||||
def test_class_call(names):
|
||||
src = 'from threading import Thread; Thread(group=1)'
|
||||
n = names(src, references=True, environment=environment)[-1]
|
||||
n = names(src, references=True)[-1]
|
||||
assert n.name == 'group'
|
||||
param_def = n.goto_assignments()[0]
|
||||
assert param_def.name == 'group'
|
||||
assert param_def.type == 'param'
|
||||
|
||||
|
||||
def test_parentheses(environment):
|
||||
n = names('("").upper', references=True, environment=environment)[-1]
|
||||
def test_parentheses(names):
|
||||
n = names('("").upper', references=True)[-1]
|
||||
assert n.goto_assignments()[0].name == 'upper'
|
||||
|
||||
|
||||
def test_import(environment):
|
||||
nms = names('from json import load', references=True, environment=environment)
|
||||
def test_import(names):
|
||||
nms = names('from json import load', references=True)
|
||||
assert nms[0].name == 'json'
|
||||
assert nms[0].type == 'module'
|
||||
n = nms[0].goto_assignments()[0]
|
||||
@@ -381,7 +368,7 @@ def test_import(environment):
|
||||
assert n.name == 'load'
|
||||
assert n.type == 'function'
|
||||
|
||||
nms = names('import os; os.path', references=True, environment=environment)
|
||||
nms = names('import os; os.path', references=True)
|
||||
assert nms[0].name == 'os'
|
||||
assert nms[0].type == 'module'
|
||||
n = nms[0].goto_assignments()[0]
|
||||
@@ -392,7 +379,7 @@ def test_import(environment):
|
||||
assert n.name == 'path'
|
||||
assert n.type == 'module'
|
||||
|
||||
nms = names('import os.path', references=True, environment=environment)
|
||||
nms = names('import os.path', references=True)
|
||||
n = nms[0].goto_assignments()[0]
|
||||
assert n.name == 'os'
|
||||
assert n.type == 'module'
|
||||
@@ -403,8 +390,8 @@ def test_import(environment):
|
||||
assert n.type == 'module'
|
||||
|
||||
|
||||
def test_import_alias(environment):
|
||||
nms = names('import json as foo', references=True, environment=environment)
|
||||
def test_import_alias(names):
|
||||
nms = names('import json as foo', references=True)
|
||||
assert nms[0].name == 'json'
|
||||
assert nms[0].type == 'module'
|
||||
assert nms[0]._name.tree_name.parent.type == 'dotted_as_name'
|
||||
|
||||
@@ -119,7 +119,8 @@ def test_generator(Script):
|
||||
|
||||
def test_in_comment(Script):
|
||||
assert Script(" # Comment").completions()
|
||||
assert Script("max_attr_value = int(2) # Cast to int for spe").completions()
|
||||
# TODO this is a bit ugly, that the behaviors in comments are different.
|
||||
assert not Script("max_attr_value = int(2) # Cast to int for spe").completions()
|
||||
|
||||
|
||||
def test_async(Script, environment):
|
||||
@@ -140,3 +141,18 @@ def test_async(Script, environment):
|
||||
|
||||
def test_with_stmt_error_recovery(Script):
|
||||
assert Script('with open('') as foo: foo.\na', line=1).completions()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'code, has_keywords', (
|
||||
('', True),
|
||||
('x;', True),
|
||||
('1', False),
|
||||
('1 ', True),
|
||||
('1\t', True),
|
||||
('1\n', True),
|
||||
('1\\\n', True),
|
||||
)
|
||||
)
|
||||
def test_keyword_completion(Script, code, has_keywords):
|
||||
assert has_keywords == any(x.is_keyword for x in Script(code).completions())
|
||||
|
||||
@@ -1,30 +1,22 @@
|
||||
"""
|
||||
Tests for `api.defined_names`.
|
||||
Tests for `api.names`.
|
||||
"""
|
||||
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
|
||||
from jedi import names
|
||||
from ..helpers import TestCase
|
||||
|
||||
|
||||
class TestDefinedNames(TestCase):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, environment):
|
||||
self.environment = environment
|
||||
|
||||
def assert_definition_names(self, definitions, names_):
|
||||
def _assert_definition_names(definitions, names_):
|
||||
assert [d.name for d in definitions] == names_
|
||||
|
||||
def check_defined_names(self, source, names_):
|
||||
definitions = names(dedent(source), environment=self.environment)
|
||||
self.assert_definition_names(definitions, names_)
|
||||
|
||||
def _check_names(names, source, names_):
|
||||
definitions = names(dedent(source))
|
||||
_assert_definition_names(definitions, names_)
|
||||
return definitions
|
||||
|
||||
def test_get_definitions_flat(self):
|
||||
self.check_defined_names("""
|
||||
|
||||
def test_get_definitions_flat(names):
|
||||
_check_names(names, """
|
||||
import module
|
||||
class Class:
|
||||
pass
|
||||
@@ -33,25 +25,27 @@ class TestDefinedNames(TestCase):
|
||||
data = None
|
||||
""", ['module', 'Class', 'func', 'data'])
|
||||
|
||||
def test_dotted_assignment(self):
|
||||
self.check_defined_names("""
|
||||
|
||||
def test_dotted_assignment(names):
|
||||
_check_names(names, """
|
||||
x = Class()
|
||||
x.y.z = None
|
||||
""", ['x', 'z']) # TODO is this behavior what we want?
|
||||
|
||||
def test_multiple_assignment(self):
|
||||
self.check_defined_names("""
|
||||
x = y = None
|
||||
""", ['x', 'y'])
|
||||
|
||||
def test_multiple_imports(self):
|
||||
self.check_defined_names("""
|
||||
def test_multiple_assignment(names):
|
||||
_check_names(names, "x = y = None", ['x', 'y'])
|
||||
|
||||
|
||||
def test_multiple_imports(names):
|
||||
_check_names(names, """
|
||||
from module import a, b
|
||||
from another_module import *
|
||||
""", ['a', 'b'])
|
||||
|
||||
def test_nested_definitions(self):
|
||||
definitions = self.check_defined_names("""
|
||||
|
||||
def test_nested_definitions(names):
|
||||
definitions = _check_names(names, """
|
||||
class Class:
|
||||
def f():
|
||||
pass
|
||||
@@ -59,12 +53,12 @@ class TestDefinedNames(TestCase):
|
||||
pass
|
||||
""", ['Class'])
|
||||
subdefinitions = definitions[0].defined_names()
|
||||
self.assert_definition_names(subdefinitions, ['f', 'g'])
|
||||
self.assertEqual([d.full_name for d in subdefinitions],
|
||||
['__main__.Class.f', '__main__.Class.g'])
|
||||
_assert_definition_names(subdefinitions, ['f', 'g'])
|
||||
assert [d.full_name for d in subdefinitions] == ['__main__.Class.f', '__main__.Class.g']
|
||||
|
||||
def test_nested_class(self):
|
||||
definitions = self.check_defined_names("""
|
||||
|
||||
def test_nested_class(names):
|
||||
definitions = _check_names(names, """
|
||||
class L1:
|
||||
class L2:
|
||||
class L3:
|
||||
@@ -75,12 +69,13 @@ class TestDefinedNames(TestCase):
|
||||
""", ['L1', 'f'])
|
||||
subdefs = definitions[0].defined_names()
|
||||
subsubdefs = subdefs[0].defined_names()
|
||||
self.assert_definition_names(subdefs, ['L2', 'f'])
|
||||
self.assert_definition_names(subsubdefs, ['L3', 'f'])
|
||||
self.assert_definition_names(subsubdefs[0].defined_names(), ['f'])
|
||||
_assert_definition_names(subdefs, ['L2', 'f'])
|
||||
_assert_definition_names(subsubdefs, ['L3', 'f'])
|
||||
_assert_definition_names(subsubdefs[0].defined_names(), ['f'])
|
||||
|
||||
def test_class_fields_with_all_scopes_false(self):
|
||||
definitions = self.check_defined_names("""
|
||||
|
||||
def test_class_fields_with_all_scopes_false(names):
|
||||
definitions = _check_names(names, """
|
||||
from module import f
|
||||
g = f(f)
|
||||
class C:
|
||||
@@ -92,11 +87,12 @@ class TestDefinedNames(TestCase):
|
||||
""", ['f', 'g', 'C', 'foo'])
|
||||
C_subdefs = definitions[-2].defined_names()
|
||||
foo_subdefs = definitions[-1].defined_names()
|
||||
self.assert_definition_names(C_subdefs, ['h'])
|
||||
self.assert_definition_names(foo_subdefs, ['x', 'bar'])
|
||||
_assert_definition_names(C_subdefs, ['h'])
|
||||
_assert_definition_names(foo_subdefs, ['x', 'bar'])
|
||||
|
||||
def test_async_stmt_with_all_scopes_false(self):
|
||||
definitions = self.check_defined_names("""
|
||||
|
||||
def test_async_stmt_with_all_scopes_false(names):
|
||||
definitions = _check_names(names, """
|
||||
from module import f
|
||||
import asyncio
|
||||
|
||||
@@ -127,36 +123,37 @@ class TestDefinedNames(TestCase):
|
||||
foo_subdefs = definitions[4].defined_names()
|
||||
async_foo_subdefs = definitions[5].defined_names()
|
||||
cinst_subdefs = definitions[6].defined_names()
|
||||
self.assert_definition_names(C_subdefs, ['h', '__init__', '__aenter__'])
|
||||
self.assert_definition_names(foo_subdefs, ['x', 'bar'])
|
||||
self.assert_definition_names(async_foo_subdefs, ['duration', 'wait', 'i'])
|
||||
_assert_definition_names(C_subdefs, ['h', '__init__', '__aenter__'])
|
||||
_assert_definition_names(foo_subdefs, ['x', 'bar'])
|
||||
_assert_definition_names(async_foo_subdefs, ['duration', 'wait', 'i'])
|
||||
# We treat d as a name outside `async with` block
|
||||
self.assert_definition_names(cinst_subdefs, [])
|
||||
_assert_definition_names(cinst_subdefs, [])
|
||||
|
||||
def test_follow_imports(environment):
|
||||
|
||||
def test_follow_imports(names):
|
||||
# github issue #344
|
||||
imp = names('import datetime', environment=environment)[0]
|
||||
imp = names('import datetime')[0]
|
||||
assert imp.name == 'datetime'
|
||||
datetime_names = [str(d.name) for d in imp.defined_names()]
|
||||
assert 'timedelta' in datetime_names
|
||||
|
||||
|
||||
def test_names_twice(environment):
|
||||
def test_names_twice(names):
|
||||
source = dedent('''
|
||||
def lol():
|
||||
pass
|
||||
''')
|
||||
|
||||
defs = names(source=source, environment=environment)
|
||||
defs = names(source=source)
|
||||
assert defs[0].defined_names() == []
|
||||
|
||||
|
||||
def test_simple_name(environment):
|
||||
defs = names('foo', references=True, environment=environment)
|
||||
def test_simple_name(names):
|
||||
defs = names('foo', references=True)
|
||||
assert not defs[0]._name.infer()
|
||||
|
||||
|
||||
def test_no_error(environment):
|
||||
def test_no_error(names):
|
||||
code = dedent("""
|
||||
def foo(a, b):
|
||||
if a == 10:
|
||||
|
||||
@@ -262,7 +262,7 @@ def test_completion_params():
|
||||
def test_completion_param_annotations():
|
||||
# Need to define this function not directly in Python. Otherwise Jedi is to
|
||||
# clever and uses the Python code instead of the signature object.
|
||||
code = 'def foo(a: 1, b: str, c: int = 1.0): pass'
|
||||
code = 'def foo(a: 1, b: str, c: int = 1.0) -> bytes: pass'
|
||||
exec_(code, locals())
|
||||
script = jedi.Interpreter('foo', [locals()])
|
||||
c, = script.completions()
|
||||
@@ -271,6 +271,9 @@ def test_completion_param_annotations():
|
||||
assert [d.name for d in b.infer()] == ['str']
|
||||
assert {d.name for d in c.infer()} == {'int', 'float'}
|
||||
|
||||
d, = jedi.Interpreter('foo()', [locals()]).goto_definitions()
|
||||
assert d.name == 'bytes'
|
||||
|
||||
|
||||
def test_keyword_argument():
|
||||
def f(some_keyword_argument):
|
||||
@@ -423,3 +426,16 @@ def test_simple_completions(code, completions):
|
||||
|
||||
defs = jedi.Interpreter(code, [locals()]).completions()
|
||||
assert [d.name for d in defs] == completions
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Python 2 doesn't have lru_cache")
|
||||
def test__wrapped__():
|
||||
from functools import lru_cache
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def syslogs_to_df():
|
||||
pass
|
||||
|
||||
c, = jedi.Interpreter('syslogs_to_df', [locals()]).completions()
|
||||
# Apparently the function starts on the line where the decorator starts.
|
||||
assert c.line == syslogs_to_df.__wrapped__.__code__.co_firstlineno + 1
|
||||
|
||||
@@ -462,3 +462,16 @@ def test_import_with_semicolon(Script):
|
||||
names = [c.name for c in Script('xzy; from abc import ').completions()]
|
||||
assert 'ABCMeta' in names
|
||||
assert 'abc' not in names
|
||||
|
||||
|
||||
def test_relative_import_star(Script):
|
||||
# Coming from github #1235
|
||||
import jedi
|
||||
|
||||
source = """
|
||||
from . import *
|
||||
furl.c
|
||||
"""
|
||||
script = jedi.Script(source,3,len("furl.c"), 'export.py')
|
||||
|
||||
assert script.completions()
|
||||
|
||||
@@ -32,3 +32,31 @@ def test_compiled_signature(Script, environment, code, sig, names, op, version):
|
||||
signature, = compiled.get_signatures()
|
||||
assert signature.to_string() == sig
|
||||
assert [n.string_name for n in signature.get_param_names()] == names
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'code, expected', [
|
||||
('def f(a, * args, x): pass\n f(', 'f(a, *args, x)'),
|
||||
('def f(a, *, x): pass\n f(', 'f(a, *, x)'),
|
||||
('def f(*, x= 3,**kwargs): pass\n f(', 'f(*, x=3, **kwargs)'),
|
||||
('def f(x,/,y,* ,z): pass\n f(', 'f(x, /, y, *, z)'),
|
||||
('def f(a, /, *, x=3, **kwargs): pass\n f(', 'f(a, /, *, x=3, **kwargs)'),
|
||||
]
|
||||
)
|
||||
def test_tree_signature(Script, environment, code, expected):
|
||||
# Only test this in the latest version, because of /
|
||||
if environment.version_info < (3, 8):
|
||||
pytest.skip()
|
||||
|
||||
sig, = Script(code).call_signatures()
|
||||
assert expected == sig._signature.to_string()
|
||||
|
||||
|
||||
def test_pow_signature(Script):
|
||||
# See github #1357
|
||||
sigs = Script('pow(').call_signatures()
|
||||
strings = {sig._signature.to_string() for sig in sigs}
|
||||
assert strings == {'pow(x: float, y: float, z: float, /) -> float',
|
||||
'pow(x: float, y: float, /) -> float',
|
||||
'pow(x: int, y: int, z: int, /) -> Any',
|
||||
'pow(x: int, y: int, /) -> Any'}
|
||||
|
||||
Reference in New Issue
Block a user