forked from VimPlug/jedi
Compare commits
125 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
005f69390c | ||
|
|
ecca190462 | ||
|
|
5d0d09bb7d | ||
|
|
972cae4859 | ||
|
|
77bc2d548a | ||
|
|
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 |
@@ -3,8 +3,6 @@ omit =
|
|||||||
jedi/_compatibility.py
|
jedi/_compatibility.py
|
||||||
jedi/evaluate/compiled/subprocess/__main__.py
|
jedi/evaluate/compiled/subprocess/__main__.py
|
||||||
jedi/__main__.py
|
jedi/__main__.py
|
||||||
# Is statically analyzed and not actually executed.
|
|
||||||
jedi/evaluate/jedi_typing.py
|
|
||||||
# For now this is not being used.
|
# For now this is not being used.
|
||||||
jedi/refactoring.py
|
jedi/refactoring.py
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,6 @@ env:
|
|||||||
- JEDI_TEST_ENVIRONMENT=37
|
- JEDI_TEST_ENVIRONMENT=37
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
|
||||||
- python: pypy
|
|
||||||
- python: 3.8-dev
|
|
||||||
include:
|
include:
|
||||||
- python: 3.6
|
- python: 3.6
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -52,5 +52,6 @@ Tobias Rzepka (@TobiasRzepka)
|
|||||||
micbou (@micbou)
|
micbou (@micbou)
|
||||||
Dima Gerasimov (@karlicoss) <karlicoss@gmail.com>
|
Dima Gerasimov (@karlicoss) <karlicoss@gmail.com>
|
||||||
Max Woerner Chase (@mwchase) <max.chase@gmail.com>
|
Max Woerner Chase (@mwchase) <max.chase@gmail.com>
|
||||||
|
Johannes Maria Frank (@jmfrank63) <jmfrank63@gmail.com>
|
||||||
|
|
||||||
Note: (@user) means a github user name.
|
Note: (@user) means a github user name.
|
||||||
|
|||||||
@@ -3,6 +3,34 @@
|
|||||||
Changelog
|
Changelog
|
||||||
---------
|
---------
|
||||||
|
|
||||||
|
0.15.1 (2019-08-13)
|
||||||
|
+++++++++++++++++++
|
||||||
|
|
||||||
|
- Small bugfix and removal of a print statement
|
||||||
|
|
||||||
|
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)
|
0.14.1 (2019-07-13)
|
||||||
+++++++++++++++++++
|
+++++++++++++++++++
|
||||||
|
|
||||||
|
|||||||
16
conftest.py
16
conftest.py
@@ -140,3 +140,19 @@ def skip_pre_python38(environment):
|
|||||||
# This if is just needed to avoid that tests ever skip way more than
|
# This if is just needed to avoid that tests ever skip way more than
|
||||||
# they should for all Python versions.
|
# they should for all Python versions.
|
||||||
pytest.skip()
|
pytest.skip()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def skip_pre_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),
|
(for Python < 3.5 you have to do ``pip install typing`` to use these),
|
||||||
and forward references.
|
and forward references.
|
||||||
|
|
||||||
Things that are missing (and this is not an exhaustive list; some of these
|
You can also use stub files.
|
||||||
are planned, others might be hard to implement and provide little worth):
|
|
||||||
|
|
||||||
- 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**
|
**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.
|
good text editor, while still having very good IDE features for Python.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '0.14.1'
|
__version__ = '0.15.1'
|
||||||
|
|
||||||
from jedi.api import Script, Interpreter, set_debug_function, \
|
from jedi.api import Script, Interpreter, set_debug_function, \
|
||||||
preload_module, names
|
preload_module, names
|
||||||
@@ -42,3 +42,6 @@ from jedi.api.environment import find_virtualenvs, find_system_environments, \
|
|||||||
get_default_environment, InvalidPythonEnvironment, create_environment, \
|
get_default_environment, InvalidPythonEnvironment, create_environment, \
|
||||||
get_system_environment
|
get_system_environment
|
||||||
from jedi.api.exceptions import InternalError
|
from jedi.api.exceptions import InternalError
|
||||||
|
# Finally load the internal plugins. This is only internal.
|
||||||
|
from jedi.plugins import registry
|
||||||
|
del registry
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from jedi.api import interpreter
|
|||||||
from jedi.api import helpers
|
from jedi.api import helpers
|
||||||
from jedi.api.completion import Completion
|
from jedi.api.completion import Completion
|
||||||
from jedi.api.environment import InterpreterEnvironment
|
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 Evaluator
|
||||||
from jedi.evaluate import imports
|
from jedi.evaluate import imports
|
||||||
from jedi.evaluate import usages
|
from jedi.evaluate import usages
|
||||||
@@ -144,7 +144,6 @@ class Script(object):
|
|||||||
'(0-%d) for line %d (%r).' % (
|
'(0-%d) for line %d (%r).' % (
|
||||||
column, line_len, line, line_string))
|
column, line_len, line, line_string))
|
||||||
self._pos = line, column
|
self._pos = line, column
|
||||||
self._path = path
|
|
||||||
|
|
||||||
cache.clear_time_caches()
|
cache.clear_time_caches()
|
||||||
debug.reset_time()
|
debug.reset_time()
|
||||||
@@ -437,6 +436,7 @@ class Interpreter(Script):
|
|||||||
>>> print(script.completions()[0].name)
|
>>> print(script.completions()[0].name)
|
||||||
upper
|
upper
|
||||||
"""
|
"""
|
||||||
|
_allow_descriptor_getattr_default = True
|
||||||
|
|
||||||
def __init__(self, source, namespaces, **kwds):
|
def __init__(self, source, namespaces, **kwds):
|
||||||
"""
|
"""
|
||||||
@@ -464,8 +464,10 @@ class Interpreter(Script):
|
|||||||
if not isinstance(environment, InterpreterEnvironment):
|
if not isinstance(environment, InterpreterEnvironment):
|
||||||
raise TypeError("The environment needs to be an InterpreterEnvironment subclass.")
|
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.namespaces = namespaces
|
||||||
|
self._evaluator.allow_descriptor_getattr = self._allow_descriptor_getattr_default
|
||||||
|
|
||||||
def _get_module(self):
|
def _get_module(self):
|
||||||
return interpreter.MixedModuleContext(
|
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.
|
the interesting information about completion and goto operations.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from parso.python.tree import search_ancestor
|
|
||||||
|
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi.evaluate.utils import unite
|
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)]
|
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):
|
class BaseDefinition(object):
|
||||||
_mapping = {
|
_mapping = {
|
||||||
'posixpath': 'os.path',
|
'posixpath': 'os.path',
|
||||||
@@ -150,6 +153,9 @@ class BaseDefinition(object):
|
|||||||
>>> defs[3]
|
>>> defs[3]
|
||||||
'function'
|
'function'
|
||||||
|
|
||||||
|
Valid values for are ``module``, ``class``, ``instance``, ``function``,
|
||||||
|
``param``, ``path`` and ``keyword``.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
tree_name = self._name.tree_name
|
tree_name = self._name.tree_name
|
||||||
resolve = False
|
resolve = False
|
||||||
@@ -330,6 +336,8 @@ class BaseDefinition(object):
|
|||||||
@memoize_method
|
@memoize_method
|
||||||
def params(self):
|
def params(self):
|
||||||
"""
|
"""
|
||||||
|
Deprecated! Will raise a warning soon. Use get_signatures()[...].params.
|
||||||
|
|
||||||
Raises an ``AttributeError`` if the definition is not callable.
|
Raises an ``AttributeError`` if the definition is not callable.
|
||||||
Otherwise returns a list of `Definition` that represents the params.
|
Otherwise returns a list of `Definition` that represents the params.
|
||||||
"""
|
"""
|
||||||
@@ -337,7 +345,10 @@ class BaseDefinition(object):
|
|||||||
# with overloading.
|
# with overloading.
|
||||||
for context in self._name.infer():
|
for context in self._name.infer():
|
||||||
for signature in context.get_signatures():
|
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':
|
if self.type == 'function' or self.type == 'class':
|
||||||
# Fallback, if no signatures were defined (which is probably by
|
# Fallback, if no signatures were defined (which is probably by
|
||||||
@@ -384,6 +395,12 @@ class BaseDefinition(object):
|
|||||||
start_index = max(index - before, 0)
|
start_index = max(index - before, 0)
|
||||||
return ''.join(lines[start_index:index + after + 1])
|
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):
|
class Completion(BaseDefinition):
|
||||||
"""
|
"""
|
||||||
@@ -598,14 +615,37 @@ class Definition(BaseDefinition):
|
|||||||
return hash((self._name.start_pos, self.module_path, self.name, self._evaluator))
|
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
|
It knows what functions you are currently in. e.g. `isinstance(` would
|
||||||
return the `isinstance` function. without `(` it would return nothing.
|
return the `isinstance` function. without `(` it would return nothing.
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, evaluator, signature):
|
||||||
|
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):
|
def __init__(self, evaluator, signature, call_details):
|
||||||
super(CallSignature, self).__init__(evaluator, signature.name)
|
super(CallSignature, self).__init__(evaluator, signature)
|
||||||
self._call_details = call_details
|
self._call_details = call_details
|
||||||
self._signature = signature
|
self._signature = signature
|
||||||
|
|
||||||
@@ -615,34 +655,60 @@ class CallSignature(Definition):
|
|||||||
The Param index of the current call.
|
The Param index of the current call.
|
||||||
Returns None if the index cannot be found in the curent call.
|
Returns None if the index cannot be found in the curent call.
|
||||||
"""
|
"""
|
||||||
return self._call_details.calculate_index(self._signature.get_param_names())
|
return self._call_details.calculate_index(
|
||||||
|
self._signature.get_param_names(resolve_stars=True)
|
||||||
@property
|
)
|
||||||
def params(self):
|
|
||||||
return [Definition(self._evaluator, n) for n in self._signature.get_param_names()]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bracket_start(self):
|
def bracket_start(self):
|
||||||
"""
|
"""
|
||||||
The indent of the bracket that is responsible for the last function
|
The line/column of the bracket that is responsible for the last
|
||||||
call.
|
function call.
|
||||||
"""
|
"""
|
||||||
return self._call_details.bracket_leaf.start_pos
|
return self._call_details.bracket_leaf.start_pos
|
||||||
|
|
||||||
@property
|
|
||||||
def _params_str(self):
|
|
||||||
return ', '.join([p.description[6:]
|
|
||||||
for p in self.params])
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s: %s index=%r params=[%s]>' % (
|
return '<%s: index=%r %s>' % (
|
||||||
type(self).__name__,
|
type(self).__name__,
|
||||||
self._name.string_name,
|
|
||||||
self.index,
|
self.index,
|
||||||
self._params_str,
|
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):
|
def _format_signatures(context):
|
||||||
return '\n'.join(
|
return '\n'.join(
|
||||||
signature.to_string()
|
signature.to_string()
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
from parso.python.token import PythonTokenTypes
|
from parso.python.token import PythonTokenTypes
|
||||||
from parso.python import tree
|
from parso.python import tree
|
||||||
from parso.tree import search_ancestor, Leaf
|
from parso.tree import search_ancestor, Leaf
|
||||||
@@ -7,12 +9,13 @@ from jedi import debug
|
|||||||
from jedi import settings
|
from jedi import settings
|
||||||
from jedi.api import classes
|
from jedi.api import classes
|
||||||
from jedi.api import helpers
|
from jedi.api import helpers
|
||||||
from jedi.evaluate import imports
|
|
||||||
from jedi.api import keywords
|
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.helpers import evaluate_call_of_leaf, parse_dotted_names
|
||||||
from jedi.evaluate.filters import get_global_filters
|
from jedi.evaluate.filters import get_global_filters
|
||||||
from jedi.evaluate.gradual.conversion import convert_contexts
|
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):
|
def get_call_signature_param_names(call_signatures):
|
||||||
@@ -82,7 +85,7 @@ def get_flow_scope_node(module_node, position):
|
|||||||
|
|
||||||
|
|
||||||
class Completion:
|
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._evaluator = evaluator
|
||||||
self._module_context = module
|
self._module_context = module
|
||||||
self._module_node = module.tree_node
|
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)
|
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
|
# The actual cursor position is not what we need to calculate
|
||||||
# everything. We want the start of the name we're on.
|
# 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._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):
|
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,
|
completions = filter_names(self._evaluator, completion_names,
|
||||||
self.stack, self._like_name)
|
self.stack, self._like_name)
|
||||||
@@ -105,7 +120,7 @@ class Completion:
|
|||||||
x.name.startswith('_'),
|
x.name.startswith('_'),
|
||||||
x.name.lower()))
|
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
|
Analyzes the context that a completion is made in and decides what to
|
||||||
return.
|
return.
|
||||||
@@ -121,19 +136,20 @@ class Completion:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
grammar = self._evaluator.grammar
|
grammar = self._evaluator.grammar
|
||||||
|
self.stack = stack = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.stack = stack = helpers.get_stack_at_position(
|
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:
|
except helpers.OnErrorLeaf as e:
|
||||||
self.stack = stack = None
|
value = e.error_leaf.value
|
||||||
if e.error_leaf.value == '.':
|
if value == '.':
|
||||||
# After ErrorLeaf's that are dots, we will not do any
|
# After ErrorLeaf's that are dots, we will not do any
|
||||||
# completions since this probably just confuses the user.
|
# completions since this probably just confuses the user.
|
||||||
return []
|
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()
|
return self._global_completions()
|
||||||
|
|
||||||
allowed_transitions = \
|
allowed_transitions = \
|
||||||
@@ -210,7 +226,7 @@ class Completion:
|
|||||||
completion_names += self._get_class_context_completions(is_function=False)
|
completion_names += self._get_class_context_completions(is_function=False)
|
||||||
|
|
||||||
if 'trailer' in nonterminals:
|
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)
|
completion_names += get_call_signature_param_names(call_signatures)
|
||||||
|
|
||||||
return completion_names
|
return completion_names
|
||||||
@@ -289,3 +305,22 @@ class Completion:
|
|||||||
# TODO we should probably check here for properties
|
# TODO we should probably check here for properties
|
||||||
if (name.api_type == 'function') == is_function:
|
if (name.api_type == 'function') == is_function:
|
||||||
yield name
|
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
|
||||||
|
|||||||
161
jedi/api/file_name.py
Normal file
161
jedi/api/file_name.py
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
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.
|
||||||
|
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], [])
|
||||||
@@ -54,8 +54,7 @@ class OnErrorLeaf(Exception):
|
|||||||
return self.args[0]
|
return self.args[0]
|
||||||
|
|
||||||
|
|
||||||
def _get_code_for_stack(code_lines, module_node, position):
|
def _get_code_for_stack(code_lines, leaf, position):
|
||||||
leaf = module_node.get_leaf_for_position(position, include_prefixes=True)
|
|
||||||
# It might happen that we're on whitespace or on a comment. This means
|
# It might happen that we're on whitespace or on a comment. This means
|
||||||
# that we would not get the right leaf.
|
# that we would not get the right leaf.
|
||||||
if leaf.start_pos >= position:
|
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)
|
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).
|
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
|
yield token
|
||||||
|
|
||||||
# The code might be indedented, just remove it.
|
# 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
|
# We use a word to tell Jedi when we have reached the start of the
|
||||||
# completion.
|
# completion.
|
||||||
# Use Z as a prefix because it's not part of a number suffix.
|
# Use Z as a prefix because it's not part of a number suffix.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import pydoc
|
import pydoc
|
||||||
|
|
||||||
from jedi.evaluate.utils import ignored
|
from jedi.evaluate.utils import ignored
|
||||||
from jedi.evaluate.names import AbstractNameDefinition
|
from jedi.evaluate.names import AbstractArbitraryName
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pydoc_data import topics as pydoc_topics
|
from pydoc_data import topics as pydoc_topics
|
||||||
@@ -19,14 +19,8 @@ def get_operator(evaluator, string, pos):
|
|||||||
return Keyword(evaluator, string, pos)
|
return Keyword(evaluator, string, pos)
|
||||||
|
|
||||||
|
|
||||||
class KeywordName(AbstractNameDefinition):
|
class KeywordName(AbstractArbitraryName):
|
||||||
api_type = u'keyword'
|
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):
|
def infer(self):
|
||||||
return [Keyword(self.evaluator, self.string_name, (0, 0))]
|
return [Keyword(self.evaluator, self.string_name, (0, 0))]
|
||||||
|
|||||||
@@ -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
|
only *evaluates* what needs to be *evaluated*. All the statements and modules
|
||||||
that are not used are just being ignored.
|
that are not used are just being ignored.
|
||||||
"""
|
"""
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
from parso.python import tree
|
from parso.python import tree
|
||||||
import parso
|
import parso
|
||||||
from parso import python_bytes_to_unicode
|
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.context.iterable import CompForContext
|
||||||
from jedi.evaluate.syntax_tree import eval_trailer, eval_expr_stmt, \
|
from jedi.evaluate.syntax_tree import eval_trailer, eval_expr_stmt, \
|
||||||
eval_node, check_tuple_assignments
|
eval_node, check_tuple_assignments
|
||||||
|
from jedi.plugins import plugin_manager
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
class Evaluator(object):
|
class Evaluator(object):
|
||||||
@@ -115,29 +106,27 @@ class Evaluator(object):
|
|||||||
self.is_analysis = False
|
self.is_analysis = False
|
||||||
self.project = project
|
self.project = project
|
||||||
self.access_cache = {}
|
self.access_cache = {}
|
||||||
|
self.allow_descriptor_getattr = False
|
||||||
|
|
||||||
self.reset_recursion_limitations()
|
self.reset_recursion_limitations()
|
||||||
self.allow_different_encoding = True
|
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,
|
def import_module(self, import_names, parent_module_context=None,
|
||||||
sys_path=None, prefer_stubs=True):
|
sys_path=None, prefer_stubs=True):
|
||||||
if sys_path is None:
|
if sys_path is None:
|
||||||
sys_path = self.get_sys_path()
|
sys_path = self.get_sys_path()
|
||||||
return self._import_module(import_names, parent_module_context,
|
return imports.import_module(self, import_names, parent_module_context,
|
||||||
sys_path, prefer_stubs=prefer_stubs)
|
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
|
@property
|
||||||
@evaluator_function_cache()
|
@evaluator_function_cache()
|
||||||
def builtins_module(self):
|
def builtins_module(self):
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts, \
|
|||||||
from jedi.evaluate.names import ParamName, TreeNameDefinition
|
from jedi.evaluate.names import ParamName, TreeNameDefinition
|
||||||
from jedi.evaluate.base_context import NO_CONTEXTS, ContextSet, ContextualizedNode
|
from jedi.evaluate.base_context import NO_CONTEXTS, ContextSet, ContextualizedNode
|
||||||
from jedi.evaluate.context import iterable
|
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
|
from jedi.evaluate.param import get_executed_params_and_issues, ExecutedParam
|
||||||
|
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ class ParamIssue(Exception):
|
|||||||
pass
|
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
|
Transforms a function or method with arguments to the signature that is
|
||||||
given as an argument clinic notation.
|
given as an argument clinic notation.
|
||||||
@@ -54,6 +55,8 @@ def repack_with_argument_clinic(string, keep_arguments_param=False):
|
|||||||
arguments = kwargs['arguments']
|
arguments = kwargs['arguments']
|
||||||
else:
|
else:
|
||||||
arguments = kwargs.pop('arguments')
|
arguments = kwargs.pop('arguments')
|
||||||
|
if not keep_arguments_param:
|
||||||
|
kwargs.pop('callback', None)
|
||||||
try:
|
try:
|
||||||
args += tuple(_iterate_argument_clinic(
|
args += tuple(_iterate_argument_clinic(
|
||||||
context.evaluator,
|
context.evaluator,
|
||||||
@@ -208,6 +211,11 @@ class TreeArguments(AbstractArguments):
|
|||||||
self._evaluator = evaluator
|
self._evaluator = evaluator
|
||||||
self.trailer = trailer # Can be None, e.g. in a class definition.
|
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):
|
def unpack(self, funcdef=None):
|
||||||
named_args = []
|
named_args = []
|
||||||
for star_count, el in unpack_arglist(self.argument_node):
|
for star_count, el in unpack_arglist(self.argument_node):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from jedi._compatibility import unicode
|
from jedi._compatibility import unicode
|
||||||
from jedi.evaluate.compiled.context import CompiledObject, CompiledName, \
|
from jedi.evaluate.compiled.context import CompiledObject, CompiledName, \
|
||||||
CompiledObjectFilter, CompiledContextName, create_from_access_path
|
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):
|
def builtin_from_name(evaluator, string):
|
||||||
@@ -16,9 +16,9 @@ def builtin_from_name(evaluator, string):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class CompiledValue(ContextWrapper):
|
class CompiledValue(LazyContextWrapper):
|
||||||
def __init__(self, instance, compiled_obj):
|
def __init__(self, compiled_obj):
|
||||||
super(CompiledValue, self).__init__(instance)
|
self.evaluator = compiled_obj.evaluator
|
||||||
self._compiled_obj = compiled_obj
|
self._compiled_obj = compiled_obj
|
||||||
|
|
||||||
def __getattribute__(self, name):
|
def __getattribute__(self, name):
|
||||||
@@ -27,6 +27,11 @@ class CompiledValue(ContextWrapper):
|
|||||||
return getattr(self._compiled_obj, name)
|
return getattr(self._compiled_obj, name)
|
||||||
return super(CompiledValue, self).__getattribute__(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):
|
def __repr__(self):
|
||||||
return '<%s: %s>' % (self.__class__.__name__, self._compiled_obj)
|
return '<%s: %s>' % (self.__class__.__name__, self._compiled_obj)
|
||||||
|
|
||||||
@@ -41,8 +46,7 @@ def create_simple_object(evaluator, obj):
|
|||||||
evaluator,
|
evaluator,
|
||||||
evaluator.compiled_subprocess.create_simple_object(obj)
|
evaluator.compiled_subprocess.create_simple_object(obj)
|
||||||
)
|
)
|
||||||
instance, = builtin_from_name(evaluator, compiled_obj.name.string_name).execute_evaluated()
|
return CompiledValue(compiled_obj)
|
||||||
return CompiledValue(instance, compiled_obj)
|
|
||||||
|
|
||||||
|
|
||||||
def get_string_context_set(evaluator):
|
def get_string_context_set(evaluator):
|
||||||
|
|||||||
@@ -84,9 +84,14 @@ def safe_getattr(obj, name, default=_sentinel):
|
|||||||
raise
|
raise
|
||||||
return default
|
return default
|
||||||
else:
|
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
|
# In case of descriptors that have get methods we cannot return
|
||||||
# it's value, because that would mean code execution.
|
# 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 getattr(obj, name)
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
@@ -175,6 +180,18 @@ def _force_unicode_decorator(func):
|
|||||||
return lambda *args, **kwargs: force_unicode(func(*args, **kwargs))
|
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):
|
class DirectObjectAccess(object):
|
||||||
def __init__(self, evaluator, obj):
|
def __init__(self, evaluator, obj):
|
||||||
self._evaluator = evaluator
|
self._evaluator = evaluator
|
||||||
@@ -328,12 +345,16 @@ class DirectObjectAccess(object):
|
|||||||
def getattr_paths(self, name, default=_sentinel):
|
def getattr_paths(self, name, default=_sentinel):
|
||||||
try:
|
try:
|
||||||
return_obj = getattr(self._obj, name)
|
return_obj = getattr(self._obj, name)
|
||||||
except AttributeError:
|
except Exception as e:
|
||||||
|
if default is _sentinel:
|
||||||
|
if isinstance(e, AttributeError):
|
||||||
# Happens e.g. in properties of
|
# Happens e.g. in properties of
|
||||||
# PyQt4.QtGui.QStyleOptionComboBox.currentText
|
# PyQt4.QtGui.QStyleOptionComboBox.currentText
|
||||||
# -> just set it to None
|
# -> just set it to None
|
||||||
if default is _sentinel:
|
|
||||||
raise
|
raise
|
||||||
|
# Just in case anything happens, return an AttributeError. It
|
||||||
|
# should not crash.
|
||||||
|
raise AttributeError
|
||||||
return_obj = default
|
return_obj = default
|
||||||
access = self._create_access(return_obj)
|
access = self._create_access(return_obj)
|
||||||
if inspect.ismodule(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))
|
raise ValueError("Object is type %s and not simple" % type(self._obj))
|
||||||
|
|
||||||
def get_api_type(self):
|
def get_api_type(self):
|
||||||
obj = self._obj
|
return get_api_type(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'
|
|
||||||
|
|
||||||
def get_access_path_tuples(self):
|
def get_access_path_tuples(self):
|
||||||
accesses = [create_access(self._evaluator, o) for o in self._get_objects_path()]
|
accesses = [create_access(self._evaluator, o) for o in self._get_objects_path()]
|
||||||
|
|||||||
@@ -264,6 +264,9 @@ class CompiledObject(Context):
|
|||||||
def negate(self):
|
def negate(self):
|
||||||
return create_from_access_path(self.evaluator, self.access_handle.negate())
|
return create_from_access_path(self.evaluator, self.access_handle.negate())
|
||||||
|
|
||||||
|
def get_metaclasses(self):
|
||||||
|
return NO_CONTEXTS
|
||||||
|
|
||||||
|
|
||||||
class CompiledName(AbstractNameDefinition):
|
class CompiledName(AbstractNameDefinition):
|
||||||
def __init__(self, evaluator, parent_context, name):
|
def __init__(self, evaluator, parent_context, name):
|
||||||
@@ -317,9 +320,6 @@ class SignatureParamName(ParamNameInterface, AbstractNameDefinition):
|
|||||||
def get_kind(self):
|
def get_kind(self):
|
||||||
return getattr(Parameter, self._signature_param.kind_name)
|
return getattr(Parameter, self._signature_param.kind_name)
|
||||||
|
|
||||||
def is_keyword_param(self):
|
|
||||||
return self._signature_param
|
|
||||||
|
|
||||||
def infer(self):
|
def infer(self):
|
||||||
p = self._signature_param
|
p = self._signature_param
|
||||||
evaluator = self.parent_context.evaluator
|
evaluator = self.parent_context.evaluator
|
||||||
@@ -377,14 +377,14 @@ class CompiledObjectFilter(AbstractFilter):
|
|||||||
|
|
||||||
def __init__(self, evaluator, compiled_object, is_instance=False):
|
def __init__(self, evaluator, compiled_object, is_instance=False):
|
||||||
self._evaluator = evaluator
|
self._evaluator = evaluator
|
||||||
self._compiled_object = compiled_object
|
self.compiled_object = compiled_object
|
||||||
self.is_instance = is_instance
|
self.is_instance = is_instance
|
||||||
|
|
||||||
def get(self, name):
|
def get(self, name):
|
||||||
return self._get(
|
return self._get(
|
||||||
name,
|
name,
|
||||||
lambda: self._compiled_object.access_handle.is_allowed_getattr(name),
|
lambda: self.compiled_object.access_handle.is_allowed_getattr(name),
|
||||||
lambda: self._compiled_object.access_handle.dir(),
|
lambda: self.compiled_object.access_handle.dir(),
|
||||||
check_has_attribute=True
|
check_has_attribute=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -399,7 +399,7 @@ class CompiledObjectFilter(AbstractFilter):
|
|||||||
# Always use unicode objects in Python 2 from here.
|
# Always use unicode objects in Python 2 from here.
|
||||||
name = force_unicode(name)
|
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)]
|
return [self._get_cached_name(name, is_empty=True)]
|
||||||
|
|
||||||
if self.is_instance and name not in dir_callback():
|
if self.is_instance and name not in dir_callback():
|
||||||
@@ -416,7 +416,7 @@ class CompiledObjectFilter(AbstractFilter):
|
|||||||
def values(self):
|
def values(self):
|
||||||
from jedi.evaluate.compiled import builtin_from_name
|
from jedi.evaluate.compiled import builtin_from_name
|
||||||
names = []
|
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:
|
for name in dir_infos:
|
||||||
names += self._get(
|
names += self._get(
|
||||||
name,
|
name,
|
||||||
@@ -431,10 +431,10 @@ class CompiledObjectFilter(AbstractFilter):
|
|||||||
return names
|
return names
|
||||||
|
|
||||||
def _create_name(self, name):
|
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):
|
def __repr__(self):
|
||||||
return "<%s: %s>" % (self.__class__.__name__, self._compiled_object)
|
return "<%s: %s>" % (self.__class__.__name__, self.compiled_object)
|
||||||
|
|
||||||
|
|
||||||
docstr_defaults = {
|
docstr_defaults = {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from jedi.evaluate.context import ModuleContext
|
|||||||
from jedi.evaluate.cache import evaluator_function_cache
|
from jedi.evaluate.cache import evaluator_function_cache
|
||||||
from jedi.evaluate.compiled.getattr_static import getattr_static
|
from jedi.evaluate.compiled.getattr_static import getattr_static
|
||||||
from jedi.evaluate.compiled.access import compiled_objects_cache, \
|
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.compiled.context import create_cached_compiled_object
|
||||||
from jedi.evaluate.gradual.conversion import to_stub
|
from jedi.evaluate.gradual.conversion import to_stub
|
||||||
|
|
||||||
@@ -50,6 +50,11 @@ class MixedObject(ContextWrapper):
|
|||||||
def get_filters(self, *args, **kwargs):
|
def get_filters(self, *args, **kwargs):
|
||||||
yield MixedObjectFilter(self.evaluator, self)
|
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):
|
def py__call__(self, arguments):
|
||||||
return (to_stub(self._wrapped_context) or self._wrapped_context).py__call__(arguments)
|
return (to_stub(self._wrapped_context) or self._wrapped_context).py__call__(arguments)
|
||||||
|
|
||||||
@@ -151,6 +156,7 @@ def _get_object_to_check(python_object):
|
|||||||
|
|
||||||
|
|
||||||
def _find_syntax_node_name(evaluator, python_object):
|
def _find_syntax_node_name(evaluator, python_object):
|
||||||
|
original_object = python_object
|
||||||
try:
|
try:
|
||||||
python_object = _get_object_to_check(python_object)
|
python_object = _get_object_to_check(python_object)
|
||||||
path = inspect.getsourcefile(python_object)
|
path = inspect.getsourcefile(python_object)
|
||||||
@@ -214,7 +220,13 @@ def _find_syntax_node_name(evaluator, python_object):
|
|||||||
# completions at some points but will lead to mostly correct type
|
# completions at some points but will lead to mostly correct type
|
||||||
# inference, because people tend to define a public name in a module only
|
# inference, because people tend to define a public name in a module only
|
||||||
# once.
|
# 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')
|
@compiled_objects_cache('mixed_cache')
|
||||||
@@ -254,7 +266,11 @@ def _create(evaluator, access_handle, parent_context, *args):
|
|||||||
if name is not None:
|
if name is not None:
|
||||||
evaluator.module_cache.add(string_names, ContextSet([module_context]))
|
evaluator.module_cache.add(string_names, ContextSet([module_context]))
|
||||||
else:
|
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()
|
module_context = parent_context.get_root_context()
|
||||||
|
|
||||||
tree_contexts = ContextSet({
|
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__()
|
||||||
@@ -100,6 +100,9 @@ class FunctionMixin(object):
|
|||||||
|
|
||||||
return FunctionExecutionContext(self.evaluator, self.parent_context, self, arguments)
|
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)):
|
class FunctionContext(use_metaclass(CachedMetaClass, FunctionMixin, FunctionAndClassBase)):
|
||||||
"""
|
"""
|
||||||
@@ -147,8 +150,8 @@ class FunctionContext(use_metaclass(CachedMetaClass, FunctionMixin, FunctionAndC
|
|||||||
def get_default_param_context(self):
|
def get_default_param_context(self):
|
||||||
return self.parent_context
|
return self.parent_context
|
||||||
|
|
||||||
def get_signatures(self):
|
def get_signature_functions(self):
|
||||||
return [TreeSignature(self)]
|
return [self]
|
||||||
|
|
||||||
|
|
||||||
class MethodContext(FunctionContext):
|
class MethodContext(FunctionContext):
|
||||||
@@ -371,14 +374,14 @@ class FunctionExecutionContext(TreeContext):
|
|||||||
class OverloadedFunctionContext(FunctionMixin, ContextWrapper):
|
class OverloadedFunctionContext(FunctionMixin, ContextWrapper):
|
||||||
def __init__(self, function, overloaded_functions):
|
def __init__(self, function, overloaded_functions):
|
||||||
super(OverloadedFunctionContext, self).__init__(function)
|
super(OverloadedFunctionContext, self).__init__(function)
|
||||||
self.overloaded_functions = overloaded_functions
|
self._overloaded_functions = overloaded_functions
|
||||||
|
|
||||||
def py__call__(self, arguments):
|
def py__call__(self, arguments):
|
||||||
debug.dbg("Execute overloaded function %s", self._wrapped_context, color='BLUE')
|
debug.dbg("Execute overloaded function %s", self._wrapped_context, color='BLUE')
|
||||||
function_executions = []
|
function_executions = []
|
||||||
context_set = NO_CONTEXTS
|
context_set = NO_CONTEXTS
|
||||||
matched = False
|
matched = False
|
||||||
for f in self.overloaded_functions:
|
for f in self._overloaded_functions:
|
||||||
function_execution = f.get_function_execution(arguments)
|
function_execution = f.get_function_execution(arguments)
|
||||||
function_executions.append(function_execution)
|
function_executions.append(function_execution)
|
||||||
if function_execution.matches_signature():
|
if function_execution.matches_signature():
|
||||||
@@ -393,39 +396,8 @@ class OverloadedFunctionContext(FunctionMixin, ContextWrapper):
|
|||||||
return NO_CONTEXTS
|
return NO_CONTEXTS
|
||||||
return ContextSet.from_sets(fe.infer() for fe in function_executions)
|
return ContextSet.from_sets(fe.infer() for fe in function_executions)
|
||||||
|
|
||||||
def get_signatures(self):
|
def get_signature_functions(self):
|
||||||
return [TreeSignature(f) for f in self.overloaded_functions]
|
return 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 _find_overload_functions(context, tree_node):
|
def _find_overload_functions(context, tree_node):
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from abc import abstractproperty
|
|||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
from jedi.evaluate import compiled
|
from jedi.evaluate import compiled
|
||||||
|
from jedi.evaluate.compiled.context import CompiledObjectFilter
|
||||||
from jedi.evaluate.helpers import contexts_from_qualified_names
|
from jedi.evaluate.helpers import contexts_from_qualified_names
|
||||||
from jedi.evaluate.filters import AbstractFilter
|
from jedi.evaluate.filters import AbstractFilter
|
||||||
from jedi.evaluate.names import ContextName, TreeNameDefinition
|
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.cache import evaluator_method_cache
|
||||||
from jedi.evaluate.arguments import AnonymousArguments, \
|
from jedi.evaluate.arguments import AnonymousArguments, \
|
||||||
ValuesArguments, TreeArgumentsWrapper
|
ValuesArguments, TreeArgumentsWrapper
|
||||||
from jedi.evaluate.context.function import FunctionExecutionContext, \
|
from jedi.evaluate.context.function import \
|
||||||
FunctionContext, FunctionMixin, OverloadedFunctionContext
|
FunctionContext, FunctionMixin, OverloadedFunctionContext
|
||||||
from jedi.evaluate.context.klass import ClassContext, apply_py__get__, \
|
from jedi.evaluate.context.klass import ClassContext, apply_py__get__, \
|
||||||
ClassFilter
|
ClassFilter
|
||||||
@@ -136,11 +137,19 @@ class AbstractInstanceContext(Context):
|
|||||||
# compiled objects to search for self variables.
|
# compiled objects to search for self variables.
|
||||||
yield SelfAttributeFilter(self.evaluator, self, cls, origin_scope)
|
yield SelfAttributeFilter(self.evaluator, self, cls, origin_scope)
|
||||||
|
|
||||||
for cls in class_context.py__mro__():
|
class_filters = class_context.get_filters(
|
||||||
if isinstance(cls, compiled.CompiledObject):
|
search_global=False,
|
||||||
yield CompiledInstanceClassFilter(self.evaluator, self, cls)
|
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:
|
else:
|
||||||
yield InstanceClassFilter(self.evaluator, self, cls, origin_scope)
|
# Propably from the metaclass.
|
||||||
|
yield f
|
||||||
|
|
||||||
def py__getitem__(self, index_context_set, contextualized_node):
|
def py__getitem__(self, index_context_set, contextualized_node):
|
||||||
names = self.get_function_slot_names(u'__getitem__')
|
names = self.get_function_slot_names(u'__getitem__')
|
||||||
@@ -223,8 +232,8 @@ class AbstractInstanceContext(Context):
|
|||||||
return class_context
|
return class_context
|
||||||
|
|
||||||
def get_signatures(self):
|
def get_signatures(self):
|
||||||
init_funcs = self.py__getattribute__('__call__')
|
call_funcs = self.py__getattribute__('__call__').py__get__(self, self.class_context)
|
||||||
return [sig.bind(self) for sig in init_funcs.get_signatures()]
|
return [s.bind(self) for s in call_funcs.get_signatures()]
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<%s of %s(%s)>" % (self.__class__.__name__, self.class_context,
|
return "<%s of %s(%s)>" % (self.__class__.__name__, self.class_context,
|
||||||
@@ -328,7 +337,6 @@ class CompiledInstanceName(compiled.CompiledName):
|
|||||||
name.string_name
|
name.string_name
|
||||||
)
|
)
|
||||||
self._instance = instance
|
self._instance = instance
|
||||||
self._class = klass
|
|
||||||
self._class_member_name = name
|
self._class_member_name = name
|
||||||
|
|
||||||
@iterator_to_context_set
|
@iterator_to_context_set
|
||||||
@@ -343,11 +351,10 @@ class CompiledInstanceName(compiled.CompiledName):
|
|||||||
class CompiledInstanceClassFilter(AbstractFilter):
|
class CompiledInstanceClassFilter(AbstractFilter):
|
||||||
name_class = CompiledInstanceName
|
name_class = CompiledInstanceName
|
||||||
|
|
||||||
def __init__(self, evaluator, instance, klass):
|
def __init__(self, evaluator, instance, f):
|
||||||
self._evaluator = evaluator
|
self._evaluator = evaluator
|
||||||
self._instance = instance
|
self._instance = instance
|
||||||
self._class = klass
|
self._class_filter = f
|
||||||
self._class_filter = next(klass.get_filters(is_instance=True))
|
|
||||||
|
|
||||||
def get(self, name):
|
def get(self, name):
|
||||||
return self._convert(self._class_filter.get(name))
|
return self._convert(self._class_filter.get(name))
|
||||||
@@ -356,8 +363,9 @@ class CompiledInstanceClassFilter(AbstractFilter):
|
|||||||
return self._convert(self._class_filter.values())
|
return self._convert(self._class_filter.values())
|
||||||
|
|
||||||
def _convert(self, names):
|
def _convert(self, names):
|
||||||
|
klass = self._class_filter.compiled_object
|
||||||
return [
|
return [
|
||||||
CompiledInstanceName(self._evaluator, self._instance, self._class, n)
|
CompiledInstanceName(self._evaluator, self._instance, klass, n)
|
||||||
for n in names
|
for n in names
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -382,15 +390,6 @@ class BoundMethod(FunctionMixin, ContextWrapper):
|
|||||||
|
|
||||||
def get_function_execution(self, arguments=None):
|
def get_function_execution(self, arguments=None):
|
||||||
arguments = self._get_arguments(arguments)
|
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)
|
return super(BoundMethod, self).get_function_execution(arguments)
|
||||||
|
|
||||||
def py__call__(self, arguments):
|
def py__call__(self, arguments):
|
||||||
@@ -400,8 +399,14 @@ class BoundMethod(FunctionMixin, ContextWrapper):
|
|||||||
function_execution = self.get_function_execution(arguments)
|
function_execution = self.get_function_execution(arguments)
|
||||||
return function_execution.infer()
|
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):
|
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):
|
def __repr__(self):
|
||||||
return '<%s: %s>' % (self.__class__.__name__, self._wrapped_context)
|
return '<%s: %s>' % (self.__class__.__name__, self._wrapped_context)
|
||||||
@@ -454,15 +459,9 @@ class InstanceClassFilter(AbstractFilter):
|
|||||||
resulting names in LazyINstanceClassName. The idea is that the class name
|
resulting names in LazyINstanceClassName. The idea is that the class name
|
||||||
filtering can be very flexible and always be reflected in instances.
|
filtering can be very flexible and always be reflected in instances.
|
||||||
"""
|
"""
|
||||||
def __init__(self, evaluator, context, class_context, origin_scope):
|
def __init__(self, evaluator, instance, class_filter):
|
||||||
self._instance = context
|
self._instance = instance
|
||||||
self._class_context = class_context
|
self._class_filter = class_filter
|
||||||
self._class_filter = next(class_context.get_filters(
|
|
||||||
search_global=False,
|
|
||||||
origin_scope=origin_scope,
|
|
||||||
is_instance=True,
|
|
||||||
))
|
|
||||||
assert isinstance(self._class_filter, ClassFilter), self._class_filter
|
|
||||||
|
|
||||||
def get(self, name):
|
def get(self, name):
|
||||||
return self._convert(self._class_filter.get(name, from_instance=True))
|
return self._convert(self._class_filter.get(name, from_instance=True))
|
||||||
@@ -471,10 +470,10 @@ class InstanceClassFilter(AbstractFilter):
|
|||||||
return self._convert(self._class_filter.values(from_instance=True))
|
return self._convert(self._class_filter.values(from_instance=True))
|
||||||
|
|
||||||
def _convert(self, names):
|
def _convert(self, names):
|
||||||
return [LazyInstanceClassName(self._instance, self._class_context, n) for n in names]
|
return [LazyInstanceClassName(self._instance, self._class_filter.context, n) for n in names]
|
||||||
|
|
||||||
def __repr__(self):
|
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):
|
class SelfAttributeFilter(ClassFilter):
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ It is important to note that:
|
|||||||
1. Array modfications work only in the current module.
|
1. Array modfications work only in the current module.
|
||||||
2. Jedi only checks Array additions; ``list.pop``, etc are ignored.
|
2. Jedi only checks Array additions; ``list.pop``, etc are ignored.
|
||||||
"""
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
from jedi._compatibility import force_unicode, is_py3
|
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.cache import evaluator_method_cache
|
||||||
from jedi.evaluate.filters import ParserTreeFilter, LazyAttributeOverwrite, \
|
from jedi.evaluate.filters import ParserTreeFilter, LazyAttributeOverwrite, \
|
||||||
publish_method
|
publish_method
|
||||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, \
|
from jedi.evaluate.base_context import ContextSet, Context, NO_CONTEXTS, \
|
||||||
TreeContext, ContextualizedNode, iterate_contexts, HelperContextMixin
|
TreeContext, ContextualizedNode, iterate_contexts, HelperContextMixin, _sentinel
|
||||||
from jedi.parser_utils import get_sync_comp_fors
|
from jedi.parser_utils import get_sync_comp_fors
|
||||||
|
|
||||||
|
|
||||||
@@ -44,6 +46,21 @@ class IterableMixin(object):
|
|||||||
def py__stop_iteration_returns(self):
|
def py__stop_iteration_returns(self):
|
||||||
return ContextSet([compiled.builtin_from_name(self.evaluator, u'None')])
|
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):
|
class GeneratorBase(LazyAttributeOverwrite, IterableMixin):
|
||||||
array_type = None
|
array_type = None
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ from jedi.evaluate.arguments import unpack_arglist, ValuesArguments
|
|||||||
from jedi.evaluate.base_context import ContextSet, iterator_to_context_set, \
|
from jedi.evaluate.base_context import ContextSet, iterator_to_context_set, \
|
||||||
NO_CONTEXTS
|
NO_CONTEXTS
|
||||||
from jedi.evaluate.context.function import FunctionAndClassBase
|
from jedi.evaluate.context.function import FunctionAndClassBase
|
||||||
|
from jedi.plugins import plugin_manager
|
||||||
|
|
||||||
|
|
||||||
def apply_py__get__(context, instance, class_context):
|
def apply_py__get__(context, instance, class_context):
|
||||||
@@ -136,8 +137,10 @@ class ClassMixin(object):
|
|||||||
def is_class(self):
|
def is_class(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def py__call__(self, arguments):
|
def py__call__(self, arguments=None):
|
||||||
from jedi.evaluate.context import TreeInstance
|
from jedi.evaluate.context import TreeInstance
|
||||||
|
if arguments is None:
|
||||||
|
arguments = ValuesArguments([])
|
||||||
return ContextSet([TreeInstance(self.evaluator, self.parent_context, self, arguments)])
|
return ContextSet([TreeInstance(self.evaluator, self.parent_context, self, arguments)])
|
||||||
|
|
||||||
def py__class__(self):
|
def py__class__(self):
|
||||||
@@ -191,13 +194,13 @@ class ClassMixin(object):
|
|||||||
|
|
||||||
def get_filters(self, search_global=False, until_position=None,
|
def get_filters(self, search_global=False, until_position=None,
|
||||||
origin_scope=None, is_instance=False):
|
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:
|
if search_global:
|
||||||
yield ParserTreeFilter(
|
yield self.get_global_filter(until_position, origin_scope)
|
||||||
self.evaluator,
|
|
||||||
context=self,
|
|
||||||
until_position=until_position,
|
|
||||||
origin_scope=origin_scope
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
for cls in self.py__mro__():
|
for cls in self.py__mro__():
|
||||||
if isinstance(cls, compiled.CompiledObject):
|
if isinstance(cls, compiled.CompiledObject):
|
||||||
@@ -214,13 +217,25 @@ class ClassMixin(object):
|
|||||||
type_ = builtin_from_name(self.evaluator, u'type')
|
type_ = builtin_from_name(self.evaluator, u'type')
|
||||||
assert isinstance(type_, ClassContext)
|
assert isinstance(type_, ClassContext)
|
||||||
if type_ != self:
|
if type_ != self:
|
||||||
for instance in type_.py__call__(ValuesArguments([])):
|
for instance in type_.py__call__():
|
||||||
instance_filters = instance.get_filters()
|
instance_filters = instance.get_filters()
|
||||||
# Filter out self filters
|
# Filter out self filters
|
||||||
next(instance_filters)
|
next(instance_filters)
|
||||||
next(instance_filters)
|
next(instance_filters)
|
||||||
yield next(instance_filters)
|
yield next(instance_filters)
|
||||||
|
|
||||||
|
def get_signatures(self):
|
||||||
|
init_funcs = self.py__call__().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)):
|
class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase)):
|
||||||
"""
|
"""
|
||||||
@@ -247,12 +262,17 @@ class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBa
|
|||||||
found.append(type_var)
|
found.append(type_var)
|
||||||
return found
|
return found
|
||||||
|
|
||||||
@evaluator_method_cache(default=())
|
def _get_bases_arguments(self):
|
||||||
def py__bases__(self):
|
|
||||||
arglist = self.tree_node.get_super_arglist()
|
arglist = self.tree_node.get_super_arglist()
|
||||||
if arglist:
|
if arglist:
|
||||||
from jedi.evaluate import arguments
|
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]
|
lst = [value for key, value in args.unpack() if key is None]
|
||||||
if lst:
|
if lst:
|
||||||
return lst
|
return lst
|
||||||
@@ -300,6 +320,25 @@ class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBa
|
|||||||
)])
|
)])
|
||||||
return ContextSet({self})
|
return ContextSet({self})
|
||||||
|
|
||||||
def get_signatures(self):
|
@plugin_manager.decorate()
|
||||||
init_funcs = self.py__getattribute__('__init__')
|
def get_metaclass_filters(self, metaclass):
|
||||||
return [sig.bind(self) for sig in init_funcs.get_signatures()]
|
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.base_context import TreeContext
|
||||||
from jedi.evaluate.names import SubModuleName
|
from jedi.evaluate.names import SubModuleName
|
||||||
from jedi.evaluate.helpers import contexts_from_qualified_names
|
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):
|
class _ModuleAttributeName(AbstractNameDefinition):
|
||||||
@@ -17,11 +19,20 @@ class _ModuleAttributeName(AbstractNameDefinition):
|
|||||||
"""
|
"""
|
||||||
api_type = u'instance'
|
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.parent_context = parent_module
|
||||||
self.string_name = string_name
|
self.string_name = string_name
|
||||||
|
self._string_value = string_value
|
||||||
|
|
||||||
def infer(self):
|
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)
|
return compiled.get_string_context_set(self.parent_context.evaluator)
|
||||||
|
|
||||||
|
|
||||||
@@ -132,9 +143,13 @@ class ModuleMixin(SubModuleDictMixin):
|
|||||||
|
|
||||||
@evaluator_method_cache()
|
@evaluator_method_cache()
|
||||||
def _module_attributes_dict(self):
|
def _module_attributes_dict(self):
|
||||||
names = ['__file__', '__package__', '__doc__', '__name__']
|
names = ['__package__', '__doc__', '__name__']
|
||||||
# All the additional module attributes are strings.
|
# 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):
|
def iter_star_filters(self, search_global=False):
|
||||||
for star_module in self.star_imports():
|
for star_module in self.star_imports():
|
||||||
|
|||||||
@@ -51,11 +51,8 @@ def _get_numpy_doc_string_cls():
|
|||||||
global _numpy_doc_string_cache
|
global _numpy_doc_string_cache
|
||||||
if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)):
|
if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)):
|
||||||
raise _numpy_doc_string_cache
|
raise _numpy_doc_string_cache
|
||||||
try:
|
|
||||||
from numpydoc.docscrape import NumpyDocString
|
from numpydoc.docscrape import NumpyDocString
|
||||||
_numpy_doc_string_cache = NumpyDocString
|
_numpy_doc_string_cache = NumpyDocString
|
||||||
except (ImportError, SyntaxError) as e:
|
|
||||||
raise
|
|
||||||
return _numpy_doc_string_cache
|
return _numpy_doc_string_cache
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ It works as follows:
|
|||||||
- execute these calls and check the input.
|
- execute these calls and check the input.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from parso.python import tree
|
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi.evaluate.cache import evaluator_function_cache
|
from jedi.evaluate.cache import evaluator_function_cache
|
||||||
|
|||||||
@@ -386,7 +386,7 @@ def get_global_filters(evaluator, context, until_position, origin_scope):
|
|||||||
>>> list(filters[1].values()) # package modules -> Also empty.
|
>>> list(filters[1].values()) # package modules -> Also empty.
|
||||||
[]
|
[]
|
||||||
>>> sorted(name.string_name for name in filters[2].values()) # Module attributes
|
>>> 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
|
Finally, it yields the builtin filter, if `include_builtin` is
|
||||||
true (default).
|
true (default).
|
||||||
@@ -409,5 +409,4 @@ def get_global_filters(evaluator, context, until_position, origin_scope):
|
|||||||
context = context.parent_context
|
context = context.parent_context
|
||||||
|
|
||||||
# Add builtins to the global scope.
|
# Add builtins to the global scope.
|
||||||
for filter in evaluator.builtins_module.get_filters():
|
yield next(evaluator.builtins_module.get_filters())
|
||||||
yield filter
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ from jedi.evaluate.cache import evaluator_method_cache
|
|||||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
|
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
|
||||||
from jedi.evaluate.gradual.typing import TypeVar, LazyGenericClass, \
|
from jedi.evaluate.gradual.typing import TypeVar, LazyGenericClass, \
|
||||||
AbstractAnnotatedClass
|
AbstractAnnotatedClass
|
||||||
|
from jedi.evaluate.gradual.typing import GenericClass
|
||||||
from jedi.evaluate.helpers import is_string
|
from jedi.evaluate.helpers import is_string
|
||||||
|
from jedi.evaluate.compiled import builtin_from_name
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi import parser_utils
|
from jedi import parser_utils
|
||||||
|
|
||||||
@@ -106,6 +108,25 @@ def _split_comment_param_declaration(decl_text):
|
|||||||
|
|
||||||
@evaluator_method_cache()
|
@evaluator_method_cache()
|
||||||
def infer_param(execution_context, param):
|
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.
|
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):
|
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
|
non_stubs = stub_module.non_stub_context_set
|
||||||
if ignore_compiled:
|
if ignore_compiled:
|
||||||
non_stubs = non_stubs.filter(lambda c: not c.is_compiled())
|
non_stubs = non_stubs.filter(lambda c: not c.is_compiled())
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
from jedi.file_io import FileIO
|
from jedi.file_io import FileIO
|
||||||
from jedi._compatibility import FileNotFoundError, cast_path
|
from jedi._compatibility import FileNotFoundError, cast_path
|
||||||
@@ -87,6 +88,7 @@ def _cache_stub_file_map(version_info):
|
|||||||
|
|
||||||
|
|
||||||
def import_module_decorator(func):
|
def import_module_decorator(func):
|
||||||
|
@wraps(func)
|
||||||
def wrapper(evaluator, import_names, parent_module_context, sys_path, prefer_stubs):
|
def wrapper(evaluator, import_names, parent_module_context, sys_path, prefer_stubs):
|
||||||
try:
|
try:
|
||||||
python_context_set = evaluator.module_cache.get(import_names)
|
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.cache import evaluator_method_cache
|
||||||
from jedi.evaluate.compiled import builtin_from_name
|
from jedi.evaluate.compiled import builtin_from_name
|
||||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, Context, \
|
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.lazy_context import LazyKnownContexts
|
||||||
from jedi.evaluate.context.iterable import SequenceLiteralContext
|
from jedi.evaluate.context.iterable import SequenceLiteralContext
|
||||||
from jedi.evaluate.arguments import repack_with_argument_clinic
|
from jedi.evaluate.arguments import repack_with_argument_clinic
|
||||||
@@ -209,6 +209,9 @@ class _TypingClassMixin(object):
|
|||||||
self.evaluator.builtins_module.py__getattribute__('object')
|
self.evaluator.builtins_module.py__getattribute__('object')
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
def get_metaclasses(self):
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class TypingClassContextWithIndex(_TypingClassMixin, TypingContextWithIndex, ClassMixin):
|
class TypingClassContextWithIndex(_TypingClassMixin, TypingContextWithIndex, ClassMixin):
|
||||||
pass
|
pass
|
||||||
@@ -241,9 +244,9 @@ def _iter_over_arguments(maybe_tuple_context, defining_context):
|
|||||||
yield ContextSet(resolve_forward_references(context_set))
|
yield ContextSet(resolve_forward_references(context_set))
|
||||||
|
|
||||||
|
|
||||||
class TypeAlias(HelperContextMixin):
|
class TypeAlias(LazyContextWrapper):
|
||||||
def __init__(self, evaluator, parent_context, origin_tree_name, actual):
|
def __init__(self, parent_context, origin_tree_name, actual):
|
||||||
self.evaluator = evaluator
|
self.evaluator = parent_context.evaluator
|
||||||
self.parent_context = parent_context
|
self.parent_context = parent_context
|
||||||
self._origin_tree_name = origin_tree_name
|
self._origin_tree_name = origin_tree_name
|
||||||
self._actual = actual # e.g. builtins.list
|
self._actual = actual # e.g. builtins.list
|
||||||
@@ -255,14 +258,10 @@ class TypeAlias(HelperContextMixin):
|
|||||||
def py__name__(self):
|
def py__name__(self):
|
||||||
return self.name.string_name
|
return self.name.string_name
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
return getattr(self._get_type_alias_class(), name)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s: %s>' % (self.__class__.__name__, self._actual)
|
return '<%s: %s>' % (self.__class__.__name__, self._actual)
|
||||||
|
|
||||||
@evaluator_method_cache()
|
def _get_wrapped_context(self):
|
||||||
def _get_type_alias_class(self):
|
|
||||||
module_name, class_name = self._actual.split('.')
|
module_name, class_name = self._actual.split('.')
|
||||||
if self.evaluator.environment.version_info.major == 2 and module_name == 'builtins':
|
if self.evaluator.environment.version_info.major == 2 and module_name == 'builtins':
|
||||||
module_name = '__builtin__'
|
module_name = '__builtin__'
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from jedi._compatibility import (FileNotFoundError, ImplicitNSInfo,
|
|||||||
force_unicode, unicode)
|
force_unicode, unicode)
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi import settings
|
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.parser_utils import get_cached_code_lines
|
||||||
from jedi.evaluate import sys_path
|
from jedi.evaluate import sys_path
|
||||||
from jedi.evaluate import helpers
|
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.base_context import ContextSet, NO_CONTEXTS
|
||||||
from jedi.evaluate.gradual.typeshed import import_module_decorator
|
from jedi.evaluate.gradual.typeshed import import_module_decorator
|
||||||
from jedi.evaluate.context.module import iter_module_names
|
from jedi.evaluate.context.module import iter_module_names
|
||||||
|
from jedi.plugins import plugin_manager
|
||||||
|
|
||||||
|
|
||||||
class ModuleCache(object):
|
class ModuleCache(object):
|
||||||
@@ -371,6 +372,7 @@ class Importer(object):
|
|||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
@plugin_manager.decorate()
|
||||||
@import_module_decorator
|
@import_module_decorator
|
||||||
def import_module(evaluator, import_names, parent_module_context, sys_path):
|
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 parso.tree import search_ancestor
|
||||||
|
|
||||||
from jedi._compatibility import Parameter
|
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
|
from jedi.cache import memoize_method
|
||||||
|
|
||||||
|
|
||||||
@@ -58,6 +58,23 @@ class AbstractNameDefinition(object):
|
|||||||
return self.parent_context.api_type
|
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):
|
class AbstractTreeName(AbstractNameDefinition):
|
||||||
def __init__(self, parent_context, tree_name):
|
def __init__(self, parent_context, tree_name):
|
||||||
self.parent_context = parent_context
|
self.parent_context = parent_context
|
||||||
@@ -150,8 +167,18 @@ class TreeNameDefinition(AbstractTreeName):
|
|||||||
return self._API_TYPES.get(definition.type, 'statement')
|
return self._API_TYPES.get(definition.type, 'statement')
|
||||||
|
|
||||||
|
|
||||||
class ParamNameInterface(object):
|
class _ParamMixin(object):
|
||||||
api_type = u'param'
|
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):
|
def _kind_string(self):
|
||||||
kind = self.get_kind()
|
kind = self.get_kind()
|
||||||
@@ -161,21 +188,74 @@ class ParamNameInterface(object):
|
|||||||
return '**'
|
return '**'
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
class ParamNameInterface(_ParamMixin):
|
||||||
|
api_type = u'param'
|
||||||
|
|
||||||
def get_kind(self):
|
def get_kind(self):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def to_string(self):
|
def to_string(self):
|
||||||
raise NotImplementedError
|
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(ParamNameInterface, AbstractTreeName):
|
@property
|
||||||
def __init__(self, parent_context, tree_name):
|
def star_count(self):
|
||||||
self.parent_context = parent_context
|
kind = self.get_kind()
|
||||||
self.tree_name = tree_name
|
if kind == Parameter.VAR_POSITIONAL:
|
||||||
|
return 1
|
||||||
|
if kind == Parameter.VAR_KEYWORD:
|
||||||
|
return 2
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
def _get_param_node(self):
|
||||||
return search_ancestor(self.tree_name, 'param')
|
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
|
@property
|
||||||
def string_name(self):
|
def string_name(self):
|
||||||
name = self.tree_name.value
|
name = self.tree_name.value
|
||||||
@@ -213,15 +293,6 @@ class ParamName(ParamNameInterface, AbstractTreeName):
|
|||||||
param_appeared = True
|
param_appeared = True
|
||||||
return Parameter.POSITIONAL_OR_KEYWORD
|
return Parameter.POSITIONAL_OR_KEYWORD
|
||||||
|
|
||||||
def to_string(self):
|
|
||||||
output = self._kind_string() + self.string_name
|
|
||||||
param_node = self._get_param_node()
|
|
||||||
if param_node.annotation is not None:
|
|
||||||
output += ': ' + param_node.annotation.get_code(include_prefix=False)
|
|
||||||
if param_node.default is not None:
|
|
||||||
output += '=' + param_node.default.get_code(include_prefix=False)
|
|
||||||
return output
|
|
||||||
|
|
||||||
def infer(self):
|
def infer(self):
|
||||||
return self.get_param().infer()
|
return self.get_param().infer()
|
||||||
|
|
||||||
@@ -231,6 +302,17 @@ class ParamName(ParamNameInterface, AbstractTreeName):
|
|||||||
return params[param_node.position_index]
|
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):
|
class ImportName(AbstractNameDefinition):
|
||||||
start_pos = (1, 0)
|
start_pos = (1, 0)
|
||||||
_level = 0
|
_level = 0
|
||||||
|
|||||||
@@ -1,24 +1,13 @@
|
|||||||
from jedi._compatibility import Parameter
|
from jedi._compatibility import Parameter
|
||||||
|
from jedi.cache import memoize_method
|
||||||
|
|
||||||
|
|
||||||
class AbstractSignature(object):
|
class _SignatureMixin(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 ''
|
|
||||||
|
|
||||||
def to_string(self):
|
def to_string(self):
|
||||||
def param_strings():
|
def param_strings():
|
||||||
is_positional = False
|
is_positional = False
|
||||||
is_kw_only = 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()
|
kind = n.get_kind()
|
||||||
is_positional |= kind == Parameter.POSITIONAL_ONLY
|
is_positional |= kind == Parameter.POSITIONAL_ONLY
|
||||||
if is_positional and kind != Parameter.POSITIONAL_ONLY:
|
if is_positional and kind != Parameter.POSITIONAL_ONLY:
|
||||||
@@ -42,15 +31,32 @@ class AbstractSignature(object):
|
|||||||
s += ' -> ' + annotation
|
s += ' -> ' + annotation
|
||||||
return s
|
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()
|
param_names = self._function_context.get_param_names()
|
||||||
if self.is_bound:
|
if self.is_bound:
|
||||||
return param_names[1:]
|
return param_names[1:]
|
||||||
return param_names
|
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):
|
class TreeSignature(AbstractSignature):
|
||||||
def __init__(self, context, function_context=None, is_bound=False):
|
def __init__(self, context, function_context=None, is_bound=False):
|
||||||
@@ -75,6 +81,14 @@ class TreeSignature(AbstractSignature):
|
|||||||
return ''
|
return ''
|
||||||
return a.get_code(include_prefix=False)
|
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):
|
class BuiltinSignature(AbstractSignature):
|
||||||
def __init__(self, context, return_string, is_bound=False):
|
def __init__(self, context, return_string, is_bound=False):
|
||||||
@@ -92,3 +106,11 @@ class BuiltinSignature(AbstractSignature):
|
|||||||
def bind(self, context):
|
def bind(self, context):
|
||||||
assert not self.is_bound
|
assert not self.is_bound
|
||||||
return BuiltinSignature(context, self._return_string, is_bound=True)
|
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.cache import evaluator_method_cache
|
||||||
from jedi.evaluate.gradual.stub_context import VersionInfo
|
from jedi.evaluate.gradual.stub_context import VersionInfo
|
||||||
from jedi.evaluate.gradual import annotation
|
from jedi.evaluate.gradual import annotation
|
||||||
|
from jedi.evaluate.context.decorator import Decoratee
|
||||||
|
from jedi.plugins import plugin_manager
|
||||||
|
|
||||||
|
|
||||||
def _limit_context_infers(func):
|
def _limit_context_infers(func):
|
||||||
@@ -148,28 +150,28 @@ def eval_node(context, element):
|
|||||||
return eval_or_test(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]
|
trailer_op, node = trailer.children[:2]
|
||||||
if node == ')': # `arglist` is optional.
|
if node == ')': # `arglist` is optional.
|
||||||
node = None
|
node = None
|
||||||
|
|
||||||
if trailer_op == '[':
|
if trailer_op == '[':
|
||||||
trailer_op, node, _ = trailer.children
|
trailer_op, node, _ = trailer.children
|
||||||
return base_contexts.get_item(
|
return atom_contexts.get_item(
|
||||||
eval_subscript_list(context.evaluator, context, node),
|
eval_subscript_list(context.evaluator, context, node),
|
||||||
ContextualizedNode(context, trailer)
|
ContextualizedNode(context, trailer)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
debug.dbg('eval_trailer: %s in %s', trailer, base_contexts)
|
debug.dbg('eval_trailer: %s in %s', trailer, atom_contexts)
|
||||||
if trailer_op == '.':
|
if trailer_op == '.':
|
||||||
return base_contexts.py__getattribute__(
|
return atom_contexts.py__getattribute__(
|
||||||
name_context=context,
|
name_context=context,
|
||||||
name_or_str=node
|
name_or_str=node
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
assert trailer_op == '(', 'trailer_op is actually %s' % trailer_op
|
assert trailer_op == '(', 'trailer_op is actually %s' % trailer_op
|
||||||
args = arguments.TreeArguments(context.evaluator, context, node, trailer)
|
args = arguments.TreeArguments(context.evaluator, context, node, trailer)
|
||||||
return base_contexts.execute(args)
|
return atom_contexts.execute(args)
|
||||||
|
|
||||||
|
|
||||||
def eval_atom(context, atom):
|
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)
|
return eval_expr_stmt(context, stmt, seek_name=name)
|
||||||
|
|
||||||
|
|
||||||
|
@plugin_manager.decorate()
|
||||||
def tree_name_to_contexts(evaluator, context, tree_name):
|
def tree_name_to_contexts(evaluator, context, tree_name):
|
||||||
context_set = NO_CONTEXTS
|
context_set = NO_CONTEXTS
|
||||||
module_node = context.get_root_context().tree_node
|
module_node = context.get_root_context().tree_node
|
||||||
@@ -666,6 +669,8 @@ def _apply_decorators(context, node):
|
|||||||
return initial
|
return initial
|
||||||
|
|
||||||
debug.dbg('decorator end %s', values, color="MAGENTA")
|
debug.dbg('decorator end %s', values, color="MAGENTA")
|
||||||
|
if values != initial:
|
||||||
|
return ContextSet([Decoratee(c, decoratee_context) for c in values])
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from weakref import WeakKeyDictionary
|
|||||||
|
|
||||||
from parso.python import tree
|
from parso.python import tree
|
||||||
from parso.cache import parser_cache
|
from parso.cache import parser_cache
|
||||||
|
from parso import split_lines
|
||||||
|
|
||||||
from jedi._compatibility import literal_eval, force_unicode
|
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.
|
to do this, but we avoid splitting all the lines again.
|
||||||
"""
|
"""
|
||||||
return parser_cache[grammar._hashed][path].lines
|
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 functools import wraps
|
||||||
from jedi.plugins.flask import FlaskPlugin
|
|
||||||
|
|
||||||
|
|
||||||
class _PluginManager(object):
|
class _PluginManager(object):
|
||||||
def __init__(self, registered_plugin_classes=()):
|
def __init__(self):
|
||||||
self._registered_plugin_classes = list(registered_plugin_classes)
|
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.
|
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):
|
def decorate(self):
|
||||||
for plugin_class in self._registered_plugin_classes:
|
def decorator(callback):
|
||||||
yield plugin_class(evaluator)
|
@wraps(callback)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return built_functions[name](*args, **kwargs)
|
||||||
|
|
||||||
def get_callbacks(self, evaluator):
|
name = callback.__name__
|
||||||
return _PluginCallbacks(self._build_chain(evaluator))
|
|
||||||
|
|
||||||
|
assert name not in self._built_functions
|
||||||
|
built_functions = self._built_functions
|
||||||
|
built_functions[name] = callback
|
||||||
|
self._cached_base_callbacks[name] = callback
|
||||||
|
|
||||||
class _PluginCallbacks(object):
|
return wrapper
|
||||||
def __init__(self, plugins):
|
|
||||||
self._plugins = list(plugins)
|
|
||||||
|
|
||||||
def decorate(self, name, callback):
|
return decorator
|
||||||
for plugin in reversed(self._plugins):
|
|
||||||
|
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.
|
# Need to reverse so the first plugin is run first.
|
||||||
callback = getattr(plugin, name)(callback)
|
try:
|
||||||
return callback
|
func = getattr(plugin, name)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
callback = func(callback)
|
||||||
|
self._built_functions[name] = callback
|
||||||
|
|
||||||
|
|
||||||
plugin_manager = _PluginManager([
|
plugin_manager = _PluginManager()
|
||||||
StdlibPlugin,
|
|
||||||
FlaskPlugin,
|
|
||||||
])
|
|
||||||
|
|||||||
@@ -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,8 +1,4 @@
|
|||||||
from jedi.plugins.base import BasePlugin
|
def import_module(callback):
|
||||||
|
|
||||||
|
|
||||||
class FlaskPlugin(BasePlugin):
|
|
||||||
def import_module(self, callback):
|
|
||||||
"""
|
"""
|
||||||
Handle "magic" Flask extension imports:
|
Handle "magic" Flask extension imports:
|
||||||
``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
|
``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
|
||||||
|
|||||||
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,10 +10,11 @@ the standard library. The usual way to understand the standard library is the
|
|||||||
compiled module that returns the types for C-builtins.
|
compiled module that returns the types for C-builtins.
|
||||||
"""
|
"""
|
||||||
import parso
|
import parso
|
||||||
|
import os
|
||||||
|
|
||||||
from jedi._compatibility import force_unicode
|
from jedi._compatibility import force_unicode, Parameter
|
||||||
from jedi.plugins.base import BasePlugin
|
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
|
from jedi.evaluate.utils import safe_property
|
||||||
from jedi.evaluate.helpers import get_str_or_none
|
from jedi.evaluate.helpers import get_str_or_none
|
||||||
from jedi.evaluate.arguments import ValuesArguments, \
|
from jedi.evaluate.arguments import ValuesArguments, \
|
||||||
repack_with_argument_clinic, AbstractArguments, TreeArgumentsWrapper
|
repack_with_argument_clinic, AbstractArguments, TreeArgumentsWrapper
|
||||||
@@ -24,11 +25,16 @@ from jedi.evaluate.base_context import ContextualizedNode, \
|
|||||||
NO_CONTEXTS, ContextSet, ContextWrapper, LazyContextWrapper
|
NO_CONTEXTS, ContextSet, ContextWrapper, LazyContextWrapper
|
||||||
from jedi.evaluate.context import ClassContext, ModuleContext, \
|
from jedi.evaluate.context import ClassContext, ModuleContext, \
|
||||||
FunctionExecutionContext
|
FunctionExecutionContext
|
||||||
|
from jedi.evaluate.context.klass import ClassMixin
|
||||||
|
from jedi.evaluate.context.function import FunctionMixin
|
||||||
from jedi.evaluate.context import iterable
|
from jedi.evaluate.context import iterable
|
||||||
from jedi.evaluate.lazy_context import LazyTreeContext, LazyKnownContext, \
|
from jedi.evaluate.lazy_context import LazyTreeContext, LazyKnownContext, \
|
||||||
LazyKnownContexts
|
LazyKnownContexts
|
||||||
|
from jedi.evaluate.names import ContextName, BaseTreeParamName
|
||||||
from jedi.evaluate.syntax_tree import is_string
|
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.
|
# Copied from Python 3.6's stdlib.
|
||||||
@@ -98,20 +104,22 @@ _NAMEDTUPLE_FIELD_TEMPLATE = '''\
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
class StdlibPlugin(BasePlugin):
|
def execute(callback):
|
||||||
def execute(self, callback):
|
|
||||||
def wrapper(context, arguments):
|
def wrapper(context, arguments):
|
||||||
|
def call():
|
||||||
|
return callback(context, arguments=arguments)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
obj_name = context.name.string_name
|
obj_name = context.name.string_name
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if context.parent_context == self._evaluator.builtins_module:
|
if context.parent_context == context.evaluator.builtins_module:
|
||||||
module_name = 'builtins'
|
module_name = 'builtins'
|
||||||
elif context.parent_context is not None and context.parent_context.is_module():
|
elif context.parent_context is not None and context.parent_context.is_module():
|
||||||
module_name = context.parent_context.py__name__()
|
module_name = context.parent_context.py__name__()
|
||||||
else:
|
else:
|
||||||
return callback(context, arguments=arguments)
|
return call()
|
||||||
|
|
||||||
if isinstance(context, BoundMethod):
|
if isinstance(context, BoundMethod):
|
||||||
if module_name == 'builtins':
|
if module_name == 'builtins':
|
||||||
@@ -119,13 +127,14 @@ class StdlibPlugin(BasePlugin):
|
|||||||
if context.class_context.py__name__() == 'property':
|
if context.class_context.py__name__() == 'property':
|
||||||
return builtins_property(
|
return builtins_property(
|
||||||
context,
|
context,
|
||||||
arguments=arguments
|
arguments=arguments,
|
||||||
|
callback=call,
|
||||||
)
|
)
|
||||||
elif context.py__name__() in ('deleter', 'getter', 'setter'):
|
elif context.py__name__() in ('deleter', 'getter', 'setter'):
|
||||||
if context.class_context.py__name__() == 'property':
|
if context.class_context.py__name__() == 'property':
|
||||||
return ContextSet([context.instance])
|
return ContextSet([context.instance])
|
||||||
|
|
||||||
return callback(context, arguments=arguments)
|
return call()
|
||||||
|
|
||||||
# for now we just support builtin functions.
|
# for now we just support builtin functions.
|
||||||
try:
|
try:
|
||||||
@@ -133,8 +142,8 @@ class StdlibPlugin(BasePlugin):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
return func(context, arguments=arguments)
|
return func(context, arguments=arguments, callback=call)
|
||||||
return callback(context, arguments=arguments)
|
return call()
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
@@ -149,15 +158,18 @@ def _follow_param(evaluator, arguments, index):
|
|||||||
|
|
||||||
|
|
||||||
def argument_clinic(string, want_obj=False, want_context=False,
|
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.
|
Works like Argument Clinic (PEP 436), to validate function params.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def f(func):
|
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):
|
def wrapper(obj, *args, **kwargs):
|
||||||
arguments = kwargs.pop('arguments')
|
arguments = kwargs.pop('arguments')
|
||||||
|
callback = kwargs.pop('callback')
|
||||||
assert not kwargs # Python 2...
|
assert not kwargs # Python 2...
|
||||||
debug.dbg('builtin start %s' % obj, color='MAGENTA')
|
debug.dbg('builtin start %s' % obj, color='MAGENTA')
|
||||||
result = NO_CONTEXTS
|
result = NO_CONTEXTS
|
||||||
@@ -169,6 +181,8 @@ def argument_clinic(string, want_obj=False, want_context=False,
|
|||||||
kwargs['evaluator'] = obj.evaluator
|
kwargs['evaluator'] = obj.evaluator
|
||||||
if want_arguments:
|
if want_arguments:
|
||||||
kwargs['arguments'] = arguments
|
kwargs['arguments'] = arguments
|
||||||
|
if want_callback:
|
||||||
|
kwargs['callback'] = callback
|
||||||
result = func(*args, **kwargs)
|
result = func(*args, **kwargs)
|
||||||
debug.dbg('builtin end: %s', result, color='MAGENTA')
|
debug.dbg('builtin end: %s', result, color='MAGENTA')
|
||||||
return result
|
return result
|
||||||
@@ -378,6 +392,9 @@ class ClassMethodGet(AttributeOverwrite, ContextWrapper):
|
|||||||
self._class = klass
|
self._class = klass
|
||||||
self._function = function
|
self._function = function
|
||||||
|
|
||||||
|
def get_signatures(self):
|
||||||
|
return self._function.get_signatures()
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
return self._wrapped_context
|
return self._wrapped_context
|
||||||
|
|
||||||
@@ -405,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.
|
Implementation of the namedtuple function.
|
||||||
|
|
||||||
@@ -428,14 +445,16 @@ def collections_namedtuple(obj, arguments):
|
|||||||
if not param_contexts:
|
if not param_contexts:
|
||||||
return NO_CONTEXTS
|
return NO_CONTEXTS
|
||||||
_fields = list(param_contexts)[0]
|
_fields = list(param_contexts)[0]
|
||||||
if isinstance(_fields, compiled.CompiledValue):
|
string = get_str_or_none(_fields)
|
||||||
fields = force_unicode(_fields.get_safe_value()).replace(',', ' ').split()
|
if string is not None:
|
||||||
|
fields = force_unicode(string).replace(',', ' ').split()
|
||||||
elif isinstance(_fields, iterable.Sequence):
|
elif isinstance(_fields, iterable.Sequence):
|
||||||
fields = [
|
fields = [
|
||||||
force_unicode(v.get_safe_value())
|
force_unicode(get_str_or_none(v))
|
||||||
for lazy_context in _fields.py__iter__()
|
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:
|
else:
|
||||||
return NO_CONTEXTS
|
return NO_CONTEXTS
|
||||||
|
|
||||||
@@ -471,17 +490,49 @@ class PartialObject(object):
|
|||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
return getattr(self._actual_context, name)
|
return getattr(self._actual_context, name)
|
||||||
|
|
||||||
def py__call__(self, arguments):
|
def _get_function(self, unpacked_arguments):
|
||||||
key, lazy_context = next(self._arguments.unpack(), (None, None))
|
key, lazy_context = next(unpacked_arguments, (None, None))
|
||||||
if key is not None or lazy_context is None:
|
if key is not None or lazy_context is None:
|
||||||
debug.warning("Partial should have a proper function %s", self._arguments)
|
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 NO_CONTEXTS
|
||||||
|
|
||||||
return lazy_context.infer().execute(
|
return func.execute(
|
||||||
MergedPartialArguments(self._arguments, arguments)
|
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):
|
class MergedPartialArguments(AbstractArguments):
|
||||||
def __init__(self, partial_arguments, call_arguments):
|
def __init__(self, partial_arguments, call_arguments):
|
||||||
self._partial_arguments = partial_arguments
|
self._partial_arguments = partial_arguments
|
||||||
@@ -498,7 +549,7 @@ class MergedPartialArguments(AbstractArguments):
|
|||||||
yield key_lazy_context
|
yield key_lazy_context
|
||||||
|
|
||||||
|
|
||||||
def functools_partial(obj, arguments):
|
def functools_partial(obj, arguments, callback):
|
||||||
return ContextSet(
|
return ContextSet(
|
||||||
PartialObject(instance, arguments)
|
PartialObject(instance, arguments)
|
||||||
for instance in obj.py__call__(arguments)
|
for instance in obj.py__call__(arguments)
|
||||||
@@ -519,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):
|
class ItemGetterCallable(ContextWrapper):
|
||||||
def __init__(self, instance, args_context_set):
|
def __init__(self, instance, args_context_set):
|
||||||
super(ItemGetterCallable, self).__init__(instance)
|
super(ItemGetterCallable, self).__init__(instance)
|
||||||
@@ -544,6 +655,33 @@ class ItemGetterCallable(ContextWrapper):
|
|||||||
return context_set
|
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)
|
@argument_clinic('*args, /', want_obj=True, want_arguments=True)
|
||||||
def _operator_itemgetter(args_context_set, obj, arguments):
|
def _operator_itemgetter(args_context_set, obj, arguments):
|
||||||
return ContextSet([
|
return ContextSet([
|
||||||
@@ -552,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 = {
|
_implemented = {
|
||||||
'builtins': {
|
'builtins': {
|
||||||
'getattr': builtins_getattr,
|
'getattr': builtins_getattr,
|
||||||
@@ -569,15 +745,15 @@ _implemented = {
|
|||||||
'deepcopy': _return_first_param,
|
'deepcopy': _return_first_param,
|
||||||
},
|
},
|
||||||
'json': {
|
'json': {
|
||||||
'load': lambda obj, arguments: NO_CONTEXTS,
|
'load': lambda obj, arguments, callback: NO_CONTEXTS,
|
||||||
'loads': lambda obj, arguments: NO_CONTEXTS,
|
'loads': lambda obj, arguments, callback: NO_CONTEXTS,
|
||||||
},
|
},
|
||||||
'collections': {
|
'collections': {
|
||||||
'namedtuple': collections_namedtuple,
|
'namedtuple': collections_namedtuple,
|
||||||
},
|
},
|
||||||
'functools': {
|
'functools': {
|
||||||
'partial': functools_partial,
|
'partial': functools_partial,
|
||||||
'wraps': _return_first_param,
|
'wraps': _functools_wraps,
|
||||||
},
|
},
|
||||||
'_weakref': {
|
'_weakref': {
|
||||||
'proxy': _return_first_param,
|
'proxy': _return_first_param,
|
||||||
@@ -597,10 +773,63 @@ _implemented = {
|
|||||||
# The _alias function just leads to some annoying type inference.
|
# The _alias function just leads to some annoying type inference.
|
||||||
# Therefore, just make it return nothing, which leads to the stubs
|
# Therefore, just make it return nothing, which leads to the stubs
|
||||||
# being used instead. This only matters for 3.7+.
|
# being used instead. This only matters for 3.7+.
|
||||||
'_alias': lambda obj, arguments: NO_CONTEXTS,
|
'_alias': lambda obj, arguments, callback: NO_CONTEXTS,
|
||||||
},
|
},
|
||||||
'dataclasses': {
|
'dataclasses': {
|
||||||
# For now this works at least better than Jedi trying to understand it.
|
# For now this works at least better than Jedi trying to understand it.
|
||||||
'dataclass': lambda obj, arguments: NO_CONTEXTS,
|
'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 os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from parso import split_lines
|
|
||||||
|
|
||||||
from jedi import Interpreter
|
from jedi import Interpreter
|
||||||
from jedi.api.helpers import get_on_completion_name
|
|
||||||
|
|
||||||
|
|
||||||
READLINE_DEBUG = False
|
READLINE_DEBUG = False
|
||||||
@@ -86,23 +83,18 @@ def setup_readline(namespace_module=__main__):
|
|||||||
logging.debug("Start REPL completion: " + repr(text))
|
logging.debug("Start REPL completion: " + repr(text))
|
||||||
interpreter = Interpreter(text, [namespace_module.__dict__])
|
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()
|
completions = interpreter.completions()
|
||||||
logging.debug("REPL completions: %s", 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:
|
except:
|
||||||
logging.error("REPL Completion error:\n" + traceback.format_exc())
|
logging.error("REPL Completion error:\n" + traceback.format_exc())
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
sys.path.pop(0)
|
sys.path.pop(0)
|
||||||
|
|
||||||
self.matches = [before + c.name_with_symbols for c in completions]
|
|
||||||
try:
|
try:
|
||||||
return self.matches[state]
|
return self.matches[state]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import recurse_class2
|
from . import recurse_class2
|
||||||
|
|
||||||
class C(recurse_class2.C):
|
class C(recurse_class2.C):
|
||||||
def a(self):
|
def a(self):
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import recurse_class1
|
from . import recurse_class1
|
||||||
|
|
||||||
class C(recurse_class1.C):
|
class C(recurse_class1.C):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -165,3 +165,19 @@ def keyword_only(a: str, *, b: str):
|
|||||||
a.startswi
|
a.startswi
|
||||||
#? ['startswith']
|
#? ['startswith']
|
||||||
b.startswi
|
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['']
|
||||||
|
|||||||
@@ -67,6 +67,18 @@ class X(): pass
|
|||||||
#? type
|
#? type
|
||||||
type(X)
|
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():
|
if os.path.isfile():
|
||||||
#? ['abspath']
|
#? ['abspath']
|
||||||
fails = os.path.abspath
|
fails = os.path.abspath
|
||||||
@@ -290,3 +302,31 @@ class Test(metaclass=Meta):
|
|||||||
result = super(Test, self).test_function()
|
result = super(Test, self).test_function()
|
||||||
#? []
|
#? []
|
||||||
result.
|
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
|
||||||
|
|||||||
@@ -315,7 +315,8 @@ def test_signature_is_definition(Script):
|
|||||||
|
|
||||||
# Now compare all the attributes that a CallSignature must also have.
|
# Now compare all the attributes that a CallSignature must also have.
|
||||||
for attr_name in dir(definition):
|
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:
|
if attr_name.startswith('_') or attr_name in dont_scan:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -442,3 +442,20 @@ def test_builtin_module_with_path(Script):
|
|||||||
assert semlock.name == 'SemLock'
|
assert semlock.name == 'SemLock'
|
||||||
assert semlock.line is None
|
assert semlock.line is None
|
||||||
assert semlock.column 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
|
import sys
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from ..helpers import root_dir
|
||||||
|
|
||||||
|
|
||||||
def test_in_whitespace(Script):
|
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):
|
def test_loading_unicode_files_with_bad_global_charset(Script, monkeypatch, tmpdir):
|
||||||
dirname = str(tmpdir.mkdir('jedi-test'))
|
dirname = str(tmpdir.mkdir('jedi-test'))
|
||||||
filename1 = os.path.join(dirname, 'test1.py')
|
filename1 = join(dirname, 'test1.py')
|
||||||
filename2 = os.path.join(dirname, 'test2.py')
|
filename2 = join(dirname, 'test2.py')
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
data = "# coding: latin-1\nfoo = 'm\xf6p'\n"
|
data = "# coding: latin-1\nfoo = 'm\xf6p'\n"
|
||||||
else:
|
else:
|
||||||
@@ -156,3 +157,111 @@ def test_with_stmt_error_recovery(Script):
|
|||||||
)
|
)
|
||||||
def test_keyword_completion(Script, code, has_keywords):
|
def test_keyword_completion(Script, code, has_keywords):
|
||||||
assert has_keywords == any(x.is_keyword for x in Script(code).completions())
|
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
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import pytest
|
|||||||
|
|
||||||
import jedi
|
import jedi
|
||||||
from jedi._compatibility import is_py3, py_version
|
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:
|
if py_version > 30:
|
||||||
def exec_(source, global_map):
|
def exec_(source, global_map):
|
||||||
@@ -197,7 +197,13 @@ def test_getitem_side_effects():
|
|||||||
_assert_interpreter_complete('foo["asdf"].upper', locals(), ['upper'])
|
_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 = []
|
lst = []
|
||||||
class Foo3:
|
class Foo3:
|
||||||
@property
|
@property
|
||||||
@@ -209,11 +215,14 @@ def test_property_error_oldstyle():
|
|||||||
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
|
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
|
||||||
_assert_interpreter_complete('foo.bar.baz', locals(), [])
|
_assert_interpreter_complete('foo.bar.baz', locals(), [])
|
||||||
|
|
||||||
|
if allow_descriptor_access_or_not:
|
||||||
|
assert lst == [1, 1]
|
||||||
|
else:
|
||||||
# There should not be side effects
|
# There should not be side effects
|
||||||
assert lst == []
|
assert lst == []
|
||||||
|
|
||||||
|
|
||||||
def test_property_error_newstyle():
|
def test_property_error_newstyle(allow_descriptor_access_or_not):
|
||||||
lst = []
|
lst = []
|
||||||
class Foo3(object):
|
class Foo3(object):
|
||||||
@property
|
@property
|
||||||
@@ -225,10 +234,25 @@ def test_property_error_newstyle():
|
|||||||
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
|
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
|
||||||
_assert_interpreter_complete('foo.bar.baz', locals(), [])
|
_assert_interpreter_complete('foo.bar.baz', locals(), [])
|
||||||
|
|
||||||
|
if allow_descriptor_access_or_not:
|
||||||
|
assert lst == [1, 1]
|
||||||
|
else:
|
||||||
# There should not be side effects
|
# There should not be side effects
|
||||||
assert lst == []
|
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 test_param_completion():
|
||||||
def foo(bar):
|
def foo(bar):
|
||||||
pass
|
pass
|
||||||
@@ -275,6 +299,7 @@ def test_completion_param_annotations():
|
|||||||
assert d.name == 'bytes'
|
assert d.name == 'bytes'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
|
||||||
def test_keyword_argument():
|
def test_keyword_argument():
|
||||||
def f(some_keyword_argument):
|
def f(some_keyword_argument):
|
||||||
pass
|
pass
|
||||||
@@ -439,3 +464,44 @@ def test__wrapped__():
|
|||||||
c, = jedi.Interpreter('syslogs_to_df', [locals()]).completions()
|
c, = jedi.Interpreter('syslogs_to_df', [locals()]).completions()
|
||||||
# Apparently the function starts on the line where the decorator starts.
|
# Apparently the function starts on the line where the decorator starts.
|
||||||
assert c.line == syslogs_to_df.__wrapped__.__code__.co_firstlineno + 1
|
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
|
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):
|
def test_django_default_project(Script):
|
||||||
@@ -13,3 +14,11 @@ def test_django_default_project(Script):
|
|||||||
c, = script.completions()
|
c, = script.completions()
|
||||||
assert c.name == "SomeModel"
|
assert c.name == "SomeModel"
|
||||||
assert script._evaluator.project._django is True
|
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
|
||||||
|
|||||||
74
test/test_api/test_signatures.py
Normal file
74
test/test_api/test_signatures.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def test_staticmethod(Script):
|
||||||
|
s, = Script('staticmethod(').call_signatures()
|
||||||
|
assert s.to_string() == 'staticmethod(f: Callable)'
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
from jedi._compatibility import force_unicode
|
||||||
|
|
||||||
|
|
||||||
def test_module_attributes(Script):
|
def test_module_attributes(Script):
|
||||||
def_, = Script('__name__').completions()
|
def_, = Script('__name__').completions()
|
||||||
assert def_.name == '__name__'
|
assert def_.name == '__name__'
|
||||||
@@ -5,3 +8,14 @@ def test_module_attributes(Script):
|
|||||||
assert def_.column is None
|
assert def_.column is None
|
||||||
str_, = def_.infer()
|
str_, = def_.infer()
|
||||||
assert str_.name == 'str'
|
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 jedi
|
||||||
import pytest
|
import pytest
|
||||||
from ..helpers import unittest
|
from ..helpers import unittest
|
||||||
|
import sys
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import numpydoc # NOQA
|
import numpydoc # NOQA
|
||||||
@@ -21,6 +22,11 @@ except ImportError:
|
|||||||
else:
|
else:
|
||||||
numpy_unavailable = False
|
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):
|
def test_function_doc(Script):
|
||||||
defs = Script("""
|
defs = Script("""
|
||||||
@@ -385,3 +391,26 @@ def test_numpy_comp_returns():
|
|||||||
)
|
)
|
||||||
names = [c.name for c in jedi.Script(s).completions()]
|
names = [c.name for c in jedi.Script(s).completions()]
|
||||||
assert 'diagonal' in names
|
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'
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import pytest
|
from textwrap import dedent
|
||||||
from operator import ge, lt
|
from operator import ge, lt
|
||||||
|
import re
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from jedi.evaluate.gradual.conversion import _stub_to_python_context_set
|
from jedi.evaluate.gradual.conversion import _stub_to_python_context_set
|
||||||
|
|
||||||
@@ -34,6 +37,31 @@ def test_compiled_signature(Script, environment, code, sig, names, op, version):
|
|||||||
assert [n.string_name for n in signature.get_param_names()] == names
|
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(
|
@pytest.mark.parametrize(
|
||||||
'code, expected', [
|
'code, expected', [
|
||||||
('def f(a, * args, x): pass\n f(', 'f(a, *args, x)'),
|
('def f(a, * args, x): pass\n f(', 'f(a, *args, x)'),
|
||||||
@@ -41,6 +69,16 @@ def test_compiled_signature(Script, environment, code, sig, names, op, version):
|
|||||||
('def f(*, x= 3,**kwargs): pass\n f(', 'f(*, x=3, **kwargs)'),
|
('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(x,/,y,* ,z): pass\n f(', 'f(x, /, y, *, z)'),
|
||||||
('def f(a, /, *, x=3, **kwargs): pass\n f(', 'f(a, /, *, x=3, **kwargs)'),
|
('def 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):
|
def test_tree_signature(Script, environment, code, expected):
|
||||||
@@ -48,15 +86,183 @@ def test_tree_signature(Script, environment, code, expected):
|
|||||||
if environment.version_info < (3, 8):
|
if environment.version_info < (3, 8):
|
||||||
pytest.skip()
|
pytest.skip()
|
||||||
|
|
||||||
|
if expected is None:
|
||||||
|
assert not Script(code).call_signatures()
|
||||||
|
else:
|
||||||
sig, = Script(code).call_signatures()
|
sig, = Script(code).call_signatures()
|
||||||
assert expected == sig._signature.to_string()
|
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):
|
def test_pow_signature(Script):
|
||||||
# See github #1357
|
# See github #1357
|
||||||
sigs = Script('pow(').call_signatures()
|
sigs = Script('pow(').call_signatures()
|
||||||
strings = {sig._signature.to_string() for sig in sigs}
|
strings = {sig.to_string() for sig in sigs}
|
||||||
assert strings == {'pow(x: float, y: float, z: float, /) -> float',
|
assert strings == {'pow(x: float, y: float, z: float, /) -> float',
|
||||||
'pow(x: float, y: float, /) -> float',
|
'pow(x: float, y: float, /) -> float',
|
||||||
'pow(x: int, y: int, z: int, /) -> Any',
|
'pow(x: int, y: int, z: int, /) -> Any',
|
||||||
'pow(x: int, y: 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