mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 22:14:27 +08:00
Compare commits
177 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35e5cf2c2a | ||
|
|
c6f0ecd223 | ||
|
|
f727e4e661 | ||
|
|
1ad4003740 | ||
|
|
1108ad9994 | ||
|
|
f7f9b1e5ec | ||
|
|
c3d40949b1 | ||
|
|
a7accf4171 | ||
|
|
ab80646b86 | ||
|
|
3d0ac09fc9 | ||
|
|
0a84678a60 | ||
|
|
4a5c992b1a | ||
|
|
04b7c99753 | ||
|
|
499408657b | ||
|
|
4ec3fb6e12 | ||
|
|
463cbb1595 | ||
|
|
03608151e8 | ||
|
|
822394663c | ||
|
|
52517f78b1 | ||
|
|
a191b7b458 | ||
|
|
e68273c0ff | ||
|
|
aeff5faa3d | ||
|
|
0fd3757a51 | ||
|
|
1b064c1078 | ||
|
|
5726c29385 | ||
|
|
7c1c4981fb | ||
|
|
81488bcd20 | ||
|
|
99008eef43 | ||
|
|
3a9dc0ca2e | ||
|
|
98a550e352 | ||
|
|
4b8505b78d | ||
|
|
b7c2bacbd2 | ||
|
|
8108122347 | ||
|
|
45dada9552 | ||
|
|
38e0cbc1d2 | ||
|
|
e008a515e3 | ||
|
|
fd1e6afd07 | ||
|
|
9dd088f3db | ||
|
|
8e1417e3ce | ||
|
|
97526aa320 | ||
|
|
16e0351897 | ||
|
|
c0c7c949fd | ||
|
|
b8bc4060dd | ||
|
|
c737e3ee40 | ||
|
|
4c3d4508e9 | ||
|
|
70bcc9405f | ||
|
|
6a82f60901 | ||
|
|
814998253a | ||
|
|
a22c6da89f | ||
|
|
876a6a5c22 | ||
|
|
642e8f2aa6 | ||
|
|
a64ef2759c | ||
|
|
d58bbce24f | ||
|
|
ca6a7215e2 | ||
|
|
93b7548f1a | ||
|
|
24db05841b | ||
|
|
375d1d57fb | ||
|
|
c2e50e1d0d | ||
|
|
7988c1d11b | ||
|
|
8ab2a5320e | ||
|
|
b5a62825ce | ||
|
|
ec70815318 | ||
|
|
a739c17a6f | ||
|
|
ab5f4b6774 | ||
|
|
a5a544cb09 | ||
|
|
7d2374ed81 | ||
|
|
97b642a3e1 | ||
|
|
1151700114 | ||
|
|
75f654b944 | ||
|
|
bb852c3e85 | ||
|
|
1fbb69b35a | ||
|
|
0352c3250a | ||
|
|
268f828963 | ||
|
|
21508a8c79 | ||
|
|
f9de26f72c | ||
|
|
22580f771c | ||
|
|
9b338f69a6 | ||
|
|
fa0424cfd6 | ||
|
|
f6808a96e0 | ||
|
|
02bd7e5bc7 | ||
|
|
e8e3e8c111 | ||
|
|
c8588191f9 | ||
|
|
97e7f608df | ||
|
|
fae2c8c060 | ||
|
|
b4f2d82867 | ||
|
|
6a480780f8 | ||
|
|
41dc5382fa | ||
|
|
ba160e72ab | ||
|
|
0703a69369 | ||
|
|
c490d37c2d | ||
|
|
84219236a7 | ||
|
|
57fd995727 | ||
|
|
a803d687e2 | ||
|
|
c7927fb141 | ||
|
|
05d9602032 | ||
|
|
e76120da06 | ||
|
|
25bbecc269 | ||
|
|
08bb9cfae7 | ||
|
|
703b747a31 | ||
|
|
ff149b74e0 | ||
|
|
3d08eb92d5 | ||
|
|
02d16ac55c | ||
|
|
18eb7622ba | ||
|
|
13dd173664 | ||
|
|
73c078ec7a | ||
|
|
cdf50e2a69 | ||
|
|
2b0b29f921 | ||
|
|
0dc60fb535 | ||
|
|
5722a3458e | ||
|
|
93c52f615a | ||
|
|
050d686a27 | ||
|
|
7156ddf607 | ||
|
|
1cccc832b6 | ||
|
|
fd4eca5e03 | ||
|
|
1d9b9cff47 | ||
|
|
f4fe113c0f | ||
|
|
c7fc715535 | ||
|
|
eeea88046e | ||
|
|
dea887d27d | ||
|
|
8329e2e969 | ||
|
|
60415033b4 | ||
|
|
a06d760f45 | ||
|
|
b7687fcfb7 | ||
|
|
0ec86d5034 | ||
|
|
cef23f44cd | ||
|
|
e889a4923e | ||
|
|
114aba462c | ||
|
|
26c7cec7b5 | ||
|
|
3e3a33ab79 | ||
|
|
7f386e0e68 | ||
|
|
82d970d2b8 | ||
|
|
3ed9e836cc | ||
|
|
f984e8d6ef | ||
|
|
670cf4d394 | ||
|
|
e85fba844c | ||
|
|
ee5557ddf6 | ||
|
|
42f72b219b | ||
|
|
374721b789 | ||
|
|
01cec186ae | ||
|
|
3fb89f9f9b | ||
|
|
a0b4e76c1a | ||
|
|
97bf83aa03 | ||
|
|
ca7658cab7 | ||
|
|
dd78f4cfbf | ||
|
|
08019075c3 | ||
|
|
943617a94f | ||
|
|
d579c0ad57 | ||
|
|
76c6104415 | ||
|
|
ef9d803ce3 | ||
|
|
a26cb42d07 | ||
|
|
6b9b2836ba | ||
|
|
abdb8de89d | ||
|
|
ac492ef598 | ||
|
|
947bfe7b78 | ||
|
|
be6c90d135 | ||
|
|
8cb059deda | ||
|
|
0f4da5c1cf | ||
|
|
de138e9114 | ||
|
|
15bb9b29a2 | ||
|
|
4c132d94b9 | ||
|
|
925fc89447 | ||
|
|
cb95dbc707 | ||
|
|
1e3b6a201d | ||
|
|
3829ef4785 | ||
|
|
b382f06be0 | ||
|
|
94faceb57c | ||
|
|
a9ff58683e | ||
|
|
fafd6b2ac6 | ||
|
|
344a03e6b2 | ||
|
|
265abe1d08 | ||
|
|
ebdae87821 | ||
|
|
56ec79d62a | ||
|
|
c413b486fb | ||
|
|
cb0a0d228a | ||
|
|
3ae4a154f9 | ||
|
|
aa2dc6be09 | ||
|
|
a62ba86d7b |
@@ -15,9 +15,6 @@ env:
|
||||
- JEDI_TEST_ENVIRONMENT=37
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- python: pypy
|
||||
- python: 3.8-dev
|
||||
include:
|
||||
- python: 3.6
|
||||
env:
|
||||
|
||||
@@ -51,5 +51,7 @@ 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>
|
||||
Johannes Maria Frank (@jmfrank63) <jmfrank63@gmail.com>
|
||||
|
||||
Note: (@user) means a github user name.
|
||||
|
||||
@@ -3,6 +3,35 @@
|
||||
Changelog
|
||||
---------
|
||||
|
||||
0.15.0 (2019-08-11)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Added file path completions, there's a **new ``Completion.type``** ``path``,
|
||||
now. Example: ``'/ho`` -> ``'/home/``
|
||||
- ``*args``/``**kwargs`` resolving. If possible Jedi replaces the parameters
|
||||
with the actual alternatives.
|
||||
- Better support for enums/dataclasses
|
||||
- When using Interpreter, properties are now executed, since a lot of people
|
||||
have complained about this. Discussion in #1299, #1347.
|
||||
|
||||
New APIs:
|
||||
|
||||
- ``Definition.get_signatures() -> List[Signature]``. Signatures are similar to
|
||||
``CallSignature``. ``Definition.params`` is therefore deprecated.
|
||||
- ``Signature.to_string()`` to format call signatures.
|
||||
- ``Signature.params -> List[ParamDefinition]``, ParamDefinition has the
|
||||
following additional attributes ``infer_default()``, ``infer_annotation()``,
|
||||
``to_string()``, and ``kind``.
|
||||
- ``Definition.execute() -> List[Definition]``, makes it possible to infer
|
||||
return values of functions.
|
||||
|
||||
|
||||
0.14.1 (2019-07-13)
|
||||
+++++++++++++++++++
|
||||
|
||||
- CallSignature.index should now be working a lot better
|
||||
- A couple of smaller bugfixes
|
||||
|
||||
0.14.0 (2019-06-20)
|
||||
+++++++++++++++++++
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ Acknowledgements
|
||||
|
||||
|
||||
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
|
||||
.. _youcompleteme: https://valloric.github.io/YouCompleteMe/
|
||||
.. _youcompleteme: https://github.com/ycm-core/YouCompleteMe
|
||||
.. _deoplete-jedi: https://github.com/zchee/deoplete-jedi
|
||||
.. _completor.vim: https://github.com/maralla/completor.vim
|
||||
.. _Jedi.el: https://github.com/tkf/emacs-jedi
|
||||
|
||||
29
conftest.py
29
conftest.py
@@ -105,6 +105,11 @@ def Script(environment):
|
||||
return partial(jedi.Script, environment=environment)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def names(environment):
|
||||
return partial(jedi.names, environment=environment)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def has_typing(environment):
|
||||
if environment.version_info >= (3, 5, 0):
|
||||
@@ -127,3 +132,27 @@ 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()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def skip_pre_python37(environment):
|
||||
if environment.version_info < (3, 7):
|
||||
# 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_python35(environment):
|
||||
if environment.version_info < (3, 5):
|
||||
# This if is just needed to avoid that tests ever skip way more than
|
||||
# they should for all Python versions.
|
||||
pytest.skip()
|
||||
|
||||
@@ -140,15 +140,8 @@ Most of the features in PEP-0484 are supported including the typing module
|
||||
(for Python < 3.5 you have to do ``pip install typing`` to use these),
|
||||
and forward references.
|
||||
|
||||
Things that are missing (and this is not an exhaustive list; some of these
|
||||
are planned, others might be hard to implement and provide little worth):
|
||||
You can also use stub files.
|
||||
|
||||
- annotating functions with comments: https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code
|
||||
- understanding ``typing.cast()``
|
||||
- stub files: https://www.python.org/dev/peps/pep-0484/#stub-files
|
||||
- ``typing.Callable``
|
||||
- ``typing.TypeVar``
|
||||
- User defined generic types: https://www.python.org/dev/peps/pep-0484/#user-defined-generic-types
|
||||
|
||||
**Sphinx style**
|
||||
|
||||
|
||||
@@ -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.15.0'
|
||||
|
||||
from jedi.api import Script, Interpreter, set_debug_function, \
|
||||
preload_module, names
|
||||
@@ -42,3 +42,6 @@ from jedi.api.environment import find_virtualenvs, find_system_environments, \
|
||||
get_default_environment, InvalidPythonEnvironment, create_environment, \
|
||||
get_system_environment
|
||||
from jedi.api.exceptions import InternalError
|
||||
# Finally load the internal plugins. This is only internal.
|
||||
from jedi.plugins import registry
|
||||
del registry
|
||||
|
||||
@@ -361,9 +361,9 @@ except NameError:
|
||||
FileNotFoundError = IOError
|
||||
|
||||
try:
|
||||
NotADirectoryError = NotADirectoryError
|
||||
IsADirectoryError = IsADirectoryError
|
||||
except NameError:
|
||||
NotADirectoryError = IOError
|
||||
IsADirectoryError = IOError
|
||||
|
||||
try:
|
||||
PermissionError = PermissionError
|
||||
|
||||
@@ -27,7 +27,7 @@ from jedi.api import interpreter
|
||||
from jedi.api import helpers
|
||||
from jedi.api.completion import Completion
|
||||
from jedi.api.environment import InterpreterEnvironment
|
||||
from jedi.api.project import get_default_project
|
||||
from jedi.api.project import get_default_project, Project
|
||||
from jedi.evaluate import Evaluator
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate import usages
|
||||
@@ -144,7 +144,6 @@ class Script(object):
|
||||
'(0-%d) for line %d (%r).' % (
|
||||
column, line_len, line, line_string))
|
||||
self._pos = line, column
|
||||
self._path = path
|
||||
|
||||
cache.clear_time_caches()
|
||||
debug.reset_time()
|
||||
@@ -365,19 +364,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 +383,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):
|
||||
@@ -441,6 +436,7 @@ class Interpreter(Script):
|
||||
>>> print(script.completions()[0].name)
|
||||
upper
|
||||
"""
|
||||
_allow_descriptor_getattr_default = True
|
||||
|
||||
def __init__(self, source, namespaces, **kwds):
|
||||
"""
|
||||
@@ -468,8 +464,10 @@ class Interpreter(Script):
|
||||
if not isinstance(environment, InterpreterEnvironment):
|
||||
raise TypeError("The environment needs to be an InterpreterEnvironment subclass.")
|
||||
|
||||
super(Interpreter, self).__init__(source, environment=environment, **kwds)
|
||||
super(Interpreter, self).__init__(source, environment=environment,
|
||||
_project=Project(os.getcwd()), **kwds)
|
||||
self.namespaces = namespaces
|
||||
self._evaluator.allow_descriptor_getattr = self._allow_descriptor_getattr_default
|
||||
|
||||
def _get_module(self):
|
||||
return interpreter.MixedModuleContext(
|
||||
|
||||
@@ -4,10 +4,9 @@ These classes are the much bigger part of the whole API, because they contain
|
||||
the interesting information about completion and goto operations.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from parso.python.tree import search_ancestor
|
||||
|
||||
from jedi import settings
|
||||
from jedi import debug
|
||||
from jedi.evaluate.utils import unite
|
||||
@@ -38,6 +37,10 @@ def defined_names(evaluator, context):
|
||||
return [Definition(evaluator, n) for n in _sort_names_by_start_pos(names)]
|
||||
|
||||
|
||||
def _contexts_to_definitions(contexts):
|
||||
return [Definition(c.evaluator, c.name) for c in contexts]
|
||||
|
||||
|
||||
class BaseDefinition(object):
|
||||
_mapping = {
|
||||
'posixpath': 'os.path',
|
||||
@@ -150,6 +153,9 @@ class BaseDefinition(object):
|
||||
>>> defs[3]
|
||||
'function'
|
||||
|
||||
Valid values for are ``module``, ``class``, ``instance``, ``function``,
|
||||
``param``, ``path`` and ``keyword``.
|
||||
|
||||
"""
|
||||
tree_name = self._name.tree_name
|
||||
resolve = False
|
||||
@@ -330,6 +336,8 @@ class BaseDefinition(object):
|
||||
@memoize_method
|
||||
def params(self):
|
||||
"""
|
||||
Deprecated! Will raise a warning soon. Use get_signatures()[...].params.
|
||||
|
||||
Raises an ``AttributeError`` if the definition is not callable.
|
||||
Otherwise returns a list of `Definition` that represents the params.
|
||||
"""
|
||||
@@ -337,7 +345,10 @@ class BaseDefinition(object):
|
||||
# with overloading.
|
||||
for context in self._name.infer():
|
||||
for signature in context.get_signatures():
|
||||
return [Definition(self._evaluator, n) for n in signature.get_param_names()]
|
||||
return [
|
||||
Definition(self._evaluator, n)
|
||||
for n in signature.get_param_names(resolve_stars=True)
|
||||
]
|
||||
|
||||
if self.type == 'function' or self.type == 'class':
|
||||
# Fallback, if no signatures were defined (which is probably by
|
||||
@@ -358,9 +369,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,
|
||||
)
|
||||
|
||||
@@ -383,6 +395,12 @@ class BaseDefinition(object):
|
||||
start_index = max(index - before, 0)
|
||||
return ''.join(lines[start_index:index + after + 1])
|
||||
|
||||
def get_signatures(self):
|
||||
return [Signature(self._evaluator, s) for s in self._name.infer().get_signatures()]
|
||||
|
||||
def execute(self):
|
||||
return _contexts_to_definitions(self._name.infer().execute_evaluated())
|
||||
|
||||
|
||||
class Completion(BaseDefinition):
|
||||
"""
|
||||
@@ -529,17 +547,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
|
||||
@@ -601,17 +615,38 @@ class Definition(BaseDefinition):
|
||||
return hash((self._name.start_pos, self.module_path, self.name, self._evaluator))
|
||||
|
||||
|
||||
class CallSignature(Definition):
|
||||
class Signature(Definition):
|
||||
"""
|
||||
`CallSignature` objects is the return value of `Script.function_definition`.
|
||||
`Signature` objects is the return value of `Script.function_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):
|
||||
super(CallSignature, self).__init__(evaluator, signature.name)
|
||||
self._index = index
|
||||
self._key_name_str = key_name_str
|
||||
self._bracket_start_pos = bracket_start_pos
|
||||
def __init__(self, evaluator, signature):
|
||||
super(Signature, self).__init__(evaluator, signature.name)
|
||||
self._signature = signature
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
"""
|
||||
:return list of ParamDefinition:
|
||||
"""
|
||||
return [ParamDefinition(self._evaluator, n)
|
||||
for n in self._signature.get_param_names(resolve_stars=True)]
|
||||
|
||||
def to_string(self):
|
||||
return self._signature.to_string()
|
||||
|
||||
|
||||
class CallSignature(Signature):
|
||||
"""
|
||||
`CallSignature` objects is the return value of `Script.call_signatures`.
|
||||
It knows what functions you are currently in. e.g. `isinstance(` would
|
||||
return the `isinstance` function with its params. Without `(` it would
|
||||
return nothing.
|
||||
"""
|
||||
def __init__(self, evaluator, signature, call_details):
|
||||
super(CallSignature, self).__init__(evaluator, signature)
|
||||
self._call_details = call_details
|
||||
self._signature = signature
|
||||
|
||||
@property
|
||||
@@ -620,53 +655,60 @@ 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
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
return [Definition(self._evaluator, n) for n in self._signature.get_param_names()]
|
||||
return self._call_details.calculate_index(
|
||||
self._signature.get_param_names(resolve_stars=True)
|
||||
)
|
||||
|
||||
@property
|
||||
def bracket_start(self):
|
||||
"""
|
||||
The indent of the bracket that is responsible for the last function
|
||||
call.
|
||||
The line/column of the bracket that is responsible for the last
|
||||
function call.
|
||||
"""
|
||||
return self._bracket_start_pos
|
||||
|
||||
@property
|
||||
def _params_str(self):
|
||||
return ', '.join([p.description[6:]
|
||||
for p in self.params])
|
||||
return self._call_details.bracket_leaf.start_pos
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s index=%r params=[%s]>' % (
|
||||
return '<%s: index=%r %s>' % (
|
||||
type(self).__name__,
|
||||
self._name.string_name,
|
||||
self._index,
|
||||
self._params_str,
|
||||
self.index,
|
||||
self._signature.to_string(),
|
||||
)
|
||||
|
||||
|
||||
class ParamDefinition(Definition):
|
||||
def infer_default(self):
|
||||
"""
|
||||
:return list of Definition:
|
||||
"""
|
||||
return _contexts_to_definitions(self._name.infer_default())
|
||||
|
||||
def infer_annotation(self, **kwargs):
|
||||
"""
|
||||
:return list of Definition:
|
||||
|
||||
:param execute_annotation: If False, the values are not executed and
|
||||
you get classes instead of instances.
|
||||
"""
|
||||
return _contexts_to_definitions(self._name.infer_annotation(**kwargs))
|
||||
|
||||
def to_string(self):
|
||||
return self._name.to_string()
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
"""
|
||||
Returns an enum instance. Returns the same values as the builtin
|
||||
:py:attr:`inspect.Parameter.kind`.
|
||||
|
||||
No support for Python < 3.4 anymore.
|
||||
"""
|
||||
if sys.version_info < (3, 5):
|
||||
raise NotImplementedError(
|
||||
'Python 2 is end-of-life, the new feature is not available for it'
|
||||
)
|
||||
return self._name.get_kind()
|
||||
|
||||
|
||||
def _format_signatures(context):
|
||||
return '\n'.join(
|
||||
signature.to_string()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import re
|
||||
|
||||
from parso.python.token import PythonTokenTypes
|
||||
from parso.python import tree
|
||||
from parso.tree import search_ancestor, Leaf
|
||||
@@ -7,12 +9,13 @@ from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi.api import classes
|
||||
from jedi.api import helpers
|
||||
from jedi.evaluate import imports
|
||||
from jedi.api import keywords
|
||||
from jedi.api.file_name import file_name_completions
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate.helpers import evaluate_call_of_leaf, parse_dotted_names
|
||||
from jedi.evaluate.filters import get_global_filters
|
||||
from jedi.evaluate.gradual.conversion import convert_contexts
|
||||
from jedi.parser_utils import get_statement_of_position
|
||||
from jedi.parser_utils import get_statement_of_position, cut_value_at_position
|
||||
|
||||
|
||||
def get_call_signature_param_names(call_signatures):
|
||||
@@ -82,7 +85,7 @@ def get_flow_scope_node(module_node, position):
|
||||
|
||||
|
||||
class Completion:
|
||||
def __init__(self, evaluator, module, code_lines, position, call_signatures_method):
|
||||
def __init__(self, evaluator, module, code_lines, position, call_signatures_callback):
|
||||
self._evaluator = evaluator
|
||||
self._module_context = module
|
||||
self._module_node = module.tree_node
|
||||
@@ -92,11 +95,23 @@ class Completion:
|
||||
self._like_name = helpers.get_on_completion_name(self._module_node, code_lines, position)
|
||||
# The actual cursor position is not what we need to calculate
|
||||
# everything. We want the start of the name we're on.
|
||||
self._original_position = position
|
||||
self._position = position[0], position[1] - len(self._like_name)
|
||||
self._call_signatures_method = call_signatures_method
|
||||
self._call_signatures_callback = call_signatures_callback
|
||||
|
||||
def completions(self):
|
||||
completion_names = self._get_context_completions()
|
||||
leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
|
||||
string, start_leaf = _extract_string_while_in_string(leaf, self._position)
|
||||
if string is not None:
|
||||
completions = list(file_name_completions(
|
||||
self._evaluator, self._module_context, start_leaf, string,
|
||||
self._like_name, self._call_signatures_callback,
|
||||
self._code_lines, self._original_position
|
||||
))
|
||||
if completions:
|
||||
return completions
|
||||
|
||||
completion_names = self._get_context_completions(leaf)
|
||||
|
||||
completions = filter_names(self._evaluator, completion_names,
|
||||
self.stack, self._like_name)
|
||||
@@ -105,7 +120,7 @@ class Completion:
|
||||
x.name.startswith('_'),
|
||||
x.name.lower()))
|
||||
|
||||
def _get_context_completions(self):
|
||||
def _get_context_completions(self, leaf):
|
||||
"""
|
||||
Analyzes the context that a completion is made in and decides what to
|
||||
return.
|
||||
@@ -121,19 +136,20 @@ class Completion:
|
||||
"""
|
||||
|
||||
grammar = self._evaluator.grammar
|
||||
self.stack = stack = None
|
||||
|
||||
try:
|
||||
self.stack = stack = helpers.get_stack_at_position(
|
||||
grammar, self._code_lines, self._module_node, self._position
|
||||
grammar, self._code_lines, leaf, self._position
|
||||
)
|
||||
except helpers.OnErrorLeaf as e:
|
||||
self.stack = stack = None
|
||||
if e.error_leaf.value == '.':
|
||||
value = e.error_leaf.value
|
||||
if value == '.':
|
||||
# After ErrorLeaf's that are dots, we will not do any
|
||||
# completions since this probably just confuses the user.
|
||||
return []
|
||||
# If we don't have a context, just use global completion.
|
||||
|
||||
# If we don't have a context, just use global completion.
|
||||
return self._global_completions()
|
||||
|
||||
allowed_transitions = \
|
||||
@@ -171,7 +187,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)):
|
||||
@@ -207,7 +226,7 @@ class Completion:
|
||||
completion_names += self._get_class_context_completions(is_function=False)
|
||||
|
||||
if 'trailer' in nonterminals:
|
||||
call_signatures = self._call_signatures_method()
|
||||
call_signatures = self._call_signatures_callback()
|
||||
completion_names += get_call_signature_param_names(call_signatures)
|
||||
|
||||
return completion_names
|
||||
@@ -286,3 +305,22 @@ class Completion:
|
||||
# TODO we should probably check here for properties
|
||||
if (name.api_type == 'function') == is_function:
|
||||
yield name
|
||||
|
||||
|
||||
def _extract_string_while_in_string(leaf, position):
|
||||
if leaf.type == 'string':
|
||||
match = re.match(r'^\w*(\'{3}|"{3}|\'|")', leaf.value)
|
||||
quote = match.group(1)
|
||||
if leaf.line == position[0] and position[1] < leaf.column + match.end():
|
||||
return None, None
|
||||
if leaf.end_pos[0] == position[0] and position[1] > leaf.end_pos[1] - len(quote):
|
||||
return None, None
|
||||
return cut_value_at_position(leaf, position)[match.end():], leaf
|
||||
|
||||
leaves = []
|
||||
while leaf is not None and leaf.line == position[0]:
|
||||
if leaf.type == 'error_leaf' and ('"' in leaf.value or "'" in leaf.value):
|
||||
return ''.join(l.get_code() for l in leaves), leaf
|
||||
leaves.insert(0, leaf)
|
||||
leaf = leaf.get_previous_leaf()
|
||||
return None, None
|
||||
|
||||
162
jedi/api/file_name.py
Normal file
162
jedi/api/file_name.py
Normal file
@@ -0,0 +1,162 @@
|
||||
import os
|
||||
|
||||
from jedi._compatibility import FileNotFoundError, force_unicode
|
||||
from jedi.evaluate.names import AbstractArbitraryName
|
||||
from jedi.api import classes
|
||||
from jedi.evaluate.helpers import get_str_or_none
|
||||
from jedi.parser_utils import get_string_quote
|
||||
|
||||
|
||||
def file_name_completions(evaluator, module_context, start_leaf, string,
|
||||
like_name, call_signatures_callback, code_lines, position):
|
||||
# First we want to find out what can actually be changed as a name.
|
||||
like_name_length = len(os.path.basename(string) + like_name)
|
||||
|
||||
addition = _get_string_additions(module_context, start_leaf)
|
||||
if addition is None:
|
||||
return
|
||||
string = addition + string
|
||||
|
||||
# Here we use basename again, because if strings are added like
|
||||
# `'foo' + 'bar`, it should complete to `foobar/`.
|
||||
must_start_with = os.path.basename(string) + like_name
|
||||
string = os.path.dirname(string)
|
||||
|
||||
sigs = call_signatures_callback()
|
||||
is_in_os_path_join = sigs and all(s.full_name == 'os.path.join' for s in sigs)
|
||||
if is_in_os_path_join:
|
||||
to_be_added = _add_os_path_join(module_context, start_leaf, sigs[0].bracket_start)
|
||||
if to_be_added is None:
|
||||
is_in_os_path_join = False
|
||||
else:
|
||||
string = to_be_added + string
|
||||
base_path = os.path.join(evaluator.project._path, string)
|
||||
try:
|
||||
listed = os.listdir(base_path)
|
||||
except FileNotFoundError:
|
||||
return
|
||||
for name in listed:
|
||||
if name.startswith(must_start_with):
|
||||
path_for_name = os.path.join(base_path, name)
|
||||
if is_in_os_path_join or not os.path.isdir(path_for_name):
|
||||
if start_leaf.type == 'string':
|
||||
quote = get_string_quote(start_leaf)
|
||||
else:
|
||||
assert start_leaf.type == 'error_leaf'
|
||||
quote = start_leaf.value
|
||||
potential_other_quote = \
|
||||
code_lines[position[0] - 1][position[1]:position[1] + len(quote)]
|
||||
# Add a quote if it's not already there.
|
||||
print(repr(quote), repr(potential_other_quote), position)
|
||||
if quote != potential_other_quote:
|
||||
name += quote
|
||||
else:
|
||||
name += os.path.sep
|
||||
|
||||
yield classes.Completion(
|
||||
evaluator,
|
||||
FileName(evaluator, name[len(must_start_with) - like_name_length:]),
|
||||
stack=None,
|
||||
like_name_length=like_name_length
|
||||
)
|
||||
|
||||
|
||||
def _get_string_additions(module_context, start_leaf):
|
||||
def iterate_nodes():
|
||||
node = addition.parent
|
||||
was_addition = True
|
||||
for child_node in reversed(node.children[:node.children.index(addition)]):
|
||||
if was_addition:
|
||||
was_addition = False
|
||||
yield child_node
|
||||
continue
|
||||
|
||||
if child_node != '+':
|
||||
break
|
||||
was_addition = True
|
||||
|
||||
addition = start_leaf.get_previous_leaf()
|
||||
if addition != '+':
|
||||
return ''
|
||||
context = module_context.create_context(start_leaf)
|
||||
return _add_strings(context, reversed(list(iterate_nodes())))
|
||||
|
||||
|
||||
def _add_strings(context, nodes, add_slash=False):
|
||||
string = ''
|
||||
first = True
|
||||
for child_node in nodes:
|
||||
contexts = context.eval_node(child_node)
|
||||
if len(contexts) != 1:
|
||||
return None
|
||||
c, = contexts
|
||||
s = get_str_or_none(c)
|
||||
if s is None:
|
||||
return None
|
||||
if not first and add_slash:
|
||||
string += os.path.sep
|
||||
string += force_unicode(s)
|
||||
first = False
|
||||
return string
|
||||
|
||||
|
||||
class FileName(AbstractArbitraryName):
|
||||
api_type = u'path'
|
||||
is_context_name = False
|
||||
|
||||
|
||||
def _add_os_path_join(module_context, start_leaf, bracket_start):
|
||||
def check(maybe_bracket, nodes):
|
||||
if maybe_bracket.start_pos != bracket_start:
|
||||
return None
|
||||
|
||||
if not nodes:
|
||||
return ''
|
||||
context = module_context.create_context(nodes[0])
|
||||
return _add_strings(context, nodes, add_slash=True) or ''
|
||||
|
||||
if start_leaf.type == 'error_leaf':
|
||||
# Unfinished string literal, like `join('`
|
||||
context_node = start_leaf.parent
|
||||
index = context_node.children.index(start_leaf)
|
||||
if index > 0:
|
||||
error_node = context_node.children[index - 1]
|
||||
if error_node.type == 'error_node' and len(error_node.children) >= 2:
|
||||
index = -2
|
||||
if error_node.children[-1].type == 'arglist':
|
||||
arglist_nodes = error_node.children[-1].children
|
||||
index -= 1
|
||||
else:
|
||||
arglist_nodes = []
|
||||
|
||||
return check(error_node.children[index + 1], arglist_nodes[::2])
|
||||
return None
|
||||
|
||||
# Maybe an arglist or some weird error case. Therefore checked below.
|
||||
searched_node_child = start_leaf
|
||||
while searched_node_child.parent is not None \
|
||||
and searched_node_child.parent.type not in ('arglist', 'trailer', 'error_node'):
|
||||
searched_node_child = searched_node_child.parent
|
||||
|
||||
if searched_node_child.get_first_leaf() is not start_leaf:
|
||||
return None
|
||||
searched_node = searched_node_child.parent
|
||||
if searched_node is None:
|
||||
return None
|
||||
|
||||
index = searched_node.children.index(searched_node_child)
|
||||
arglist_nodes = searched_node.children[:index]
|
||||
if searched_node.type == 'arglist':
|
||||
trailer = searched_node.parent
|
||||
if trailer.type == 'error_node':
|
||||
trailer_index = trailer.children.index(searched_node)
|
||||
assert trailer_index >= 2
|
||||
assert trailer.children[trailer_index - 1] == '('
|
||||
return check(trailer.children[trailer_index - 1], arglist_nodes[::2])
|
||||
elif trailer.type == 'trailer':
|
||||
return check(trailer.children[0], arglist_nodes[::2])
|
||||
elif searched_node.type == 'trailer':
|
||||
return check(searched_node.children[0], [])
|
||||
elif searched_node.type == 'error_node':
|
||||
# Stuff like `join(""`
|
||||
return check(arglist_nodes[-1], [])
|
||||
@@ -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
|
||||
@@ -54,8 +54,7 @@ class OnErrorLeaf(Exception):
|
||||
return self.args[0]
|
||||
|
||||
|
||||
def _get_code_for_stack(code_lines, module_node, position):
|
||||
leaf = module_node.get_leaf_for_position(position, include_prefixes=True)
|
||||
def _get_code_for_stack(code_lines, leaf, position):
|
||||
# It might happen that we're on whitespace or on a comment. This means
|
||||
# that we would not get the right leaf.
|
||||
if leaf.start_pos >= position:
|
||||
@@ -95,7 +94,7 @@ def _get_code_for_stack(code_lines, module_node, position):
|
||||
return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position)
|
||||
|
||||
|
||||
def get_stack_at_position(grammar, code_lines, module_node, pos):
|
||||
def get_stack_at_position(grammar, code_lines, leaf, pos):
|
||||
"""
|
||||
Returns the possible node names (e.g. import_from, xor_test or yield_stmt).
|
||||
"""
|
||||
@@ -119,7 +118,7 @@ def get_stack_at_position(grammar, code_lines, module_node, pos):
|
||||
yield token
|
||||
|
||||
# The code might be indedented, just remove it.
|
||||
code = dedent(_get_code_for_stack(code_lines, module_node, pos))
|
||||
code = dedent(_get_code_for_stack(code_lines, leaf, pos))
|
||||
# We use a word to tell Jedi when we have reached the start of the
|
||||
# completion.
|
||||
# Use Z as a prefix because it's not part of a number suffix.
|
||||
@@ -159,10 +158,139 @@ def evaluate_goto_definition(evaluator, context, leaf):
|
||||
return definitions
|
||||
|
||||
|
||||
CallSignatureDetails = namedtuple(
|
||||
'CallSignatureDetails',
|
||||
['bracket_leaf', 'call_index', 'keyword_name_str']
|
||||
)
|
||||
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,23 +299,22 @@ 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:
|
||||
# Checked if the argument
|
||||
key_str = last.children[0].value
|
||||
elif last == '=':
|
||||
key_str = nodes_before[-2].value
|
||||
last = nodes_before[-1]
|
||||
if last.type == 'argument' and last.children[1] == '=' \
|
||||
and last.children[1].end_pos <= position:
|
||||
# Checked if the argument
|
||||
key_str = last.children[0].value
|
||||
elif last == '=':
|
||||
key_str = nodes_before[-2].value
|
||||
|
||||
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 +325,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 +337,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 +350,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)
|
||||
if result is not None:
|
||||
return result
|
||||
additional_children = []
|
||||
for n in reversed(node.children):
|
||||
if n.start_pos < position:
|
||||
if n.type == 'error_node':
|
||||
result = _get_call_signature_details_from_error_node(
|
||||
n, additional_children, position
|
||||
)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
additional_children[0:0] = n.children
|
||||
continue
|
||||
additional_children.insert(0, n)
|
||||
|
||||
if node.type == 'trailer' and node.children[0] == '(':
|
||||
leaf = node.get_previous_leaf()
|
||||
if leaf is None:
|
||||
return None
|
||||
return CallSignatureDetails(
|
||||
node.children[0], *_get_index_and_key(node.children, position))
|
||||
return CallDetails(node.children[0], node.children, position)
|
||||
|
||||
node = node.parent
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pydoc
|
||||
|
||||
from jedi.evaluate.utils import ignored
|
||||
from jedi.evaluate.names import AbstractNameDefinition
|
||||
from jedi.evaluate.names import AbstractArbitraryName
|
||||
|
||||
try:
|
||||
from pydoc_data import topics as pydoc_topics
|
||||
@@ -19,14 +19,8 @@ def get_operator(evaluator, string, pos):
|
||||
return Keyword(evaluator, string, pos)
|
||||
|
||||
|
||||
class KeywordName(AbstractNameDefinition):
|
||||
class KeywordName(AbstractArbitraryName):
|
||||
api_type = u'keyword'
|
||||
is_context_name = False
|
||||
|
||||
def __init__(self, evaluator, name):
|
||||
self.evaluator = evaluator
|
||||
self.string_name = name
|
||||
self.parent_context = evaluator.builtins_module
|
||||
|
||||
def infer(self):
|
||||
return [Keyword(self.evaluator, self.string_name, (0, 0))]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -62,8 +62,6 @@ I need to mention now that lazy evaluation is really good because it
|
||||
only *evaluates* what needs to be *evaluated*. All the statements and modules
|
||||
that are not used are just being ignored.
|
||||
"""
|
||||
from functools import partial
|
||||
|
||||
from parso.python import tree
|
||||
import parso
|
||||
from parso import python_bytes_to_unicode
|
||||
@@ -84,14 +82,7 @@ from jedi.evaluate.context import ClassContext, FunctionContext, \
|
||||
from jedi.evaluate.context.iterable import CompForContext
|
||||
from jedi.evaluate.syntax_tree import eval_trailer, eval_expr_stmt, \
|
||||
eval_node, check_tuple_assignments
|
||||
|
||||
|
||||
def _execute(context, arguments):
|
||||
debug.dbg('execute: %s %s', context, arguments)
|
||||
with debug.increase_indent_cm():
|
||||
context_set = context.py__call__(arguments=arguments)
|
||||
debug.dbg('execute result: %s in %s', context_set, context)
|
||||
return context_set
|
||||
from jedi.plugins import plugin_manager
|
||||
|
||||
|
||||
class Evaluator(object):
|
||||
@@ -115,28 +106,26 @@ class Evaluator(object):
|
||||
self.is_analysis = False
|
||||
self.project = project
|
||||
self.access_cache = {}
|
||||
self.allow_descriptor_getattr = False
|
||||
|
||||
self.reset_recursion_limitations()
|
||||
self.allow_different_encoding = True
|
||||
|
||||
# Plugin API
|
||||
from jedi.plugins import plugin_manager
|
||||
plugin_callbacks = plugin_manager.get_callbacks(self)
|
||||
self.execute = plugin_callbacks.decorate('execute', callback=_execute)
|
||||
self._import_module = partial(
|
||||
plugin_callbacks.decorate(
|
||||
'import_module',
|
||||
callback=imports.import_module
|
||||
),
|
||||
self,
|
||||
)
|
||||
|
||||
def import_module(self, import_names, parent_module_context=None,
|
||||
sys_path=None, prefer_stubs=True):
|
||||
if sys_path is None:
|
||||
sys_path = self.get_sys_path()
|
||||
return self._import_module(import_names, parent_module_context,
|
||||
sys_path, prefer_stubs=prefer_stubs)
|
||||
return imports.import_module(self, import_names, parent_module_context,
|
||||
sys_path, prefer_stubs=prefer_stubs)
|
||||
|
||||
@staticmethod
|
||||
@plugin_manager.decorate()
|
||||
def execute(context, arguments):
|
||||
debug.dbg('execute: %s %s', context, arguments)
|
||||
with debug.increase_indent_cm():
|
||||
context_set = context.py__call__(arguments=arguments)
|
||||
debug.dbg('execute result: %s in %s', context_set, context)
|
||||
return context_set
|
||||
|
||||
@property
|
||||
@evaluator_function_cache()
|
||||
@@ -318,11 +307,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:
|
||||
|
||||
@@ -11,6 +11,7 @@ from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts, \
|
||||
from jedi.evaluate.names import ParamName, TreeNameDefinition
|
||||
from jedi.evaluate.base_context import NO_CONTEXTS, ContextSet, ContextualizedNode
|
||||
from jedi.evaluate.context import iterable
|
||||
from jedi.evaluate.cache import evaluator_as_method_param_cache
|
||||
from jedi.evaluate.param import get_executed_params_and_issues, ExecutedParam
|
||||
|
||||
|
||||
@@ -35,7 +36,7 @@ class ParamIssue(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def repack_with_argument_clinic(string, keep_arguments_param=False):
|
||||
def repack_with_argument_clinic(string, keep_arguments_param=False, keep_callback_param=False):
|
||||
"""
|
||||
Transforms a function or method with arguments to the signature that is
|
||||
given as an argument clinic notation.
|
||||
@@ -54,6 +55,8 @@ def repack_with_argument_clinic(string, keep_arguments_param=False):
|
||||
arguments = kwargs['arguments']
|
||||
else:
|
||||
arguments = kwargs.pop('arguments')
|
||||
if not keep_arguments_param:
|
||||
kwargs.pop('callback', None)
|
||||
try:
|
||||
args += tuple(_iterate_argument_clinic(
|
||||
context.evaluator,
|
||||
@@ -208,6 +211,11 @@ class TreeArguments(AbstractArguments):
|
||||
self._evaluator = evaluator
|
||||
self.trailer = trailer # Can be None, e.g. in a class definition.
|
||||
|
||||
@classmethod
|
||||
@evaluator_as_method_param_cache()
|
||||
def create_cached(cls, *args, **kwargs):
|
||||
return cls(*args, **kwargs)
|
||||
|
||||
def unpack(self, funcdef=None):
|
||||
named_args = []
|
||||
for star_count, el in unpack_arglist(self.argument_node):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from jedi._compatibility import unicode
|
||||
from jedi.evaluate.compiled.context import CompiledObject, CompiledName, \
|
||||
CompiledObjectFilter, CompiledContextName, create_from_access_path
|
||||
from jedi.evaluate.base_context import ContextWrapper
|
||||
from jedi.evaluate.base_context import ContextWrapper, LazyContextWrapper
|
||||
|
||||
|
||||
def builtin_from_name(evaluator, string):
|
||||
@@ -16,9 +16,9 @@ def builtin_from_name(evaluator, string):
|
||||
return context
|
||||
|
||||
|
||||
class CompiledValue(ContextWrapper):
|
||||
def __init__(self, instance, compiled_obj):
|
||||
super(CompiledValue, self).__init__(instance)
|
||||
class CompiledValue(LazyContextWrapper):
|
||||
def __init__(self, compiled_obj):
|
||||
self.evaluator = compiled_obj.evaluator
|
||||
self._compiled_obj = compiled_obj
|
||||
|
||||
def __getattribute__(self, name):
|
||||
@@ -27,6 +27,11 @@ class CompiledValue(ContextWrapper):
|
||||
return getattr(self._compiled_obj, name)
|
||||
return super(CompiledValue, self).__getattribute__(name)
|
||||
|
||||
def _get_wrapped_context(self):
|
||||
instance, = builtin_from_name(
|
||||
self.evaluator, self._compiled_obj.name.string_name).execute_evaluated()
|
||||
return instance
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._compiled_obj)
|
||||
|
||||
@@ -41,8 +46,7 @@ def create_simple_object(evaluator, obj):
|
||||
evaluator,
|
||||
evaluator.compiled_subprocess.create_simple_object(obj)
|
||||
)
|
||||
instance, = builtin_from_name(evaluator, compiled_obj.name.string_name).execute_evaluated()
|
||||
return CompiledValue(instance, compiled_obj)
|
||||
return CompiledValue(compiled_obj)
|
||||
|
||||
|
||||
def get_string_context_set(evaluator):
|
||||
|
||||
@@ -84,9 +84,14 @@ def safe_getattr(obj, name, default=_sentinel):
|
||||
raise
|
||||
return default
|
||||
else:
|
||||
if type(attr) in ALLOWED_DESCRIPTOR_ACCESS:
|
||||
if isinstance(attr, ALLOWED_DESCRIPTOR_ACCESS):
|
||||
# In case of descriptors that have get methods we cannot return
|
||||
# it's value, because that would mean code execution.
|
||||
# Since it's an isinstance call, code execution is still possible,
|
||||
# but this is not really a security feature, but much more of a
|
||||
# safety feature. Code execution is basically always possible when
|
||||
# a module is imported. This is here so people don't shoot
|
||||
# themselves in the foot.
|
||||
return getattr(obj, name)
|
||||
return attr
|
||||
|
||||
@@ -175,6 +180,18 @@ def _force_unicode_decorator(func):
|
||||
return lambda *args, **kwargs: force_unicode(func(*args, **kwargs))
|
||||
|
||||
|
||||
def get_api_type(obj):
|
||||
if inspect.isclass(obj):
|
||||
return u'class'
|
||||
elif inspect.ismodule(obj):
|
||||
return u'module'
|
||||
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
|
||||
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
|
||||
return u'function'
|
||||
# Everything else...
|
||||
return u'instance'
|
||||
|
||||
|
||||
class DirectObjectAccess(object):
|
||||
def __init__(self, evaluator, obj):
|
||||
self._evaluator = evaluator
|
||||
@@ -328,12 +345,16 @@ class DirectObjectAccess(object):
|
||||
def getattr_paths(self, name, default=_sentinel):
|
||||
try:
|
||||
return_obj = getattr(self._obj, name)
|
||||
except AttributeError:
|
||||
# Happens e.g. in properties of
|
||||
# PyQt4.QtGui.QStyleOptionComboBox.currentText
|
||||
# -> just set it to None
|
||||
except Exception as e:
|
||||
if default is _sentinel:
|
||||
raise
|
||||
if isinstance(e, AttributeError):
|
||||
# Happens e.g. in properties of
|
||||
# PyQt4.QtGui.QStyleOptionComboBox.currentText
|
||||
# -> just set it to None
|
||||
raise
|
||||
# Just in case anything happens, return an AttributeError. It
|
||||
# should not crash.
|
||||
raise AttributeError
|
||||
return_obj = default
|
||||
access = self._create_access(return_obj)
|
||||
if inspect.ismodule(return_obj):
|
||||
@@ -352,16 +373,7 @@ class DirectObjectAccess(object):
|
||||
raise ValueError("Object is type %s and not simple" % type(self._obj))
|
||||
|
||||
def get_api_type(self):
|
||||
obj = self._obj
|
||||
if self.is_class():
|
||||
return u'class'
|
||||
elif inspect.ismodule(obj):
|
||||
return u'module'
|
||||
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
|
||||
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
|
||||
return u'function'
|
||||
# Everything else...
|
||||
return u'instance'
|
||||
return get_api_type(self._obj)
|
||||
|
||||
def get_access_path_tuples(self):
|
||||
accesses = [create_access(self._evaluator, o) for o in self._get_objects_path()]
|
||||
@@ -412,7 +424,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 +460,17 @@ class DirectObjectAccess(object):
|
||||
# the signature. In that case we just want a simple escape for now.
|
||||
raise ValueError
|
||||
|
||||
def get_return_annotation(self):
|
||||
try:
|
||||
o = self._obj.__annotations__.get('return')
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
if o is None:
|
||||
return None
|
||||
|
||||
return self._create_access_path(o)
|
||||
|
||||
def negate(self):
|
||||
return self._create_access_path(-self._obj)
|
||||
|
||||
|
||||
@@ -46,6 +46,11 @@ class CompiledObject(Context):
|
||||
self.access_handle = access_handle
|
||||
|
||||
def py__call__(self, arguments):
|
||||
return_annotation = self.access_handle.get_return_annotation()
|
||||
if return_annotation is not None:
|
||||
# TODO the return annotation may also be a string.
|
||||
return create_from_access_path(self.evaluator, return_annotation).execute_annotation()
|
||||
|
||||
try:
|
||||
self.access_handle.getattr_paths(u'__call__')
|
||||
except AttributeError:
|
||||
@@ -122,7 +127,10 @@ class CompiledObject(Context):
|
||||
signature_params = self.access_handle.get_signature_params()
|
||||
except ValueError: # Has no signature
|
||||
params_str, ret = self._parse_function_doc()
|
||||
tokens = params_str.split(',')
|
||||
if not params_str:
|
||||
tokens = []
|
||||
else:
|
||||
tokens = params_str.split(',')
|
||||
if self.access_handle.ismethoddescriptor():
|
||||
tokens.insert(0, 'self')
|
||||
for p in tokens:
|
||||
@@ -256,6 +264,9 @@ class CompiledObject(Context):
|
||||
def negate(self):
|
||||
return create_from_access_path(self.evaluator, self.access_handle.negate())
|
||||
|
||||
def get_metaclasses(self):
|
||||
return NO_CONTEXTS
|
||||
|
||||
|
||||
class CompiledName(AbstractNameDefinition):
|
||||
def __init__(self, evaluator, parent_context, name):
|
||||
@@ -276,7 +287,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 +300,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 +310,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:
|
||||
@@ -307,9 +320,6 @@ class SignatureParamName(AbstractNameDefinition, ParamNameInterface):
|
||||
def get_kind(self):
|
||||
return getattr(Parameter, self._signature_param.kind_name)
|
||||
|
||||
def is_keyword_param(self):
|
||||
return self._signature_param
|
||||
|
||||
def infer(self):
|
||||
p = self._signature_param
|
||||
evaluator = self.parent_context.evaluator
|
||||
@@ -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
|
||||
@@ -369,14 +377,14 @@ class CompiledObjectFilter(AbstractFilter):
|
||||
|
||||
def __init__(self, evaluator, compiled_object, is_instance=False):
|
||||
self._evaluator = evaluator
|
||||
self._compiled_object = compiled_object
|
||||
self.compiled_object = compiled_object
|
||||
self.is_instance = is_instance
|
||||
|
||||
def get(self, name):
|
||||
return self._get(
|
||||
name,
|
||||
lambda: self._compiled_object.access_handle.is_allowed_getattr(name),
|
||||
lambda: self._compiled_object.access_handle.dir(),
|
||||
lambda: self.compiled_object.access_handle.is_allowed_getattr(name),
|
||||
lambda: self.compiled_object.access_handle.dir(),
|
||||
check_has_attribute=True
|
||||
)
|
||||
|
||||
@@ -391,7 +399,7 @@ class CompiledObjectFilter(AbstractFilter):
|
||||
# Always use unicode objects in Python 2 from here.
|
||||
name = force_unicode(name)
|
||||
|
||||
if is_descriptor or not has_attribute:
|
||||
if (is_descriptor and not self._evaluator.allow_descriptor_getattr) or not has_attribute:
|
||||
return [self._get_cached_name(name, is_empty=True)]
|
||||
|
||||
if self.is_instance and name not in dir_callback():
|
||||
@@ -408,7 +416,7 @@ class CompiledObjectFilter(AbstractFilter):
|
||||
def values(self):
|
||||
from jedi.evaluate.compiled import builtin_from_name
|
||||
names = []
|
||||
needs_type_completions, dir_infos = self._compiled_object.access_handle.get_dir_infos()
|
||||
needs_type_completions, dir_infos = self.compiled_object.access_handle.get_dir_infos()
|
||||
for name in dir_infos:
|
||||
names += self._get(
|
||||
name,
|
||||
@@ -423,10 +431,10 @@ class CompiledObjectFilter(AbstractFilter):
|
||||
return names
|
||||
|
||||
def _create_name(self, name):
|
||||
return self.name_class(self._evaluator, self._compiled_object, name)
|
||||
return self.name_class(self._evaluator, self.compiled_object, name)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s>" % (self.__class__.__name__, self._compiled_object)
|
||||
return "<%s: %s>" % (self.__class__.__name__, self.compiled_object)
|
||||
|
||||
|
||||
docstr_defaults = {
|
||||
|
||||
@@ -4,6 +4,7 @@ Used only for REPL Completion.
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
|
||||
from jedi.parser_utils import get_cached_code_lines
|
||||
|
||||
@@ -17,7 +18,7 @@ from jedi.evaluate.context import ModuleContext
|
||||
from jedi.evaluate.cache import evaluator_function_cache
|
||||
from jedi.evaluate.compiled.getattr_static import getattr_static
|
||||
from jedi.evaluate.compiled.access import compiled_objects_cache, \
|
||||
ALLOWED_GETITEM_TYPES
|
||||
ALLOWED_GETITEM_TYPES, get_api_type
|
||||
from jedi.evaluate.compiled.context import create_cached_compiled_object
|
||||
from jedi.evaluate.gradual.conversion import to_stub
|
||||
|
||||
@@ -49,6 +50,11 @@ class MixedObject(ContextWrapper):
|
||||
def get_filters(self, *args, **kwargs):
|
||||
yield MixedObjectFilter(self.evaluator, self)
|
||||
|
||||
def get_signatures(self):
|
||||
# Prefer `inspect.signature` over somehow analyzing Python code. It
|
||||
# should be very precise, especially for stuff like `partial`.
|
||||
return self.compiled_object.get_signatures()
|
||||
|
||||
def py__call__(self, arguments):
|
||||
return (to_stub(self._wrapped_context) or self._wrapped_context).py__call__(arguments)
|
||||
|
||||
@@ -131,6 +137,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 +155,8 @@ 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):
|
||||
original_object = python_object
|
||||
try:
|
||||
python_object = _get_object_to_check(python_object)
|
||||
path = inspect.getsourcefile(python_object)
|
||||
@@ -213,7 +220,13 @@ def _find_syntax_node_name(evaluator, access_handle):
|
||||
# completions at some points but will lead to mostly correct type
|
||||
# inference, because people tend to define a public name in a module only
|
||||
# once.
|
||||
return module_node, names[-1].parent, file_io, code_lines
|
||||
tree_node = names[-1].parent
|
||||
if tree_node.type == 'funcdef' and get_api_type(original_object) == 'instance':
|
||||
# If an instance is given and we're landing on a function (e.g.
|
||||
# partial in 3.5), something is completely wrong and we should not
|
||||
# return that.
|
||||
return None
|
||||
return module_node, tree_node, file_io, code_lines
|
||||
|
||||
|
||||
@compiled_objects_cache('mixed_cache')
|
||||
@@ -224,10 +237,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})
|
||||
|
||||
@@ -251,7 +266,11 @@ def _create(evaluator, access_handle, parent_context, *args):
|
||||
if name is not None:
|
||||
evaluator.module_cache.add(string_names, ContextSet([module_context]))
|
||||
else:
|
||||
assert parent_context.tree_node.get_root_node() == module_node
|
||||
if parent_context.tree_node.get_root_node() != module_node:
|
||||
# This happens e.g. when __module__ is wrong, or when using
|
||||
# TypeVar('foo'), where Jedi uses 'foo' as the name and
|
||||
# Python's TypeVar('foo').__module__ will be typing.
|
||||
return ContextSet({compiled_object})
|
||||
module_context = parent_context.get_root_context()
|
||||
|
||||
tree_contexts = ContextSet({
|
||||
|
||||
15
jedi/evaluate/context/decorator.py
Normal file
15
jedi/evaluate/context/decorator.py
Normal file
@@ -0,0 +1,15 @@
|
||||
'''
|
||||
Decorators are not really contexts, however we need some wrappers to improve
|
||||
docstrings and other things around decorators.
|
||||
'''
|
||||
|
||||
from jedi.evaluate.base_context import ContextWrapper
|
||||
|
||||
|
||||
class Decoratee(ContextWrapper):
|
||||
def __init__(self, wrapped_context, original_context):
|
||||
self._wrapped_context = wrapped_context
|
||||
self._original_context = original_context
|
||||
|
||||
def py__doc__(self):
|
||||
return self._original_context.py__doc__()
|
||||
@@ -64,9 +64,10 @@ 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):
|
||||
yield filter
|
||||
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):
|
||||
from jedi.evaluate.context.instance import BoundMethod
|
||||
@@ -99,6 +100,9 @@ class FunctionMixin(object):
|
||||
|
||||
return FunctionExecutionContext(self.evaluator, self.parent_context, self, arguments)
|
||||
|
||||
def get_signatures(self):
|
||||
return [TreeSignature(f) for f in self.get_signature_functions()]
|
||||
|
||||
|
||||
class FunctionContext(use_metaclass(CachedMetaClass, FunctionMixin, FunctionAndClassBase)):
|
||||
"""
|
||||
@@ -146,8 +150,8 @@ class FunctionContext(use_metaclass(CachedMetaClass, FunctionMixin, FunctionAndC
|
||||
def get_default_param_context(self):
|
||||
return self.parent_context
|
||||
|
||||
def get_signatures(self):
|
||||
return [TreeSignature(self)]
|
||||
def get_signature_functions(self):
|
||||
return [self]
|
||||
|
||||
|
||||
class MethodContext(FunctionContext):
|
||||
@@ -370,14 +374,14 @@ class FunctionExecutionContext(TreeContext):
|
||||
class OverloadedFunctionContext(FunctionMixin, ContextWrapper):
|
||||
def __init__(self, function, overloaded_functions):
|
||||
super(OverloadedFunctionContext, self).__init__(function)
|
||||
self.overloaded_functions = overloaded_functions
|
||||
self._overloaded_functions = overloaded_functions
|
||||
|
||||
def py__call__(self, arguments):
|
||||
debug.dbg("Execute overloaded function %s", self._wrapped_context, color='BLUE')
|
||||
function_executions = []
|
||||
context_set = NO_CONTEXTS
|
||||
matched = False
|
||||
for f in self.overloaded_functions:
|
||||
for f in self._overloaded_functions:
|
||||
function_execution = f.get_function_execution(arguments)
|
||||
function_executions.append(function_execution)
|
||||
if function_execution.matches_signature():
|
||||
@@ -392,39 +396,8 @@ class OverloadedFunctionContext(FunctionMixin, ContextWrapper):
|
||||
return NO_CONTEXTS
|
||||
return ContextSet.from_sets(fe.infer() for fe in function_executions)
|
||||
|
||||
def get_signatures(self):
|
||||
return [TreeSignature(f) for f in self.overloaded_functions]
|
||||
|
||||
|
||||
def signature_matches(function_context, arguments):
|
||||
unpacked_arguments = arguments.unpack()
|
||||
key_args = {}
|
||||
for param_node in function_context.tree_node.get_params():
|
||||
while True:
|
||||
key, argument = next(unpacked_arguments, (None, None))
|
||||
if key is None or argument is None:
|
||||
break
|
||||
key_args[key] = argument
|
||||
if argument is None:
|
||||
argument = key_args.pop(param_node.name.value, None)
|
||||
if argument is None:
|
||||
# This signature has an parameter more than arguments were given.
|
||||
return bool(param_node.star_count == 1)
|
||||
|
||||
if param_node.annotation is not None:
|
||||
if param_node.star_count == 2:
|
||||
return False # TODO allow this
|
||||
|
||||
annotation_contexts = function_context.evaluator.eval_element(
|
||||
function_context.get_default_param_context(),
|
||||
param_node.annotation
|
||||
)
|
||||
argument_contexts = argument.infer().py__class__()
|
||||
if not any(c1.is_sub_class_of(c2)
|
||||
for c1 in argument_contexts
|
||||
for c2 in annotation_contexts):
|
||||
return False
|
||||
return True
|
||||
def get_signature_functions(self):
|
||||
return self._overloaded_functions
|
||||
|
||||
|
||||
def _find_overload_functions(context, tree_node):
|
||||
|
||||
@@ -3,6 +3,7 @@ from abc import abstractproperty
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate.compiled.context import CompiledObjectFilter
|
||||
from jedi.evaluate.helpers import contexts_from_qualified_names
|
||||
from jedi.evaluate.filters import AbstractFilter
|
||||
from jedi.evaluate.names import ContextName, TreeNameDefinition
|
||||
@@ -12,7 +13,7 @@ from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.arguments import AnonymousArguments, \
|
||||
ValuesArguments, TreeArgumentsWrapper
|
||||
from jedi.evaluate.context.function import FunctionExecutionContext, \
|
||||
from jedi.evaluate.context.function import \
|
||||
FunctionContext, FunctionMixin, OverloadedFunctionContext
|
||||
from jedi.evaluate.context.klass import ClassContext, apply_py__get__, \
|
||||
ClassFilter
|
||||
@@ -136,11 +137,19 @@ class AbstractInstanceContext(Context):
|
||||
# compiled objects to search for self variables.
|
||||
yield SelfAttributeFilter(self.evaluator, self, cls, origin_scope)
|
||||
|
||||
for cls in class_context.py__mro__():
|
||||
if isinstance(cls, compiled.CompiledObject):
|
||||
yield CompiledInstanceClassFilter(self.evaluator, self, cls)
|
||||
class_filters = class_context.get_filters(
|
||||
search_global=False,
|
||||
origin_scope=origin_scope,
|
||||
is_instance=True,
|
||||
)
|
||||
for f in class_filters:
|
||||
if isinstance(f, ClassFilter):
|
||||
yield InstanceClassFilter(self.evaluator, self, f)
|
||||
elif isinstance(f, CompiledObjectFilter):
|
||||
yield CompiledInstanceClassFilter(self.evaluator, self, f)
|
||||
else:
|
||||
yield InstanceClassFilter(self.evaluator, self, cls, origin_scope)
|
||||
# Propably from the metaclass.
|
||||
yield f
|
||||
|
||||
def py__getitem__(self, index_context_set, contextualized_node):
|
||||
names = self.get_function_slot_names(u'__getitem__')
|
||||
@@ -223,8 +232,8 @@ class AbstractInstanceContext(Context):
|
||||
return class_context
|
||||
|
||||
def get_signatures(self):
|
||||
init_funcs = self.py__getattribute__('__call__')
|
||||
return [sig.bind(self) for sig in init_funcs.get_signatures()]
|
||||
call_funcs = self.py__getattribute__('__call__').py__get__(self, self.class_context)
|
||||
return [s.bind(self) for s in call_funcs.get_signatures()]
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s of %s(%s)>" % (self.__class__.__name__, self.class_context,
|
||||
@@ -328,7 +337,6 @@ class CompiledInstanceName(compiled.CompiledName):
|
||||
name.string_name
|
||||
)
|
||||
self._instance = instance
|
||||
self._class = klass
|
||||
self._class_member_name = name
|
||||
|
||||
@iterator_to_context_set
|
||||
@@ -343,11 +351,10 @@ class CompiledInstanceName(compiled.CompiledName):
|
||||
class CompiledInstanceClassFilter(AbstractFilter):
|
||||
name_class = CompiledInstanceName
|
||||
|
||||
def __init__(self, evaluator, instance, klass):
|
||||
def __init__(self, evaluator, instance, f):
|
||||
self._evaluator = evaluator
|
||||
self._instance = instance
|
||||
self._class = klass
|
||||
self._class_filter = next(klass.get_filters(is_instance=True))
|
||||
self._class_filter = f
|
||||
|
||||
def get(self, name):
|
||||
return self._convert(self._class_filter.get(name))
|
||||
@@ -356,8 +363,9 @@ class CompiledInstanceClassFilter(AbstractFilter):
|
||||
return self._convert(self._class_filter.values())
|
||||
|
||||
def _convert(self, names):
|
||||
klass = self._class_filter.compiled_object
|
||||
return [
|
||||
CompiledInstanceName(self._evaluator, self._instance, self._class, n)
|
||||
CompiledInstanceName(self._evaluator, self._instance, klass, n)
|
||||
for n in names
|
||||
]
|
||||
|
||||
@@ -382,15 +390,6 @@ class BoundMethod(FunctionMixin, ContextWrapper):
|
||||
|
||||
def get_function_execution(self, arguments=None):
|
||||
arguments = self._get_arguments(arguments)
|
||||
|
||||
if isinstance(self._wrapped_context, compiled.CompiledObject):
|
||||
# This is kind of weird, because it's coming from a compiled object
|
||||
# and we're not sure if we want that in the future.
|
||||
# TODO remove?!
|
||||
return FunctionExecutionContext(
|
||||
self.evaluator, self.parent_context, self, arguments
|
||||
)
|
||||
|
||||
return super(BoundMethod, self).get_function_execution(arguments)
|
||||
|
||||
def py__call__(self, arguments):
|
||||
@@ -400,8 +399,14 @@ class BoundMethod(FunctionMixin, ContextWrapper):
|
||||
function_execution = self.get_function_execution(arguments)
|
||||
return function_execution.infer()
|
||||
|
||||
def get_signature_functions(self):
|
||||
return [
|
||||
BoundMethod(self.instance, f)
|
||||
for f in self._wrapped_context.get_signature_functions()
|
||||
]
|
||||
|
||||
def get_signatures(self):
|
||||
return [sig.bind(self) for sig in self._wrapped_context.get_signatures()]
|
||||
return [sig.bind(self) for sig in super(BoundMethod, self).get_signatures()]
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._wrapped_context)
|
||||
@@ -454,26 +459,21 @@ class InstanceClassFilter(AbstractFilter):
|
||||
resulting names in LazyINstanceClassName. The idea is that the class name
|
||||
filtering can be very flexible and always be reflected in instances.
|
||||
"""
|
||||
def __init__(self, evaluator, context, class_context, origin_scope):
|
||||
self._instance = context
|
||||
self._class_context = class_context
|
||||
self._class_filter = next(class_context.get_filters(
|
||||
search_global=False,
|
||||
origin_scope=origin_scope,
|
||||
is_instance=True,
|
||||
))
|
||||
def __init__(self, evaluator, instance, class_filter):
|
||||
self._instance = instance
|
||||
self._class_filter = 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]
|
||||
return [LazyInstanceClassName(self._instance, self._class_filter.context, n) for n in names]
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s for %s>' % (self.__class__.__name__, self._class_context)
|
||||
return '<%s for %s>' % (self.__class__.__name__, self._class_filter.context)
|
||||
|
||||
|
||||
class SelfAttributeFilter(ClassFilter):
|
||||
@@ -503,7 +503,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
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ It is important to note that:
|
||||
1. Array modfications work only in the current module.
|
||||
2. Jedi only checks Array additions; ``list.pop``, etc are ignored.
|
||||
"""
|
||||
import sys
|
||||
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi._compatibility import force_unicode, is_py3
|
||||
@@ -35,8 +37,8 @@ from jedi.evaluate.utils import safe_property, to_list
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.filters import ParserTreeFilter, LazyAttributeOverwrite, \
|
||||
publish_method
|
||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, \
|
||||
TreeContext, ContextualizedNode, iterate_contexts, HelperContextMixin
|
||||
from jedi.evaluate.base_context import ContextSet, Context, NO_CONTEXTS, \
|
||||
TreeContext, ContextualizedNode, iterate_contexts, HelperContextMixin, _sentinel
|
||||
from jedi.parser_utils import get_sync_comp_fors
|
||||
|
||||
|
||||
@@ -44,6 +46,21 @@ class IterableMixin(object):
|
||||
def py__stop_iteration_returns(self):
|
||||
return ContextSet([compiled.builtin_from_name(self.evaluator, u'None')])
|
||||
|
||||
# At the moment, safe values are simple values like "foo", 1 and not
|
||||
# lists/dicts. Therefore as a small speed optimization we can just do the
|
||||
# default instead of resolving the lazy wrapped contexts, that are just
|
||||
# doing this in the end as well.
|
||||
# This mostly speeds up patterns like `sys.version_info >= (3, 0)` in
|
||||
# typeshed.
|
||||
if sys.version_info[0] == 2:
|
||||
# Python 2...........
|
||||
def get_safe_value(self, default=_sentinel):
|
||||
if default is _sentinel:
|
||||
raise ValueError("There exists no safe value for context %s" % self)
|
||||
return default
|
||||
else:
|
||||
get_safe_value = Context.get_safe_value
|
||||
|
||||
|
||||
class GeneratorBase(LazyAttributeOverwrite, IterableMixin):
|
||||
array_type = None
|
||||
|
||||
@@ -45,10 +45,11 @@ 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
|
||||
from jedi.plugins import plugin_manager
|
||||
|
||||
|
||||
def apply_py__get__(context, instance, class_context):
|
||||
@@ -109,13 +110,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):
|
||||
@@ -177,13 +192,13 @@ class ClassMixin(object):
|
||||
|
||||
def get_filters(self, search_global=False, until_position=None,
|
||||
origin_scope=None, is_instance=False):
|
||||
metaclasses = self.get_metaclasses()
|
||||
if metaclasses:
|
||||
for f in self.get_metaclass_filters(metaclasses):
|
||||
yield f
|
||||
|
||||
if search_global:
|
||||
yield ParserTreeFilter(
|
||||
self.evaluator,
|
||||
context=self,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope
|
||||
)
|
||||
yield self.get_global_filter(until_position, origin_scope)
|
||||
else:
|
||||
for cls in self.py__mro__():
|
||||
if isinstance(cls, compiled.CompiledObject):
|
||||
@@ -198,8 +213,26 @@ 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)
|
||||
|
||||
def get_signatures(self):
|
||||
init_funcs = self.execute_evaluated().py__getattribute__('__init__')
|
||||
return [sig.bind(self) for sig in init_funcs.get_signatures()]
|
||||
|
||||
def get_global_filter(self, until_position=None, origin_scope=None):
|
||||
return ParserTreeFilter(
|
||||
self.evaluator,
|
||||
context=self,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope
|
||||
)
|
||||
|
||||
|
||||
class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase)):
|
||||
@@ -227,12 +260,17 @@ class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBa
|
||||
found.append(type_var)
|
||||
return found
|
||||
|
||||
@evaluator_method_cache(default=())
|
||||
def py__bases__(self):
|
||||
def _get_bases_arguments(self):
|
||||
arglist = self.tree_node.get_super_arglist()
|
||||
if arglist:
|
||||
from jedi.evaluate import arguments
|
||||
args = arguments.TreeArguments(self.evaluator, self.parent_context, arglist)
|
||||
return arguments.TreeArguments(self.evaluator, self.parent_context, arglist)
|
||||
return None
|
||||
|
||||
@evaluator_method_cache(default=())
|
||||
def py__bases__(self):
|
||||
args = self._get_bases_arguments()
|
||||
if args is not None:
|
||||
lst = [value for key, value in args.unpack() if key is None]
|
||||
if lst:
|
||||
return lst
|
||||
@@ -280,6 +318,25 @@ class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBa
|
||||
)])
|
||||
return ContextSet({self})
|
||||
|
||||
def get_signatures(self):
|
||||
init_funcs = self.py__getattribute__('__init__')
|
||||
return [sig.bind(self) for sig in init_funcs.get_signatures()]
|
||||
@plugin_manager.decorate()
|
||||
def get_metaclass_filters(self, metaclass):
|
||||
debug.dbg('Unprocessed metaclass %s', metaclass)
|
||||
return []
|
||||
|
||||
@evaluator_method_cache(default=NO_CONTEXTS)
|
||||
def get_metaclasses(self):
|
||||
args = self._get_bases_arguments()
|
||||
if args is not None:
|
||||
m = [value for key, value in args.unpack() if key == 'metaclass']
|
||||
metaclasses = ContextSet.from_sets(lazy_context.infer() for lazy_context in m)
|
||||
metaclasses = ContextSet(m for m in metaclasses if m.is_class())
|
||||
if metaclasses:
|
||||
return metaclasses
|
||||
|
||||
for lazy_base in self.py__bases__():
|
||||
for context in lazy_base.infer():
|
||||
if context.is_class():
|
||||
contexts = context.get_metaclasses()
|
||||
if contexts:
|
||||
return contexts
|
||||
return NO_CONTEXTS
|
||||
|
||||
@@ -9,6 +9,8 @@ from jedi.evaluate import compiled
|
||||
from jedi.evaluate.base_context import TreeContext
|
||||
from jedi.evaluate.names import SubModuleName
|
||||
from jedi.evaluate.helpers import contexts_from_qualified_names
|
||||
from jedi.evaluate.compiled import create_simple_object
|
||||
from jedi.evaluate.base_context import ContextSet
|
||||
|
||||
|
||||
class _ModuleAttributeName(AbstractNameDefinition):
|
||||
@@ -17,11 +19,20 @@ class _ModuleAttributeName(AbstractNameDefinition):
|
||||
"""
|
||||
api_type = u'instance'
|
||||
|
||||
def __init__(self, parent_module, string_name):
|
||||
def __init__(self, parent_module, string_name, string_value=None):
|
||||
self.parent_context = parent_module
|
||||
self.string_name = string_name
|
||||
self._string_value = string_value
|
||||
|
||||
def infer(self):
|
||||
if self._string_value is not None:
|
||||
s = self._string_value
|
||||
if self.parent_context.evaluator.environment.version_info.major == 2 \
|
||||
and not isinstance(s, bytes):
|
||||
s = s.encode('utf-8')
|
||||
return ContextSet([
|
||||
create_simple_object(self.parent_context.evaluator, s)
|
||||
])
|
||||
return compiled.get_string_context_set(self.parent_context.evaluator)
|
||||
|
||||
|
||||
@@ -132,9 +143,13 @@ class ModuleMixin(SubModuleDictMixin):
|
||||
|
||||
@evaluator_method_cache()
|
||||
def _module_attributes_dict(self):
|
||||
names = ['__file__', '__package__', '__doc__', '__name__']
|
||||
names = ['__package__', '__doc__', '__name__']
|
||||
# All the additional module attributes are strings.
|
||||
return dict((n, _ModuleAttributeName(self, n)) for n in names)
|
||||
dct = dict((n, _ModuleAttributeName(self, n)) for n in names)
|
||||
file = self.py__file__()
|
||||
if file is not None:
|
||||
dct['__file__'] = _ModuleAttributeName(self, '__file__', file)
|
||||
return dct
|
||||
|
||||
def iter_star_filters(self, search_global=False):
|
||||
for star_module in self.star_imports():
|
||||
@@ -145,13 +160,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()
|
||||
|
||||
@@ -51,11 +51,8 @@ def _get_numpy_doc_string_cls():
|
||||
global _numpy_doc_string_cache
|
||||
if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)):
|
||||
raise _numpy_doc_string_cache
|
||||
try:
|
||||
from numpydoc.docscrape import NumpyDocString
|
||||
_numpy_doc_string_cache = NumpyDocString
|
||||
except (ImportError, SyntaxError) as e:
|
||||
raise
|
||||
from numpydoc.docscrape import NumpyDocString
|
||||
_numpy_doc_string_cache = NumpyDocString
|
||||
return _numpy_doc_string_cache
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ It works as follows:
|
||||
- execute these calls and check the input.
|
||||
"""
|
||||
|
||||
from parso.python import tree
|
||||
from jedi import settings
|
||||
from jedi import debug
|
||||
from jedi.evaluate.cache import evaluator_function_cache
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
|
||||
@@ -384,7 +386,7 @@ def get_global_filters(evaluator, context, until_position, origin_scope):
|
||||
>>> list(filters[1].values()) # package modules -> Also empty.
|
||||
[]
|
||||
>>> sorted(name.string_name for name in filters[2].values()) # Module attributes
|
||||
['__doc__', '__file__', '__name__', '__package__']
|
||||
['__doc__', '__name__', '__package__']
|
||||
|
||||
Finally, it yields the builtin filter, if `include_builtin` is
|
||||
true (default).
|
||||
@@ -407,5 +409,4 @@ def get_global_filters(evaluator, context, until_position, origin_scope):
|
||||
context = context.parent_context
|
||||
|
||||
# Add builtins to the global scope.
|
||||
for filter in evaluator.builtins_module.get_filters():
|
||||
yield filter
|
||||
yield next(evaluator.builtins_module.get_filters())
|
||||
|
||||
@@ -14,7 +14,9 @@ from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
|
||||
from jedi.evaluate.gradual.typing import TypeVar, LazyGenericClass, \
|
||||
AbstractAnnotatedClass
|
||||
from jedi.evaluate.gradual.typing import GenericClass
|
||||
from jedi.evaluate.helpers import is_string
|
||||
from jedi.evaluate.compiled import builtin_from_name
|
||||
from jedi import debug
|
||||
from jedi import parser_utils
|
||||
|
||||
@@ -106,6 +108,25 @@ def _split_comment_param_declaration(decl_text):
|
||||
|
||||
@evaluator_method_cache()
|
||||
def infer_param(execution_context, param):
|
||||
contexts = _infer_param(execution_context, param)
|
||||
evaluator = execution_context.evaluator
|
||||
if param.star_count == 1:
|
||||
tuple_ = builtin_from_name(evaluator, 'tuple')
|
||||
return ContextSet([GenericClass(
|
||||
tuple_,
|
||||
generics=(contexts,),
|
||||
) for c in contexts])
|
||||
elif param.star_count == 2:
|
||||
dct = builtin_from_name(evaluator, 'dict')
|
||||
return ContextSet([GenericClass(
|
||||
dct,
|
||||
generics=(ContextSet([builtin_from_name(evaluator, 'str')]), contexts),
|
||||
) for c in contexts])
|
||||
pass
|
||||
return contexts
|
||||
|
||||
|
||||
def _infer_param(execution_context, param):
|
||||
"""
|
||||
Infers the type of a function parameter, using type annotations.
|
||||
"""
|
||||
|
||||
@@ -40,7 +40,8 @@ def _stub_to_python_context_set(stub_context, ignore_compiled=False):
|
||||
|
||||
|
||||
def _infer_from_stub(stub_module, qualified_names, ignore_compiled):
|
||||
assert isinstance(stub_module, StubModuleContext), stub_module
|
||||
from jedi.evaluate.compiled.mixed import MixedObject
|
||||
assert isinstance(stub_module, (StubModuleContext, MixedObject)), stub_module
|
||||
non_stubs = stub_module.non_stub_context_set
|
||||
if ignore_compiled:
|
||||
non_stubs = non_stubs.filter(lambda c: not c.is_compiled())
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import re
|
||||
from functools import wraps
|
||||
|
||||
from jedi.file_io import FileIO
|
||||
from jedi._compatibility import FileNotFoundError, cast_path
|
||||
@@ -87,6 +88,7 @@ def _cache_stub_file_map(version_info):
|
||||
|
||||
|
||||
def import_module_decorator(func):
|
||||
@wraps(func)
|
||||
def wrapper(evaluator, import_names, parent_module_context, sys_path, prefer_stubs):
|
||||
try:
|
||||
python_context_set = evaluator.module_cache.get(import_names)
|
||||
|
||||
@@ -10,7 +10,7 @@ from jedi import debug
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.compiled import builtin_from_name
|
||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, Context, \
|
||||
iterator_to_context_set, HelperContextMixin, ContextWrapper
|
||||
iterator_to_context_set, ContextWrapper, LazyContextWrapper
|
||||
from jedi.evaluate.lazy_context import LazyKnownContexts
|
||||
from jedi.evaluate.context.iterable import SequenceLiteralContext
|
||||
from jedi.evaluate.arguments import repack_with_argument_clinic
|
||||
@@ -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)
|
||||
@@ -204,6 +209,9 @@ class _TypingClassMixin(object):
|
||||
self.evaluator.builtins_module.py__getattribute__('object')
|
||||
)]
|
||||
|
||||
def get_metaclasses(self):
|
||||
return []
|
||||
|
||||
|
||||
class TypingClassContextWithIndex(_TypingClassMixin, TypingContextWithIndex, ClassMixin):
|
||||
pass
|
||||
@@ -236,9 +244,9 @@ def _iter_over_arguments(maybe_tuple_context, defining_context):
|
||||
yield ContextSet(resolve_forward_references(context_set))
|
||||
|
||||
|
||||
class TypeAlias(HelperContextMixin):
|
||||
def __init__(self, evaluator, parent_context, origin_tree_name, actual):
|
||||
self.evaluator = evaluator
|
||||
class TypeAlias(LazyContextWrapper):
|
||||
def __init__(self, parent_context, origin_tree_name, actual):
|
||||
self.evaluator = parent_context.evaluator
|
||||
self.parent_context = parent_context
|
||||
self._origin_tree_name = origin_tree_name
|
||||
self._actual = actual # e.g. builtins.list
|
||||
@@ -250,14 +258,10 @@ class TypeAlias(HelperContextMixin):
|
||||
def py__name__(self):
|
||||
return self.name.string_name
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._get_type_alias_class(), name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._actual)
|
||||
|
||||
@evaluator_method_cache()
|
||||
def _get_type_alias_class(self):
|
||||
def _get_wrapped_context(self):
|
||||
module_name, class_name = self._actual.split('.')
|
||||
if self.evaluator.environment.version_info.major == 2 and module_name == 'builtins':
|
||||
module_name = '__builtin__'
|
||||
@@ -456,6 +460,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):
|
||||
|
||||
@@ -21,7 +21,7 @@ from jedi._compatibility import (FileNotFoundError, ImplicitNSInfo,
|
||||
force_unicode, unicode)
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi.file_io import KnownContentFileIO, FolderIO, FileIO
|
||||
from jedi.file_io import KnownContentFileIO, FileIO
|
||||
from jedi.parser_utils import get_cached_code_lines
|
||||
from jedi.evaluate import sys_path
|
||||
from jedi.evaluate import helpers
|
||||
@@ -33,6 +33,7 @@ from jedi.evaluate.names import ImportName, SubModuleName
|
||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
|
||||
from jedi.evaluate.gradual.typeshed import import_module_decorator
|
||||
from jedi.evaluate.context.module import iter_module_names
|
||||
from jedi.plugins import plugin_manager
|
||||
|
||||
|
||||
class ModuleCache(object):
|
||||
@@ -371,6 +372,7 @@ class Importer(object):
|
||||
return names
|
||||
|
||||
|
||||
@plugin_manager.decorate()
|
||||
@import_module_decorator
|
||||
def import_module(evaluator, import_names, parent_module_context, sys_path):
|
||||
"""
|
||||
|
||||
@@ -3,7 +3,7 @@ from abc import abstractmethod
|
||||
from parso.tree import search_ancestor
|
||||
|
||||
from jedi._compatibility import Parameter
|
||||
from jedi.evaluate.base_context import ContextSet
|
||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
|
||||
from jedi.cache import memoize_method
|
||||
|
||||
|
||||
@@ -58,6 +58,23 @@ class AbstractNameDefinition(object):
|
||||
return self.parent_context.api_type
|
||||
|
||||
|
||||
class AbstractArbitraryName(AbstractNameDefinition):
|
||||
"""
|
||||
When you e.g. want to complete dicts keys, you probably want to complete
|
||||
string literals, which is not really a name, but for Jedi we use this
|
||||
concept of Name for completions as well.
|
||||
"""
|
||||
is_context_name = False
|
||||
|
||||
def __init__(self, evaluator, string):
|
||||
self.evaluator = evaluator
|
||||
self.string_name = string
|
||||
self.parent_context = evaluator.builtins_module
|
||||
|
||||
def infer(self):
|
||||
return NO_CONTEXTS
|
||||
|
||||
|
||||
class AbstractTreeName(AbstractNameDefinition):
|
||||
def __init__(self, parent_context, tree_name):
|
||||
self.parent_context = parent_context
|
||||
@@ -150,24 +167,104 @@ class TreeNameDefinition(AbstractTreeName):
|
||||
return self._API_TYPES.get(definition.type, 'statement')
|
||||
|
||||
|
||||
class ParamNameInterface(object):
|
||||
class _ParamMixin(object):
|
||||
def maybe_positional_argument(self, include_star=True):
|
||||
options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
|
||||
if include_star:
|
||||
options.append(Parameter.VAR_POSITIONAL)
|
||||
return self.get_kind() in options
|
||||
|
||||
def maybe_keyword_argument(self, include_stars=True):
|
||||
options = [Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
|
||||
if include_stars:
|
||||
options.append(Parameter.VAR_KEYWORD)
|
||||
return self.get_kind() in options
|
||||
|
||||
def _kind_string(self):
|
||||
kind = self.get_kind()
|
||||
if kind == Parameter.VAR_POSITIONAL: # *args
|
||||
return '*'
|
||||
if kind == Parameter.VAR_KEYWORD: # **kwargs
|
||||
return '**'
|
||||
return ''
|
||||
|
||||
|
||||
class ParamNameInterface(_ParamMixin):
|
||||
api_type = u'param'
|
||||
|
||||
def get_kind(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def to_string(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_param(self):
|
||||
# TODO document better where this is used and when. Currently it has
|
||||
# very limited use, but is still in use. It's currently not even
|
||||
# clear what values would be allowed.
|
||||
return None
|
||||
|
||||
class ParamName(AbstractTreeName, ParamNameInterface):
|
||||
api_type = u'param'
|
||||
@property
|
||||
def star_count(self):
|
||||
kind = self.get_kind()
|
||||
if kind == Parameter.VAR_POSITIONAL:
|
||||
return 1
|
||||
if kind == Parameter.VAR_KEYWORD:
|
||||
return 2
|
||||
return 0
|
||||
|
||||
def __init__(self, parent_context, tree_name):
|
||||
self.parent_context = parent_context
|
||||
self.tree_name = tree_name
|
||||
|
||||
class BaseTreeParamName(ParamNameInterface, AbstractTreeName):
|
||||
annotation_node = None
|
||||
default_node = None
|
||||
|
||||
def to_string(self):
|
||||
output = self._kind_string() + self.string_name
|
||||
annotation = self.annotation_node
|
||||
default = self.default_node
|
||||
if annotation is not None:
|
||||
output += ': ' + annotation.get_code(include_prefix=False)
|
||||
if default is not None:
|
||||
output += '=' + default.get_code(include_prefix=False)
|
||||
return output
|
||||
|
||||
|
||||
class ParamName(BaseTreeParamName):
|
||||
def _get_param_node(self):
|
||||
return search_ancestor(self.tree_name, 'param')
|
||||
|
||||
@property
|
||||
def annotation_node(self):
|
||||
return self._get_param_node().annotation
|
||||
|
||||
def infer_annotation(self, execute_annotation=True):
|
||||
node = self.annotation_node
|
||||
if node is None:
|
||||
return NO_CONTEXTS
|
||||
contexts = self.parent_context.parent_context.eval_node(node)
|
||||
if execute_annotation:
|
||||
contexts = contexts.execute_annotation()
|
||||
return contexts
|
||||
|
||||
def infer_default(self):
|
||||
node = self.default_node
|
||||
if node is None:
|
||||
return NO_CONTEXTS
|
||||
return self.parent_context.parent_context.eval_node(node)
|
||||
|
||||
@property
|
||||
def default_node(self):
|
||||
return self._get_param_node().default
|
||||
|
||||
@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,23 +272,26 @@ class ParamName(AbstractTreeName, ParamNameInterface):
|
||||
if tree_param.star_count == 2: # **kwargs
|
||||
return Parameter.VAR_KEYWORD
|
||||
|
||||
parent = tree_param.parent
|
||||
for p in parent.children:
|
||||
if p.type == 'param':
|
||||
if p.star_count:
|
||||
return Parameter.KEYWORD_ONLY
|
||||
if p == tree_param:
|
||||
break
|
||||
return Parameter.POSITIONAL_OR_KEYWORD
|
||||
# Params starting with __ are an equivalent to positional only
|
||||
# variables in typeshed.
|
||||
if tree_param.name.value.startswith('__'):
|
||||
return Parameter.POSITIONAL_ONLY
|
||||
|
||||
def to_string(self):
|
||||
output = 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)
|
||||
if param_node.default is not None:
|
||||
output += '=' + param_node.default.get_code(include_prefix=False)
|
||||
return output
|
||||
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:
|
||||
param_appeared = True
|
||||
return Parameter.POSITIONAL_OR_KEYWORD
|
||||
|
||||
def infer(self):
|
||||
return self.get_param().infer()
|
||||
@@ -202,6 +302,17 @@ class ParamName(AbstractTreeName, ParamNameInterface):
|
||||
return params[param_node.position_index]
|
||||
|
||||
|
||||
class ParamNameWrapper(_ParamMixin):
|
||||
def __init__(self, param_name):
|
||||
self._wrapped_param_name = param_name
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._wrapped_param_name, name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._wrapped_param_name)
|
||||
|
||||
|
||||
class ImportName(AbstractNameDefinition):
|
||||
start_pos = (1, 0)
|
||||
_level = 0
|
||||
|
||||
@@ -1,31 +1,22 @@
|
||||
from jedi._compatibility import Parameter
|
||||
from jedi.cache import memoize_method
|
||||
|
||||
|
||||
class AbstractSignature(object):
|
||||
def __init__(self, context, is_bound=False):
|
||||
self.context = context
|
||||
self.is_bound = is_bound
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.context.name
|
||||
|
||||
@property
|
||||
def annotation_string(self):
|
||||
return ''
|
||||
|
||||
class _SignatureMixin(object):
|
||||
def to_string(self):
|
||||
def param_strings():
|
||||
is_positional = False
|
||||
is_kw_only = False
|
||||
for n in self.get_param_names():
|
||||
for n in self.get_param_names(resolve_stars=True):
|
||||
kind = n.get_kind()
|
||||
is_positional |= kind == Parameter.POSITIONAL_ONLY
|
||||
if is_positional and kind != Parameter.POSITIONAL_ONLY:
|
||||
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
|
||||
|
||||
@@ -40,15 +31,32 @@ class AbstractSignature(object):
|
||||
s += ' -> ' + annotation
|
||||
return s
|
||||
|
||||
def bind(self, context):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_param_names(self):
|
||||
class AbstractSignature(_SignatureMixin):
|
||||
def __init__(self, context, is_bound=False):
|
||||
self.context = context
|
||||
self.is_bound = is_bound
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.context.name
|
||||
|
||||
@property
|
||||
def annotation_string(self):
|
||||
return ''
|
||||
|
||||
def get_param_names(self, resolve_stars=False):
|
||||
param_names = self._function_context.get_param_names()
|
||||
if self.is_bound:
|
||||
return param_names[1:]
|
||||
return param_names
|
||||
|
||||
def bind(self, context):
|
||||
raise NotImplementedError
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s, %s>' % (self.__class__.__name__, self.context, self._function_context)
|
||||
|
||||
|
||||
class TreeSignature(AbstractSignature):
|
||||
def __init__(self, context, function_context=None, is_bound=False):
|
||||
@@ -73,6 +81,14 @@ class TreeSignature(AbstractSignature):
|
||||
return ''
|
||||
return a.get_code(include_prefix=False)
|
||||
|
||||
@memoize_method
|
||||
def get_param_names(self, resolve_stars=False):
|
||||
params = super(TreeSignature, self).get_param_names(resolve_stars=False)
|
||||
if resolve_stars:
|
||||
from jedi.evaluate.star_args import process_params
|
||||
params = process_params(params)
|
||||
return params
|
||||
|
||||
|
||||
class BuiltinSignature(AbstractSignature):
|
||||
def __init__(self, context, return_string, is_bound=False):
|
||||
@@ -90,3 +106,11 @@ class BuiltinSignature(AbstractSignature):
|
||||
def bind(self, context):
|
||||
assert not self.is_bound
|
||||
return BuiltinSignature(context, self._return_string, is_bound=True)
|
||||
|
||||
|
||||
class SignatureWrapper(_SignatureMixin):
|
||||
def __init__(self, wrapped_signature):
|
||||
self._wrapped_signature = wrapped_signature
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._wrapped_signature, name)
|
||||
|
||||
206
jedi/evaluate/star_args.py
Normal file
206
jedi/evaluate/star_args.py
Normal file
@@ -0,0 +1,206 @@
|
||||
"""
|
||||
This module is responsible for evaluating *args and **kwargs for signatures.
|
||||
|
||||
This means for example in this case::
|
||||
|
||||
def foo(a, b, c): ...
|
||||
|
||||
def bar(*args):
|
||||
return foo(1, *args)
|
||||
|
||||
The signature here for bar should be `bar(b, c)` instead of bar(*args).
|
||||
"""
|
||||
|
||||
from jedi._compatibility import Parameter
|
||||
from jedi.evaluate.utils import to_list
|
||||
from jedi.evaluate.names import ParamNameWrapper
|
||||
|
||||
|
||||
def _iter_nodes_for_param(param_name):
|
||||
from parso.python.tree import search_ancestor
|
||||
from jedi.evaluate.arguments import TreeArguments
|
||||
|
||||
execution_context = param_name.parent_context
|
||||
function_node = execution_context.tree_node
|
||||
module_node = function_node.get_root_node()
|
||||
start = function_node.children[-1].start_pos
|
||||
end = function_node.children[-1].end_pos
|
||||
for name in module_node.get_used_names().get(param_name.string_name):
|
||||
if start <= name.start_pos < end:
|
||||
# Is used in the function
|
||||
argument = name.parent
|
||||
if argument.type == 'argument' \
|
||||
and argument.children[0] == '*' * param_name.star_count:
|
||||
# No support for Python <= 3.4 here, but they are end-of-life
|
||||
# anyway
|
||||
trailer = search_ancestor(argument, 'trailer')
|
||||
if trailer is not None: # Make sure we're in a function
|
||||
context = execution_context.create_context(trailer)
|
||||
if _goes_to_param_name(param_name, context, name):
|
||||
contexts = _to_callables(context, trailer)
|
||||
|
||||
args = TreeArguments.create_cached(
|
||||
execution_context.evaluator,
|
||||
context=context,
|
||||
argument_node=trailer.children[1],
|
||||
trailer=trailer,
|
||||
)
|
||||
for c in contexts:
|
||||
yield c, args
|
||||
else:
|
||||
assert False
|
||||
|
||||
|
||||
def _goes_to_param_name(param_name, context, potential_name):
|
||||
if potential_name.type != 'name':
|
||||
return False
|
||||
from jedi.evaluate.names import TreeNameDefinition
|
||||
found = TreeNameDefinition(context, potential_name).goto()
|
||||
return any(param_name.parent_context == p.parent_context
|
||||
and param_name.start_pos == p.start_pos
|
||||
for p in found)
|
||||
|
||||
|
||||
def _to_callables(context, trailer):
|
||||
from jedi.evaluate.syntax_tree import eval_trailer
|
||||
|
||||
atom_expr = trailer.parent
|
||||
index = atom_expr.children[0] == 'await'
|
||||
# Eval atom first
|
||||
contexts = context.eval_node(atom_expr.children[index])
|
||||
for trailer2 in atom_expr.children[index + 1:]:
|
||||
if trailer == trailer2:
|
||||
break
|
||||
contexts = eval_trailer(context, contexts, trailer2)
|
||||
return contexts
|
||||
|
||||
|
||||
def _remove_given_params(arguments, param_names):
|
||||
count = 0
|
||||
used_keys = set()
|
||||
for key, _ in arguments.unpack():
|
||||
if key is None:
|
||||
count += 1
|
||||
else:
|
||||
used_keys.add(key)
|
||||
|
||||
for p in param_names:
|
||||
if count and p.maybe_positional_argument():
|
||||
count -= 1
|
||||
continue
|
||||
if p.string_name in used_keys and p.maybe_keyword_argument():
|
||||
continue
|
||||
yield p
|
||||
|
||||
|
||||
@to_list
|
||||
def process_params(param_names, star_count=3): # default means both * and **
|
||||
used_names = set()
|
||||
arg_callables = []
|
||||
kwarg_callables = []
|
||||
|
||||
kw_only_names = []
|
||||
kwarg_names = []
|
||||
arg_names = []
|
||||
original_arg_name = None
|
||||
original_kwarg_name = None
|
||||
for p in param_names:
|
||||
kind = p.get_kind()
|
||||
if kind == Parameter.VAR_POSITIONAL:
|
||||
if star_count & 1:
|
||||
arg_callables = _iter_nodes_for_param(p)
|
||||
original_arg_name = p
|
||||
elif p.get_kind() == Parameter.VAR_KEYWORD:
|
||||
if star_count & 2:
|
||||
kwarg_callables = list(_iter_nodes_for_param(p))
|
||||
original_kwarg_name = p
|
||||
elif kind == Parameter.KEYWORD_ONLY:
|
||||
if star_count & 2:
|
||||
kw_only_names.append(p)
|
||||
elif kind == Parameter.POSITIONAL_ONLY:
|
||||
if star_count & 1:
|
||||
yield p
|
||||
else:
|
||||
if star_count == 1:
|
||||
yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
|
||||
elif star_count == 2:
|
||||
kw_only_names.append(ParamNameFixedKind(p, Parameter.KEYWORD_ONLY))
|
||||
else:
|
||||
used_names.add(p.string_name)
|
||||
yield p
|
||||
|
||||
longest_param_names = ()
|
||||
found_arg_signature = False
|
||||
found_kwarg_signature = False
|
||||
for func_and_argument in arg_callables:
|
||||
func, arguments = func_and_argument
|
||||
new_star_count = star_count
|
||||
if func_and_argument in kwarg_callables:
|
||||
kwarg_callables.remove(func_and_argument)
|
||||
else:
|
||||
new_star_count = 1
|
||||
|
||||
for signature in func.get_signatures():
|
||||
found_arg_signature = True
|
||||
if new_star_count == 3:
|
||||
found_kwarg_signature = True
|
||||
args_for_this_func = []
|
||||
for p in process_params(
|
||||
list(_remove_given_params(
|
||||
arguments,
|
||||
signature.get_param_names(resolve_stars=False)
|
||||
)), new_star_count):
|
||||
if p.get_kind() == Parameter.VAR_KEYWORD:
|
||||
kwarg_names.append(p)
|
||||
elif p.get_kind() == Parameter.VAR_POSITIONAL:
|
||||
arg_names.append(p)
|
||||
elif p.get_kind() == Parameter.KEYWORD_ONLY:
|
||||
kw_only_names.append(p)
|
||||
else:
|
||||
args_for_this_func.append(p)
|
||||
if len(args_for_this_func) > len(longest_param_names):
|
||||
longest_param_names = args_for_this_func
|
||||
|
||||
for p in longest_param_names:
|
||||
if star_count == 1 and p.get_kind() != Parameter.VAR_POSITIONAL:
|
||||
yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
|
||||
else:
|
||||
if p.get_kind() == Parameter.POSITIONAL_OR_KEYWORD:
|
||||
used_names.add(p.string_name)
|
||||
yield p
|
||||
|
||||
if not found_arg_signature and original_arg_name is not None:
|
||||
yield original_arg_name
|
||||
elif arg_names:
|
||||
yield arg_names[0]
|
||||
|
||||
for p in kw_only_names:
|
||||
if p.string_name in used_names:
|
||||
continue
|
||||
yield p
|
||||
used_names.add(p.string_name)
|
||||
|
||||
for func, arguments in kwarg_callables:
|
||||
for signature in func.get_signatures():
|
||||
found_kwarg_signature = True
|
||||
for p in process_params(
|
||||
list(_remove_given_params(
|
||||
arguments,
|
||||
signature.get_param_names(resolve_stars=False)
|
||||
)), star_count=2):
|
||||
if p.get_kind() != Parameter.KEYWORD_ONLY or not kwarg_names:
|
||||
yield p
|
||||
|
||||
if not found_kwarg_signature and original_kwarg_name is not None:
|
||||
yield original_kwarg_name
|
||||
elif kwarg_names:
|
||||
yield kwarg_names[0]
|
||||
|
||||
|
||||
class ParamNameFixedKind(ParamNameWrapper):
|
||||
def __init__(self, param_name, new_kind):
|
||||
super(ParamNameFixedKind, self).__init__(param_name)
|
||||
self._new_kind = new_kind
|
||||
|
||||
def get_kind(self):
|
||||
return self._new_kind
|
||||
@@ -26,6 +26,8 @@ from jedi.evaluate.compiled.access import COMPARISON_OPERATORS
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.gradual.stub_context import VersionInfo
|
||||
from jedi.evaluate.gradual import annotation
|
||||
from jedi.evaluate.context.decorator import Decoratee
|
||||
from jedi.plugins import plugin_manager
|
||||
|
||||
|
||||
def _limit_context_infers(func):
|
||||
@@ -148,28 +150,28 @@ def eval_node(context, element):
|
||||
return eval_or_test(context, element)
|
||||
|
||||
|
||||
def eval_trailer(context, base_contexts, trailer):
|
||||
def eval_trailer(context, atom_contexts, trailer):
|
||||
trailer_op, node = trailer.children[:2]
|
||||
if node == ')': # `arglist` is optional.
|
||||
node = None
|
||||
|
||||
if trailer_op == '[':
|
||||
trailer_op, node, _ = trailer.children
|
||||
return base_contexts.get_item(
|
||||
return atom_contexts.get_item(
|
||||
eval_subscript_list(context.evaluator, context, node),
|
||||
ContextualizedNode(context, trailer)
|
||||
)
|
||||
else:
|
||||
debug.dbg('eval_trailer: %s in %s', trailer, base_contexts)
|
||||
debug.dbg('eval_trailer: %s in %s', trailer, atom_contexts)
|
||||
if trailer_op == '.':
|
||||
return base_contexts.py__getattribute__(
|
||||
return atom_contexts.py__getattribute__(
|
||||
name_context=context,
|
||||
name_or_str=node
|
||||
)
|
||||
else:
|
||||
assert trailer_op == '(', 'trailer_op is actually %s' % trailer_op
|
||||
args = arguments.TreeArguments(context.evaluator, context, node, trailer)
|
||||
return base_contexts.execute(args)
|
||||
return atom_contexts.execute(args)
|
||||
|
||||
|
||||
def eval_atom(context, atom):
|
||||
@@ -544,6 +546,7 @@ def _remove_statements(evaluator, context, stmt, name):
|
||||
return eval_expr_stmt(context, stmt, seek_name=name)
|
||||
|
||||
|
||||
@plugin_manager.decorate()
|
||||
def tree_name_to_contexts(evaluator, context, tree_name):
|
||||
context_set = NO_CONTEXTS
|
||||
module_node = context.get_root_context().tree_node
|
||||
@@ -666,6 +669,8 @@ def _apply_decorators(context, node):
|
||||
return initial
|
||||
|
||||
debug.dbg('decorator end %s', values, color="MAGENTA")
|
||||
if values != initial:
|
||||
return ContextSet([Decoratee(c, decoratee_context) for c in values])
|
||||
return values
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from weakref import WeakKeyDictionary
|
||||
|
||||
from parso.python import tree
|
||||
from parso.cache import parser_cache
|
||||
from parso import split_lines
|
||||
|
||||
from jedi._compatibility import literal_eval, force_unicode
|
||||
|
||||
@@ -278,3 +279,19 @@ def get_cached_code_lines(grammar, path):
|
||||
to do this, but we avoid splitting all the lines again.
|
||||
"""
|
||||
return parser_cache[grammar._hashed][path].lines
|
||||
|
||||
|
||||
def cut_value_at_position(leaf, position):
|
||||
"""
|
||||
Cuts of the value of the leaf at position
|
||||
"""
|
||||
lines = split_lines(leaf.value, keepends=True)[:position[0] - leaf.line + 1]
|
||||
column = position[1]
|
||||
if leaf.line == position[0]:
|
||||
column -= leaf.column
|
||||
lines[-1] = lines[-1][:column]
|
||||
return ''.join(lines)
|
||||
|
||||
|
||||
def get_string_quote(leaf):
|
||||
return re.match('\w*("""|\'{3}|"|\')', leaf.value).group(1)
|
||||
|
||||
@@ -1,37 +1,47 @@
|
||||
from jedi.plugins.stdlib import StdlibPlugin
|
||||
from jedi.plugins.flask import FlaskPlugin
|
||||
from functools import wraps
|
||||
|
||||
|
||||
class _PluginManager(object):
|
||||
def __init__(self, registered_plugin_classes=()):
|
||||
self._registered_plugin_classes = list(registered_plugin_classes)
|
||||
def __init__(self):
|
||||
self._registered_plugins = []
|
||||
self._cached_base_callbacks = {}
|
||||
self._built_functions = {}
|
||||
|
||||
def register(self, plugin_class):
|
||||
def register(self, *plugins):
|
||||
"""
|
||||
Makes it possible to register your plugin.
|
||||
"""
|
||||
self._registered_plugins.append(plugin_class)
|
||||
self._registered_plugins.extend(plugins)
|
||||
self._build_functions()
|
||||
|
||||
def _build_chain(self, evaluator):
|
||||
for plugin_class in self._registered_plugin_classes:
|
||||
yield plugin_class(evaluator)
|
||||
def decorate(self):
|
||||
def decorator(callback):
|
||||
@wraps(callback)
|
||||
def wrapper(*args, **kwargs):
|
||||
return built_functions[name](*args, **kwargs)
|
||||
|
||||
def get_callbacks(self, evaluator):
|
||||
return _PluginCallbacks(self._build_chain(evaluator))
|
||||
name = callback.__name__
|
||||
|
||||
assert name not in self._built_functions
|
||||
built_functions = self._built_functions
|
||||
built_functions[name] = callback
|
||||
self._cached_base_callbacks[name] = callback
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
def _build_functions(self):
|
||||
for name, callback in self._cached_base_callbacks.items():
|
||||
for plugin in reversed(self._registered_plugins):
|
||||
# Need to reverse so the first plugin is run first.
|
||||
try:
|
||||
func = getattr(plugin, name)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
callback = func(callback)
|
||||
self._built_functions[name] = callback
|
||||
|
||||
|
||||
class _PluginCallbacks(object):
|
||||
def __init__(self, plugins):
|
||||
self._plugins = list(plugins)
|
||||
|
||||
def decorate(self, name, callback):
|
||||
for plugin in reversed(self._plugins):
|
||||
# Need to reverse so the first plugin is run first.
|
||||
callback = getattr(plugin, name)(callback)
|
||||
return callback
|
||||
|
||||
|
||||
plugin_manager = _PluginManager([
|
||||
StdlibPlugin,
|
||||
FlaskPlugin,
|
||||
])
|
||||
plugin_manager = _PluginManager()
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
class BasePlugin(object):
|
||||
"""
|
||||
Plugins are created each time an evaluator is created.
|
||||
"""
|
||||
def __init__(self, evaluator):
|
||||
# In __init__ you can do some caching.
|
||||
self._evaluator = evaluator
|
||||
|
||||
def execute(self, callback):
|
||||
"""
|
||||
Decorates the execute(context, arguments) function.
|
||||
"""
|
||||
return callback
|
||||
|
||||
def import_module(self, callback):
|
||||
"""
|
||||
Decorates the
|
||||
import_module(evaluator, import_path, sys_path, add_error_callback)
|
||||
function.
|
||||
"""
|
||||
return callback
|
||||
@@ -1,25 +1,21 @@
|
||||
from jedi.plugins.base import BasePlugin
|
||||
|
||||
|
||||
class FlaskPlugin(BasePlugin):
|
||||
def import_module(self, callback):
|
||||
"""
|
||||
Handle "magic" Flask extension imports:
|
||||
``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
|
||||
"""
|
||||
def wrapper(evaluator, import_names, module_context, *args, **kwargs):
|
||||
if len(import_names) == 3 and import_names[:2] == ('flask', 'ext'):
|
||||
# New style.
|
||||
ipath = (u'flask_' + import_names[2]),
|
||||
context_set = callback(evaluator, ipath, None, *args, **kwargs)
|
||||
if context_set:
|
||||
return context_set
|
||||
context_set = callback(evaluator, (u'flaskext',), None, *args, **kwargs)
|
||||
return callback(
|
||||
evaluator,
|
||||
(u'flaskext', import_names[2]),
|
||||
next(iter(context_set)),
|
||||
*args, **kwargs
|
||||
)
|
||||
return callback(evaluator, import_names, module_context, *args, **kwargs)
|
||||
return wrapper
|
||||
def import_module(callback):
|
||||
"""
|
||||
Handle "magic" Flask extension imports:
|
||||
``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
|
||||
"""
|
||||
def wrapper(evaluator, import_names, module_context, *args, **kwargs):
|
||||
if len(import_names) == 3 and import_names[:2] == ('flask', 'ext'):
|
||||
# New style.
|
||||
ipath = (u'flask_' + import_names[2]),
|
||||
context_set = callback(evaluator, ipath, None, *args, **kwargs)
|
||||
if context_set:
|
||||
return context_set
|
||||
context_set = callback(evaluator, (u'flaskext',), None, *args, **kwargs)
|
||||
return callback(
|
||||
evaluator,
|
||||
(u'flaskext', import_names[2]),
|
||||
next(iter(context_set)),
|
||||
*args, **kwargs
|
||||
)
|
||||
return callback(evaluator, import_names, module_context, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
10
jedi/plugins/registry.py
Normal file
10
jedi/plugins/registry.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
This is not a plugin, this is just the place were plugins are registered.
|
||||
"""
|
||||
|
||||
from jedi.plugins import stdlib
|
||||
from jedi.plugins import flask
|
||||
from jedi.plugins import plugin_manager
|
||||
|
||||
|
||||
plugin_manager.register(stdlib, flask)
|
||||
@@ -10,26 +10,31 @@ the standard library. The usual way to understand the standard library is the
|
||||
compiled module that returns the types for C-builtins.
|
||||
"""
|
||||
import parso
|
||||
import os
|
||||
|
||||
from jedi._compatibility import force_unicode
|
||||
from jedi.plugins.base import BasePlugin
|
||||
from jedi._compatibility import force_unicode, Parameter
|
||||
from jedi import debug
|
||||
from jedi.evaluate.utils import safe_property
|
||||
from jedi.evaluate.helpers import get_str_or_none
|
||||
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.klass import ClassMixin
|
||||
from jedi.evaluate.context.function import FunctionMixin
|
||||
from jedi.evaluate.context import iterable
|
||||
from jedi.evaluate.lazy_context import LazyTreeContext, LazyKnownContext, \
|
||||
LazyKnownContexts
|
||||
from jedi.evaluate.names import ContextName, BaseTreeParamName
|
||||
from jedi.evaluate.syntax_tree import is_string
|
||||
from jedi.evaluate.filters import AttributeOverwrite, publish_method
|
||||
from jedi.evaluate.filters import AttributeOverwrite, publish_method, \
|
||||
ParserTreeFilter, DictFilter
|
||||
from jedi.evaluate.signature import AbstractSignature, SignatureWrapper
|
||||
|
||||
|
||||
# Copied from Python 3.6's stdlib.
|
||||
@@ -99,45 +104,48 @@ _NAMEDTUPLE_FIELD_TEMPLATE = '''\
|
||||
'''
|
||||
|
||||
|
||||
class StdlibPlugin(BasePlugin):
|
||||
def execute(self, callback):
|
||||
def wrapper(context, arguments):
|
||||
try:
|
||||
obj_name = context.name.string_name
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if context.parent_context == self._evaluator.builtins_module:
|
||||
module_name = 'builtins'
|
||||
elif context.parent_context is not None and context.parent_context.is_module():
|
||||
module_name = context.parent_context.py__name__()
|
||||
else:
|
||||
return callback(context, arguments=arguments)
|
||||
|
||||
if isinstance(context, BoundMethod):
|
||||
if module_name == 'builtins':
|
||||
if context.py__name__() == '__get__':
|
||||
if context.class_context.py__name__() == 'property':
|
||||
return builtins_property(
|
||||
context,
|
||||
arguments=arguments
|
||||
)
|
||||
elif context.py__name__() in ('deleter', 'getter', 'setter'):
|
||||
if context.class_context.py__name__() == 'property':
|
||||
return ContextSet([context.instance])
|
||||
|
||||
return callback(context, arguments=arguments)
|
||||
|
||||
# for now we just support builtin functions.
|
||||
try:
|
||||
func = _implemented[module_name][obj_name]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return func(context, arguments=arguments)
|
||||
def execute(callback):
|
||||
def wrapper(context, arguments):
|
||||
def call():
|
||||
return callback(context, arguments=arguments)
|
||||
|
||||
return wrapper
|
||||
try:
|
||||
obj_name = context.name.string_name
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if context.parent_context == context.evaluator.builtins_module:
|
||||
module_name = 'builtins'
|
||||
elif context.parent_context is not None and context.parent_context.is_module():
|
||||
module_name = context.parent_context.py__name__()
|
||||
else:
|
||||
return call()
|
||||
|
||||
if isinstance(context, BoundMethod):
|
||||
if module_name == 'builtins':
|
||||
if context.py__name__() == '__get__':
|
||||
if context.class_context.py__name__() == 'property':
|
||||
return builtins_property(
|
||||
context,
|
||||
arguments=arguments,
|
||||
callback=call,
|
||||
)
|
||||
elif context.py__name__() in ('deleter', 'getter', 'setter'):
|
||||
if context.class_context.py__name__() == 'property':
|
||||
return ContextSet([context.instance])
|
||||
|
||||
return call()
|
||||
|
||||
# for now we just support builtin functions.
|
||||
try:
|
||||
func = _implemented[module_name][obj_name]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return func(context, arguments=arguments, callback=call)
|
||||
return call()
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def _follow_param(evaluator, arguments, index):
|
||||
@@ -150,15 +158,18 @@ def _follow_param(evaluator, arguments, index):
|
||||
|
||||
|
||||
def argument_clinic(string, want_obj=False, want_context=False,
|
||||
want_arguments=False, want_evaluator=False):
|
||||
want_arguments=False, want_evaluator=False,
|
||||
want_callback=False):
|
||||
"""
|
||||
Works like Argument Clinic (PEP 436), to validate function params.
|
||||
"""
|
||||
|
||||
def f(func):
|
||||
@repack_with_argument_clinic(string, keep_arguments_param=True)
|
||||
@repack_with_argument_clinic(string, keep_arguments_param=True,
|
||||
keep_callback_param=True)
|
||||
def wrapper(obj, *args, **kwargs):
|
||||
arguments = kwargs.pop('arguments')
|
||||
callback = kwargs.pop('callback')
|
||||
assert not kwargs # Python 2...
|
||||
debug.dbg('builtin start %s' % obj, color='MAGENTA')
|
||||
result = NO_CONTEXTS
|
||||
@@ -170,6 +181,8 @@ def argument_clinic(string, want_obj=False, want_context=False,
|
||||
kwargs['evaluator'] = obj.evaluator
|
||||
if want_arguments:
|
||||
kwargs['arguments'] = arguments
|
||||
if want_callback:
|
||||
kwargs['callback'] = callback
|
||||
result = func(*args, **kwargs)
|
||||
debug.dbg('builtin end: %s', result, color='MAGENTA')
|
||||
return result
|
||||
@@ -230,20 +243,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
|
||||
|
||||
@@ -361,6 +392,9 @@ class ClassMethodGet(AttributeOverwrite, ContextWrapper):
|
||||
self._class = klass
|
||||
self._function = function
|
||||
|
||||
def get_signatures(self):
|
||||
return self._function.get_signatures()
|
||||
|
||||
def get_object(self):
|
||||
return self._wrapped_context
|
||||
|
||||
@@ -388,7 +422,7 @@ def builtins_classmethod(functions, obj, arguments):
|
||||
)
|
||||
|
||||
|
||||
def collections_namedtuple(obj, arguments):
|
||||
def collections_namedtuple(obj, arguments, callback):
|
||||
"""
|
||||
Implementation of the namedtuple function.
|
||||
|
||||
@@ -411,14 +445,16 @@ def collections_namedtuple(obj, arguments):
|
||||
if not param_contexts:
|
||||
return NO_CONTEXTS
|
||||
_fields = list(param_contexts)[0]
|
||||
if isinstance(_fields, compiled.CompiledValue):
|
||||
fields = force_unicode(_fields.get_safe_value()).replace(',', ' ').split()
|
||||
string = get_str_or_none(_fields)
|
||||
if string is not None:
|
||||
fields = force_unicode(string).replace(',', ' ').split()
|
||||
elif isinstance(_fields, iterable.Sequence):
|
||||
fields = [
|
||||
force_unicode(v.get_safe_value())
|
||||
force_unicode(get_str_or_none(v))
|
||||
for lazy_context in _fields.py__iter__()
|
||||
for v in lazy_context.infer() if is_string(v)
|
||||
for v in lazy_context.infer()
|
||||
]
|
||||
fields = [f for f in fields if f is not None]
|
||||
else:
|
||||
return NO_CONTEXTS
|
||||
|
||||
@@ -454,17 +490,49 @@ class PartialObject(object):
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._actual_context, name)
|
||||
|
||||
def py__call__(self, arguments):
|
||||
key, lazy_context = next(self._arguments.unpack(), (None, None))
|
||||
def _get_function(self, unpacked_arguments):
|
||||
key, lazy_context = next(unpacked_arguments, (None, None))
|
||||
if key is not None or lazy_context is None:
|
||||
debug.warning("Partial should have a proper function %s", self._arguments)
|
||||
return None
|
||||
return lazy_context.infer()
|
||||
|
||||
def get_signatures(self):
|
||||
unpacked_arguments = self._arguments.unpack()
|
||||
func = self._get_function(unpacked_arguments)
|
||||
if func is None:
|
||||
return []
|
||||
|
||||
arg_count = 0
|
||||
keys = set()
|
||||
for key, _ in unpacked_arguments:
|
||||
if key is None:
|
||||
arg_count += 1
|
||||
else:
|
||||
keys.add(key)
|
||||
return [PartialSignature(s, arg_count, keys) for s in func.get_signatures()]
|
||||
|
||||
def py__call__(self, arguments):
|
||||
func = self._get_function(self._arguments.unpack())
|
||||
if func is None:
|
||||
return NO_CONTEXTS
|
||||
|
||||
return lazy_context.infer().execute(
|
||||
return func.execute(
|
||||
MergedPartialArguments(self._arguments, arguments)
|
||||
)
|
||||
|
||||
|
||||
class PartialSignature(SignatureWrapper):
|
||||
def __init__(self, wrapped_signature, skipped_arg_count, skipped_arg_set):
|
||||
super(PartialSignature, self).__init__(wrapped_signature)
|
||||
self._skipped_arg_count = skipped_arg_count
|
||||
self._skipped_arg_set = skipped_arg_set
|
||||
|
||||
def get_param_names(self, resolve_stars=False):
|
||||
names = self._wrapped_signature.get_param_names()[self._skipped_arg_count:]
|
||||
return [n for n in names if n.string_name not in self._skipped_arg_set]
|
||||
|
||||
|
||||
class MergedPartialArguments(AbstractArguments):
|
||||
def __init__(self, partial_arguments, call_arguments):
|
||||
self._partial_arguments = partial_arguments
|
||||
@@ -481,7 +549,7 @@ class MergedPartialArguments(AbstractArguments):
|
||||
yield key_lazy_context
|
||||
|
||||
|
||||
def functools_partial(obj, arguments):
|
||||
def functools_partial(obj, arguments, callback):
|
||||
return ContextSet(
|
||||
PartialObject(instance, arguments)
|
||||
for instance in obj.py__call__(arguments)
|
||||
@@ -502,6 +570,66 @@ def _random_choice(sequences):
|
||||
)
|
||||
|
||||
|
||||
def _dataclass(obj, arguments, callback):
|
||||
for c in _follow_param(obj.evaluator, arguments, 0):
|
||||
if c.is_class():
|
||||
return ContextSet([DataclassWrapper(c)])
|
||||
else:
|
||||
return ContextSet([obj])
|
||||
return NO_CONTEXTS
|
||||
|
||||
|
||||
class DataclassWrapper(ContextWrapper, ClassMixin):
|
||||
def get_signatures(self):
|
||||
param_names = []
|
||||
for cls in reversed(list(self.py__mro__())):
|
||||
if isinstance(cls, DataclassWrapper):
|
||||
filter_ = cls.get_global_filter()
|
||||
# .values ordering is not guaranteed, at least not in
|
||||
# Python < 3.6, when dicts where not ordered, which is an
|
||||
# implementation detail anyway.
|
||||
for name in sorted(filter_.values(), key=lambda name: name.start_pos):
|
||||
d = name.tree_name.get_definition()
|
||||
annassign = d.children[1]
|
||||
if d.type == 'expr_stmt' and annassign.type == 'annassign':
|
||||
if len(annassign.children) < 4:
|
||||
default = None
|
||||
else:
|
||||
default = annassign.children[3]
|
||||
param_names.append(DataclassParamName(
|
||||
parent_context=cls.parent_context,
|
||||
tree_name=name.tree_name,
|
||||
annotation_node=annassign.children[1],
|
||||
default_node=default,
|
||||
))
|
||||
return [DataclassSignature(cls, param_names)]
|
||||
|
||||
|
||||
class DataclassSignature(AbstractSignature):
|
||||
def __init__(self, context, param_names):
|
||||
super(DataclassSignature, self).__init__(context)
|
||||
self._param_names = param_names
|
||||
|
||||
def get_param_names(self, resolve_stars=False):
|
||||
return self._param_names
|
||||
|
||||
|
||||
class DataclassParamName(BaseTreeParamName):
|
||||
def __init__(self, parent_context, tree_name, annotation_node, default_node):
|
||||
super(DataclassParamName, self).__init__(parent_context, tree_name)
|
||||
self.annotation_node = annotation_node
|
||||
self.default_node = default_node
|
||||
|
||||
def get_kind(self):
|
||||
return Parameter.POSITIONAL_OR_KEYWORD
|
||||
|
||||
def infer(self):
|
||||
if self.annotation_node is None:
|
||||
return NO_CONTEXTS
|
||||
else:
|
||||
return self.parent_context.eval_node(self.annotation_node)
|
||||
|
||||
|
||||
class ItemGetterCallable(ContextWrapper):
|
||||
def __init__(self, instance, args_context_set):
|
||||
super(ItemGetterCallable, self).__init__(instance)
|
||||
@@ -527,6 +655,33 @@ class ItemGetterCallable(ContextWrapper):
|
||||
return context_set
|
||||
|
||||
|
||||
@argument_clinic('func, /')
|
||||
def _functools_wraps(funcs):
|
||||
return ContextSet(WrapsCallable(func) for func in funcs)
|
||||
|
||||
|
||||
class WrapsCallable(ContextWrapper):
|
||||
# XXX this is not the correct wrapped context, it should be a weird
|
||||
# partials object, but it doesn't matter, because it's always used as a
|
||||
# decorator anyway.
|
||||
@repack_with_argument_clinic('func, /')
|
||||
def py__call__(self, funcs):
|
||||
return ContextSet({Wrapped(func, self._wrapped_context) for func in funcs})
|
||||
|
||||
|
||||
class Wrapped(ContextWrapper, FunctionMixin):
|
||||
def __init__(self, func, original_function):
|
||||
super(Wrapped, self).__init__(func)
|
||||
self._original_function = original_function
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._original_function.name
|
||||
|
||||
def get_signature_functions(self):
|
||||
return [self]
|
||||
|
||||
|
||||
@argument_clinic('*args, /', want_obj=True, want_arguments=True)
|
||||
def _operator_itemgetter(args_context_set, obj, arguments):
|
||||
return ContextSet([
|
||||
@@ -535,6 +690,44 @@ def _operator_itemgetter(args_context_set, obj, arguments):
|
||||
])
|
||||
|
||||
|
||||
def _create_string_input_function(func):
|
||||
@argument_clinic('string, /', want_obj=True, want_arguments=True)
|
||||
def wrapper(strings, obj, arguments):
|
||||
def iterate():
|
||||
for context in strings:
|
||||
s = get_str_or_none(context)
|
||||
if s is not None:
|
||||
s = func(s)
|
||||
yield compiled.create_simple_object(context.evaluator, s)
|
||||
contexts = ContextSet(iterate())
|
||||
if contexts:
|
||||
return contexts
|
||||
return obj.py__call__(arguments)
|
||||
return wrapper
|
||||
|
||||
|
||||
@argument_clinic('*args, /', want_callback=True)
|
||||
def _os_path_join(args_set, callback):
|
||||
if len(args_set) == 1:
|
||||
string = u''
|
||||
sequence, = args_set
|
||||
is_first = True
|
||||
for lazy_context in sequence.py__iter__():
|
||||
string_contexts = lazy_context.infer()
|
||||
if len(string_contexts) != 1:
|
||||
break
|
||||
s = get_str_or_none(next(iter(string_contexts)))
|
||||
if s is None:
|
||||
break
|
||||
if not is_first:
|
||||
string += os.path.sep
|
||||
string += force_unicode(s)
|
||||
is_first = False
|
||||
else:
|
||||
return ContextSet([compiled.create_simple_object(sequence.evaluator, string)])
|
||||
return callback()
|
||||
|
||||
|
||||
_implemented = {
|
||||
'builtins': {
|
||||
'getattr': builtins_getattr,
|
||||
@@ -552,15 +745,15 @@ _implemented = {
|
||||
'deepcopy': _return_first_param,
|
||||
},
|
||||
'json': {
|
||||
'load': lambda obj, arguments: NO_CONTEXTS,
|
||||
'loads': lambda obj, arguments: NO_CONTEXTS,
|
||||
'load': lambda obj, arguments, callback: NO_CONTEXTS,
|
||||
'loads': lambda obj, arguments, callback: NO_CONTEXTS,
|
||||
},
|
||||
'collections': {
|
||||
'namedtuple': collections_namedtuple,
|
||||
},
|
||||
'functools': {
|
||||
'partial': functools_partial,
|
||||
'wraps': _return_first_param,
|
||||
'wraps': _functools_wraps,
|
||||
},
|
||||
'_weakref': {
|
||||
'proxy': _return_first_param,
|
||||
@@ -580,6 +773,63 @@ _implemented = {
|
||||
# The _alias function just leads to some annoying type inference.
|
||||
# Therefore, just make it return nothing, which leads to the stubs
|
||||
# being used instead. This only matters for 3.7+.
|
||||
'_alias': lambda obj, arguments: NO_CONTEXTS,
|
||||
'_alias': lambda obj, arguments, callback: NO_CONTEXTS,
|
||||
},
|
||||
'dataclasses': {
|
||||
# For now this works at least better than Jedi trying to understand it.
|
||||
'dataclass': _dataclass
|
||||
},
|
||||
'os.path': {
|
||||
'dirname': _create_string_input_function(os.path.dirname),
|
||||
'abspath': _create_string_input_function(os.path.abspath),
|
||||
'relpath': _create_string_input_function(os.path.relpath),
|
||||
'join': _os_path_join,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_metaclass_filters(func):
|
||||
def wrapper(cls, metaclasses):
|
||||
for metaclass in metaclasses:
|
||||
if metaclass.py__name__() == 'EnumMeta' \
|
||||
and metaclass.get_root_context().py__name__() == 'enum':
|
||||
filter_ = ParserTreeFilter(cls.evaluator, context=cls)
|
||||
return [DictFilter({
|
||||
name.string_name: EnumInstance(cls, name).name for name in filter_.values()
|
||||
})]
|
||||
return func(cls, metaclasses)
|
||||
return wrapper
|
||||
|
||||
|
||||
class EnumInstance(LazyContextWrapper):
|
||||
def __init__(self, cls, name):
|
||||
self.evaluator = cls.evaluator
|
||||
self._cls = cls # Corresponds to super().__self__
|
||||
self._name = name
|
||||
self.tree_node = self._name.tree_name
|
||||
|
||||
@safe_property
|
||||
def name(self):
|
||||
return ContextName(self, self._name.tree_name)
|
||||
|
||||
def _get_wrapped_context(self):
|
||||
obj, = self._cls.execute_evaluated()
|
||||
return obj
|
||||
|
||||
def get_filters(self, search_global=False, position=None, origin_scope=None):
|
||||
yield DictFilter(dict(
|
||||
name=compiled.create_simple_object(self.evaluator, self._name.string_name).name,
|
||||
value=self._name,
|
||||
))
|
||||
for f in self._get_wrapped_context().get_filters():
|
||||
yield f
|
||||
|
||||
|
||||
def tree_name_to_contexts(func):
|
||||
def wrapper(evaluator, context, tree_name):
|
||||
if tree_name.value == 'sep' and context.is_module() and context.py__name__() == 'os.path':
|
||||
return ContextSet({
|
||||
compiled.create_simple_object(evaluator, os.path.sep),
|
||||
})
|
||||
return func(evaluator, context, tree_name)
|
||||
return wrapper
|
||||
|
||||
@@ -11,10 +11,7 @@ import re
|
||||
import os
|
||||
import sys
|
||||
|
||||
from parso import split_lines
|
||||
|
||||
from jedi import Interpreter
|
||||
from jedi.api.helpers import get_on_completion_name
|
||||
|
||||
|
||||
READLINE_DEBUG = False
|
||||
@@ -86,23 +83,18 @@ def setup_readline(namespace_module=__main__):
|
||||
logging.debug("Start REPL completion: " + repr(text))
|
||||
interpreter = Interpreter(text, [namespace_module.__dict__])
|
||||
|
||||
lines = split_lines(text)
|
||||
position = (len(lines), len(lines[-1]))
|
||||
name = get_on_completion_name(
|
||||
interpreter._module_node,
|
||||
lines,
|
||||
position
|
||||
)
|
||||
before = text[:len(text) - len(name)]
|
||||
completions = interpreter.completions()
|
||||
logging.debug("REPL completions: %s", completions)
|
||||
|
||||
self.matches = [
|
||||
text[:len(text) - c._like_name_length] + c.name_with_symbols
|
||||
for c in completions
|
||||
]
|
||||
except:
|
||||
logging.error("REPL Completion error:\n" + traceback.format_exc())
|
||||
raise
|
||||
finally:
|
||||
sys.path.pop(0)
|
||||
|
||||
self.matches = [before + c.name_with_symbols for c in completions]
|
||||
try:
|
||||
return self.matches[state]
|
||||
except IndexError:
|
||||
|
||||
@@ -1 +1 @@
|
||||
parso>=0.3.0
|
||||
parso>=0.5.0
|
||||
|
||||
4
setup.py
4
setup.py
@@ -37,7 +37,8 @@ setup(name='jedi',
|
||||
install_requires=install_requires,
|
||||
extras_require={
|
||||
'testing': [
|
||||
'pytest>=3.1.0',
|
||||
# Pytest 5 doesn't support Python 2 and Python 3.4 anymore.
|
||||
'pytest>=3.1.0,<5.0.0',
|
||||
# docopt for sith doctests
|
||||
'docopt',
|
||||
# coloroma for colored debug output
|
||||
@@ -60,6 +61,7 @@ setup(name='jedi',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Topic :: Text Editors :: Integrated Development Environments (IDE)',
|
||||
'Topic :: Utilities',
|
||||
|
||||
@@ -18,10 +18,12 @@ int(str)
|
||||
str..
|
||||
#? []
|
||||
a(0):.
|
||||
#? 2 ['and', 'or', 'if', 'is', 'in', 'not']
|
||||
#? 2 []
|
||||
0x0
|
||||
#? ['and', 'or', 'if', 'is', 'in', 'not']
|
||||
#? []
|
||||
1j
|
||||
#? ['and', 'or', 'if', 'is', 'in', 'not']
|
||||
1j
|
||||
x = None()
|
||||
#?
|
||||
x
|
||||
|
||||
@@ -279,7 +279,7 @@ V(1).c()
|
||||
V(1).d()
|
||||
# Only keywords should be possible to complete.
|
||||
#? ['is', 'in', 'not', 'and', 'or', 'if']
|
||||
V(1).d()
|
||||
V(1).d()
|
||||
|
||||
|
||||
# -----------------
|
||||
|
||||
@@ -310,11 +310,14 @@ follow_statement(1)
|
||||
# class decorators should just be ignored
|
||||
@should_ignore
|
||||
class A():
|
||||
x = 3
|
||||
def ret(self):
|
||||
return 1
|
||||
|
||||
#? int()
|
||||
A().ret()
|
||||
#? int()
|
||||
A().x
|
||||
|
||||
|
||||
# -----------------
|
||||
|
||||
@@ -109,6 +109,23 @@ class X():
|
||||
#! 3 []
|
||||
X(foo=x)
|
||||
|
||||
|
||||
# Multiple inheritance
|
||||
class Foo:
|
||||
def foo(self):
|
||||
print("foo")
|
||||
class Bar:
|
||||
def bar(self):
|
||||
print("bar")
|
||||
class Baz(Foo, Bar):
|
||||
def baz(self):
|
||||
#! ['def foo']
|
||||
super().foo
|
||||
#! ['def bar']
|
||||
super().bar
|
||||
#! ['instance Foo']
|
||||
super()
|
||||
|
||||
# -----------------
|
||||
# imports
|
||||
# -----------------
|
||||
@@ -203,9 +220,10 @@ for key, value in [(1,2)]:
|
||||
#! ['for key, value in [(1,2)]: key']
|
||||
key
|
||||
|
||||
for i in []:
|
||||
#! ['for i in []: i']
|
||||
i
|
||||
#! 4 ['for y in [1]: y']
|
||||
for y in [1]:
|
||||
#! ['for y in [1]: y']
|
||||
y
|
||||
|
||||
# -----------------
|
||||
# decorator
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import recurse_class2
|
||||
from . import recurse_class2
|
||||
|
||||
class C(recurse_class2.C):
|
||||
def a(self):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import recurse_class1
|
||||
from . import recurse_class1
|
||||
|
||||
class C(recurse_class1.C):
|
||||
pass
|
||||
|
||||
@@ -165,3 +165,19 @@ def keyword_only(a: str, *, b: str):
|
||||
a.startswi
|
||||
#? ['startswith']
|
||||
b.startswi
|
||||
|
||||
|
||||
def argskwargs(*args: int, **kwargs: float):
|
||||
"""
|
||||
This might be a bit confusing, but is part of the standard.
|
||||
args is changed to Tuple[int] in this case and kwargs to Dict[str, float],
|
||||
which makes sense if you think about it a bit.
|
||||
"""
|
||||
#? tuple()
|
||||
args
|
||||
#? int()
|
||||
args[0]
|
||||
#? str()
|
||||
next(iter(kwargs.keys()))
|
||||
#? float()
|
||||
kwargs['']
|
||||
|
||||
@@ -256,6 +256,31 @@ for key in x.keys():
|
||||
for value in x.values():
|
||||
#? int()
|
||||
value
|
||||
|
||||
WrappingType = typing.NewType('WrappingType', str) # Chosen arbitrarily
|
||||
y = WrappingType(0) # Per https://github.com/davidhalter/jedi/issues/1015#issuecomment-355795929
|
||||
#? str()
|
||||
y
|
||||
|
||||
def testnewtype(y):
|
||||
"""
|
||||
:type y: WrappingType
|
||||
"""
|
||||
#? str()
|
||||
y
|
||||
#? ["upper"]
|
||||
y.u
|
||||
|
||||
WrappingType2 = typing.NewType()
|
||||
|
||||
def testnewtype2(y):
|
||||
"""
|
||||
:type y: WrappingType2
|
||||
"""
|
||||
#?
|
||||
y
|
||||
#? []
|
||||
y.
|
||||
# python >= 3.4
|
||||
|
||||
class TestDefaultDict(typing.DefaultDict[str, int]):
|
||||
@@ -295,7 +320,6 @@ import typing as t
|
||||
def union2(x: t.Union[int, str]):
|
||||
#? int() str()
|
||||
x
|
||||
|
||||
from typing import Union
|
||||
def union3(x: Union[int, str]):
|
||||
#? int() str()
|
||||
|
||||
@@ -38,12 +38,16 @@ for char in NOT_DEFINED:
|
||||
char
|
||||
|
||||
|
||||
# -------------------------
|
||||
# instance/class vars
|
||||
# -------------------------
|
||||
|
||||
class Foo():
|
||||
bar: int
|
||||
baz: typing.ClassVar[str]
|
||||
|
||||
|
||||
#? int()
|
||||
#?
|
||||
Foo.bar
|
||||
#? int()
|
||||
Foo().bar
|
||||
@@ -51,3 +55,61 @@ Foo().bar
|
||||
Foo.baz
|
||||
#? str()
|
||||
Foo().baz
|
||||
|
||||
class VarClass:
|
||||
var_instance1: int = 1
|
||||
var_instance2: float
|
||||
var_class1: typing.ClassVar[str] = 1
|
||||
var_class2: typing.ClassVar[bytes]
|
||||
|
||||
def __init__(self):
|
||||
#? int()
|
||||
d.var_instance1
|
||||
#? float()
|
||||
d.var_instance2
|
||||
#? str()
|
||||
d.var_class1
|
||||
#? bytes()
|
||||
d.var_class2
|
||||
#? []
|
||||
d.int
|
||||
#? ['var_class1', 'var_class2', 'var_instance1', 'var_instance2']
|
||||
self.var_
|
||||
|
||||
|
||||
#? ['var_class1', 'var_class2']
|
||||
VarClass.var_
|
||||
#?
|
||||
VarClass.var_instance1
|
||||
#?
|
||||
VarClass.var_instance2
|
||||
#? str()
|
||||
VarClass.var_class1
|
||||
#? bytes()
|
||||
VarClass.var_class2
|
||||
#? []
|
||||
VarClass.int
|
||||
|
||||
d = VarClass()
|
||||
#? ['var_class1', 'var_class2', 'var_instance1', 'var_instance2']
|
||||
d.var_
|
||||
#? int()
|
||||
d.var_instance1
|
||||
#? float()
|
||||
d.var_instance2
|
||||
#? str()
|
||||
d.var_class1
|
||||
#? bytes()
|
||||
d.var_class2
|
||||
#? []
|
||||
d.int
|
||||
|
||||
|
||||
|
||||
import dataclasses
|
||||
@dataclasses.dataclass
|
||||
class DC:
|
||||
name: int = 1
|
||||
|
||||
#? int()
|
||||
DC().name
|
||||
|
||||
@@ -67,6 +67,18 @@ class X(): pass
|
||||
#? type
|
||||
type(X)
|
||||
|
||||
# -----------------
|
||||
# type() calls with multiple parameters
|
||||
# -----------------
|
||||
|
||||
X = type('X', (object,), dict(a=1))
|
||||
|
||||
# Doesn't work yet.
|
||||
#?
|
||||
X.a
|
||||
#?
|
||||
X
|
||||
|
||||
if os.path.isfile():
|
||||
#? ['abspath']
|
||||
fails = os.path.abspath
|
||||
@@ -290,3 +302,31 @@ class Test(metaclass=Meta):
|
||||
result = super(Test, self).test_function()
|
||||
#? []
|
||||
result.
|
||||
|
||||
# -----------------
|
||||
# Enum
|
||||
# -----------------
|
||||
|
||||
# python >= 3.4
|
||||
import enum
|
||||
|
||||
class X(enum.Enum):
|
||||
attr_x = 3
|
||||
attr_y = 2.0
|
||||
|
||||
#? ['mro']
|
||||
X.mro
|
||||
#? ['attr_x', 'attr_y']
|
||||
X.attr_
|
||||
#? str()
|
||||
X.attr_x.name
|
||||
#? int()
|
||||
X.attr_x.value
|
||||
#? str()
|
||||
X.attr_y.name
|
||||
#? float()
|
||||
X.attr_y.value
|
||||
#? str()
|
||||
X().name
|
||||
#? float()
|
||||
X().attr_x.attr_y.value
|
||||
|
||||
@@ -73,7 +73,7 @@ def _check_number(Script, source, result='float'):
|
||||
|
||||
def test_completion_on_number_literals(Script):
|
||||
# No completions on an int literal (is a float).
|
||||
assert [c.name for c in Script('1.').completions()] \
|
||||
assert [c.name for c in Script('1. ').completions()] \
|
||||
== ['and', 'if', 'in', 'is', 'not', 'or']
|
||||
|
||||
# Multiple points after an int literal basically mean that there's a float
|
||||
@@ -109,9 +109,12 @@ def test_completion_on_complex_literals(Script):
|
||||
_check_number(Script, '1j.', 'complex')
|
||||
_check_number(Script, '44.j.', 'complex')
|
||||
_check_number(Script, '4.0j.', 'complex')
|
||||
# No dot no completion - I thought, but 4j is actually a literall after
|
||||
# No dot no completion - I thought, but 4j is actually a literal after
|
||||
# which a keyword like or is allowed. Good times, haha!
|
||||
assert ({c.name for c in Script('4j').completions()} ==
|
||||
# However this has been disabled again, because it apparently annoyed
|
||||
# users. So no completion after j without a space :)
|
||||
assert not Script('4j').completions()
|
||||
assert ({c.name for c in Script('4j ').completions()} ==
|
||||
{'if', 'and', 'in', 'is', 'not', 'or'})
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
import inspect
|
||||
import warnings
|
||||
|
||||
import pytest
|
||||
|
||||
from ..helpers import TestCase
|
||||
from jedi import cache
|
||||
from jedi.parser_utils import get_call_signature
|
||||
from jedi import Interpreter
|
||||
|
||||
|
||||
def assert_signature(Script, source, expected_name, expected_index=0, line=None, column=None):
|
||||
@@ -43,7 +44,7 @@ class TestCallSignatures(TestCase):
|
||||
run(s1, 'sorted', 0, 7)
|
||||
run(s1, 'sorted', 1, 9)
|
||||
run(s1, 'sorted', 1, 10)
|
||||
run(s1, 'sorted', 1, 11)
|
||||
run(s1, 'sorted', None, 11)
|
||||
run(s1, 'bool', 0, 15)
|
||||
|
||||
s2 = "abs(), "
|
||||
@@ -272,6 +273,13 @@ def test_int_params(Script):
|
||||
assert sig2.params[0].name == 'x'
|
||||
|
||||
|
||||
def test_pow_params(Script):
|
||||
# See Github #1357.
|
||||
for sig in Script('pow(').call_signatures():
|
||||
param_names = [p.name for p in sig.params]
|
||||
assert param_names in (['x', 'y'], ['x', 'y', 'z'])
|
||||
|
||||
|
||||
def test_param_name(Script):
|
||||
sigs = Script('open(something,').call_signatures()
|
||||
for sig in sigs:
|
||||
@@ -307,7 +315,8 @@ def test_signature_is_definition(Script):
|
||||
|
||||
# Now compare all the attributes that a CallSignature must also have.
|
||||
for attr_name in dir(definition):
|
||||
dont_scan = ['defined_names', 'parent', 'goto_assignments', 'infer', 'params']
|
||||
dont_scan = ['defined_names', 'parent', 'goto_assignments', 'infer',
|
||||
'params', 'get_signatures', 'execute']
|
||||
if attr_name.startswith('_') or attr_name in dont_scan:
|
||||
continue
|
||||
|
||||
@@ -394,6 +403,148 @@ def test_keyword_argument_index(Script, environment):
|
||||
assert get(both + 'foo(a, b, c').index == 0
|
||||
|
||||
|
||||
code1 = 'def f(u, /, v=3, *, abc, abd, xyz): pass'
|
||||
code2 = 'def g(u, /, v=3, *, abc, abd, xyz, **kwargs): pass'
|
||||
code3 = 'def h(u, /, v, *args, x=1, y): pass'
|
||||
code4 = 'def i(u, /, v, *args, x=1, y, **kwargs): pass'
|
||||
|
||||
|
||||
_calls = [
|
||||
# No *args, **kwargs
|
||||
(code1, 'f(', 0),
|
||||
(code1, 'f(a', 0),
|
||||
(code1, 'f(a,', 1),
|
||||
(code1, 'f(a,b', 1),
|
||||
(code1, 'f(a,b,', 2),
|
||||
(code1, 'f(a,b,c', None),
|
||||
(code1, 'f(a,b,a', 2),
|
||||
(code1, 'f(a,b,a=', None),
|
||||
(code1, 'f(a,b,abc', 2),
|
||||
(code1, 'f(a,b,abc=(', 2),
|
||||
(code1, 'f(a,b,abc=(f,1,2,3', 2),
|
||||
(code1, 'f(a,b,abd', 3),
|
||||
(code1, 'f(a,b,x', 4),
|
||||
(code1, 'f(a,b,xy', 4),
|
||||
(code1, 'f(a,b,xyz=', 4),
|
||||
(code1, 'f(a,b,xy=', None),
|
||||
(code1, 'f(u=', (0, None)),
|
||||
(code1, 'f(v=', 1),
|
||||
|
||||
# **kwargs
|
||||
(code2, 'g(a,b,a', 2),
|
||||
(code2, 'g(a,b,abc', 2),
|
||||
(code2, 'g(a,b,abd', 3),
|
||||
(code2, 'g(a,b,arr', 5),
|
||||
(code2, 'g(a,b,xy', 4),
|
||||
(code2, 'g(a,b,xyz=', 4),
|
||||
(code2, 'g(a,b,xy=', 5),
|
||||
(code2, 'g(a,b,abc=1,abd=4,', 4),
|
||||
(code2, 'g(a,b,abc=1,xyz=3,abd=4,', 5),
|
||||
(code2, 'g(a,b,abc=1,abd=4,lala', 5),
|
||||
(code2, 'g(a,b,abc=1,abd=4,lala=', 5),
|
||||
(code2, 'g(a,b,abc=1,abd=4,abd=', 5),
|
||||
(code2, 'g(a,b,kw', 5),
|
||||
(code2, 'g(a,b,kwargs=', 5),
|
||||
(code2, 'g(u=', (0, 5)),
|
||||
(code2, 'g(v=', 1),
|
||||
|
||||
# *args
|
||||
(code3, 'h(a,b,c', 2),
|
||||
(code3, 'h(a,b,c,', 2),
|
||||
(code3, 'h(a,b,c,d', 2),
|
||||
(code3, 'h(a,b,c,d[', 2),
|
||||
(code3, 'h(a,b,c,(3,', 2),
|
||||
(code3, 'h(a,b,c,(3,)', 2),
|
||||
(code3, 'h(a,b,args=', None),
|
||||
(code3, 'h(u,v=', 1),
|
||||
(code3, 'h(u=', (0, None)),
|
||||
(code3, 'h(u,*xxx', 1),
|
||||
(code3, 'h(u,*xxx,*yyy', 1),
|
||||
(code3, 'h(u,*[]', 1),
|
||||
(code3, 'h(u,*', 1),
|
||||
(code3, 'h(u,*, *', 1),
|
||||
(code3, 'h(u,1,**', 3),
|
||||
(code3, 'h(u,**y', 1),
|
||||
(code3, 'h(u,x=2,**', 1),
|
||||
(code3, 'h(u,x=2,**y', 1),
|
||||
(code3, 'h(u,v=2,**y', 3),
|
||||
(code3, 'h(u,x=2,**vv', 1),
|
||||
|
||||
# *args, **kwargs
|
||||
(code4, 'i(a,b,c,d', 2),
|
||||
(code4, 'i(a,b,c,d,e', 2),
|
||||
(code4, 'i(a,b,c,d,e=', 5),
|
||||
(code4, 'i(a,b,c,d,e=3', 5),
|
||||
(code4, 'i(a,b,c,d=,x=', 3),
|
||||
(code4, 'i(a,b,c,d=5,x=4', 3),
|
||||
(code4, 'i(a,b,c,d=5,x=4,y', 4),
|
||||
(code4, 'i(a,b,c,d=5,x=4,y=3,', 5),
|
||||
(code4, 'i(a,b,c,d=5,y=4,x=3,', 5),
|
||||
(code4, 'i(a,b,c,d=4,', 3),
|
||||
(code4, 'i(a,b,c,x=1,d=,', 4),
|
||||
|
||||
# Error nodes
|
||||
(code4, 'i(1, [a,b', 1),
|
||||
(code4, 'i(1, [a,b=,', 2),
|
||||
(code4, 'i(1, [a?b,', 2),
|
||||
(code4, 'i(1, [a?b,*', 2),
|
||||
(code4, 'i(?b,*r,c', 1),
|
||||
(code4, 'i(?*', 0),
|
||||
(code4, 'i(?**', (0, 1)),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('ending', ['', ')'])
|
||||
@pytest.mark.parametrize('code, call, expected_index', _calls)
|
||||
def test_signature_index(skip_python2, Script, environment, code, call, expected_index, ending):
|
||||
if isinstance(expected_index, tuple):
|
||||
expected_index = expected_index[environment.version_info > (3, 8)]
|
||||
if environment.version_info < (3, 8):
|
||||
code = code.replace('/,', '')
|
||||
|
||||
sig, = Script(code + '\n' + call + ending, column=len(call)).call_signatures()
|
||||
index = sig.index
|
||||
assert expected_index == index
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Python 2 doesn't support __signature__")
|
||||
@pytest.mark.parametrize('code', ['foo', 'instance.foo'])
|
||||
def test_arg_defaults(Script, environment, code):
|
||||
def foo(arg="bla", arg1=1):
|
||||
pass
|
||||
|
||||
class Klass:
|
||||
def foo(self, arg="bla", arg1=1):
|
||||
pass
|
||||
|
||||
instance = Klass()
|
||||
|
||||
src = dedent("""
|
||||
def foo2(arg="bla", arg1=1):
|
||||
pass
|
||||
|
||||
class Klass2:
|
||||
def foo2(self, arg="bla", arg1=1):
|
||||
pass
|
||||
|
||||
instance = Klass2()
|
||||
""")
|
||||
|
||||
executed_locals = dict()
|
||||
exec(src, None, executed_locals)
|
||||
locals_ = locals()
|
||||
|
||||
def iter_scripts():
|
||||
yield Interpreter(code + '(', namespaces=[locals_])
|
||||
yield Script(src + code + "2(")
|
||||
yield Interpreter(code + '2(', namespaces=[executed_locals])
|
||||
|
||||
for script in iter_scripts():
|
||||
signatures = script.call_signatures()
|
||||
assert signatures[0].params[0].description in ('param arg="bla"', "param arg='bla'")
|
||||
assert signatures[0].params[1].description == 'param arg1=1'
|
||||
|
||||
|
||||
def test_bracket_start(Script):
|
||||
def bracket_start(src):
|
||||
signatures = Script(src).call_signatures()
|
||||
|
||||
@@ -7,19 +7,16 @@ from inspect import cleandoc
|
||||
import pytest
|
||||
|
||||
import jedi
|
||||
from jedi import __doc__ as jedi_doc, names
|
||||
from ..helpers import TestCase
|
||||
from jedi import __doc__ as jedi_doc
|
||||
from jedi.evaluate.compiled import CompiledContextName
|
||||
|
||||
|
||||
def test_is_keyword(Script):
|
||||
#results = Script('import ', 1, 1, None).goto_definitions()
|
||||
#assert len(results) == 1 and results[0].is_keyword is True
|
||||
results = Script('str', 1, 1, None).goto_definitions()
|
||||
assert len(results) == 1 and results[0].is_keyword is False
|
||||
|
||||
|
||||
def test_basedefinition_type(Script, environment):
|
||||
def test_basedefinition_type(Script, names):
|
||||
def make_definitions():
|
||||
"""
|
||||
Return a list of definitions for parametrized tests.
|
||||
@@ -44,7 +41,7 @@ def test_basedefinition_type(Script, environment):
|
||||
""")
|
||||
|
||||
definitions = []
|
||||
definitions += names(source, environment=environment)
|
||||
definitions += names(source)
|
||||
|
||||
source += dedent("""
|
||||
variable = sys or C or x or f or g or g() or h""")
|
||||
@@ -102,8 +99,8 @@ def test_function_call_signature_in_doc(Script):
|
||||
assert "f(x, y=1, z='a')" in str(doc)
|
||||
|
||||
|
||||
def test_param_docstring():
|
||||
param = jedi.names("def test(parameter): pass", all_scopes=True)[1]
|
||||
def test_param_docstring(names):
|
||||
param = names("def test(parameter): pass", all_scopes=True)[1]
|
||||
assert param.name == 'parameter'
|
||||
assert param.docstring() == ''
|
||||
|
||||
@@ -140,28 +137,32 @@ def test_completion_docstring(Script, jedi_path):
|
||||
|
||||
docstr('abcd=3;abcd', '')
|
||||
docstr('"hello"\nabcd=3\nabcd', '')
|
||||
docstr(dedent('''
|
||||
docstr(
|
||||
dedent('''
|
||||
def x():
|
||||
"hello"
|
||||
0
|
||||
x'''),
|
||||
'hello'
|
||||
)
|
||||
docstr(dedent('''
|
||||
docstr(
|
||||
dedent('''
|
||||
def x():
|
||||
"hello";0
|
||||
x'''),
|
||||
'hello'
|
||||
)
|
||||
# Shouldn't work with a tuple.
|
||||
docstr(dedent('''
|
||||
docstr(
|
||||
dedent('''
|
||||
def x():
|
||||
"hello",0
|
||||
x'''),
|
||||
''
|
||||
)
|
||||
# Should also not work if we rename something.
|
||||
docstr(dedent('''
|
||||
docstr(
|
||||
dedent('''
|
||||
def x():
|
||||
"hello"
|
||||
y = x
|
||||
@@ -216,75 +217,61 @@ 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),
|
||||
references=True,
|
||||
all_scopes=True,
|
||||
environment=self.environment
|
||||
)[index]
|
||||
|
||||
def _bool_is_definitions(self, source):
|
||||
ns = names(dedent(source), 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]
|
||||
@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,
|
||||
)[index]
|
||||
assert d.name == name
|
||||
assert d.is_definition() == is_definition
|
||||
|
||||
|
||||
class TestParent(TestCase):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, Script):
|
||||
self.Script = Script
|
||||
@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))
|
||||
assert [name.is_definition() for name in ns] == expected
|
||||
|
||||
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')
|
||||
assert parent.type == 'module'
|
||||
parent = _parent('foo=1\nfoo')
|
||||
assert parent.type == 'module'
|
||||
|
||||
parent = self._parent('''
|
||||
def spam():
|
||||
if 1:
|
||||
y=1
|
||||
y''')
|
||||
assert parent.name == 'spam'
|
||||
assert parent.parent().type == 'module'
|
||||
parent = _parent('''
|
||||
def spam():
|
||||
if 1:
|
||||
y=1
|
||||
y''')
|
||||
assert parent.name == 'spam'
|
||||
assert parent.parent().type == 'module'
|
||||
|
||||
def test_on_function(self):
|
||||
parent = self._parent('''\
|
||||
def spam():
|
||||
pass''', 1, len('def spam'))
|
||||
assert parent.name == ''
|
||||
assert parent.type == 'module'
|
||||
|
||||
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'
|
||||
|
||||
|
||||
def test_parent_on_completion(Script):
|
||||
@@ -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'
|
||||
@@ -455,3 +442,20 @@ def test_builtin_module_with_path(Script):
|
||||
assert semlock.name == 'SemLock'
|
||||
assert semlock.line is None
|
||||
assert semlock.column is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'code, description', [
|
||||
('int', 'instance int'),
|
||||
('str.index', 'instance int'),
|
||||
('1', None),
|
||||
]
|
||||
)
|
||||
def test_execute(Script, code, description):
|
||||
definition, = Script(code).goto_assignments()
|
||||
definitions = definition.execute()
|
||||
if description is None:
|
||||
assert not definitions
|
||||
else:
|
||||
d, = definitions
|
||||
assert d.description == description
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import os
|
||||
from os.path import join, sep as s
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
from ..helpers import root_dir
|
||||
|
||||
|
||||
def test_in_whitespace(Script):
|
||||
@@ -69,8 +70,8 @@ def test_points_in_completion(Script):
|
||||
|
||||
def test_loading_unicode_files_with_bad_global_charset(Script, monkeypatch, tmpdir):
|
||||
dirname = str(tmpdir.mkdir('jedi-test'))
|
||||
filename1 = os.path.join(dirname, 'test1.py')
|
||||
filename2 = os.path.join(dirname, 'test2.py')
|
||||
filename1 = join(dirname, 'test1.py')
|
||||
filename2 = join(dirname, 'test2.py')
|
||||
if sys.version_info < (3, 0):
|
||||
data = "# coding: latin-1\nfoo = 'm\xf6p'\n"
|
||||
else:
|
||||
@@ -119,7 +120,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 +142,126 @@ 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())
|
||||
|
||||
|
||||
f1 = join(root_dir, 'example.py')
|
||||
f2 = join(root_dir, 'test', 'example.py')
|
||||
os_path = 'from os.path import *\n'
|
||||
# os.path.sep escaped
|
||||
se = s * 2 if s == '\\' else s
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'file, code, column, expected', [
|
||||
# General tests / relative paths
|
||||
(None, '"comp', None, ['ile', 'lex']), # No files like comp
|
||||
(None, '"test', None, [s]),
|
||||
(None, '"test', 4, ['t' + s]),
|
||||
('example.py', '"test%scomp' % s, None, ['letion' + s]),
|
||||
('example.py', 'r"comp"', None, "A LOT"),
|
||||
('example.py', 'r"tes"', None, "A LOT"),
|
||||
('example.py', 'r"tes"', 5, ['t' + s]),
|
||||
('example.py', 'r" tes"', 6, []),
|
||||
('test%sexample.py' % se, 'r"tes"', 5, ['t' + s]),
|
||||
('test%sexample.py' % se, 'r"test%scomp"' % s, 5, ['t' + s]),
|
||||
('test%sexample.py' % se, 'r"test%scomp"' % s, 11, ['letion' + s]),
|
||||
('test%sexample.py' % se, '"%s"' % join('test', 'completion', 'basi'), 21, ['c.py']),
|
||||
('example.py', 'rb"' + join('..', 'jedi', 'tes'), None, ['t' + s]),
|
||||
|
||||
# Absolute paths
|
||||
(None, '"' + join(root_dir, 'test', 'test_ca'), None, ['che.py"']),
|
||||
(None, '"%s"' % join(root_dir, 'test', 'test_ca'), len(root_dir) + 14, ['che.py']),
|
||||
|
||||
# Longer quotes
|
||||
('example.py', 'r"""test', None, [s]),
|
||||
('example.py', 'r"""\ntest', None, []),
|
||||
('example.py', 'u"""tes\n', (1, 7), ['t' + s]),
|
||||
('example.py', '"""test%stest_cache.p"""' % s, 20, ['y']),
|
||||
('example.py', '"""test%stest_cache.p"""' % s, 19, ['py"""']),
|
||||
|
||||
# Adding
|
||||
('example.py', '"test" + "%stest_cac' % se, None, ['he.py"']),
|
||||
('example.py', '"test" + "%s" + "test_cac' % se, None, ['he.py"']),
|
||||
('example.py', 'x = 1 + "test', None, []),
|
||||
('example.py', 'x = f("te" + "st)', 16, [s]),
|
||||
('example.py', 'x = f("te" + "st', 16, [s]),
|
||||
('example.py', 'x = f("te" + "st"', 16, [s]),
|
||||
('example.py', 'x = f("te" + "st")', 16, [s]),
|
||||
('example.py', 'x = f("t" + "est")', 16, [s]),
|
||||
# This is actually not correct, but for now leave it here, because of
|
||||
# Python 2.
|
||||
('example.py', 'x = f(b"t" + "est")', 17, [s]),
|
||||
('example.py', '"test" + "', None, [s]),
|
||||
|
||||
# __file__
|
||||
(f1, os_path + 'dirname(__file__) + "%stest' % s, None, [s]),
|
||||
(f2, os_path + 'dirname(__file__) + "%stest_ca' % se, None, ['che.py"']),
|
||||
(f2, os_path + 'dirname(abspath(__file__)) + sep + "test_ca', None, ['che.py"']),
|
||||
(f2, os_path + 'join(dirname(__file__), "completion") + sep + "basi', None, ['c.py"']),
|
||||
(f2, os_path + 'join("test", "completion") + sep + "basi', None, ['c.py"']),
|
||||
|
||||
# inside join
|
||||
(f2, os_path + 'join(dirname(__file__), "completion", "basi', None, ['c.py"']),
|
||||
(f2, os_path + 'join(dirname(__file__), "completion", "basi)', 43, ['c.py"']),
|
||||
(f2, os_path + 'join(dirname(__file__), "completion", "basi")', 43, ['c.py']),
|
||||
(f2, os_path + 'join(dirname(__file__), "completion", "basi)', 35, ['']),
|
||||
(f2, os_path + 'join(dirname(__file__), "completion", "basi)', 33, ['on"']),
|
||||
(f2, os_path + 'join(dirname(__file__), "completion", "basi")', 33, ['on"']),
|
||||
|
||||
# join with one argument. join will not get evaluated and the result is
|
||||
# that directories and in a slash. This is unfortunate, but doesn't
|
||||
# really matter.
|
||||
(f2, os_path + 'join("tes', 9, ['t"']),
|
||||
(f2, os_path + 'join(\'tes)', 9, ["t'"]),
|
||||
(f2, os_path + 'join(r"tes"', 10, ['t']),
|
||||
(f2, os_path + 'join("""tes""")', 11, ['t']),
|
||||
|
||||
# Almost like join but not really
|
||||
(f2, os_path + 'join["tes', 9, ['t' + s]),
|
||||
(f2, os_path + 'join["tes"', 9, ['t' + s]),
|
||||
(f2, os_path + 'join["tes"]', 9, ['t' + s]),
|
||||
(f2, os_path + 'join[dirname(__file__), "completi', 33, []),
|
||||
(f2, os_path + 'join[dirname(__file__), "completi"', 33, []),
|
||||
(f2, os_path + 'join[dirname(__file__), "completi"]', 33, []),
|
||||
|
||||
# With full paths
|
||||
(f2, 'import os\nos.path.join(os.path.dirname(__file__), "completi', 49, ['on"']),
|
||||
(f2, 'import os\nos.path.join(os.path.dirname(__file__), "completi"', 49, ['on']),
|
||||
(f2, 'import os\nos.path.join(os.path.dirname(__file__), "completi")', 49, ['on']),
|
||||
|
||||
# With alias
|
||||
(f2, 'import os.path as p as p\np.join(p.dirname(__file__), "completi', None, ['on"']),
|
||||
(f2, 'from os.path import dirname, join as j\nj(dirname(__file__), "completi',
|
||||
None, ['on"']),
|
||||
|
||||
# Trying to break it
|
||||
(f2, os_path + 'join(["tes', 10, ['t' + s]),
|
||||
(f2, os_path + 'join(["tes"]', 10, ['t' + s]),
|
||||
(f2, os_path + 'join(["tes"])', 10, ['t' + s]),
|
||||
(f2, os_path + 'join("test", "test_cac" + x,', 22, ['he.py']),
|
||||
]
|
||||
)
|
||||
def test_file_path_completions(Script, file, code, column, expected):
|
||||
line = None
|
||||
if isinstance(column, tuple):
|
||||
line, column = column
|
||||
comps = Script(code, path=file, line=line, column=column).completions()
|
||||
if expected == "A LOT":
|
||||
assert len(comps) > 100 # This is basically global completions.
|
||||
else:
|
||||
assert [c.complete for c in comps] == expected
|
||||
|
||||
@@ -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
|
||||
def _assert_definition_names(definitions, names_):
|
||||
assert [d.name for d in definitions] == names_
|
||||
|
||||
|
||||
class TestDefinedNames(TestCase):
|
||||
@pytest.fixture(autouse=True)
|
||||
def init(self, environment):
|
||||
self.environment = environment
|
||||
def _check_names(names, source, names_):
|
||||
definitions = names(dedent(source))
|
||||
_assert_definition_names(definitions, names_)
|
||||
return definitions
|
||||
|
||||
def assert_definition_names(self, 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_)
|
||||
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,130 +25,135 @@ class TestDefinedNames(TestCase):
|
||||
data = None
|
||||
""", ['module', 'Class', 'func', 'data'])
|
||||
|
||||
def test_dotted_assignment(self):
|
||||
self.check_defined_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_dotted_assignment(names):
|
||||
_check_names(names, """
|
||||
x = Class()
|
||||
x.y.z = None
|
||||
""", ['x', 'z']) # TODO is this behavior what we want?
|
||||
|
||||
def test_multiple_imports(self):
|
||||
self.check_defined_names("""
|
||||
from module import a, b
|
||||
from another_module import *
|
||||
""", ['a', 'b'])
|
||||
|
||||
def test_nested_definitions(self):
|
||||
definitions = self.check_defined_names("""
|
||||
class Class:
|
||||
def f():
|
||||
pass
|
||||
def g():
|
||||
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'])
|
||||
def test_multiple_assignment(names):
|
||||
_check_names(names, "x = y = None", ['x', 'y'])
|
||||
|
||||
def test_nested_class(self):
|
||||
definitions = self.check_defined_names("""
|
||||
class L1:
|
||||
class L2:
|
||||
class L3:
|
||||
def f(): pass
|
||||
|
||||
def test_multiple_imports(names):
|
||||
_check_names(names, """
|
||||
from module import a, b
|
||||
from another_module import *
|
||||
""", ['a', 'b'])
|
||||
|
||||
|
||||
def test_nested_definitions(names):
|
||||
definitions = _check_names(names, """
|
||||
class Class:
|
||||
def f():
|
||||
pass
|
||||
def g():
|
||||
pass
|
||||
""", ['Class'])
|
||||
subdefinitions = definitions[0].defined_names()
|
||||
_assert_definition_names(subdefinitions, ['f', 'g'])
|
||||
assert [d.full_name for d in subdefinitions] == ['__main__.Class.f', '__main__.Class.g']
|
||||
|
||||
|
||||
def test_nested_class(names):
|
||||
definitions = _check_names(names, """
|
||||
class L1:
|
||||
class L2:
|
||||
class L3:
|
||||
def f(): pass
|
||||
def f(): pass
|
||||
def f(): pass
|
||||
""", ['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'])
|
||||
def f(): pass
|
||||
""", ['L1', 'f'])
|
||||
subdefs = definitions[0].defined_names()
|
||||
subsubdefs = subdefs[0].defined_names()
|
||||
_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("""
|
||||
from module import f
|
||||
g = f(f)
|
||||
class C:
|
||||
h = g
|
||||
|
||||
def foo(x=a):
|
||||
bar = x
|
||||
return bar
|
||||
""", ['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'])
|
||||
def test_class_fields_with_all_scopes_false(names):
|
||||
definitions = _check_names(names, """
|
||||
from module import f
|
||||
g = f(f)
|
||||
class C:
|
||||
h = g
|
||||
|
||||
def test_async_stmt_with_all_scopes_false(self):
|
||||
definitions = self.check_defined_names("""
|
||||
from module import f
|
||||
import asyncio
|
||||
def foo(x=a):
|
||||
bar = x
|
||||
return bar
|
||||
""", ['f', 'g', 'C', 'foo'])
|
||||
C_subdefs = definitions[-2].defined_names()
|
||||
foo_subdefs = definitions[-1].defined_names()
|
||||
_assert_definition_names(C_subdefs, ['h'])
|
||||
_assert_definition_names(foo_subdefs, ['x', 'bar'])
|
||||
|
||||
g = f(f)
|
||||
class C:
|
||||
h = g
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
async def __aenter__(self):
|
||||
pass
|
||||
def test_async_stmt_with_all_scopes_false(names):
|
||||
definitions = _check_names(names, """
|
||||
from module import f
|
||||
import asyncio
|
||||
|
||||
def foo(x=a):
|
||||
bar = x
|
||||
return bar
|
||||
g = f(f)
|
||||
class C:
|
||||
h = g
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
async def async_foo(duration):
|
||||
async def wait():
|
||||
await asyncio.sleep(100)
|
||||
for i in range(duration//100):
|
||||
await wait()
|
||||
return duration//100*100
|
||||
async def __aenter__(self):
|
||||
pass
|
||||
|
||||
async with C() as cinst:
|
||||
d = cinst
|
||||
""", ['f', 'asyncio', 'g', 'C', 'foo', 'async_foo', 'cinst', 'd'])
|
||||
C_subdefs = definitions[3].defined_names()
|
||||
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'])
|
||||
# We treat d as a name outside `async with` block
|
||||
self.assert_definition_names(cinst_subdefs, [])
|
||||
def foo(x=a):
|
||||
bar = x
|
||||
return bar
|
||||
|
||||
def test_follow_imports(environment):
|
||||
async def async_foo(duration):
|
||||
async def wait():
|
||||
await asyncio.sleep(100)
|
||||
for i in range(duration//100):
|
||||
await wait()
|
||||
return duration//100*100
|
||||
|
||||
async with C() as cinst:
|
||||
d = cinst
|
||||
""", ['f', 'asyncio', 'g', 'C', 'foo', 'async_foo', 'cinst', 'd'])
|
||||
C_subdefs = definitions[3].defined_names()
|
||||
foo_subdefs = definitions[4].defined_names()
|
||||
async_foo_subdefs = definitions[5].defined_names()
|
||||
cinst_subdefs = definitions[6].defined_names()
|
||||
_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
|
||||
_assert_definition_names(cinst_subdefs, [])
|
||||
|
||||
|
||||
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:
|
||||
|
||||
@@ -7,8 +7,8 @@ import pytest
|
||||
|
||||
import jedi
|
||||
from jedi._compatibility import is_py3, py_version
|
||||
from jedi.evaluate.compiled import mixed
|
||||
|
||||
from jedi.evaluate.compiled import mixed, context
|
||||
from importlib import import_module
|
||||
|
||||
if py_version > 30:
|
||||
def exec_(source, global_map):
|
||||
@@ -197,7 +197,13 @@ def test_getitem_side_effects():
|
||||
_assert_interpreter_complete('foo["asdf"].upper', locals(), ['upper'])
|
||||
|
||||
|
||||
def test_property_error_oldstyle():
|
||||
@pytest.fixture(params=[False, True])
|
||||
def allow_descriptor_access_or_not(request, monkeypatch):
|
||||
monkeypatch.setattr(jedi.Interpreter, '_allow_descriptor_getattr_default', request.param)
|
||||
return request.param
|
||||
|
||||
|
||||
def test_property_error_oldstyle(allow_descriptor_access_or_not):
|
||||
lst = []
|
||||
class Foo3:
|
||||
@property
|
||||
@@ -209,11 +215,14 @@ def test_property_error_oldstyle():
|
||||
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
|
||||
_assert_interpreter_complete('foo.bar.baz', locals(), [])
|
||||
|
||||
# There should not be side effects
|
||||
assert lst == []
|
||||
if allow_descriptor_access_or_not:
|
||||
assert lst == [1, 1]
|
||||
else:
|
||||
# There should not be side effects
|
||||
assert lst == []
|
||||
|
||||
|
||||
def test_property_error_newstyle():
|
||||
def test_property_error_newstyle(allow_descriptor_access_or_not):
|
||||
lst = []
|
||||
class Foo3(object):
|
||||
@property
|
||||
@@ -225,10 +234,25 @@ def test_property_error_newstyle():
|
||||
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
|
||||
_assert_interpreter_complete('foo.bar.baz', locals(), [])
|
||||
|
||||
# There should not be side effects
|
||||
assert lst == []
|
||||
if allow_descriptor_access_or_not:
|
||||
assert lst == [1, 1]
|
||||
else:
|
||||
# There should not be side effects
|
||||
assert lst == []
|
||||
|
||||
|
||||
def test_property_content():
|
||||
class Foo3(object):
|
||||
@property
|
||||
def bar(self):
|
||||
return 1
|
||||
|
||||
foo = Foo3()
|
||||
def_, = jedi.Interpreter('foo.bar', [locals()]).goto_definitions()
|
||||
assert def_.name == 'int'
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
|
||||
def test_param_completion():
|
||||
def foo(bar):
|
||||
pass
|
||||
@@ -262,7 +286,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,7 +295,11 @@ 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'
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
|
||||
def test_keyword_argument():
|
||||
def f(some_keyword_argument):
|
||||
pass
|
||||
@@ -423,3 +451,57 @@ 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
|
||||
|
||||
|
||||
@pytest.mark.parametrize('module_name', ['sys', 'time'])
|
||||
def test_core_module_completes(module_name):
|
||||
module = import_module(module_name)
|
||||
assert jedi.Interpreter(module_name + '.\n', [locals()]).completions()
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
|
||||
@pytest.mark.parametrize(
|
||||
'code, expected, index', [
|
||||
('a(', ['a', 'b', 'c'], 0),
|
||||
('b(', ['b', 'c'], 0),
|
||||
# Might or might not be correct, because c is given as a keyword
|
||||
# argument as well, but that is just what inspect.signature returns.
|
||||
('c(', ['b', 'c'], 0),
|
||||
]
|
||||
)
|
||||
def test_partial_signatures(code, expected, index):
|
||||
import functools
|
||||
|
||||
def func(a, b, c):
|
||||
pass
|
||||
|
||||
a = functools.partial(func)
|
||||
b = functools.partial(func, 1)
|
||||
c = functools.partial(func, 1, c=2)
|
||||
|
||||
sig, = jedi.Interpreter(code, [locals()]).call_signatures()
|
||||
assert sig.name == 'partial'
|
||||
assert [p.name for p in sig.params] == expected
|
||||
assert index == sig.index
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
|
||||
def test_type_var():
|
||||
"""This was an issue before, see Github #1369"""
|
||||
import typing
|
||||
x = typing.TypeVar('myvar')
|
||||
def_, = jedi.Interpreter('x', [locals()]).goto_definitions()
|
||||
assert def_.name == 'TypeVar'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
|
||||
from ..helpers import get_example_dir
|
||||
from ..helpers import get_example_dir, set_cwd, root_dir
|
||||
from jedi import Interpreter
|
||||
|
||||
|
||||
def test_django_default_project(Script):
|
||||
@@ -13,3 +14,11 @@ def test_django_default_project(Script):
|
||||
c, = script.completions()
|
||||
assert c.name == "SomeModel"
|
||||
assert script._evaluator.project._django is True
|
||||
|
||||
|
||||
def test_interpreter_project_path():
|
||||
# Run from anywhere it should be the cwd.
|
||||
dir = os.path.join(root_dir, 'test')
|
||||
with set_cwd(dir):
|
||||
project = Interpreter('', [locals()])._evaluator.project
|
||||
assert project._path == dir
|
||||
|
||||
69
test/test_api/test_signatures.py
Normal file
69
test/test_api/test_signatures.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
_tuple_code = 'from typing import Tuple\ndef f(x: Tuple[int]): ...\nf'
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'code, expected_params, execute_annotation', [
|
||||
('def f(x: 1, y): ...\nf', [None, None], True),
|
||||
('def f(x: 1, y): ...\nf', ['instance int', None], False),
|
||||
('def f(x: int): ...\nf', ['instance int'], True),
|
||||
('from typing import List\ndef f(x: List[int]): ...\nf', ['instance list'], True),
|
||||
('from typing import List\ndef f(x: List[int]): ...\nf', ['class list'], False),
|
||||
(_tuple_code, ['Tuple: _SpecialForm = ...'], True),
|
||||
(_tuple_code, ['Tuple: _SpecialForm = ...'], False),
|
||||
('x=str\ndef f(p: x): ...\nx=int\nf', ['instance int'], True),
|
||||
|
||||
('def f(*args, **kwargs): ...\nf', [None, None], False),
|
||||
('def f(*args: int, **kwargs: str): ...\nf', ['class int', 'class str'], False),
|
||||
]
|
||||
)
|
||||
def test_param_annotation(Script, code, expected_params, execute_annotation, skip_python2):
|
||||
func, = Script(code).goto_assignments()
|
||||
sig, = func.get_signatures()
|
||||
for p, expected in zip(sig.params, expected_params):
|
||||
annotations = p.infer_annotation(execute_annotation=execute_annotation)
|
||||
if expected is None:
|
||||
assert not annotations
|
||||
else:
|
||||
annotation, = annotations
|
||||
assert annotation.description == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'code, expected_params', [
|
||||
('def f(x=1, y=int, z): pass\nf', ['instance int', 'class int', None]),
|
||||
('def f(*args, **kwargs): pass\nf', [None, None]),
|
||||
('x=1\ndef f(p=x): pass\nx=""\nf', ['instance int']),
|
||||
]
|
||||
)
|
||||
def test_param_default(Script, code, expected_params):
|
||||
func, = Script(code).goto_assignments()
|
||||
sig, = func.get_signatures()
|
||||
for p, expected in zip(sig.params, expected_params):
|
||||
annotations = p.infer_default()
|
||||
if expected is None:
|
||||
assert not annotations
|
||||
else:
|
||||
annotation, = annotations
|
||||
assert annotation.description == expected
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 5), reason="Python <3.5 doesn't support __signature__")
|
||||
@pytest.mark.parametrize(
|
||||
'code, index, param_code, kind', [
|
||||
('def f(x=1): pass\nf', 0, 'x=1', 'POSITIONAL_OR_KEYWORD'),
|
||||
('def f(*args:int): pass\nf', 0, '*args: int', 'VAR_POSITIONAL'),
|
||||
('def f(**kwargs: List[x]): pass\nf', 0, '**kwargs: List[x]', 'VAR_KEYWORD'),
|
||||
('def f(*, x:int=5): pass\nf', 0, 'x: int=5', 'KEYWORD_ONLY'),
|
||||
('def f(*args, x): pass\nf', 1, 'x', 'KEYWORD_ONLY'),
|
||||
]
|
||||
)
|
||||
def test_param_kind_and_name(code, index, param_code, kind, Script, skip_python2):
|
||||
func, = Script(code).goto_assignments()
|
||||
sig, = func.get_signatures()
|
||||
param = sig.params[index]
|
||||
assert param.to_string() == param_code
|
||||
assert param.kind.name == kind
|
||||
@@ -1,3 +1,6 @@
|
||||
from jedi._compatibility import force_unicode
|
||||
|
||||
|
||||
def test_module_attributes(Script):
|
||||
def_, = Script('__name__').completions()
|
||||
assert def_.name == '__name__'
|
||||
@@ -5,3 +8,14 @@ def test_module_attributes(Script):
|
||||
assert def_.column is None
|
||||
str_, = def_.infer()
|
||||
assert str_.name == 'str'
|
||||
|
||||
|
||||
def test_module__file__(Script, environment):
|
||||
assert not Script('__file__').goto_definitions()
|
||||
def_, = Script('__file__', path='example.py').goto_definitions()
|
||||
value = force_unicode(def_._name._context.get_safe_value())
|
||||
assert value.endswith('example.py')
|
||||
|
||||
def_, = Script('import antigravity; antigravity.__file__').goto_definitions()
|
||||
value = force_unicode(def_._name._context.get_safe_value())
|
||||
assert value.endswith('.py')
|
||||
|
||||
@@ -6,6 +6,7 @@ from textwrap import dedent
|
||||
import jedi
|
||||
import pytest
|
||||
from ..helpers import unittest
|
||||
import sys
|
||||
|
||||
try:
|
||||
import numpydoc # NOQA
|
||||
@@ -21,6 +22,11 @@ except ImportError:
|
||||
else:
|
||||
numpy_unavailable = False
|
||||
|
||||
if sys.version_info.major == 2:
|
||||
# In Python 2 there's an issue with tox/docutils that makes the tests fail,
|
||||
# Python 2 is soon end-of-life, so just don't support numpydoc for it anymore.
|
||||
numpydoc_unavailable = True
|
||||
|
||||
|
||||
def test_function_doc(Script):
|
||||
defs = Script("""
|
||||
@@ -385,3 +391,26 @@ def test_numpy_comp_returns():
|
||||
)
|
||||
names = [c.name for c in jedi.Script(s).completions()]
|
||||
assert 'diagonal' in names
|
||||
|
||||
|
||||
def test_decorator(Script):
|
||||
code = dedent('''
|
||||
def decorator(name=None):
|
||||
def _decorate(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
"""wrapper docstring"""
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
return _decorate
|
||||
|
||||
|
||||
@decorator('testing')
|
||||
def check_user(f):
|
||||
"""Nice docstring"""
|
||||
pass
|
||||
|
||||
check_user''')
|
||||
|
||||
d, = Script(code).goto_definitions()
|
||||
assert d.docstring(raw=True) == 'Nice docstring'
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import pytest
|
||||
from textwrap import dedent
|
||||
from operator import ge, lt
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from jedi.evaluate.gradual.conversion import _stub_to_python_context_set
|
||||
|
||||
@@ -32,3 +35,234 @@ 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
|
||||
|
||||
|
||||
classmethod_code = '''
|
||||
class X:
|
||||
@classmethod
|
||||
def x(cls, a, b):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def static(a, b):
|
||||
pass
|
||||
'''
|
||||
|
||||
|
||||
partial_code = '''
|
||||
import functools
|
||||
|
||||
def func(a, b, c):
|
||||
pass
|
||||
|
||||
a = functools.partial(func)
|
||||
b = functools.partial(func, 1)
|
||||
c = functools.partial(func, 1, c=2)
|
||||
d = functools.partial()
|
||||
'''
|
||||
|
||||
|
||||
@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)'),
|
||||
|
||||
(classmethod_code + 'X.x(', 'x(cls, a, b)'),
|
||||
(classmethod_code + 'X().x(', 'x(cls, a, b)'),
|
||||
(classmethod_code + 'X.static(', 'static(a, b)'),
|
||||
(classmethod_code + 'X().static(', 'static(a, b)'),
|
||||
|
||||
(partial_code + 'a(', 'func(a, b, c)'),
|
||||
(partial_code + 'b(', 'func(b, c)'),
|
||||
(partial_code + 'c(', 'func(b)'),
|
||||
(partial_code + 'd(', None),
|
||||
]
|
||||
)
|
||||
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()
|
||||
|
||||
if expected is None:
|
||||
assert not Script(code).call_signatures()
|
||||
else:
|
||||
sig, = Script(code).call_signatures()
|
||||
assert expected == sig.to_string()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'combination, expected', [
|
||||
# Functions
|
||||
('full_redirect(simple)', 'b, *, c'),
|
||||
('full_redirect(simple4)', 'b, x: int'),
|
||||
('full_redirect(a)', 'b, *args'),
|
||||
('full_redirect(kw)', 'b, *, c, **kwargs'),
|
||||
('full_redirect(akw)', 'c, *args, **kwargs'),
|
||||
|
||||
# Non functions
|
||||
('full_redirect(lambda x, y: ...)', 'y'),
|
||||
('full_redirect()', '*args, **kwargs'),
|
||||
('full_redirect(1)', '*args, **kwargs'),
|
||||
|
||||
# Classes / inheritance
|
||||
('full_redirect(C)', 'z, *, c'),
|
||||
('full_redirect(C())', 'y'),
|
||||
('D', 'D(a, z, /)'),
|
||||
('D()', 'D(x, y)'),
|
||||
('D().foo', 'foo(a, *, bar, z, **kwargs)'),
|
||||
|
||||
# Merging
|
||||
('two_redirects(simple, simple)', 'a, b, *, c'),
|
||||
('two_redirects(simple2, simple2)', 'x'),
|
||||
('two_redirects(akw, kw)', 'a, c, *args, **kwargs'),
|
||||
('two_redirects(kw, akw)', 'a, b, *args, c, **kwargs'),
|
||||
|
||||
('combined_redirect(simple, simple2)', 'a, b, /, *, x'),
|
||||
('combined_redirect(simple, simple3)', 'a, b, /, *, a, x: int'),
|
||||
('combined_redirect(simple2, simple)', 'x, /, *, a, b, c'),
|
||||
('combined_redirect(simple3, simple)', 'a, x: int, /, *, a, b, c'),
|
||||
|
||||
('combined_redirect(simple, kw)', 'a, b, /, *, a, b, c, **kwargs'),
|
||||
('combined_redirect(kw, simple)', 'a, b, /, *, a, b, c'),
|
||||
|
||||
('combined_lot_of_args(kw, simple4)', '*, b'),
|
||||
('combined_lot_of_args(simple4, kw)', '*, b, c, **kwargs'),
|
||||
|
||||
('combined_redirect(combined_redirect(simple2, simple4), combined_redirect(kw, simple5))',
|
||||
'x, /, *, y'),
|
||||
('combined_redirect(combined_redirect(simple4, simple2), combined_redirect(simple5, kw))',
|
||||
'a, b, x: int, /, *, a, b, c, **kwargs'),
|
||||
('combined_redirect(combined_redirect(a, kw), combined_redirect(kw, simple5))',
|
||||
'a, b, /, *args, y'),
|
||||
|
||||
('no_redirect(kw)', '*args, **kwargs'),
|
||||
('no_redirect(akw)', '*args, **kwargs'),
|
||||
('no_redirect(simple)', '*args, **kwargs'),
|
||||
]
|
||||
)
|
||||
def test_nested_signatures(Script, environment, combination, expected, skip_pre_python35):
|
||||
code = dedent('''
|
||||
def simple(a, b, *, c): ...
|
||||
def simple2(x): ...
|
||||
def simple3(a, x: int): ...
|
||||
def simple4(a, b, x: int): ...
|
||||
def simple5(y): ...
|
||||
def a(a, b, *args): ...
|
||||
def kw(a, b, *, c, **kwargs): ...
|
||||
def akw(a, c, *args, **kwargs): ...
|
||||
|
||||
def no_redirect(func):
|
||||
return lambda *args, **kwargs: func(1)
|
||||
def full_redirect(func):
|
||||
return lambda *args, **kwargs: func(1, *args, **kwargs)
|
||||
def two_redirects(func1, func2):
|
||||
return lambda *args, **kwargs: func1(*args, **kwargs) + func2(1, *args, **kwargs)
|
||||
def combined_redirect(func1, func2):
|
||||
return lambda *args, **kwargs: func1(*args) + func2(**kwargs)
|
||||
def combined_lot_of_args(func1, func2):
|
||||
return lambda *args, **kwargs: func1(1, 2, 3, 4, *args) + func2(a=3, x=1, y=1, **kwargs)
|
||||
|
||||
class C:
|
||||
def __init__(self, a, z, *, c): ...
|
||||
def __call__(self, x, y): ...
|
||||
|
||||
def foo(self, bar, z, **kwargs): ...
|
||||
|
||||
class D(C):
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
||||
def foo(self, a, **kwargs):
|
||||
super().foo(**kwargs)
|
||||
''')
|
||||
code += 'z = ' + combination + '\nz('
|
||||
sig, = Script(code).call_signatures()
|
||||
computed = sig.to_string()
|
||||
if not re.match(r'\w+\(', expected):
|
||||
expected = '<lambda>(' + expected + ')'
|
||||
assert expected == computed
|
||||
|
||||
|
||||
def test_pow_signature(Script):
|
||||
# See github #1357
|
||||
sigs = Script('pow(').call_signatures()
|
||||
strings = {sig.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'}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'code, signature', [
|
||||
[dedent('''
|
||||
import functools
|
||||
def f(x):
|
||||
pass
|
||||
def x(f):
|
||||
@functools.wraps(f)
|
||||
def wrapper(*args):
|
||||
# Have no arguments here, but because of wraps, the signature
|
||||
# should still be f's.
|
||||
return f(*args)
|
||||
return wrapper
|
||||
|
||||
x(f)('''), 'f(x, /)'],
|
||||
[dedent('''
|
||||
import functools
|
||||
def f(x):
|
||||
pass
|
||||
def x(f):
|
||||
@functools.wraps(f)
|
||||
def wrapper():
|
||||
# Have no arguments here, but because of wraps, the signature
|
||||
# should still be f's.
|
||||
return 1
|
||||
return wrapper
|
||||
|
||||
x(f)('''), 'f()'],
|
||||
]
|
||||
)
|
||||
def test_wraps_signature(Script, code, signature, skip_pre_python35):
|
||||
sigs = Script(code).call_signatures()
|
||||
assert {sig.to_string() for sig in sigs} == {signature}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'start, start_params', [
|
||||
['@dataclass\nclass X:', []],
|
||||
['@dataclass(eq=True)\nclass X:', []],
|
||||
[dedent('''
|
||||
class Y():
|
||||
y: int
|
||||
@dataclass
|
||||
class X(Y):'''), []],
|
||||
[dedent('''
|
||||
@dataclass
|
||||
class Y():
|
||||
y: int
|
||||
z = 5
|
||||
@dataclass
|
||||
class X(Y):'''), ['y']],
|
||||
]
|
||||
)
|
||||
def test_dataclass_signature(Script, skip_pre_python37, start, start_params):
|
||||
code = dedent('''
|
||||
name: str
|
||||
foo = 3
|
||||
price: float
|
||||
quantity: int = 0.0
|
||||
|
||||
X(''')
|
||||
|
||||
code = 'from dataclasses import dataclass\n' + start + code
|
||||
|
||||
sig, = Script(code).call_signatures()
|
||||
assert [p.name for p in sig.params] == start_params + ['name', 'price', 'quantity']
|
||||
quantity, = sig.params[-1].infer()
|
||||
assert quantity.name == 'int'
|
||||
price, = sig.params[-2].infer()
|
||||
assert price.name == 'float'
|
||||
|
||||
Reference in New Issue
Block a user