57 Commits

Author SHA1 Message Date
Dave Halter
60415033b4 Prepare the v0.14.1 release 2019-07-13 16:00:27 +02:00
Dave Halter
a06d760f45 Use fixture names everywhere 2019-07-10 23:26:59 -07:00
Dave Halter
b7687fcfb7 Cleanup a test file 2019-07-10 23:23:18 -07:00
Dave Halter
0ec86d5034 Use parametrize instead of TestCase 2019-07-10 23:22:10 -07:00
Dave Halter
cef23f44cd Remove a TestCase class usage 2019-07-10 19:32:19 -07:00
Dave Halter
e889a4923e Use pytest.mark.parametrize for something instad of a class 2019-07-10 19:04:12 -07:00
Dave Halter
114aba462c Use the names fixture even more 2019-07-10 19:00:24 -07:00
Dave Halter
26c7cec7b5 Use the names fixture more 2019-07-10 18:39:33 -07:00
Dave Halter
3e3a33ab79 A small rename 2019-07-10 18:38:24 -07:00
Dave Halter
7f386e0e68 Refactor names tests 2019-07-10 18:34:40 -07:00
Dave Halter
82d970d2b8 A small refactoring 2019-07-10 18:24:21 -07:00
Dave Halter
3ed9e836cc Make sure __wrapped__ works properly when using an Interpreter, fixes #1353 2019-07-10 16:12:57 -07:00
Dave Halter
f984e8d6ef Small refactoring 2019-07-10 15:38:41 -07:00
Dave Halter
670cf4d394 Make API param names appear without leading double underscores, fixes #1357 again 2019-07-10 12:10:12 -07:00
Dave Halter
e85fba844c Fix some call signature tests 2019-07-09 00:46:53 -07:00
Dave Halter
ee5557ddf6 Make expected index work in Python 3 2019-07-09 00:37:33 -07:00
Dave Halter
42f72b219b Test both closing brackets and non-closing brackets for CallSignature.index 2019-07-09 00:16:53 -07:00
Dave Halter
374721b789 Fix a case with errors 2019-07-09 00:04:53 -07:00
Dave Halter
01cec186ae Move some code around 2019-07-08 22:52:04 -07:00
Dave Halter
3fb89f9f9b Fix some kwargs cases 2019-07-08 22:38:22 -07:00
Dave Halter
a0b4e76c1a Fix some *args issues 2019-07-08 17:03:45 -07:00
Dave Halter
97bf83aa03 Deal better with some error nodes 2019-07-08 14:26:11 -07:00
Dave Halter
ca7658cab7 Delete unused code 2019-07-08 13:29:46 -07:00
Dave Halter
dd78f4cfbf Fix some error node handling for call signatures 2019-07-08 13:22:07 -07:00
Dave Halter
08019075c3 Fix CallSignature index for a looot of cases, fixes #1364,#1363 2019-07-08 12:40:58 -07:00
Dave Halter
943617a94f Use recursion rather than other stuff 2019-07-05 23:51:24 -07:00
Dave Halter
d579c0ad57 Even more refactorings 2019-07-05 15:24:39 -07:00
Dave Halter
76c6104415 small name refactoring 2019-07-05 14:35:48 -07:00
Dave Halter
ef9d803ce3 Refactor some call details 2019-07-05 14:30:59 -07:00
Dave Halter
a26cb42d07 Disable a test for Python 2 2019-07-04 09:31:22 -07:00
Dave Halter
6b9b2836ba Fix pow() signature, fixes #1357
This commit changes how params starting with __ are viewed as positional only params
2019-07-04 00:29:57 -07:00
Dave Halter
abdb8de89d Merge branch 'master' of github.com:davidhalter/jedi 2019-07-03 23:49:18 -07:00
Dave Halter
ac492ef598 Fix signature to_string 2019-07-03 23:44:58 -07:00
Dave Halter
947bfe7b78 Fix an issue with keyword params, fixes #1356 2019-07-03 22:35:46 -07:00
Dave Halter
be6c90d135 Simplify some test code for param defaults, see #1356 2019-07-03 19:43:32 -07:00
Dave Halter
8cb059deda Merge branch 'function_signature_in_interpreter' of https://github.com/linupi/jedi 2019-07-03 19:11:36 -07:00
Arnaud Limbourg
0f4da5c1cf Update link to YouCompleteMe
The source repository changed.
2019-07-03 14:20:31 -07:00
Dave Halter
de138e9114 Improve a bit of dataclasses support, so at least the attributes can be seen
see #1213
2019-07-03 09:21:57 -07:00
Linus Pithan
15bb9b29a2 adding test_kwarg_defaults to point out missing default value of kwargs in certain cases 2019-07-02 11:52:57 +02:00
Dave Halter
4c132d94b9 Make sure in tests that pep 0526 variables are also able to be used when using self, see #933 2019-07-01 23:34:28 -07:00
mwchase
925fc89447 Get typing.NewType working (#1344)
Squashed from the following commits:

* Update pep0484.py

(I don't think I want to know why the cursor jumped to the beginning of the line with every keystroke in GitHub's online editor. Change was entered backwards.)

* Added test for inline use of NewType. Currently assuming that wrapped instances should get the underlying type.

* Altered tests per https://github.com/davidhalter/jedi/issues/1015#issuecomment-356131566

* Add NewTypeFunction to typing evaluation module

* Update AUTHORS.txt

* Add a new test, and a speculative justification

For now, address only the second comment

* Copy code from third comment on the PR

From inspection, I *believe* I understand what this code is doing, and as such, I believe this should cause the new test I added in response to the second comment to fail, because that test is based on faulty assumptions.

* Explicitly discard the key from the tuple

* Update pep0484_typing.py

* Test for the wrapped type, not the wrapper "type"

* Change the return value from calling a NewType
2019-07-01 22:42:59 -07:00
Dave Halter
cb95dbc707 Cannot use pytest 5 yet 2019-07-01 22:30:59 -07:00
Dave Halter
1e3b6a201d Fix filters for classes and functions 2019-07-01 22:24:29 -07:00
Dave Halter
3829ef4785 Fix some small things to get more tests passing 2019-07-01 21:52:03 -07:00
Dave Halter
b382f06be0 A better repr for Definition 2019-07-01 19:40:06 -07:00
Dave Halter
94faceb57c Merge branch 'master' of github.com:davidhalter/jedi 2019-06-30 22:14:02 -07:00
Dave Halter
a9ff58683e Fix ClassVar filter for instances 2019-06-26 22:56:30 +02:00
Dave Halter
fafd6b2ac6 Keyword completions are no longer possible directly after a number, fixes #1085 2019-06-26 15:04:46 +02:00
Nelson, Karl E
344a03e6b2 Fix for EmptyCompiledName 2019-06-24 23:51:19 +02:00
Dave Halter
265abe1d08 Fix super call goto for multiple inheritance, fixes #1311 2019-06-24 09:53:56 +02:00
Dave Halter
ebdae87821 goto should always goto definitions, fixes #1304 2019-06-24 01:25:26 +02:00
Dave Halter
56ec79d62a Fix star imports checks, fixes #1235 2019-06-22 16:45:56 +02:00
Dave Halter
c413b486fb Actually import IsADirectoryError 2019-06-22 15:43:11 +02:00
Dave Halter
cb0a0d228a Add 3.8 to supported versions 2019-06-22 14:45:22 +02:00
Dave Halter
3ae4a154f9 Fix project search if a directory is called manage.py, fixes #1314 2019-06-22 14:04:32 +02:00
Dave Halter
aa2dc6be09 Return annotations for compiled objects now help to infer
However only if it's a type, if it's a string, it doesn't work, yet

Fixes #1347
2019-06-22 00:15:20 +02:00
Nathaniel J. Smith
a62ba86d7b Update parso requirement
Fixes #1348
2019-06-21 10:12:34 +02:00
41 changed files with 934 additions and 351 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -361,9 +361,9 @@ except NameError:
FileNotFoundError = IOError
try:
NotADirectoryError = NotADirectoryError
IsADirectoryError = IsADirectoryError
except NameError:
NotADirectoryError = IOError
IsADirectoryError = IOError
try:
PermissionError = PermissionError

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1 +1 @@
parso>=0.3.0
parso>=0.5.0

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -109,8 +109,11 @@ 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!
# 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'})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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