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) Tobias Rzepka (@TobiasRzepka)
micbou (@micbou) micbou (@micbou)
Dima Gerasimov (@karlicoss) <karlicoss@gmail.com> Dima Gerasimov (@karlicoss) <karlicoss@gmail.com>
Max Woerner Chase (@mwchase) <max.chase@gmail.com>
Note: (@user) means a github user name. Note: (@user) means a github user name.

View File

@@ -3,6 +3,12 @@
Changelog 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) 0.14.0 (2019-06-20)
+++++++++++++++++++ +++++++++++++++++++

View File

@@ -203,7 +203,7 @@ Acknowledgements
.. _jedi-vim: https://github.com/davidhalter/jedi-vim .. _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 .. _deoplete-jedi: https://github.com/zchee/deoplete-jedi
.. _completor.vim: https://github.com/maralla/completor.vim .. _completor.vim: https://github.com/maralla/completor.vim
.. _Jedi.el: https://github.com/tkf/emacs-jedi .. _Jedi.el: https://github.com/tkf/emacs-jedi

View File

@@ -105,6 +105,11 @@ def Script(environment):
return partial(jedi.Script, environment=environment) return partial(jedi.Script, environment=environment)
@pytest.fixture(scope='session')
def names(environment):
return partial(jedi.names, environment=environment)
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def has_typing(environment): def has_typing(environment):
if environment.version_info >= (3, 5, 0): 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 # This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions. # they should for all Python versions.
pytest.skip() 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. 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, \ from jedi.api import Script, Interpreter, set_debug_function, \
preload_module, names preload_module, names

View File

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

View File

@@ -365,19 +365,18 @@ class Script(object):
:rtype: list of :class:`classes.CallSignature` :rtype: list of :class:`classes.CallSignature`
""" """
call_signature_details = \ call_details = helpers.get_call_signature_details(self._module_node, self._pos)
helpers.get_call_signature_details(self._module_node, self._pos) if call_details is None:
if call_signature_details is None:
return [] return []
context = self._evaluator.create_context( context = self._evaluator.create_context(
self._get_module(), self._get_module(),
call_signature_details.bracket_leaf call_details.bracket_leaf
) )
definitions = helpers.cache_call_signatures( definitions = helpers.cache_call_signatures(
self._evaluator, self._evaluator,
context, context,
call_signature_details.bracket_leaf, call_details.bracket_leaf,
self._code_lines, self._code_lines,
self._pos self._pos
) )
@@ -385,10 +384,7 @@ class Script(object):
# TODO here we use stubs instead of the actual contexts. We should use # TODO here we use stubs instead of the actual contexts. We should use
# the signatures from stubs, but the actual contexts, probably?! # the signatures from stubs, but the actual contexts, probably?!
return [classes.CallSignature(self._evaluator, signature, return [classes.CallSignature(self._evaluator, signature, call_details)
call_signature_details.bracket_leaf.start_pos,
call_signature_details.call_index,
call_signature_details.keyword_name_str)
for signature in definitions.get_signatures()] for signature in definitions.get_signatures()]
def _analysis(self): def _analysis(self):

View File

@@ -358,9 +358,10 @@ class BaseDefinition(object):
return Definition(self._evaluator, context.name) return Definition(self._evaluator, context.name)
def __repr__(self): def __repr__(self):
return "<%s full_name=%r, description=%r>" % ( return "<%s %sname=%r, description=%r>" % (
self.__class__.__name__, self.__class__.__name__,
self.full_name, 'full_' if self.full_name else '',
self.full_name or self.name,
self.description, self.description,
) )
@@ -529,17 +530,13 @@ class Definition(BaseDefinition):
""" """
typ = self.type typ = self.type
tree_name = self._name.tree_name 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 in ('function', 'class', 'module', 'instance') or tree_name is None:
if typ == 'function': if typ == 'function':
# For the description we want a short and a pythonic way. # For the description we want a short and a pythonic way.
typ = 'def' typ = 'def'
return typ + ' ' + self._name.string_name 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 definition = tree_name.get_definition() or tree_name
# Remove the prefix, because that's not what we want for get_code # Remove the prefix, because that's not what we want for get_code
@@ -607,11 +604,9 @@ class CallSignature(Definition):
It knows what functions you are currently in. e.g. `isinstance(` would It knows what functions you are currently in. e.g. `isinstance(` would
return the `isinstance` function. without `(` it would return nothing. 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) super(CallSignature, self).__init__(evaluator, signature.name)
self._index = index self._call_details = call_details
self._key_name_str = key_name_str
self._bracket_start_pos = bracket_start_pos
self._signature = signature self._signature = signature
@property @property
@@ -620,26 +615,7 @@ class CallSignature(Definition):
The Param index of the current call. The Param index of the current call.
Returns None if the index cannot be found in the curent call. Returns None if the index cannot be found in the curent call.
""" """
if self._key_name_str is not None: return self._call_details.calculate_index(self._signature.get_param_names())
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
@property @property
def params(self): def params(self):
@@ -651,7 +627,7 @@ class CallSignature(Definition):
The indent of the bracket that is responsible for the last function The indent of the bracket that is responsible for the last function
call. call.
""" """
return self._bracket_start_pos return self._call_details.bracket_leaf.start_pos
@property @property
def _params_str(self): def _params_str(self):
@@ -662,7 +638,7 @@ class CallSignature(Definition):
return '<%s: %s index=%r params=[%s]>' % ( return '<%s: %s index=%r params=[%s]>' % (
type(self).__name__, type(self).__name__,
self._name.string_name, self._name.string_name,
self._index, self.index,
self._params_str, self._params_str,
) )

View File

@@ -171,7 +171,10 @@ class Completion:
elif type_ == 'for_stmt': elif type_ == 'for_stmt':
allowed_transitions.append('else') 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, if any(t in allowed_transitions for t in (PythonTokenTypes.NAME,
PythonTokenTypes.INDENT)): PythonTokenTypes.INDENT)):

View File

@@ -8,7 +8,7 @@ from textwrap import dedent
from parso.python.parser import Parser from parso.python.parser import Parser
from parso.python import tree 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.base_context import NO_CONTEXTS
from jedi.evaluate.syntax_tree import eval_atom from jedi.evaluate.syntax_tree import eval_atom
from jedi.evaluate.helpers import evaluate_call_of_leaf from jedi.evaluate.helpers import evaluate_call_of_leaf
@@ -159,10 +159,139 @@ def evaluate_goto_definition(evaluator, context, leaf):
return definitions return definitions
CallSignatureDetails = namedtuple( class CallDetails(object):
'CallSignatureDetails', def __init__(self, bracket_leaf, children, position):
['bracket_leaf', 'call_index', 'keyword_name_str'] ['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): 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] nodes_before = [c for c in nodes if c.start_pos < position]
if nodes_before[-1].type == 'arglist': 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 key_str = None
if nodes_before:
last = nodes_before[-1] last = nodes_before[-1]
if last.type == 'argument' and last.children[1] == '=' \ if last.type == 'argument' and last.children[1] == '=' \
and last.children[1].end_pos <= position: and last.children[1].end_pos <= position:
@@ -187,7 +315,7 @@ def _get_index_and_key(nodes, position):
return nodes_before.count(','), key_str 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))): for index, element in reversed(list(enumerate(node.children))):
# `index > 0` means that it's a trailer and not an atom. # `index > 0` means that it's a trailer and not an atom.
if element == '(' and element.end_pos <= position and index > 0: 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: if name is None:
continue continue
if name.type == 'name' or name.parent.type in ('trailer', 'atom'): if name.type == 'name' or name.parent.type in ('trailer', 'atom'):
return CallSignatureDetails( return CallDetails(element, children + additional_children, position)
element,
*_get_index_and_key(children, position)
)
def get_call_signature_details(module, position): def get_call_signature_details(module, position):
@@ -213,6 +338,7 @@ def get_call_signature_details(module, position):
return None return None
if leaf == ')': if leaf == ')':
# TODO is this ok?
if leaf.end_pos == position: if leaf.end_pos == position:
leaf = leaf.get_next_leaf() 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. # makes it feel strange to have a call signature.
return None return None
for n in node.children[::-1]: additional_children = []
if n.start_pos < position and n.type == 'error_node': for n in reversed(node.children):
result = _get_call_signature_details_from_error_node(n, position) 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: if result is not None:
return result return result
additional_children[0:0] = n.children
continue
additional_children.insert(0, n)
if node.type == 'trailer' and node.children[0] == '(': if node.type == 'trailer' and node.children[0] == '(':
leaf = node.get_previous_leaf() leaf = node.get_previous_leaf()
if leaf is None: if leaf is None:
return None return None
return CallSignatureDetails( return CallDetails(node.children[0], node.children, position)
node.children[0], *_get_index_and_key(node.children, position))
node = node.parent node = node.parent

View File

@@ -1,7 +1,7 @@
import os import os
import json import json
from jedi._compatibility import FileNotFoundError, NotADirectoryError, PermissionError from jedi._compatibility import FileNotFoundError, PermissionError, IsADirectoryError
from jedi.api.environment import SameEnvironment, \ from jedi.api.environment import SameEnvironment, \
get_cached_default_environment get_cached_default_environment
from jedi.api.exceptions import WrongVersion from jedi.api.exceptions import WrongVersion
@@ -153,7 +153,7 @@ def _is_django_path(directory):
try: try:
with open(os.path.join(directory, 'manage.py'), 'rb') as f: with open(os.path.join(directory, 'manage.py'), 'rb') as f:
return b"DJANGO_SETTINGS_MODULE" in f.read() return b"DJANGO_SETTINGS_MODULE" in f.read()
except (FileNotFoundError, NotADirectoryError, PermissionError): except (FileNotFoundError, IsADirectoryError, PermissionError):
return False return False
return False return False
@@ -169,7 +169,7 @@ def get_default_project(path=None):
for dir in traverse_parents(check, include_current=True): for dir in traverse_parents(check, include_current=True):
try: try:
return Project.load(dir) return Project.load(dir)
except (FileNotFoundError, NotADirectoryError, PermissionError): except (FileNotFoundError, IsADirectoryError, PermissionError):
pass pass
if first_no_init_file is None: if first_no_init_file is None:

View File

@@ -318,11 +318,11 @@ class Evaluator(object):
return [TreeNameDefinition(context, name)] return [TreeNameDefinition(context, name)]
elif type_ == 'param': elif type_ == 'param':
return [ParamName(context, name)] return [ParamName(context, name)]
elif type_ in ('funcdef', 'classdef'):
return [TreeNameDefinition(context, name)]
elif type_ in ('import_from', 'import_name'): elif type_ in ('import_from', 'import_name'):
module_names = imports.infer_import(context, name, is_goto=True) module_names = imports.infer_import(context, name, is_goto=True)
return module_names return module_names
else:
return [TreeNameDefinition(context, name)]
else: else:
contexts = self._follow_error_node_imports_if_possible(context, name) contexts = self._follow_error_node_imports_if_possible(context, name)
if contexts is not None: if contexts is not None:

View File

@@ -241,7 +241,7 @@ class _ContextWrapperBase(HelperContextMixin):
return cls(*args, **kwargs) return cls(*args, **kwargs)
def __getattr__(self, name): def __getattr__(self, name):
assert name != '_wrapped_context' assert name != '_wrapped_context', 'Problem with _get_wrapped_context'
return getattr(self._wrapped_context, name) return getattr(self._wrapped_context, name)

View File

@@ -412,7 +412,7 @@ class DirectObjectAccess(object):
name=p.name, name=p.name,
has_default=p.default is not p.empty, has_default=p.default is not p.empty,
default=self._create_access_path(p.default), 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, has_annotation=p.annotation is not p.empty,
annotation=self._create_access_path(p.annotation), annotation=self._create_access_path(p.annotation),
annotation_string=str(p.default), 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. # the signature. In that case we just want a simple escape for now.
raise ValueError 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): def negate(self):
return self._create_access_path(-self._obj) return self._create_access_path(-self._obj)

View File

@@ -46,6 +46,11 @@ class CompiledObject(Context):
self.access_handle = access_handle self.access_handle = access_handle
def py__call__(self, arguments): 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: try:
self.access_handle.getattr_paths(u'__call__') self.access_handle.getattr_paths(u'__call__')
except AttributeError: except AttributeError:
@@ -122,6 +127,9 @@ class CompiledObject(Context):
signature_params = self.access_handle.get_signature_params() signature_params = self.access_handle.get_signature_params()
except ValueError: # Has no signature except ValueError: # Has no signature
params_str, ret = self._parse_function_doc() params_str, ret = self._parse_function_doc()
if not params_str:
tokens = []
else:
tokens = params_str.split(',') tokens = params_str.split(',')
if self.access_handle.ismethoddescriptor(): if self.access_handle.ismethoddescriptor():
tokens.insert(0, 'self') tokens.insert(0, 'self')
@@ -276,7 +284,11 @@ class CompiledName(AbstractNameDefinition):
@property @property
def api_type(self): 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 @underscore_memoization
def infer(self): def infer(self):
@@ -285,9 +297,7 @@ class CompiledName(AbstractNameDefinition):
)]) )])
class SignatureParamName(AbstractNameDefinition, ParamNameInterface): class SignatureParamName(ParamNameInterface, AbstractNameDefinition):
api_type = u'param'
def __init__(self, compiled_obj, signature_param): def __init__(self, compiled_obj, signature_param):
self.parent_context = compiled_obj.parent_context self.parent_context = compiled_obj.parent_context
self._signature_param = signature_param self._signature_param = signature_param
@@ -297,7 +307,7 @@ class SignatureParamName(AbstractNameDefinition, ParamNameInterface):
return self._signature_param.name return self._signature_param.name
def to_string(self): def to_string(self):
s = self.string_name s = self._kind_string() + self.string_name
if self._signature_param.has_annotation: if self._signature_param.has_annotation:
s += ': ' + self._signature_param.annotation_string s += ': ' + self._signature_param.annotation_string
if self._signature_param.has_default: if self._signature_param.has_default:
@@ -322,9 +332,7 @@ class SignatureParamName(AbstractNameDefinition, ParamNameInterface):
return contexts return contexts
class UnresolvableParamName(AbstractNameDefinition, ParamNameInterface): class UnresolvableParamName(ParamNameInterface, AbstractNameDefinition):
api_type = u'param'
def __init__(self, compiled_obj, name, default): def __init__(self, compiled_obj, name, default):
self.parent_context = compiled_obj.parent_context self.parent_context = compiled_obj.parent_context
self.string_name = name self.string_name = name

View File

@@ -4,6 +4,7 @@ Used only for REPL Completion.
import inspect import inspect
import os import os
import sys
from jedi.parser_utils import get_cached_code_lines 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): def _get_object_to_check(python_object):
"""Check if inspect.getfile has a chance to find the source.""" """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 if (inspect.ismodule(python_object) or
inspect.isclass(python_object) or inspect.isclass(python_object) or
inspect.ismethod(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. raise TypeError # Prevents computation of `repr` within inspect.
def _find_syntax_node_name(evaluator, access_handle): def _find_syntax_node_name(evaluator, python_object):
# 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
try: try:
python_object = _get_object_to_check(python_object) python_object = _get_object_to_check(python_object)
path = inspect.getsourcefile(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 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: if result is None:
# TODO Care about generics from stuff like `[1]` and don't return like this. # 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): if type(python_object) in (dict, list, tuple):
return ContextSet({compiled_object}) return ContextSet({compiled_object})

View File

@@ -64,8 +64,9 @@ class FunctionMixin(object):
origin_scope=origin_scope origin_scope=origin_scope
) )
else: else:
scope = self.py__class__() cls = self.py__class__()
for filter in scope.get_filters(search_global=False, origin_scope=origin_scope): for instance in cls.execute_evaluated():
for filter in instance.get_filters(search_global=False, origin_scope=origin_scope):
yield filter yield filter
def py__get__(self, instance, class_context): def py__get__(self, instance, class_context):

View File

@@ -462,12 +462,13 @@ class InstanceClassFilter(AbstractFilter):
origin_scope=origin_scope, origin_scope=origin_scope,
is_instance=True, is_instance=True,
)) ))
assert isinstance(self._class_filter, ClassFilter), self._class_filter
def get(self, name): 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): def values(self):
return self._convert(self._class_filter.values()) return self._convert(self._class_filter.values(from_instance=True))
def _convert(self, names): def _convert(self, names):
return [LazyInstanceClassName(self._instance, self._class_context, n) for n in names] return [LazyInstanceClassName(self._instance, self._class_context, n) for n in names]
@@ -503,7 +504,7 @@ class SelfAttributeFilter(ClassFilter):
if trailer.type == 'trailer' \ if trailer.type == 'trailer' \
and len(trailer.parent.children) == 2 \ and len(trailer.parent.children) == 2 \
and trailer.children[0] == '.': 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. # TODO filter non-self assignments.
yield name yield name

View File

@@ -45,7 +45,7 @@ from jedi.evaluate import compiled
from jedi.evaluate.lazy_context import LazyKnownContexts from jedi.evaluate.lazy_context import LazyKnownContexts
from jedi.evaluate.filters import ParserTreeFilter from jedi.evaluate.filters import ParserTreeFilter
from jedi.evaluate.names import TreeNameDefinition, ContextName 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, \ from jedi.evaluate.base_context import ContextSet, iterator_to_context_set, \
NO_CONTEXTS NO_CONTEXTS
from jedi.evaluate.context.function import FunctionAndClassBase from jedi.evaluate.context.function import FunctionAndClassBase
@@ -109,13 +109,27 @@ class ClassFilter(ParserTreeFilter):
node = get_cached_parent_scope(self._used_names, node) node = get_cached_parent_scope(self._used_names, node)
return False 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('__') \ return not name.value.startswith('__') or name.value.endswith('__') \
or self._equals_origin_scope() or self._equals_origin_scope()
def _filter(self, names): def _filter(self, names, from_instance=False):
names = super(ClassFilter, self)._filter(names) 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): class ClassMixin(object):
@@ -198,8 +212,14 @@ class ClassMixin(object):
if not is_instance: if not is_instance:
from jedi.evaluate.compiled import builtin_from_name from jedi.evaluate.compiled import builtin_from_name
type_ = builtin_from_name(self.evaluator, u'type') type_ = builtin_from_name(self.evaluator, u'type')
assert isinstance(type_, ClassContext)
if type_ != self: 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)): 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. # to push the star imports into Evaluator.module_cache, if we reenable this.
@evaluator_method_cache([]) @evaluator_method_cache([])
def star_imports(self): def star_imports(self):
from jedi.evaluate.imports import infer_import from jedi.evaluate.imports import Importer
modules = [] modules = []
for i in self.tree_node.iter_imports(): for i in self.tree_node.iter_imports():
if i.is_star_import(): if i.is_star_import():
name = i.get_paths()[-1][-1] new = Importer(
new = infer_import(self, name) self.evaluator,
import_path=i.get_paths()[-1],
module_context=self,
level=i.level
).follow()
for module in new: for module in new:
if isinstance(module, ModuleContext): if isinstance(module, ModuleContext):
modules += module.star_imports() modules += module.star_imports()

View File

@@ -74,20 +74,22 @@ class AbstractUsedNamesFilter(AbstractFilter):
self._used_names = self._module_node.get_used_names() self._used_names = self._module_node.get_used_names()
self.context = context self.context = context
def get(self, name): def get(self, name, **filter_kwargs):
return self._convert_names(self._filter( 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): def _convert_names(self, names):
return [self.name_class(self.context, name) for name in names] return [self.name_class(self.context, name) for name in names]
def values(self): def values(self, **filter_kwargs):
return self._convert_names( return self._convert_names(
name name
for name_key in self._used_names for name_key in self._used_names
for name in self._filter( 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, \ from jedi.evaluate.names import NameWrapper, AbstractTreeName, \
AbstractNameDefinition, ContextName AbstractNameDefinition, ContextName
from jedi.evaluate.helpers import is_string 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() _PROXY_CLASS_TYPES = 'Tuple Generic Protocol Callable Type'.split()
_TYPE_ALIAS_TYPES = { _TYPE_ALIAS_TYPES = {
@@ -54,12 +54,15 @@ class _BaseTypingContext(Context):
return self._tree_name return self._tree_name
def get_filters(self, *args, **kwargs): def get_filters(self, *args, **kwargs):
# TODO this is obviously wrong. # TODO this is obviously wrong. Is it though?
class EmptyFilter(): class EmptyFilter(ClassFilter):
def get(self, name): def __init__(self):
pass
def get(self, name, **kwargs):
return [] return []
def values(self): def values(self, **kwargs):
return [] return []
yield EmptyFilter() yield EmptyFilter()
@@ -112,6 +115,8 @@ class TypingModuleName(NameWrapper):
yield builtin_from_name(evaluator, u'True') yield builtin_from_name(evaluator, u'True')
elif name == 'overload': elif name == 'overload':
yield OverloadFunction.create_cached(evaluator, self.parent_context, self.tree_name) 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': elif name == 'cast':
# TODO implement cast # TODO implement cast
yield CastFunction.create_cached(evaluator, self.parent_context, self.tree_name) yield CastFunction.create_cached(evaluator, self.parent_context, self.tree_name)
@@ -456,6 +461,32 @@ class OverloadFunction(_BaseTypingContext):
return func_context_set 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): class CastFunction(_BaseTypingContext):
@repack_with_argument_clinic('type, object, /') @repack_with_argument_clinic('type, object, /')
def py__call__(self, type_context_set, object_context_set): def py__call__(self, type_context_set, object_context_set):

View File

@@ -151,6 +151,16 @@ class TreeNameDefinition(AbstractTreeName):
class ParamNameInterface(object): 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): def get_kind(self):
raise NotImplementedError raise NotImplementedError
@@ -158,9 +168,7 @@ class ParamNameInterface(object):
raise NotImplementedError raise NotImplementedError
class ParamName(AbstractTreeName, ParamNameInterface): class ParamName(ParamNameInterface, AbstractTreeName):
api_type = u'param'
def __init__(self, parent_context, tree_name): def __init__(self, parent_context, tree_name):
self.parent_context = parent_context self.parent_context = parent_context
self.tree_name = tree_name self.tree_name = tree_name
@@ -168,6 +176,15 @@ class ParamName(AbstractTreeName, ParamNameInterface):
def _get_param_node(self): def _get_param_node(self):
return search_ancestor(self.tree_name, 'param') 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): def get_kind(self):
tree_param = self._get_param_node() tree_param = self._get_param_node()
if tree_param.star_count == 1: # *args if tree_param.star_count == 1: # *args
@@ -175,17 +192,29 @@ class ParamName(AbstractTreeName, ParamNameInterface):
if tree_param.star_count == 2: # **kwargs if tree_param.star_count == 2: # **kwargs
return Parameter.VAR_KEYWORD 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 parent = tree_param.parent
param_appeared = False
for p in parent.children: 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.type == 'param':
if p.star_count: if p.star_count:
return Parameter.KEYWORD_ONLY return Parameter.KEYWORD_ONLY
if p == tree_param: if p == tree_param:
break param_appeared = True
return Parameter.POSITIONAL_OR_KEYWORD return Parameter.POSITIONAL_OR_KEYWORD
def to_string(self): def to_string(self):
output = self.string_name output = self._kind_string() + self.string_name
param_node = self._get_param_node() param_node = self._get_param_node()
if param_node.annotation is not None: if param_node.annotation is not None:
output += ': ' + param_node.annotation.get_code(include_prefix=False) output += ': ' + param_node.annotation.get_code(include_prefix=False)

View File

@@ -25,7 +25,9 @@ class AbstractSignature(object):
yield '/' yield '/'
is_positional = False 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 '*' yield '*'
is_kw_only = True is_kw_only = True

View File

@@ -19,10 +19,9 @@ from jedi.evaluate.arguments import ValuesArguments, \
repack_with_argument_clinic, AbstractArguments, TreeArgumentsWrapper repack_with_argument_clinic, AbstractArguments, TreeArgumentsWrapper
from jedi.evaluate import analysis from jedi.evaluate import analysis
from jedi.evaluate import compiled from jedi.evaluate import compiled
from jedi.evaluate.context.instance import \ from jedi.evaluate.context.instance import BoundMethod, InstanceArguments
AbstractInstanceContext, BoundMethod, InstanceArguments
from jedi.evaluate.base_context import ContextualizedNode, \ from jedi.evaluate.base_context import ContextualizedNode, \
NO_CONTEXTS, ContextSet, ContextWrapper NO_CONTEXTS, ContextSet, ContextWrapper, LazyContextWrapper
from jedi.evaluate.context import ClassContext, ModuleContext, \ from jedi.evaluate.context import ClassContext, ModuleContext, \
FunctionExecutionContext FunctionExecutionContext
from jedi.evaluate.context import iterable from jedi.evaluate.context import iterable
@@ -230,20 +229,38 @@ def builtins_type(objects, bases, dicts):
return objects.py__class__() return objects.py__class__()
class SuperInstance(AbstractInstanceContext): class SuperInstance(LazyContextWrapper):
"""To be used like the object ``super`` returns.""" """To be used like the object ``super`` returns."""
def __init__(self, evaluator, cls): def __init__(self, evaluator, instance):
su = cls.py_mro()[1] self.evaluator = evaluator
super().__init__(evaluator, su and su[0] or self) 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) @argument_clinic('[type[, obj]], /', want_context=True)
def builtins_super(types, objects, context): def builtins_super(types, objects, context):
# TODO make this able to detect multiple inheritance super
if isinstance(context, FunctionExecutionContext): if isinstance(context, FunctionExecutionContext):
if isinstance(context.var_args, InstanceArguments): if isinstance(context.var_args, InstanceArguments):
su = context.var_args.instance.py__class__().py__bases__() instance = context.var_args.instance
return su[0].infer().execute_evaluated() # 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 return NO_CONTEXTS
@@ -582,4 +599,8 @@ _implemented = {
# being used instead. This only matters for 3.7+. # being used instead. This only matters for 3.7+.
'_alias': lambda obj, arguments: NO_CONTEXTS, '_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, install_requires=install_requires,
extras_require={ extras_require={
'testing': [ '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 for sith doctests
'docopt', 'docopt',
# coloroma for colored debug output # coloroma for colored debug output
@@ -60,6 +61,7 @@ setup(name='jedi',
'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Text Editors :: Integrated Development Environments (IDE)', 'Topic :: Text Editors :: Integrated Development Environments (IDE)',
'Topic :: Utilities', 'Topic :: Utilities',

View File

@@ -18,8 +18,10 @@ int(str)
str.. str..
#? [] #? []
a(0):. a(0):.
#? 2 ['and', 'or', 'if', 'is', 'in', 'not'] #? 2 []
0x0 0x0
#? []
1j
#? ['and', 'or', 'if', 'is', 'in', 'not'] #? ['and', 'or', 'if', 'is', 'in', 'not']
1j 1j
x = None() x = None()

View File

@@ -310,11 +310,14 @@ follow_statement(1)
# class decorators should just be ignored # class decorators should just be ignored
@should_ignore @should_ignore
class A(): class A():
x = 3
def ret(self): def ret(self):
return 1 return 1
#? int() #? int()
A().ret() A().ret()
#? int()
A().x
# ----------------- # -----------------

View File

@@ -109,6 +109,23 @@ class X():
#! 3 [] #! 3 []
X(foo=x) 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 # imports
# ----------------- # -----------------
@@ -203,9 +220,10 @@ for key, value in [(1,2)]:
#! ['for key, value in [(1,2)]: key'] #! ['for key, value in [(1,2)]: key']
key key
for i in []: #! 4 ['for y in [1]: y']
#! ['for i in []: i'] for y in [1]:
i #! ['for y in [1]: y']
y
# ----------------- # -----------------
# decorator # decorator

View File

@@ -256,6 +256,31 @@ for key in x.keys():
for value in x.values(): for value in x.values():
#? int() #? int()
value 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 # python >= 3.4
class TestDefaultDict(typing.DefaultDict[str, int]): class TestDefaultDict(typing.DefaultDict[str, int]):
@@ -295,7 +320,6 @@ import typing as t
def union2(x: t.Union[int, str]): def union2(x: t.Union[int, str]):
#? int() str() #? int() str()
x x
from typing import Union from typing import Union
def union3(x: Union[int, str]): def union3(x: Union[int, str]):
#? int() str() #? int() str()

View File

@@ -38,12 +38,16 @@ for char in NOT_DEFINED:
char char
# -------------------------
# instance/class vars
# -------------------------
class Foo(): class Foo():
bar: int bar: int
baz: typing.ClassVar[str] baz: typing.ClassVar[str]
#? int() #?
Foo.bar Foo.bar
#? int() #? int()
Foo().bar Foo().bar
@@ -51,3 +55,61 @@ Foo().bar
Foo.baz Foo.baz
#? str() #? str()
Foo().baz 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

@@ -73,7 +73,7 @@ def _check_number(Script, source, result='float'):
def test_completion_on_number_literals(Script): def test_completion_on_number_literals(Script):
# No completions on an int literal (is a float). # 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'] == ['and', 'if', 'in', 'is', 'not', 'or']
# Multiple points after an int literal basically mean that there's a float # 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, '1j.', 'complex')
_check_number(Script, '44.j.', 'complex') _check_number(Script, '44.j.', 'complex')
_check_number(Script, '4.0j.', '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! # 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'}) {'if', 'and', 'in', 'is', 'not', 'or'})

View File

@@ -1,12 +1,13 @@
import sys
from textwrap import dedent from textwrap import dedent
import inspect import inspect
import warnings
import pytest import pytest
from ..helpers import TestCase from ..helpers import TestCase
from jedi import cache from jedi import cache
from jedi.parser_utils import get_call_signature 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): 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', 0, 7)
run(s1, 'sorted', 1, 9) run(s1, 'sorted', 1, 9)
run(s1, 'sorted', 1, 10) run(s1, 'sorted', 1, 10)
run(s1, 'sorted', 1, 11) run(s1, 'sorted', None, 11)
run(s1, 'bool', 0, 15) run(s1, 'bool', 0, 15)
s2 = "abs(), " s2 = "abs(), "
@@ -272,6 +273,13 @@ def test_int_params(Script):
assert sig2.params[0].name == 'x' 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): def test_param_name(Script):
sigs = Script('open(something,').call_signatures() sigs = Script('open(something,').call_signatures()
for sig in sigs: for sig in sigs:
@@ -394,6 +402,148 @@ def test_keyword_argument_index(Script, environment):
assert get(both + 'foo(a, b, c').index == 0 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 test_bracket_start(Script):
def bracket_start(src): def bracket_start(src):
signatures = Script(src).call_signatures() signatures = Script(src).call_signatures()

View File

@@ -7,19 +7,16 @@ from inspect import cleandoc
import pytest import pytest
import jedi import jedi
from jedi import __doc__ as jedi_doc, names from jedi import __doc__ as jedi_doc
from ..helpers import TestCase
from jedi.evaluate.compiled import CompiledContextName from jedi.evaluate.compiled import CompiledContextName
def test_is_keyword(Script): 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() results = Script('str', 1, 1, None).goto_definitions()
assert len(results) == 1 and results[0].is_keyword is False 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(): def make_definitions():
""" """
Return a list of definitions for parametrized tests. Return a list of definitions for parametrized tests.
@@ -44,7 +41,7 @@ def test_basedefinition_type(Script, environment):
""") """)
definitions = [] definitions = []
definitions += names(source, environment=environment) definitions += names(source)
source += dedent(""" source += dedent("""
variable = sys or C or x or f or g or g() or h""") 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) assert "f(x, y=1, z='a')" in str(doc)
def test_param_docstring(): def test_param_docstring(names):
param = jedi.names("def test(parameter): pass", all_scopes=True)[1] param = names("def test(parameter): pass", all_scopes=True)[1]
assert param.name == 'parameter' assert param.name == 'parameter'
assert param.docstring() == '' assert param.docstring() == ''
@@ -140,28 +137,32 @@ def test_completion_docstring(Script, jedi_path):
docstr('abcd=3;abcd', '') docstr('abcd=3;abcd', '')
docstr('"hello"\nabcd=3\nabcd', '') docstr('"hello"\nabcd=3\nabcd', '')
docstr(dedent(''' docstr(
dedent('''
def x(): def x():
"hello" "hello"
0 0
x'''), x'''),
'hello' 'hello'
) )
docstr(dedent(''' docstr(
dedent('''
def x(): def x():
"hello";0 "hello";0
x'''), x'''),
'hello' 'hello'
) )
# Shouldn't work with a tuple. # Shouldn't work with a tuple.
docstr(dedent(''' docstr(
dedent('''
def x(): def x():
"hello",0 "hello",0
x'''), x'''),
'' ''
) )
# Should also not work if we rename something. # Should also not work if we rename something.
docstr(dedent(''' docstr(
dedent('''
def x(): def x():
"hello" "hello"
y = x 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=""'] assert [p.description for p in sig.params] == ['param a', 'param b=5', 'param c=""']
class TestIsDefinition(TestCase): @pytest.mark.parametrize(
@pytest.fixture(autouse=True) 'code, index, name, is_definition', [
def init(self, environment): ('name', 0, 'name', False),
self.environment = environment ('a = f(x)', 0, 'a', True),
('a = f(x)', 1, 'f', False),
def _def(self, source, index=-1): ('a = f(x)', 2, 'x', False),
return names( ]
dedent(source), )
def test_is_definition(names, code, index, name, is_definition):
d = names(
dedent(code),
references=True, references=True,
all_scopes=True, all_scopes=True,
environment=self.environment
)[index] )[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. # Assure that names are definitely sorted.
ns = sorted(ns, key=lambda name: (name.line, name.column)) ns = sorted(ns, key=lambda name: (name.line, name.column))
return [name.is_definition() for name in ns] assert [name.is_definition() for name in ns] == expected
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]
class TestParent(TestCase): def test_parent(Script):
@pytest.fixture(autouse=True) def _parent(source, line=None, column=None):
def init(self, Script): def_, = Script(dedent(source), line, column).goto_assignments()
self.Script = Script
def _parent(self, source, line=None, column=None):
def_, = self.Script(dedent(source), line, column).goto_assignments()
return def_.parent() return def_.parent()
def test_parent(self): parent = _parent('foo=1\nfoo')
parent = self._parent('foo=1\nfoo')
assert parent.type == 'module' assert parent.type == 'module'
parent = self._parent(''' parent = _parent('''
def spam(): def spam():
if 1: if 1:
y=1 y=1
@@ -279,10 +265,11 @@ class TestParent(TestCase):
assert parent.name == 'spam' assert parent.name == 'spam'
assert parent.parent().type == 'module' assert parent.parent().type == 'module'
def test_on_function(self):
parent = self._parent('''\ def test_parent_on_function(Script):
def spam(): code = 'def spam():\n pass'
pass''', 1, len('def spam')) def_, = Script(code, line=1, column=len('def spam')).goto_assignments()
parent = def_.parent()
assert parent.name == '' assert parent.name == ''
assert parent.type == 'module' assert parent.type == 'module'
@@ -331,8 +318,8 @@ different as an implementation.
""" """
def test_goto_assignment_repetition(environment): def test_goto_assignment_repetition(names):
defs = names('a = 1; a', references=True, definitions=False, environment=environment) defs = names('a = 1; a', references=True, definitions=False)
# Repeat on the same variable. Shouldn't change once we're on a # Repeat on the same variable. Shouldn't change once we're on a
# definition. # definition.
for _ in range(3): for _ in range(3):
@@ -341,34 +328,34 @@ def test_goto_assignment_repetition(environment):
assert ass[0].description == 'a = 1' assert ass[0].description == 'a = 1'
def test_goto_assignments_named_params(environment): def test_goto_assignments_named_params(names):
src = """\ src = """\
def foo(a=1, bar=2): def foo(a=1, bar=2):
pass pass
foo(bar=1) foo(bar=1)
""" """
bar = names(dedent(src), references=True, environment=environment)[-1] bar = names(dedent(src), references=True)[-1]
param = bar.goto_assignments()[0] param = bar.goto_assignments()[0]
assert (param.line, param.column) == (1, 13) assert (param.line, param.column) == (1, 13)
assert param.type == 'param' assert param.type == 'param'
def test_class_call(environment): def test_class_call(names):
src = 'from threading import Thread; Thread(group=1)' 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' assert n.name == 'group'
param_def = n.goto_assignments()[0] param_def = n.goto_assignments()[0]
assert param_def.name == 'group' assert param_def.name == 'group'
assert param_def.type == 'param' assert param_def.type == 'param'
def test_parentheses(environment): def test_parentheses(names):
n = names('("").upper', references=True, environment=environment)[-1] n = names('("").upper', references=True)[-1]
assert n.goto_assignments()[0].name == 'upper' assert n.goto_assignments()[0].name == 'upper'
def test_import(environment): def test_import(names):
nms = names('from json import load', references=True, environment=environment) nms = names('from json import load', references=True)
assert nms[0].name == 'json' assert nms[0].name == 'json'
assert nms[0].type == 'module' assert nms[0].type == 'module'
n = nms[0].goto_assignments()[0] n = nms[0].goto_assignments()[0]
@@ -381,7 +368,7 @@ def test_import(environment):
assert n.name == 'load' assert n.name == 'load'
assert n.type == 'function' 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].name == 'os'
assert nms[0].type == 'module' assert nms[0].type == 'module'
n = nms[0].goto_assignments()[0] n = nms[0].goto_assignments()[0]
@@ -392,7 +379,7 @@ def test_import(environment):
assert n.name == 'path' assert n.name == 'path'
assert n.type == 'module' 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] n = nms[0].goto_assignments()[0]
assert n.name == 'os' assert n.name == 'os'
assert n.type == 'module' assert n.type == 'module'
@@ -403,8 +390,8 @@ def test_import(environment):
assert n.type == 'module' assert n.type == 'module'
def test_import_alias(environment): def test_import_alias(names):
nms = names('import json as foo', references=True, environment=environment) nms = names('import json as foo', references=True)
assert nms[0].name == 'json' assert nms[0].name == 'json'
assert nms[0].type == 'module' assert nms[0].type == 'module'
assert nms[0]._name.tree_name.parent.type == 'dotted_as_name' 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): def test_in_comment(Script):
assert Script(" # Comment").completions() 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): def test_async(Script, environment):
@@ -140,3 +141,18 @@ def test_async(Script, environment):
def test_with_stmt_error_recovery(Script): def test_with_stmt_error_recovery(Script):
assert Script('with open('') as foo: foo.\na', line=1).completions() 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 from textwrap import dedent
import pytest
from jedi import names def _assert_definition_names(definitions, 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_):
assert [d.name for d in definitions] == names_ assert [d.name for d in definitions] == names_
def check_defined_names(self, source, names_):
definitions = names(dedent(source), environment=self.environment) def _check_names(names, source, names_):
self.assert_definition_names(definitions, names_) definitions = names(dedent(source))
_assert_definition_names(definitions, names_)
return definitions return definitions
def test_get_definitions_flat(self):
self.check_defined_names(""" def test_get_definitions_flat(names):
_check_names(names, """
import module import module
class Class: class Class:
pass pass
@@ -33,25 +25,27 @@ class TestDefinedNames(TestCase):
data = None data = None
""", ['module', 'Class', 'func', 'data']) """, ['module', 'Class', 'func', 'data'])
def test_dotted_assignment(self):
self.check_defined_names(""" def test_dotted_assignment(names):
_check_names(names, """
x = Class() x = Class()
x.y.z = None x.y.z = None
""", ['x', 'z']) # TODO is this behavior what we want? """, ['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): def test_multiple_assignment(names):
self.check_defined_names(""" _check_names(names, "x = y = None", ['x', 'y'])
def test_multiple_imports(names):
_check_names(names, """
from module import a, b from module import a, b
from another_module import * from another_module import *
""", ['a', 'b']) """, ['a', 'b'])
def test_nested_definitions(self):
definitions = self.check_defined_names(""" def test_nested_definitions(names):
definitions = _check_names(names, """
class Class: class Class:
def f(): def f():
pass pass
@@ -59,12 +53,12 @@ class TestDefinedNames(TestCase):
pass pass
""", ['Class']) """, ['Class'])
subdefinitions = definitions[0].defined_names() subdefinitions = definitions[0].defined_names()
self.assert_definition_names(subdefinitions, ['f', 'g']) _assert_definition_names(subdefinitions, ['f', 'g'])
self.assertEqual([d.full_name for d in subdefinitions], assert [d.full_name for d in subdefinitions] == ['__main__.Class.f', '__main__.Class.g']
['__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 L1:
class L2: class L2:
class L3: class L3:
@@ -75,12 +69,13 @@ class TestDefinedNames(TestCase):
""", ['L1', 'f']) """, ['L1', 'f'])
subdefs = definitions[0].defined_names() subdefs = definitions[0].defined_names()
subsubdefs = subdefs[0].defined_names() subsubdefs = subdefs[0].defined_names()
self.assert_definition_names(subdefs, ['L2', 'f']) _assert_definition_names(subdefs, ['L2', 'f'])
self.assert_definition_names(subsubdefs, ['L3', 'f']) _assert_definition_names(subsubdefs, ['L3', 'f'])
self.assert_definition_names(subsubdefs[0].defined_names(), ['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 from module import f
g = f(f) g = f(f)
class C: class C:
@@ -92,11 +87,12 @@ class TestDefinedNames(TestCase):
""", ['f', 'g', 'C', 'foo']) """, ['f', 'g', 'C', 'foo'])
C_subdefs = definitions[-2].defined_names() C_subdefs = definitions[-2].defined_names()
foo_subdefs = definitions[-1].defined_names() foo_subdefs = definitions[-1].defined_names()
self.assert_definition_names(C_subdefs, ['h']) _assert_definition_names(C_subdefs, ['h'])
self.assert_definition_names(foo_subdefs, ['x', 'bar']) _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 from module import f
import asyncio import asyncio
@@ -127,36 +123,37 @@ class TestDefinedNames(TestCase):
foo_subdefs = definitions[4].defined_names() foo_subdefs = definitions[4].defined_names()
async_foo_subdefs = definitions[5].defined_names() async_foo_subdefs = definitions[5].defined_names()
cinst_subdefs = definitions[6].defined_names() cinst_subdefs = definitions[6].defined_names()
self.assert_definition_names(C_subdefs, ['h', '__init__', '__aenter__']) _assert_definition_names(C_subdefs, ['h', '__init__', '__aenter__'])
self.assert_definition_names(foo_subdefs, ['x', 'bar']) _assert_definition_names(foo_subdefs, ['x', 'bar'])
self.assert_definition_names(async_foo_subdefs, ['duration', 'wait', 'i']) _assert_definition_names(async_foo_subdefs, ['duration', 'wait', 'i'])
# We treat d as a name outside `async with` block # 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 # github issue #344
imp = names('import datetime', environment=environment)[0] imp = names('import datetime')[0]
assert imp.name == 'datetime' assert imp.name == 'datetime'
datetime_names = [str(d.name) for d in imp.defined_names()] datetime_names = [str(d.name) for d in imp.defined_names()]
assert 'timedelta' in datetime_names assert 'timedelta' in datetime_names
def test_names_twice(environment): def test_names_twice(names):
source = dedent(''' source = dedent('''
def lol(): def lol():
pass pass
''') ''')
defs = names(source=source, environment=environment) defs = names(source=source)
assert defs[0].defined_names() == [] assert defs[0].defined_names() == []
def test_simple_name(environment): def test_simple_name(names):
defs = names('foo', references=True, environment=environment) defs = names('foo', references=True)
assert not defs[0]._name.infer() assert not defs[0]._name.infer()
def test_no_error(environment): def test_no_error(names):
code = dedent(""" code = dedent("""
def foo(a, b): def foo(a, b):
if a == 10: if a == 10:

View File

@@ -262,7 +262,7 @@ def test_completion_params():
def test_completion_param_annotations(): def test_completion_param_annotations():
# Need to define this function not directly in Python. Otherwise Jedi is to # Need to define this function not directly in Python. Otherwise Jedi is to
# clever and uses the Python code instead of the signature object. # 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()) exec_(code, locals())
script = jedi.Interpreter('foo', [locals()]) script = jedi.Interpreter('foo', [locals()])
c, = script.completions() 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 b.infer()] == ['str']
assert {d.name for d in c.infer()} == {'int', 'float'} 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 test_keyword_argument():
def f(some_keyword_argument): def f(some_keyword_argument):
@@ -423,3 +426,16 @@ def test_simple_completions(code, completions):
defs = jedi.Interpreter(code, [locals()]).completions() defs = jedi.Interpreter(code, [locals()]).completions()
assert [d.name for d in defs] == 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()] names = [c.name for c in Script('xzy; from abc import ').completions()]
assert 'ABCMeta' in names assert 'ABCMeta' in names
assert 'abc' not 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() signature, = compiled.get_signatures()
assert signature.to_string() == sig assert signature.to_string() == sig
assert [n.string_name for n in signature.get_param_names()] == names 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'}