mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-07 06:24:27 +08:00
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/evaluate/compiled/subprocess/__main__.py
|
||||
jedi/__main__.py
|
||||
# Is statically analyzed and not actually executed.
|
||||
jedi/evaluate/jedi_typing.py
|
||||
# For now this is not being used.
|
||||
jedi/refactoring.py
|
||||
|
||||
|
||||
@@ -15,9 +15,6 @@ env:
|
||||
- JEDI_TEST_ENVIRONMENT=37
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- python: pypy
|
||||
- python: 3.8-dev
|
||||
include:
|
||||
- python: 3.6
|
||||
env:
|
||||
|
||||
@@ -52,5 +52,6 @@ Tobias Rzepka (@TobiasRzepka)
|
||||
micbou (@micbou)
|
||||
Dima Gerasimov (@karlicoss) <karlicoss@gmail.com>
|
||||
Max Woerner Chase (@mwchase) <max.chase@gmail.com>
|
||||
Johannes Maria Frank (@jmfrank63) <jmfrank63@gmail.com>
|
||||
|
||||
Note: (@user) means a github user name.
|
||||
|
||||
@@ -3,6 +3,34 @@
|
||||
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)
|
||||
+++++++++++++++++++
|
||||
|
||||
|
||||
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
|
||||
# they should for all Python versions.
|
||||
pytest.skip()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def skip_pre_python37(environment):
|
||||
if environment.version_info < (3, 7):
|
||||
# This if is just needed to avoid that tests ever skip way more than
|
||||
# they should for all Python versions.
|
||||
pytest.skip()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def skip_pre_python35(environment):
|
||||
if environment.version_info < (3, 5):
|
||||
# This if is just needed to avoid that tests ever skip way more than
|
||||
# they should for all Python versions.
|
||||
pytest.skip()
|
||||
|
||||
@@ -140,15 +140,8 @@ Most of the features in PEP-0484 are supported including the typing module
|
||||
(for Python < 3.5 you have to do ``pip install typing`` to use these),
|
||||
and forward references.
|
||||
|
||||
Things that are missing (and this is not an exhaustive list; some of these
|
||||
are planned, others might be hard to implement and provide little worth):
|
||||
You can also use stub files.
|
||||
|
||||
- annotating functions with comments: https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code
|
||||
- understanding ``typing.cast()``
|
||||
- stub files: https://www.python.org/dev/peps/pep-0484/#stub-files
|
||||
- ``typing.Callable``
|
||||
- ``typing.TypeVar``
|
||||
- User defined generic types: https://www.python.org/dev/peps/pep-0484/#user-defined-generic-types
|
||||
|
||||
**Sphinx style**
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ As you see Jedi is pretty simple and allows you to concentrate on writing a
|
||||
good text editor, while still having very good IDE features for Python.
|
||||
"""
|
||||
|
||||
__version__ = '0.14.1'
|
||||
__version__ = '0.15.1'
|
||||
|
||||
from jedi.api import Script, Interpreter, set_debug_function, \
|
||||
preload_module, names
|
||||
@@ -42,3 +42,6 @@ from jedi.api.environment import find_virtualenvs, find_system_environments, \
|
||||
get_default_environment, InvalidPythonEnvironment, create_environment, \
|
||||
get_system_environment
|
||||
from jedi.api.exceptions import InternalError
|
||||
# Finally load the internal plugins. This is only internal.
|
||||
from jedi.plugins import registry
|
||||
del registry
|
||||
|
||||
@@ -27,7 +27,7 @@ from jedi.api import interpreter
|
||||
from jedi.api import helpers
|
||||
from jedi.api.completion import Completion
|
||||
from jedi.api.environment import InterpreterEnvironment
|
||||
from jedi.api.project import get_default_project
|
||||
from jedi.api.project import get_default_project, Project
|
||||
from jedi.evaluate import Evaluator
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate import usages
|
||||
@@ -144,7 +144,6 @@ class Script(object):
|
||||
'(0-%d) for line %d (%r).' % (
|
||||
column, line_len, line, line_string))
|
||||
self._pos = line, column
|
||||
self._path = path
|
||||
|
||||
cache.clear_time_caches()
|
||||
debug.reset_time()
|
||||
@@ -437,6 +436,7 @@ class Interpreter(Script):
|
||||
>>> print(script.completions()[0].name)
|
||||
upper
|
||||
"""
|
||||
_allow_descriptor_getattr_default = True
|
||||
|
||||
def __init__(self, source, namespaces, **kwds):
|
||||
"""
|
||||
@@ -464,8 +464,10 @@ class Interpreter(Script):
|
||||
if not isinstance(environment, InterpreterEnvironment):
|
||||
raise TypeError("The environment needs to be an InterpreterEnvironment subclass.")
|
||||
|
||||
super(Interpreter, self).__init__(source, environment=environment, **kwds)
|
||||
super(Interpreter, self).__init__(source, environment=environment,
|
||||
_project=Project(os.getcwd()), **kwds)
|
||||
self.namespaces = namespaces
|
||||
self._evaluator.allow_descriptor_getattr = self._allow_descriptor_getattr_default
|
||||
|
||||
def _get_module(self):
|
||||
return interpreter.MixedModuleContext(
|
||||
|
||||
@@ -4,10 +4,9 @@ These classes are the much bigger part of the whole API, because they contain
|
||||
the interesting information about completion and goto operations.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from parso.python.tree import search_ancestor
|
||||
|
||||
from jedi import settings
|
||||
from jedi import debug
|
||||
from jedi.evaluate.utils import unite
|
||||
@@ -38,6 +37,10 @@ def defined_names(evaluator, context):
|
||||
return [Definition(evaluator, n) for n in _sort_names_by_start_pos(names)]
|
||||
|
||||
|
||||
def _contexts_to_definitions(contexts):
|
||||
return [Definition(c.evaluator, c.name) for c in contexts]
|
||||
|
||||
|
||||
class BaseDefinition(object):
|
||||
_mapping = {
|
||||
'posixpath': 'os.path',
|
||||
@@ -150,6 +153,9 @@ class BaseDefinition(object):
|
||||
>>> defs[3]
|
||||
'function'
|
||||
|
||||
Valid values for are ``module``, ``class``, ``instance``, ``function``,
|
||||
``param``, ``path`` and ``keyword``.
|
||||
|
||||
"""
|
||||
tree_name = self._name.tree_name
|
||||
resolve = False
|
||||
@@ -330,6 +336,8 @@ class BaseDefinition(object):
|
||||
@memoize_method
|
||||
def params(self):
|
||||
"""
|
||||
Deprecated! Will raise a warning soon. Use get_signatures()[...].params.
|
||||
|
||||
Raises an ``AttributeError`` if the definition is not callable.
|
||||
Otherwise returns a list of `Definition` that represents the params.
|
||||
"""
|
||||
@@ -337,7 +345,10 @@ class BaseDefinition(object):
|
||||
# with overloading.
|
||||
for context in self._name.infer():
|
||||
for signature in context.get_signatures():
|
||||
return [Definition(self._evaluator, n) for n in signature.get_param_names()]
|
||||
return [
|
||||
Definition(self._evaluator, n)
|
||||
for n in signature.get_param_names(resolve_stars=True)
|
||||
]
|
||||
|
||||
if self.type == 'function' or self.type == 'class':
|
||||
# Fallback, if no signatures were defined (which is probably by
|
||||
@@ -384,6 +395,12 @@ class BaseDefinition(object):
|
||||
start_index = max(index - before, 0)
|
||||
return ''.join(lines[start_index:index + after + 1])
|
||||
|
||||
def get_signatures(self):
|
||||
return [Signature(self._evaluator, s) for s in self._name.infer().get_signatures()]
|
||||
|
||||
def execute(self):
|
||||
return _contexts_to_definitions(self._name.infer().execute_evaluated())
|
||||
|
||||
|
||||
class Completion(BaseDefinition):
|
||||
"""
|
||||
@@ -598,14 +615,37 @@ class Definition(BaseDefinition):
|
||||
return hash((self._name.start_pos, self.module_path, self.name, self._evaluator))
|
||||
|
||||
|
||||
class CallSignature(Definition):
|
||||
class Signature(Definition):
|
||||
"""
|
||||
`CallSignature` objects is the return value of `Script.function_definition`.
|
||||
`Signature` objects is the return value of `Script.function_definition`.
|
||||
It knows what functions you are currently in. e.g. `isinstance(` would
|
||||
return the `isinstance` function. without `(` it would return nothing.
|
||||
"""
|
||||
def __init__(self, evaluator, signature):
|
||||
super(Signature, self).__init__(evaluator, signature.name)
|
||||
self._signature = signature
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
"""
|
||||
:return list of ParamDefinition:
|
||||
"""
|
||||
return [ParamDefinition(self._evaluator, n)
|
||||
for n in self._signature.get_param_names(resolve_stars=True)]
|
||||
|
||||
def to_string(self):
|
||||
return self._signature.to_string()
|
||||
|
||||
|
||||
class CallSignature(Signature):
|
||||
"""
|
||||
`CallSignature` objects is the return value of `Script.call_signatures`.
|
||||
It knows what functions you are currently in. e.g. `isinstance(` would
|
||||
return the `isinstance` function with its params. Without `(` it would
|
||||
return nothing.
|
||||
"""
|
||||
def __init__(self, evaluator, signature, call_details):
|
||||
super(CallSignature, self).__init__(evaluator, signature.name)
|
||||
super(CallSignature, self).__init__(evaluator, signature)
|
||||
self._call_details = call_details
|
||||
self._signature = signature
|
||||
|
||||
@@ -615,34 +655,60 @@ class CallSignature(Definition):
|
||||
The Param index of the current call.
|
||||
Returns None if the index cannot be found in the curent call.
|
||||
"""
|
||||
return self._call_details.calculate_index(self._signature.get_param_names())
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
return [Definition(self._evaluator, n) for n in self._signature.get_param_names()]
|
||||
return self._call_details.calculate_index(
|
||||
self._signature.get_param_names(resolve_stars=True)
|
||||
)
|
||||
|
||||
@property
|
||||
def bracket_start(self):
|
||||
"""
|
||||
The indent of the bracket that is responsible for the last function
|
||||
call.
|
||||
The line/column of the bracket that is responsible for the last
|
||||
function call.
|
||||
"""
|
||||
return self._call_details.bracket_leaf.start_pos
|
||||
|
||||
@property
|
||||
def _params_str(self):
|
||||
return ', '.join([p.description[6:]
|
||||
for p in self.params])
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s index=%r params=[%s]>' % (
|
||||
return '<%s: index=%r %s>' % (
|
||||
type(self).__name__,
|
||||
self._name.string_name,
|
||||
self.index,
|
||||
self._params_str,
|
||||
self._signature.to_string(),
|
||||
)
|
||||
|
||||
|
||||
class ParamDefinition(Definition):
|
||||
def infer_default(self):
|
||||
"""
|
||||
:return list of Definition:
|
||||
"""
|
||||
return _contexts_to_definitions(self._name.infer_default())
|
||||
|
||||
def infer_annotation(self, **kwargs):
|
||||
"""
|
||||
:return list of Definition:
|
||||
|
||||
:param execute_annotation: If False, the values are not executed and
|
||||
you get classes instead of instances.
|
||||
"""
|
||||
return _contexts_to_definitions(self._name.infer_annotation(**kwargs))
|
||||
|
||||
def to_string(self):
|
||||
return self._name.to_string()
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
"""
|
||||
Returns an enum instance. Returns the same values as the builtin
|
||||
:py:attr:`inspect.Parameter.kind`.
|
||||
|
||||
No support for Python < 3.4 anymore.
|
||||
"""
|
||||
if sys.version_info < (3, 5):
|
||||
raise NotImplementedError(
|
||||
'Python 2 is end-of-life, the new feature is not available for it'
|
||||
)
|
||||
return self._name.get_kind()
|
||||
|
||||
|
||||
def _format_signatures(context):
|
||||
return '\n'.join(
|
||||
signature.to_string()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import re
|
||||
|
||||
from parso.python.token import PythonTokenTypes
|
||||
from parso.python import tree
|
||||
from parso.tree import search_ancestor, Leaf
|
||||
@@ -7,12 +9,13 @@ from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi.api import classes
|
||||
from jedi.api import helpers
|
||||
from jedi.evaluate import imports
|
||||
from jedi.api import keywords
|
||||
from jedi.api.file_name import file_name_completions
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate.helpers import evaluate_call_of_leaf, parse_dotted_names
|
||||
from jedi.evaluate.filters import get_global_filters
|
||||
from jedi.evaluate.gradual.conversion import convert_contexts
|
||||
from jedi.parser_utils import get_statement_of_position
|
||||
from jedi.parser_utils import get_statement_of_position, cut_value_at_position
|
||||
|
||||
|
||||
def get_call_signature_param_names(call_signatures):
|
||||
@@ -82,7 +85,7 @@ def get_flow_scope_node(module_node, position):
|
||||
|
||||
|
||||
class Completion:
|
||||
def __init__(self, evaluator, module, code_lines, position, call_signatures_method):
|
||||
def __init__(self, evaluator, module, code_lines, position, call_signatures_callback):
|
||||
self._evaluator = evaluator
|
||||
self._module_context = module
|
||||
self._module_node = module.tree_node
|
||||
@@ -92,11 +95,23 @@ class Completion:
|
||||
self._like_name = helpers.get_on_completion_name(self._module_node, code_lines, position)
|
||||
# The actual cursor position is not what we need to calculate
|
||||
# everything. We want the start of the name we're on.
|
||||
self._original_position = position
|
||||
self._position = position[0], position[1] - len(self._like_name)
|
||||
self._call_signatures_method = call_signatures_method
|
||||
self._call_signatures_callback = call_signatures_callback
|
||||
|
||||
def completions(self):
|
||||
completion_names = self._get_context_completions()
|
||||
leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
|
||||
string, start_leaf = _extract_string_while_in_string(leaf, self._position)
|
||||
if string is not None:
|
||||
completions = list(file_name_completions(
|
||||
self._evaluator, self._module_context, start_leaf, string,
|
||||
self._like_name, self._call_signatures_callback,
|
||||
self._code_lines, self._original_position
|
||||
))
|
||||
if completions:
|
||||
return completions
|
||||
|
||||
completion_names = self._get_context_completions(leaf)
|
||||
|
||||
completions = filter_names(self._evaluator, completion_names,
|
||||
self.stack, self._like_name)
|
||||
@@ -105,7 +120,7 @@ class Completion:
|
||||
x.name.startswith('_'),
|
||||
x.name.lower()))
|
||||
|
||||
def _get_context_completions(self):
|
||||
def _get_context_completions(self, leaf):
|
||||
"""
|
||||
Analyzes the context that a completion is made in and decides what to
|
||||
return.
|
||||
@@ -121,19 +136,20 @@ class Completion:
|
||||
"""
|
||||
|
||||
grammar = self._evaluator.grammar
|
||||
self.stack = stack = None
|
||||
|
||||
try:
|
||||
self.stack = stack = helpers.get_stack_at_position(
|
||||
grammar, self._code_lines, self._module_node, self._position
|
||||
grammar, self._code_lines, leaf, self._position
|
||||
)
|
||||
except helpers.OnErrorLeaf as e:
|
||||
self.stack = stack = None
|
||||
if e.error_leaf.value == '.':
|
||||
value = e.error_leaf.value
|
||||
if value == '.':
|
||||
# After ErrorLeaf's that are dots, we will not do any
|
||||
# completions since this probably just confuses the user.
|
||||
return []
|
||||
# If we don't have a context, just use global completion.
|
||||
|
||||
# If we don't have a context, just use global completion.
|
||||
return self._global_completions()
|
||||
|
||||
allowed_transitions = \
|
||||
@@ -210,7 +226,7 @@ class Completion:
|
||||
completion_names += self._get_class_context_completions(is_function=False)
|
||||
|
||||
if 'trailer' in nonterminals:
|
||||
call_signatures = self._call_signatures_method()
|
||||
call_signatures = self._call_signatures_callback()
|
||||
completion_names += get_call_signature_param_names(call_signatures)
|
||||
|
||||
return completion_names
|
||||
@@ -289,3 +305,22 @@ class Completion:
|
||||
# TODO we should probably check here for properties
|
||||
if (name.api_type == 'function') == is_function:
|
||||
yield name
|
||||
|
||||
|
||||
def _extract_string_while_in_string(leaf, position):
|
||||
if leaf.type == 'string':
|
||||
match = re.match(r'^\w*(\'{3}|"{3}|\'|")', leaf.value)
|
||||
quote = match.group(1)
|
||||
if leaf.line == position[0] and position[1] < leaf.column + match.end():
|
||||
return None, None
|
||||
if leaf.end_pos[0] == position[0] and position[1] > leaf.end_pos[1] - len(quote):
|
||||
return None, None
|
||||
return cut_value_at_position(leaf, position)[match.end():], leaf
|
||||
|
||||
leaves = []
|
||||
while leaf is not None and leaf.line == position[0]:
|
||||
if leaf.type == 'error_leaf' and ('"' in leaf.value or "'" in leaf.value):
|
||||
return ''.join(l.get_code() for l in leaves), leaf
|
||||
leaves.insert(0, leaf)
|
||||
leaf = leaf.get_previous_leaf()
|
||||
return None, None
|
||||
|
||||
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]
|
||||
|
||||
|
||||
def _get_code_for_stack(code_lines, module_node, position):
|
||||
leaf = module_node.get_leaf_for_position(position, include_prefixes=True)
|
||||
def _get_code_for_stack(code_lines, leaf, position):
|
||||
# It might happen that we're on whitespace or on a comment. This means
|
||||
# that we would not get the right leaf.
|
||||
if leaf.start_pos >= position:
|
||||
@@ -95,7 +94,7 @@ def _get_code_for_stack(code_lines, module_node, position):
|
||||
return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position)
|
||||
|
||||
|
||||
def get_stack_at_position(grammar, code_lines, module_node, pos):
|
||||
def get_stack_at_position(grammar, code_lines, leaf, pos):
|
||||
"""
|
||||
Returns the possible node names (e.g. import_from, xor_test or yield_stmt).
|
||||
"""
|
||||
@@ -119,7 +118,7 @@ def get_stack_at_position(grammar, code_lines, module_node, pos):
|
||||
yield token
|
||||
|
||||
# The code might be indedented, just remove it.
|
||||
code = dedent(_get_code_for_stack(code_lines, module_node, pos))
|
||||
code = dedent(_get_code_for_stack(code_lines, leaf, pos))
|
||||
# We use a word to tell Jedi when we have reached the start of the
|
||||
# completion.
|
||||
# Use Z as a prefix because it's not part of a number suffix.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import pydoc
|
||||
|
||||
from jedi.evaluate.utils import ignored
|
||||
from jedi.evaluate.names import AbstractNameDefinition
|
||||
from jedi.evaluate.names import AbstractArbitraryName
|
||||
|
||||
try:
|
||||
from pydoc_data import topics as pydoc_topics
|
||||
@@ -19,14 +19,8 @@ def get_operator(evaluator, string, pos):
|
||||
return Keyword(evaluator, string, pos)
|
||||
|
||||
|
||||
class KeywordName(AbstractNameDefinition):
|
||||
class KeywordName(AbstractArbitraryName):
|
||||
api_type = u'keyword'
|
||||
is_context_name = False
|
||||
|
||||
def __init__(self, evaluator, name):
|
||||
self.evaluator = evaluator
|
||||
self.string_name = name
|
||||
self.parent_context = evaluator.builtins_module
|
||||
|
||||
def infer(self):
|
||||
return [Keyword(self.evaluator, self.string_name, (0, 0))]
|
||||
|
||||
@@ -62,8 +62,6 @@ I need to mention now that lazy evaluation is really good because it
|
||||
only *evaluates* what needs to be *evaluated*. All the statements and modules
|
||||
that are not used are just being ignored.
|
||||
"""
|
||||
from functools import partial
|
||||
|
||||
from parso.python import tree
|
||||
import parso
|
||||
from parso import python_bytes_to_unicode
|
||||
@@ -84,14 +82,7 @@ from jedi.evaluate.context import ClassContext, FunctionContext, \
|
||||
from jedi.evaluate.context.iterable import CompForContext
|
||||
from jedi.evaluate.syntax_tree import eval_trailer, eval_expr_stmt, \
|
||||
eval_node, check_tuple_assignments
|
||||
|
||||
|
||||
def _execute(context, arguments):
|
||||
debug.dbg('execute: %s %s', context, arguments)
|
||||
with debug.increase_indent_cm():
|
||||
context_set = context.py__call__(arguments=arguments)
|
||||
debug.dbg('execute result: %s in %s', context_set, context)
|
||||
return context_set
|
||||
from jedi.plugins import plugin_manager
|
||||
|
||||
|
||||
class Evaluator(object):
|
||||
@@ -115,28 +106,26 @@ class Evaluator(object):
|
||||
self.is_analysis = False
|
||||
self.project = project
|
||||
self.access_cache = {}
|
||||
self.allow_descriptor_getattr = False
|
||||
|
||||
self.reset_recursion_limitations()
|
||||
self.allow_different_encoding = True
|
||||
|
||||
# Plugin API
|
||||
from jedi.plugins import plugin_manager
|
||||
plugin_callbacks = plugin_manager.get_callbacks(self)
|
||||
self.execute = plugin_callbacks.decorate('execute', callback=_execute)
|
||||
self._import_module = partial(
|
||||
plugin_callbacks.decorate(
|
||||
'import_module',
|
||||
callback=imports.import_module
|
||||
),
|
||||
self,
|
||||
)
|
||||
|
||||
def import_module(self, import_names, parent_module_context=None,
|
||||
sys_path=None, prefer_stubs=True):
|
||||
if sys_path is None:
|
||||
sys_path = self.get_sys_path()
|
||||
return self._import_module(import_names, parent_module_context,
|
||||
sys_path, prefer_stubs=prefer_stubs)
|
||||
return imports.import_module(self, import_names, parent_module_context,
|
||||
sys_path, prefer_stubs=prefer_stubs)
|
||||
|
||||
@staticmethod
|
||||
@plugin_manager.decorate()
|
||||
def execute(context, arguments):
|
||||
debug.dbg('execute: %s %s', context, arguments)
|
||||
with debug.increase_indent_cm():
|
||||
context_set = context.py__call__(arguments=arguments)
|
||||
debug.dbg('execute result: %s in %s', context_set, context)
|
||||
return context_set
|
||||
|
||||
@property
|
||||
@evaluator_function_cache()
|
||||
|
||||
@@ -11,6 +11,7 @@ from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts, \
|
||||
from jedi.evaluate.names import ParamName, TreeNameDefinition
|
||||
from jedi.evaluate.base_context import NO_CONTEXTS, ContextSet, ContextualizedNode
|
||||
from jedi.evaluate.context import iterable
|
||||
from jedi.evaluate.cache import evaluator_as_method_param_cache
|
||||
from jedi.evaluate.param import get_executed_params_and_issues, ExecutedParam
|
||||
|
||||
|
||||
@@ -35,7 +36,7 @@ class ParamIssue(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def repack_with_argument_clinic(string, keep_arguments_param=False):
|
||||
def repack_with_argument_clinic(string, keep_arguments_param=False, keep_callback_param=False):
|
||||
"""
|
||||
Transforms a function or method with arguments to the signature that is
|
||||
given as an argument clinic notation.
|
||||
@@ -54,6 +55,8 @@ def repack_with_argument_clinic(string, keep_arguments_param=False):
|
||||
arguments = kwargs['arguments']
|
||||
else:
|
||||
arguments = kwargs.pop('arguments')
|
||||
if not keep_arguments_param:
|
||||
kwargs.pop('callback', None)
|
||||
try:
|
||||
args += tuple(_iterate_argument_clinic(
|
||||
context.evaluator,
|
||||
@@ -208,6 +211,11 @@ class TreeArguments(AbstractArguments):
|
||||
self._evaluator = evaluator
|
||||
self.trailer = trailer # Can be None, e.g. in a class definition.
|
||||
|
||||
@classmethod
|
||||
@evaluator_as_method_param_cache()
|
||||
def create_cached(cls, *args, **kwargs):
|
||||
return cls(*args, **kwargs)
|
||||
|
||||
def unpack(self, funcdef=None):
|
||||
named_args = []
|
||||
for star_count, el in unpack_arglist(self.argument_node):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from jedi._compatibility import unicode
|
||||
from jedi.evaluate.compiled.context import CompiledObject, CompiledName, \
|
||||
CompiledObjectFilter, CompiledContextName, create_from_access_path
|
||||
from jedi.evaluate.base_context import ContextWrapper
|
||||
from jedi.evaluate.base_context import ContextWrapper, LazyContextWrapper
|
||||
|
||||
|
||||
def builtin_from_name(evaluator, string):
|
||||
@@ -16,9 +16,9 @@ def builtin_from_name(evaluator, string):
|
||||
return context
|
||||
|
||||
|
||||
class CompiledValue(ContextWrapper):
|
||||
def __init__(self, instance, compiled_obj):
|
||||
super(CompiledValue, self).__init__(instance)
|
||||
class CompiledValue(LazyContextWrapper):
|
||||
def __init__(self, compiled_obj):
|
||||
self.evaluator = compiled_obj.evaluator
|
||||
self._compiled_obj = compiled_obj
|
||||
|
||||
def __getattribute__(self, name):
|
||||
@@ -27,6 +27,11 @@ class CompiledValue(ContextWrapper):
|
||||
return getattr(self._compiled_obj, name)
|
||||
return super(CompiledValue, self).__getattribute__(name)
|
||||
|
||||
def _get_wrapped_context(self):
|
||||
instance, = builtin_from_name(
|
||||
self.evaluator, self._compiled_obj.name.string_name).execute_evaluated()
|
||||
return instance
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._compiled_obj)
|
||||
|
||||
@@ -41,8 +46,7 @@ def create_simple_object(evaluator, obj):
|
||||
evaluator,
|
||||
evaluator.compiled_subprocess.create_simple_object(obj)
|
||||
)
|
||||
instance, = builtin_from_name(evaluator, compiled_obj.name.string_name).execute_evaluated()
|
||||
return CompiledValue(instance, compiled_obj)
|
||||
return CompiledValue(compiled_obj)
|
||||
|
||||
|
||||
def get_string_context_set(evaluator):
|
||||
|
||||
@@ -84,9 +84,14 @@ def safe_getattr(obj, name, default=_sentinel):
|
||||
raise
|
||||
return default
|
||||
else:
|
||||
if type(attr) in ALLOWED_DESCRIPTOR_ACCESS:
|
||||
if isinstance(attr, ALLOWED_DESCRIPTOR_ACCESS):
|
||||
# In case of descriptors that have get methods we cannot return
|
||||
# it's value, because that would mean code execution.
|
||||
# Since it's an isinstance call, code execution is still possible,
|
||||
# but this is not really a security feature, but much more of a
|
||||
# safety feature. Code execution is basically always possible when
|
||||
# a module is imported. This is here so people don't shoot
|
||||
# themselves in the foot.
|
||||
return getattr(obj, name)
|
||||
return attr
|
||||
|
||||
@@ -175,6 +180,18 @@ def _force_unicode_decorator(func):
|
||||
return lambda *args, **kwargs: force_unicode(func(*args, **kwargs))
|
||||
|
||||
|
||||
def get_api_type(obj):
|
||||
if inspect.isclass(obj):
|
||||
return u'class'
|
||||
elif inspect.ismodule(obj):
|
||||
return u'module'
|
||||
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
|
||||
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
|
||||
return u'function'
|
||||
# Everything else...
|
||||
return u'instance'
|
||||
|
||||
|
||||
class DirectObjectAccess(object):
|
||||
def __init__(self, evaluator, obj):
|
||||
self._evaluator = evaluator
|
||||
@@ -328,12 +345,16 @@ class DirectObjectAccess(object):
|
||||
def getattr_paths(self, name, default=_sentinel):
|
||||
try:
|
||||
return_obj = getattr(self._obj, name)
|
||||
except AttributeError:
|
||||
# Happens e.g. in properties of
|
||||
# PyQt4.QtGui.QStyleOptionComboBox.currentText
|
||||
# -> just set it to None
|
||||
except Exception as e:
|
||||
if default is _sentinel:
|
||||
raise
|
||||
if isinstance(e, AttributeError):
|
||||
# Happens e.g. in properties of
|
||||
# PyQt4.QtGui.QStyleOptionComboBox.currentText
|
||||
# -> just set it to None
|
||||
raise
|
||||
# Just in case anything happens, return an AttributeError. It
|
||||
# should not crash.
|
||||
raise AttributeError
|
||||
return_obj = default
|
||||
access = self._create_access(return_obj)
|
||||
if inspect.ismodule(return_obj):
|
||||
@@ -352,16 +373,7 @@ class DirectObjectAccess(object):
|
||||
raise ValueError("Object is type %s and not simple" % type(self._obj))
|
||||
|
||||
def get_api_type(self):
|
||||
obj = self._obj
|
||||
if self.is_class():
|
||||
return u'class'
|
||||
elif inspect.ismodule(obj):
|
||||
return u'module'
|
||||
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
|
||||
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
|
||||
return u'function'
|
||||
# Everything else...
|
||||
return u'instance'
|
||||
return get_api_type(self._obj)
|
||||
|
||||
def get_access_path_tuples(self):
|
||||
accesses = [create_access(self._evaluator, o) for o in self._get_objects_path()]
|
||||
|
||||
@@ -264,6 +264,9 @@ class CompiledObject(Context):
|
||||
def negate(self):
|
||||
return create_from_access_path(self.evaluator, self.access_handle.negate())
|
||||
|
||||
def get_metaclasses(self):
|
||||
return NO_CONTEXTS
|
||||
|
||||
|
||||
class CompiledName(AbstractNameDefinition):
|
||||
def __init__(self, evaluator, parent_context, name):
|
||||
@@ -317,9 +320,6 @@ class SignatureParamName(ParamNameInterface, AbstractNameDefinition):
|
||||
def get_kind(self):
|
||||
return getattr(Parameter, self._signature_param.kind_name)
|
||||
|
||||
def is_keyword_param(self):
|
||||
return self._signature_param
|
||||
|
||||
def infer(self):
|
||||
p = self._signature_param
|
||||
evaluator = self.parent_context.evaluator
|
||||
@@ -377,14 +377,14 @@ class CompiledObjectFilter(AbstractFilter):
|
||||
|
||||
def __init__(self, evaluator, compiled_object, is_instance=False):
|
||||
self._evaluator = evaluator
|
||||
self._compiled_object = compiled_object
|
||||
self.compiled_object = compiled_object
|
||||
self.is_instance = is_instance
|
||||
|
||||
def get(self, name):
|
||||
return self._get(
|
||||
name,
|
||||
lambda: self._compiled_object.access_handle.is_allowed_getattr(name),
|
||||
lambda: self._compiled_object.access_handle.dir(),
|
||||
lambda: self.compiled_object.access_handle.is_allowed_getattr(name),
|
||||
lambda: self.compiled_object.access_handle.dir(),
|
||||
check_has_attribute=True
|
||||
)
|
||||
|
||||
@@ -399,7 +399,7 @@ class CompiledObjectFilter(AbstractFilter):
|
||||
# Always use unicode objects in Python 2 from here.
|
||||
name = force_unicode(name)
|
||||
|
||||
if is_descriptor or not has_attribute:
|
||||
if (is_descriptor and not self._evaluator.allow_descriptor_getattr) or not has_attribute:
|
||||
return [self._get_cached_name(name, is_empty=True)]
|
||||
|
||||
if self.is_instance and name not in dir_callback():
|
||||
@@ -416,7 +416,7 @@ class CompiledObjectFilter(AbstractFilter):
|
||||
def values(self):
|
||||
from jedi.evaluate.compiled import builtin_from_name
|
||||
names = []
|
||||
needs_type_completions, dir_infos = self._compiled_object.access_handle.get_dir_infos()
|
||||
needs_type_completions, dir_infos = self.compiled_object.access_handle.get_dir_infos()
|
||||
for name in dir_infos:
|
||||
names += self._get(
|
||||
name,
|
||||
@@ -431,10 +431,10 @@ class CompiledObjectFilter(AbstractFilter):
|
||||
return names
|
||||
|
||||
def _create_name(self, name):
|
||||
return self.name_class(self._evaluator, self._compiled_object, name)
|
||||
return self.name_class(self._evaluator, self.compiled_object, name)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s>" % (self.__class__.__name__, self._compiled_object)
|
||||
return "<%s: %s>" % (self.__class__.__name__, self.compiled_object)
|
||||
|
||||
|
||||
docstr_defaults = {
|
||||
|
||||
@@ -18,7 +18,7 @@ from jedi.evaluate.context import ModuleContext
|
||||
from jedi.evaluate.cache import evaluator_function_cache
|
||||
from jedi.evaluate.compiled.getattr_static import getattr_static
|
||||
from jedi.evaluate.compiled.access import compiled_objects_cache, \
|
||||
ALLOWED_GETITEM_TYPES
|
||||
ALLOWED_GETITEM_TYPES, get_api_type
|
||||
from jedi.evaluate.compiled.context import create_cached_compiled_object
|
||||
from jedi.evaluate.gradual.conversion import to_stub
|
||||
|
||||
@@ -50,6 +50,11 @@ class MixedObject(ContextWrapper):
|
||||
def get_filters(self, *args, **kwargs):
|
||||
yield MixedObjectFilter(self.evaluator, self)
|
||||
|
||||
def get_signatures(self):
|
||||
# Prefer `inspect.signature` over somehow analyzing Python code. It
|
||||
# should be very precise, especially for stuff like `partial`.
|
||||
return self.compiled_object.get_signatures()
|
||||
|
||||
def py__call__(self, arguments):
|
||||
return (to_stub(self._wrapped_context) or self._wrapped_context).py__call__(arguments)
|
||||
|
||||
@@ -151,6 +156,7 @@ def _get_object_to_check(python_object):
|
||||
|
||||
|
||||
def _find_syntax_node_name(evaluator, python_object):
|
||||
original_object = python_object
|
||||
try:
|
||||
python_object = _get_object_to_check(python_object)
|
||||
path = inspect.getsourcefile(python_object)
|
||||
@@ -214,7 +220,13 @@ def _find_syntax_node_name(evaluator, python_object):
|
||||
# completions at some points but will lead to mostly correct type
|
||||
# inference, because people tend to define a public name in a module only
|
||||
# once.
|
||||
return module_node, names[-1].parent, file_io, code_lines
|
||||
tree_node = names[-1].parent
|
||||
if tree_node.type == 'funcdef' and get_api_type(original_object) == 'instance':
|
||||
# If an instance is given and we're landing on a function (e.g.
|
||||
# partial in 3.5), something is completely wrong and we should not
|
||||
# return that.
|
||||
return None
|
||||
return module_node, tree_node, file_io, code_lines
|
||||
|
||||
|
||||
@compiled_objects_cache('mixed_cache')
|
||||
@@ -254,7 +266,11 @@ def _create(evaluator, access_handle, parent_context, *args):
|
||||
if name is not None:
|
||||
evaluator.module_cache.add(string_names, ContextSet([module_context]))
|
||||
else:
|
||||
assert parent_context.tree_node.get_root_node() == module_node
|
||||
if parent_context.tree_node.get_root_node() != module_node:
|
||||
# This happens e.g. when __module__ is wrong, or when using
|
||||
# TypeVar('foo'), where Jedi uses 'foo' as the name and
|
||||
# Python's TypeVar('foo').__module__ will be typing.
|
||||
return ContextSet({compiled_object})
|
||||
module_context = parent_context.get_root_context()
|
||||
|
||||
tree_contexts = ContextSet({
|
||||
|
||||
15
jedi/evaluate/context/decorator.py
Normal file
15
jedi/evaluate/context/decorator.py
Normal file
@@ -0,0 +1,15 @@
|
||||
'''
|
||||
Decorators are not really contexts, however we need some wrappers to improve
|
||||
docstrings and other things around decorators.
|
||||
'''
|
||||
|
||||
from jedi.evaluate.base_context import ContextWrapper
|
||||
|
||||
|
||||
class Decoratee(ContextWrapper):
|
||||
def __init__(self, wrapped_context, original_context):
|
||||
self._wrapped_context = wrapped_context
|
||||
self._original_context = original_context
|
||||
|
||||
def py__doc__(self):
|
||||
return self._original_context.py__doc__()
|
||||
@@ -100,6 +100,9 @@ class FunctionMixin(object):
|
||||
|
||||
return FunctionExecutionContext(self.evaluator, self.parent_context, self, arguments)
|
||||
|
||||
def get_signatures(self):
|
||||
return [TreeSignature(f) for f in self.get_signature_functions()]
|
||||
|
||||
|
||||
class FunctionContext(use_metaclass(CachedMetaClass, FunctionMixin, FunctionAndClassBase)):
|
||||
"""
|
||||
@@ -147,8 +150,8 @@ class FunctionContext(use_metaclass(CachedMetaClass, FunctionMixin, FunctionAndC
|
||||
def get_default_param_context(self):
|
||||
return self.parent_context
|
||||
|
||||
def get_signatures(self):
|
||||
return [TreeSignature(self)]
|
||||
def get_signature_functions(self):
|
||||
return [self]
|
||||
|
||||
|
||||
class MethodContext(FunctionContext):
|
||||
@@ -371,14 +374,14 @@ class FunctionExecutionContext(TreeContext):
|
||||
class OverloadedFunctionContext(FunctionMixin, ContextWrapper):
|
||||
def __init__(self, function, overloaded_functions):
|
||||
super(OverloadedFunctionContext, self).__init__(function)
|
||||
self.overloaded_functions = overloaded_functions
|
||||
self._overloaded_functions = overloaded_functions
|
||||
|
||||
def py__call__(self, arguments):
|
||||
debug.dbg("Execute overloaded function %s", self._wrapped_context, color='BLUE')
|
||||
function_executions = []
|
||||
context_set = NO_CONTEXTS
|
||||
matched = False
|
||||
for f in self.overloaded_functions:
|
||||
for f in self._overloaded_functions:
|
||||
function_execution = f.get_function_execution(arguments)
|
||||
function_executions.append(function_execution)
|
||||
if function_execution.matches_signature():
|
||||
@@ -393,39 +396,8 @@ class OverloadedFunctionContext(FunctionMixin, ContextWrapper):
|
||||
return NO_CONTEXTS
|
||||
return ContextSet.from_sets(fe.infer() for fe in function_executions)
|
||||
|
||||
def get_signatures(self):
|
||||
return [TreeSignature(f) for f in self.overloaded_functions]
|
||||
|
||||
|
||||
def signature_matches(function_context, arguments):
|
||||
unpacked_arguments = arguments.unpack()
|
||||
key_args = {}
|
||||
for param_node in function_context.tree_node.get_params():
|
||||
while True:
|
||||
key, argument = next(unpacked_arguments, (None, None))
|
||||
if key is None or argument is None:
|
||||
break
|
||||
key_args[key] = argument
|
||||
if argument is None:
|
||||
argument = key_args.pop(param_node.name.value, None)
|
||||
if argument is None:
|
||||
# This signature has an parameter more than arguments were given.
|
||||
return bool(param_node.star_count == 1)
|
||||
|
||||
if param_node.annotation is not None:
|
||||
if param_node.star_count == 2:
|
||||
return False # TODO allow this
|
||||
|
||||
annotation_contexts = function_context.evaluator.eval_element(
|
||||
function_context.get_default_param_context(),
|
||||
param_node.annotation
|
||||
)
|
||||
argument_contexts = argument.infer().py__class__()
|
||||
if not any(c1.is_sub_class_of(c2)
|
||||
for c1 in argument_contexts
|
||||
for c2 in annotation_contexts):
|
||||
return False
|
||||
return True
|
||||
def get_signature_functions(self):
|
||||
return self._overloaded_functions
|
||||
|
||||
|
||||
def _find_overload_functions(context, tree_node):
|
||||
|
||||
@@ -3,6 +3,7 @@ from abc import abstractproperty
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate.compiled.context import CompiledObjectFilter
|
||||
from jedi.evaluate.helpers import contexts_from_qualified_names
|
||||
from jedi.evaluate.filters import AbstractFilter
|
||||
from jedi.evaluate.names import ContextName, TreeNameDefinition
|
||||
@@ -12,7 +13,7 @@ from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.arguments import AnonymousArguments, \
|
||||
ValuesArguments, TreeArgumentsWrapper
|
||||
from jedi.evaluate.context.function import FunctionExecutionContext, \
|
||||
from jedi.evaluate.context.function import \
|
||||
FunctionContext, FunctionMixin, OverloadedFunctionContext
|
||||
from jedi.evaluate.context.klass import ClassContext, apply_py__get__, \
|
||||
ClassFilter
|
||||
@@ -136,11 +137,19 @@ class AbstractInstanceContext(Context):
|
||||
# compiled objects to search for self variables.
|
||||
yield SelfAttributeFilter(self.evaluator, self, cls, origin_scope)
|
||||
|
||||
for cls in class_context.py__mro__():
|
||||
if isinstance(cls, compiled.CompiledObject):
|
||||
yield CompiledInstanceClassFilter(self.evaluator, self, cls)
|
||||
class_filters = class_context.get_filters(
|
||||
search_global=False,
|
||||
origin_scope=origin_scope,
|
||||
is_instance=True,
|
||||
)
|
||||
for f in class_filters:
|
||||
if isinstance(f, ClassFilter):
|
||||
yield InstanceClassFilter(self.evaluator, self, f)
|
||||
elif isinstance(f, CompiledObjectFilter):
|
||||
yield CompiledInstanceClassFilter(self.evaluator, self, f)
|
||||
else:
|
||||
yield InstanceClassFilter(self.evaluator, self, cls, origin_scope)
|
||||
# Propably from the metaclass.
|
||||
yield f
|
||||
|
||||
def py__getitem__(self, index_context_set, contextualized_node):
|
||||
names = self.get_function_slot_names(u'__getitem__')
|
||||
@@ -223,8 +232,8 @@ class AbstractInstanceContext(Context):
|
||||
return class_context
|
||||
|
||||
def get_signatures(self):
|
||||
init_funcs = self.py__getattribute__('__call__')
|
||||
return [sig.bind(self) for sig in init_funcs.get_signatures()]
|
||||
call_funcs = self.py__getattribute__('__call__').py__get__(self, self.class_context)
|
||||
return [s.bind(self) for s in call_funcs.get_signatures()]
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s of %s(%s)>" % (self.__class__.__name__, self.class_context,
|
||||
@@ -328,7 +337,6 @@ class CompiledInstanceName(compiled.CompiledName):
|
||||
name.string_name
|
||||
)
|
||||
self._instance = instance
|
||||
self._class = klass
|
||||
self._class_member_name = name
|
||||
|
||||
@iterator_to_context_set
|
||||
@@ -343,11 +351,10 @@ class CompiledInstanceName(compiled.CompiledName):
|
||||
class CompiledInstanceClassFilter(AbstractFilter):
|
||||
name_class = CompiledInstanceName
|
||||
|
||||
def __init__(self, evaluator, instance, klass):
|
||||
def __init__(self, evaluator, instance, f):
|
||||
self._evaluator = evaluator
|
||||
self._instance = instance
|
||||
self._class = klass
|
||||
self._class_filter = next(klass.get_filters(is_instance=True))
|
||||
self._class_filter = f
|
||||
|
||||
def get(self, name):
|
||||
return self._convert(self._class_filter.get(name))
|
||||
@@ -356,8 +363,9 @@ class CompiledInstanceClassFilter(AbstractFilter):
|
||||
return self._convert(self._class_filter.values())
|
||||
|
||||
def _convert(self, names):
|
||||
klass = self._class_filter.compiled_object
|
||||
return [
|
||||
CompiledInstanceName(self._evaluator, self._instance, self._class, n)
|
||||
CompiledInstanceName(self._evaluator, self._instance, klass, n)
|
||||
for n in names
|
||||
]
|
||||
|
||||
@@ -382,15 +390,6 @@ class BoundMethod(FunctionMixin, ContextWrapper):
|
||||
|
||||
def get_function_execution(self, arguments=None):
|
||||
arguments = self._get_arguments(arguments)
|
||||
|
||||
if isinstance(self._wrapped_context, compiled.CompiledObject):
|
||||
# This is kind of weird, because it's coming from a compiled object
|
||||
# and we're not sure if we want that in the future.
|
||||
# TODO remove?!
|
||||
return FunctionExecutionContext(
|
||||
self.evaluator, self.parent_context, self, arguments
|
||||
)
|
||||
|
||||
return super(BoundMethod, self).get_function_execution(arguments)
|
||||
|
||||
def py__call__(self, arguments):
|
||||
@@ -400,8 +399,14 @@ class BoundMethod(FunctionMixin, ContextWrapper):
|
||||
function_execution = self.get_function_execution(arguments)
|
||||
return function_execution.infer()
|
||||
|
||||
def get_signature_functions(self):
|
||||
return [
|
||||
BoundMethod(self.instance, f)
|
||||
for f in self._wrapped_context.get_signature_functions()
|
||||
]
|
||||
|
||||
def get_signatures(self):
|
||||
return [sig.bind(self) for sig in self._wrapped_context.get_signatures()]
|
||||
return [sig.bind(self) for sig in super(BoundMethod, self).get_signatures()]
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._wrapped_context)
|
||||
@@ -454,15 +459,9 @@ class InstanceClassFilter(AbstractFilter):
|
||||
resulting names in LazyINstanceClassName. The idea is that the class name
|
||||
filtering can be very flexible and always be reflected in instances.
|
||||
"""
|
||||
def __init__(self, evaluator, context, class_context, origin_scope):
|
||||
self._instance = context
|
||||
self._class_context = class_context
|
||||
self._class_filter = next(class_context.get_filters(
|
||||
search_global=False,
|
||||
origin_scope=origin_scope,
|
||||
is_instance=True,
|
||||
))
|
||||
assert isinstance(self._class_filter, ClassFilter), self._class_filter
|
||||
def __init__(self, evaluator, instance, class_filter):
|
||||
self._instance = instance
|
||||
self._class_filter = class_filter
|
||||
|
||||
def get(self, name):
|
||||
return self._convert(self._class_filter.get(name, from_instance=True))
|
||||
@@ -471,10 +470,10 @@ class InstanceClassFilter(AbstractFilter):
|
||||
return self._convert(self._class_filter.values(from_instance=True))
|
||||
|
||||
def _convert(self, names):
|
||||
return [LazyInstanceClassName(self._instance, self._class_context, n) for n in names]
|
||||
return [LazyInstanceClassName(self._instance, self._class_filter.context, n) for n in names]
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s for %s>' % (self.__class__.__name__, self._class_context)
|
||||
return '<%s for %s>' % (self.__class__.__name__, self._class_filter.context)
|
||||
|
||||
|
||||
class SelfAttributeFilter(ClassFilter):
|
||||
|
||||
@@ -20,6 +20,8 @@ It is important to note that:
|
||||
1. Array modfications work only in the current module.
|
||||
2. Jedi only checks Array additions; ``list.pop``, etc are ignored.
|
||||
"""
|
||||
import sys
|
||||
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi._compatibility import force_unicode, is_py3
|
||||
@@ -35,8 +37,8 @@ from jedi.evaluate.utils import safe_property, to_list
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.filters import ParserTreeFilter, LazyAttributeOverwrite, \
|
||||
publish_method
|
||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, \
|
||||
TreeContext, ContextualizedNode, iterate_contexts, HelperContextMixin
|
||||
from jedi.evaluate.base_context import ContextSet, Context, NO_CONTEXTS, \
|
||||
TreeContext, ContextualizedNode, iterate_contexts, HelperContextMixin, _sentinel
|
||||
from jedi.parser_utils import get_sync_comp_fors
|
||||
|
||||
|
||||
@@ -44,6 +46,21 @@ class IterableMixin(object):
|
||||
def py__stop_iteration_returns(self):
|
||||
return ContextSet([compiled.builtin_from_name(self.evaluator, u'None')])
|
||||
|
||||
# At the moment, safe values are simple values like "foo", 1 and not
|
||||
# lists/dicts. Therefore as a small speed optimization we can just do the
|
||||
# default instead of resolving the lazy wrapped contexts, that are just
|
||||
# doing this in the end as well.
|
||||
# This mostly speeds up patterns like `sys.version_info >= (3, 0)` in
|
||||
# typeshed.
|
||||
if sys.version_info[0] == 2:
|
||||
# Python 2...........
|
||||
def get_safe_value(self, default=_sentinel):
|
||||
if default is _sentinel:
|
||||
raise ValueError("There exists no safe value for context %s" % self)
|
||||
return default
|
||||
else:
|
||||
get_safe_value = Context.get_safe_value
|
||||
|
||||
|
||||
class GeneratorBase(LazyAttributeOverwrite, IterableMixin):
|
||||
array_type = None
|
||||
|
||||
@@ -49,6 +49,7 @@ from jedi.evaluate.arguments import unpack_arglist, ValuesArguments
|
||||
from jedi.evaluate.base_context import ContextSet, iterator_to_context_set, \
|
||||
NO_CONTEXTS
|
||||
from jedi.evaluate.context.function import FunctionAndClassBase
|
||||
from jedi.plugins import plugin_manager
|
||||
|
||||
|
||||
def apply_py__get__(context, instance, class_context):
|
||||
@@ -136,8 +137,10 @@ class ClassMixin(object):
|
||||
def is_class(self):
|
||||
return True
|
||||
|
||||
def py__call__(self, arguments):
|
||||
def py__call__(self, arguments=None):
|
||||
from jedi.evaluate.context import TreeInstance
|
||||
if arguments is None:
|
||||
arguments = ValuesArguments([])
|
||||
return ContextSet([TreeInstance(self.evaluator, self.parent_context, self, arguments)])
|
||||
|
||||
def py__class__(self):
|
||||
@@ -191,13 +194,13 @@ class ClassMixin(object):
|
||||
|
||||
def get_filters(self, search_global=False, until_position=None,
|
||||
origin_scope=None, is_instance=False):
|
||||
metaclasses = self.get_metaclasses()
|
||||
if metaclasses:
|
||||
for f in self.get_metaclass_filters(metaclasses):
|
||||
yield f
|
||||
|
||||
if search_global:
|
||||
yield ParserTreeFilter(
|
||||
self.evaluator,
|
||||
context=self,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope
|
||||
)
|
||||
yield self.get_global_filter(until_position, origin_scope)
|
||||
else:
|
||||
for cls in self.py__mro__():
|
||||
if isinstance(cls, compiled.CompiledObject):
|
||||
@@ -214,13 +217,25 @@ class ClassMixin(object):
|
||||
type_ = builtin_from_name(self.evaluator, u'type')
|
||||
assert isinstance(type_, ClassContext)
|
||||
if type_ != self:
|
||||
for instance in type_.py__call__(ValuesArguments([])):
|
||||
for instance in type_.py__call__():
|
||||
instance_filters = instance.get_filters()
|
||||
# Filter out self filters
|
||||
next(instance_filters)
|
||||
next(instance_filters)
|
||||
yield next(instance_filters)
|
||||
|
||||
def get_signatures(self):
|
||||
init_funcs = self.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)):
|
||||
"""
|
||||
@@ -247,12 +262,17 @@ class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBa
|
||||
found.append(type_var)
|
||||
return found
|
||||
|
||||
@evaluator_method_cache(default=())
|
||||
def py__bases__(self):
|
||||
def _get_bases_arguments(self):
|
||||
arglist = self.tree_node.get_super_arglist()
|
||||
if arglist:
|
||||
from jedi.evaluate import arguments
|
||||
args = arguments.TreeArguments(self.evaluator, self.parent_context, arglist)
|
||||
return arguments.TreeArguments(self.evaluator, self.parent_context, arglist)
|
||||
return None
|
||||
|
||||
@evaluator_method_cache(default=())
|
||||
def py__bases__(self):
|
||||
args = self._get_bases_arguments()
|
||||
if args is not None:
|
||||
lst = [value for key, value in args.unpack() if key is None]
|
||||
if lst:
|
||||
return lst
|
||||
@@ -300,6 +320,25 @@ class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBa
|
||||
)])
|
||||
return ContextSet({self})
|
||||
|
||||
def get_signatures(self):
|
||||
init_funcs = self.py__getattribute__('__init__')
|
||||
return [sig.bind(self) for sig in init_funcs.get_signatures()]
|
||||
@plugin_manager.decorate()
|
||||
def get_metaclass_filters(self, metaclass):
|
||||
debug.dbg('Unprocessed metaclass %s', metaclass)
|
||||
return []
|
||||
|
||||
@evaluator_method_cache(default=NO_CONTEXTS)
|
||||
def get_metaclasses(self):
|
||||
args = self._get_bases_arguments()
|
||||
if args is not None:
|
||||
m = [value for key, value in args.unpack() if key == 'metaclass']
|
||||
metaclasses = ContextSet.from_sets(lazy_context.infer() for lazy_context in m)
|
||||
metaclasses = ContextSet(m for m in metaclasses if m.is_class())
|
||||
if metaclasses:
|
||||
return metaclasses
|
||||
|
||||
for lazy_base in self.py__bases__():
|
||||
for context in lazy_base.infer():
|
||||
if context.is_class():
|
||||
contexts = context.get_metaclasses()
|
||||
if contexts:
|
||||
return contexts
|
||||
return NO_CONTEXTS
|
||||
|
||||
@@ -9,6 +9,8 @@ from jedi.evaluate import compiled
|
||||
from jedi.evaluate.base_context import TreeContext
|
||||
from jedi.evaluate.names import SubModuleName
|
||||
from jedi.evaluate.helpers import contexts_from_qualified_names
|
||||
from jedi.evaluate.compiled import create_simple_object
|
||||
from jedi.evaluate.base_context import ContextSet
|
||||
|
||||
|
||||
class _ModuleAttributeName(AbstractNameDefinition):
|
||||
@@ -17,11 +19,20 @@ class _ModuleAttributeName(AbstractNameDefinition):
|
||||
"""
|
||||
api_type = u'instance'
|
||||
|
||||
def __init__(self, parent_module, string_name):
|
||||
def __init__(self, parent_module, string_name, string_value=None):
|
||||
self.parent_context = parent_module
|
||||
self.string_name = string_name
|
||||
self._string_value = string_value
|
||||
|
||||
def infer(self):
|
||||
if self._string_value is not None:
|
||||
s = self._string_value
|
||||
if self.parent_context.evaluator.environment.version_info.major == 2 \
|
||||
and not isinstance(s, bytes):
|
||||
s = s.encode('utf-8')
|
||||
return ContextSet([
|
||||
create_simple_object(self.parent_context.evaluator, s)
|
||||
])
|
||||
return compiled.get_string_context_set(self.parent_context.evaluator)
|
||||
|
||||
|
||||
@@ -132,9 +143,13 @@ class ModuleMixin(SubModuleDictMixin):
|
||||
|
||||
@evaluator_method_cache()
|
||||
def _module_attributes_dict(self):
|
||||
names = ['__file__', '__package__', '__doc__', '__name__']
|
||||
names = ['__package__', '__doc__', '__name__']
|
||||
# All the additional module attributes are strings.
|
||||
return dict((n, _ModuleAttributeName(self, n)) for n in names)
|
||||
dct = dict((n, _ModuleAttributeName(self, n)) for n in names)
|
||||
file = self.py__file__()
|
||||
if file is not None:
|
||||
dct['__file__'] = _ModuleAttributeName(self, '__file__', file)
|
||||
return dct
|
||||
|
||||
def iter_star_filters(self, search_global=False):
|
||||
for star_module in self.star_imports():
|
||||
|
||||
@@ -51,11 +51,8 @@ def _get_numpy_doc_string_cls():
|
||||
global _numpy_doc_string_cache
|
||||
if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)):
|
||||
raise _numpy_doc_string_cache
|
||||
try:
|
||||
from numpydoc.docscrape import NumpyDocString
|
||||
_numpy_doc_string_cache = NumpyDocString
|
||||
except (ImportError, SyntaxError) as e:
|
||||
raise
|
||||
from numpydoc.docscrape import NumpyDocString
|
||||
_numpy_doc_string_cache = NumpyDocString
|
||||
return _numpy_doc_string_cache
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ It works as follows:
|
||||
- execute these calls and check the input.
|
||||
"""
|
||||
|
||||
from parso.python import tree
|
||||
from jedi import settings
|
||||
from jedi import debug
|
||||
from jedi.evaluate.cache import evaluator_function_cache
|
||||
|
||||
@@ -386,7 +386,7 @@ def get_global_filters(evaluator, context, until_position, origin_scope):
|
||||
>>> list(filters[1].values()) # package modules -> Also empty.
|
||||
[]
|
||||
>>> sorted(name.string_name for name in filters[2].values()) # Module attributes
|
||||
['__doc__', '__file__', '__name__', '__package__']
|
||||
['__doc__', '__name__', '__package__']
|
||||
|
||||
Finally, it yields the builtin filter, if `include_builtin` is
|
||||
true (default).
|
||||
@@ -409,5 +409,4 @@ def get_global_filters(evaluator, context, until_position, origin_scope):
|
||||
context = context.parent_context
|
||||
|
||||
# Add builtins to the global scope.
|
||||
for filter in evaluator.builtins_module.get_filters():
|
||||
yield filter
|
||||
yield next(evaluator.builtins_module.get_filters())
|
||||
|
||||
@@ -14,7 +14,9 @@ from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
|
||||
from jedi.evaluate.gradual.typing import TypeVar, LazyGenericClass, \
|
||||
AbstractAnnotatedClass
|
||||
from jedi.evaluate.gradual.typing import GenericClass
|
||||
from jedi.evaluate.helpers import is_string
|
||||
from jedi.evaluate.compiled import builtin_from_name
|
||||
from jedi import debug
|
||||
from jedi import parser_utils
|
||||
|
||||
@@ -106,6 +108,25 @@ def _split_comment_param_declaration(decl_text):
|
||||
|
||||
@evaluator_method_cache()
|
||||
def infer_param(execution_context, param):
|
||||
contexts = _infer_param(execution_context, param)
|
||||
evaluator = execution_context.evaluator
|
||||
if param.star_count == 1:
|
||||
tuple_ = builtin_from_name(evaluator, 'tuple')
|
||||
return ContextSet([GenericClass(
|
||||
tuple_,
|
||||
generics=(contexts,),
|
||||
) for c in contexts])
|
||||
elif param.star_count == 2:
|
||||
dct = builtin_from_name(evaluator, 'dict')
|
||||
return ContextSet([GenericClass(
|
||||
dct,
|
||||
generics=(ContextSet([builtin_from_name(evaluator, 'str')]), contexts),
|
||||
) for c in contexts])
|
||||
pass
|
||||
return contexts
|
||||
|
||||
|
||||
def _infer_param(execution_context, param):
|
||||
"""
|
||||
Infers the type of a function parameter, using type annotations.
|
||||
"""
|
||||
|
||||
@@ -40,7 +40,8 @@ def _stub_to_python_context_set(stub_context, ignore_compiled=False):
|
||||
|
||||
|
||||
def _infer_from_stub(stub_module, qualified_names, ignore_compiled):
|
||||
assert isinstance(stub_module, StubModuleContext), stub_module
|
||||
from jedi.evaluate.compiled.mixed import MixedObject
|
||||
assert isinstance(stub_module, (StubModuleContext, MixedObject)), stub_module
|
||||
non_stubs = stub_module.non_stub_context_set
|
||||
if ignore_compiled:
|
||||
non_stubs = non_stubs.filter(lambda c: not c.is_compiled())
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import re
|
||||
from functools import wraps
|
||||
|
||||
from jedi.file_io import FileIO
|
||||
from jedi._compatibility import FileNotFoundError, cast_path
|
||||
@@ -87,6 +88,7 @@ def _cache_stub_file_map(version_info):
|
||||
|
||||
|
||||
def import_module_decorator(func):
|
||||
@wraps(func)
|
||||
def wrapper(evaluator, import_names, parent_module_context, sys_path, prefer_stubs):
|
||||
try:
|
||||
python_context_set = evaluator.module_cache.get(import_names)
|
||||
|
||||
@@ -10,7 +10,7 @@ from jedi import debug
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.compiled import builtin_from_name
|
||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, Context, \
|
||||
iterator_to_context_set, HelperContextMixin, ContextWrapper
|
||||
iterator_to_context_set, ContextWrapper, LazyContextWrapper
|
||||
from jedi.evaluate.lazy_context import LazyKnownContexts
|
||||
from jedi.evaluate.context.iterable import SequenceLiteralContext
|
||||
from jedi.evaluate.arguments import repack_with_argument_clinic
|
||||
@@ -209,6 +209,9 @@ class _TypingClassMixin(object):
|
||||
self.evaluator.builtins_module.py__getattribute__('object')
|
||||
)]
|
||||
|
||||
def get_metaclasses(self):
|
||||
return []
|
||||
|
||||
|
||||
class TypingClassContextWithIndex(_TypingClassMixin, TypingContextWithIndex, ClassMixin):
|
||||
pass
|
||||
@@ -241,9 +244,9 @@ def _iter_over_arguments(maybe_tuple_context, defining_context):
|
||||
yield ContextSet(resolve_forward_references(context_set))
|
||||
|
||||
|
||||
class TypeAlias(HelperContextMixin):
|
||||
def __init__(self, evaluator, parent_context, origin_tree_name, actual):
|
||||
self.evaluator = evaluator
|
||||
class TypeAlias(LazyContextWrapper):
|
||||
def __init__(self, parent_context, origin_tree_name, actual):
|
||||
self.evaluator = parent_context.evaluator
|
||||
self.parent_context = parent_context
|
||||
self._origin_tree_name = origin_tree_name
|
||||
self._actual = actual # e.g. builtins.list
|
||||
@@ -255,14 +258,10 @@ class TypeAlias(HelperContextMixin):
|
||||
def py__name__(self):
|
||||
return self.name.string_name
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._get_type_alias_class(), name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._actual)
|
||||
|
||||
@evaluator_method_cache()
|
||||
def _get_type_alias_class(self):
|
||||
def _get_wrapped_context(self):
|
||||
module_name, class_name = self._actual.split('.')
|
||||
if self.evaluator.environment.version_info.major == 2 and module_name == 'builtins':
|
||||
module_name = '__builtin__'
|
||||
|
||||
@@ -21,7 +21,7 @@ from jedi._compatibility import (FileNotFoundError, ImplicitNSInfo,
|
||||
force_unicode, unicode)
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi.file_io import KnownContentFileIO, FolderIO, FileIO
|
||||
from jedi.file_io import KnownContentFileIO, FileIO
|
||||
from jedi.parser_utils import get_cached_code_lines
|
||||
from jedi.evaluate import sys_path
|
||||
from jedi.evaluate import helpers
|
||||
@@ -33,6 +33,7 @@ from jedi.evaluate.names import ImportName, SubModuleName
|
||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
|
||||
from jedi.evaluate.gradual.typeshed import import_module_decorator
|
||||
from jedi.evaluate.context.module import iter_module_names
|
||||
from jedi.plugins import plugin_manager
|
||||
|
||||
|
||||
class ModuleCache(object):
|
||||
@@ -371,6 +372,7 @@ class Importer(object):
|
||||
return names
|
||||
|
||||
|
||||
@plugin_manager.decorate()
|
||||
@import_module_decorator
|
||||
def import_module(evaluator, import_names, parent_module_context, sys_path):
|
||||
"""
|
||||
|
||||
@@ -3,7 +3,7 @@ from abc import abstractmethod
|
||||
from parso.tree import search_ancestor
|
||||
|
||||
from jedi._compatibility import Parameter
|
||||
from jedi.evaluate.base_context import ContextSet
|
||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
|
||||
from jedi.cache import memoize_method
|
||||
|
||||
|
||||
@@ -58,6 +58,23 @@ class AbstractNameDefinition(object):
|
||||
return self.parent_context.api_type
|
||||
|
||||
|
||||
class AbstractArbitraryName(AbstractNameDefinition):
|
||||
"""
|
||||
When you e.g. want to complete dicts keys, you probably want to complete
|
||||
string literals, which is not really a name, but for Jedi we use this
|
||||
concept of Name for completions as well.
|
||||
"""
|
||||
is_context_name = False
|
||||
|
||||
def __init__(self, evaluator, string):
|
||||
self.evaluator = evaluator
|
||||
self.string_name = string
|
||||
self.parent_context = evaluator.builtins_module
|
||||
|
||||
def infer(self):
|
||||
return NO_CONTEXTS
|
||||
|
||||
|
||||
class AbstractTreeName(AbstractNameDefinition):
|
||||
def __init__(self, parent_context, tree_name):
|
||||
self.parent_context = parent_context
|
||||
@@ -150,8 +167,18 @@ class TreeNameDefinition(AbstractTreeName):
|
||||
return self._API_TYPES.get(definition.type, 'statement')
|
||||
|
||||
|
||||
class ParamNameInterface(object):
|
||||
api_type = u'param'
|
||||
class _ParamMixin(object):
|
||||
def maybe_positional_argument(self, include_star=True):
|
||||
options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
|
||||
if include_star:
|
||||
options.append(Parameter.VAR_POSITIONAL)
|
||||
return self.get_kind() in options
|
||||
|
||||
def maybe_keyword_argument(self, include_stars=True):
|
||||
options = [Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
|
||||
if include_stars:
|
||||
options.append(Parameter.VAR_KEYWORD)
|
||||
return self.get_kind() in options
|
||||
|
||||
def _kind_string(self):
|
||||
kind = self.get_kind()
|
||||
@@ -161,21 +188,74 @@ class ParamNameInterface(object):
|
||||
return '**'
|
||||
return ''
|
||||
|
||||
|
||||
class ParamNameInterface(_ParamMixin):
|
||||
api_type = u'param'
|
||||
|
||||
def get_kind(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def to_string(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_param(self):
|
||||
# TODO document better where this is used and when. Currently it has
|
||||
# very limited use, but is still in use. It's currently not even
|
||||
# clear what values would be allowed.
|
||||
return None
|
||||
|
||||
class ParamName(ParamNameInterface, AbstractTreeName):
|
||||
def __init__(self, parent_context, tree_name):
|
||||
self.parent_context = parent_context
|
||||
self.tree_name = tree_name
|
||||
@property
|
||||
def star_count(self):
|
||||
kind = self.get_kind()
|
||||
if kind == Parameter.VAR_POSITIONAL:
|
||||
return 1
|
||||
if kind == Parameter.VAR_KEYWORD:
|
||||
return 2
|
||||
return 0
|
||||
|
||||
|
||||
class BaseTreeParamName(ParamNameInterface, AbstractTreeName):
|
||||
annotation_node = None
|
||||
default_node = None
|
||||
|
||||
def to_string(self):
|
||||
output = self._kind_string() + self.string_name
|
||||
annotation = self.annotation_node
|
||||
default = self.default_node
|
||||
if annotation is not None:
|
||||
output += ': ' + annotation.get_code(include_prefix=False)
|
||||
if default is not None:
|
||||
output += '=' + default.get_code(include_prefix=False)
|
||||
return output
|
||||
|
||||
|
||||
class ParamName(BaseTreeParamName):
|
||||
def _get_param_node(self):
|
||||
return search_ancestor(self.tree_name, 'param')
|
||||
|
||||
@property
|
||||
def annotation_node(self):
|
||||
return self._get_param_node().annotation
|
||||
|
||||
def infer_annotation(self, execute_annotation=True):
|
||||
node = self.annotation_node
|
||||
if node is None:
|
||||
return NO_CONTEXTS
|
||||
contexts = self.parent_context.parent_context.eval_node(node)
|
||||
if execute_annotation:
|
||||
contexts = contexts.execute_annotation()
|
||||
return contexts
|
||||
|
||||
def infer_default(self):
|
||||
node = self.default_node
|
||||
if node is None:
|
||||
return NO_CONTEXTS
|
||||
return self.parent_context.parent_context.eval_node(node)
|
||||
|
||||
@property
|
||||
def default_node(self):
|
||||
return self._get_param_node().default
|
||||
|
||||
@property
|
||||
def string_name(self):
|
||||
name = self.tree_name.value
|
||||
@@ -213,15 +293,6 @@ class ParamName(ParamNameInterface, AbstractTreeName):
|
||||
param_appeared = True
|
||||
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):
|
||||
return self.get_param().infer()
|
||||
|
||||
@@ -231,6 +302,17 @@ class ParamName(ParamNameInterface, AbstractTreeName):
|
||||
return params[param_node.position_index]
|
||||
|
||||
|
||||
class ParamNameWrapper(_ParamMixin):
|
||||
def __init__(self, param_name):
|
||||
self._wrapped_param_name = param_name
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._wrapped_param_name, name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._wrapped_param_name)
|
||||
|
||||
|
||||
class ImportName(AbstractNameDefinition):
|
||||
start_pos = (1, 0)
|
||||
_level = 0
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
from jedi._compatibility import Parameter
|
||||
from jedi.cache import memoize_method
|
||||
|
||||
|
||||
class AbstractSignature(object):
|
||||
def __init__(self, context, is_bound=False):
|
||||
self.context = context
|
||||
self.is_bound = is_bound
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.context.name
|
||||
|
||||
@property
|
||||
def annotation_string(self):
|
||||
return ''
|
||||
|
||||
class _SignatureMixin(object):
|
||||
def to_string(self):
|
||||
def param_strings():
|
||||
is_positional = False
|
||||
is_kw_only = False
|
||||
for n in self.get_param_names():
|
||||
for n in self.get_param_names(resolve_stars=True):
|
||||
kind = n.get_kind()
|
||||
is_positional |= kind == Parameter.POSITIONAL_ONLY
|
||||
if is_positional and kind != Parameter.POSITIONAL_ONLY:
|
||||
@@ -42,15 +31,32 @@ class AbstractSignature(object):
|
||||
s += ' -> ' + annotation
|
||||
return s
|
||||
|
||||
def bind(self, context):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_param_names(self):
|
||||
class AbstractSignature(_SignatureMixin):
|
||||
def __init__(self, context, is_bound=False):
|
||||
self.context = context
|
||||
self.is_bound = is_bound
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.context.name
|
||||
|
||||
@property
|
||||
def annotation_string(self):
|
||||
return ''
|
||||
|
||||
def get_param_names(self, resolve_stars=False):
|
||||
param_names = self._function_context.get_param_names()
|
||||
if self.is_bound:
|
||||
return param_names[1:]
|
||||
return param_names
|
||||
|
||||
def bind(self, context):
|
||||
raise NotImplementedError
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s, %s>' % (self.__class__.__name__, self.context, self._function_context)
|
||||
|
||||
|
||||
class TreeSignature(AbstractSignature):
|
||||
def __init__(self, context, function_context=None, is_bound=False):
|
||||
@@ -75,6 +81,14 @@ class TreeSignature(AbstractSignature):
|
||||
return ''
|
||||
return a.get_code(include_prefix=False)
|
||||
|
||||
@memoize_method
|
||||
def get_param_names(self, resolve_stars=False):
|
||||
params = super(TreeSignature, self).get_param_names(resolve_stars=False)
|
||||
if resolve_stars:
|
||||
from jedi.evaluate.star_args import process_params
|
||||
params = process_params(params)
|
||||
return params
|
||||
|
||||
|
||||
class BuiltinSignature(AbstractSignature):
|
||||
def __init__(self, context, return_string, is_bound=False):
|
||||
@@ -92,3 +106,11 @@ class BuiltinSignature(AbstractSignature):
|
||||
def bind(self, context):
|
||||
assert not self.is_bound
|
||||
return BuiltinSignature(context, self._return_string, is_bound=True)
|
||||
|
||||
|
||||
class SignatureWrapper(_SignatureMixin):
|
||||
def __init__(self, wrapped_signature):
|
||||
self._wrapped_signature = wrapped_signature
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._wrapped_signature, name)
|
||||
|
||||
206
jedi/evaluate/star_args.py
Normal file
206
jedi/evaluate/star_args.py
Normal file
@@ -0,0 +1,206 @@
|
||||
"""
|
||||
This module is responsible for evaluating *args and **kwargs for signatures.
|
||||
|
||||
This means for example in this case::
|
||||
|
||||
def foo(a, b, c): ...
|
||||
|
||||
def bar(*args):
|
||||
return foo(1, *args)
|
||||
|
||||
The signature here for bar should be `bar(b, c)` instead of bar(*args).
|
||||
"""
|
||||
|
||||
from jedi._compatibility import Parameter
|
||||
from jedi.evaluate.utils import to_list
|
||||
from jedi.evaluate.names import ParamNameWrapper
|
||||
|
||||
|
||||
def _iter_nodes_for_param(param_name):
|
||||
from parso.python.tree import search_ancestor
|
||||
from jedi.evaluate.arguments import TreeArguments
|
||||
|
||||
execution_context = param_name.parent_context
|
||||
function_node = execution_context.tree_node
|
||||
module_node = function_node.get_root_node()
|
||||
start = function_node.children[-1].start_pos
|
||||
end = function_node.children[-1].end_pos
|
||||
for name in module_node.get_used_names().get(param_name.string_name):
|
||||
if start <= name.start_pos < end:
|
||||
# Is used in the function
|
||||
argument = name.parent
|
||||
if argument.type == 'argument' \
|
||||
and argument.children[0] == '*' * param_name.star_count:
|
||||
# No support for Python <= 3.4 here, but they are end-of-life
|
||||
# anyway
|
||||
trailer = search_ancestor(argument, 'trailer')
|
||||
if trailer is not None: # Make sure we're in a function
|
||||
context = execution_context.create_context(trailer)
|
||||
if _goes_to_param_name(param_name, context, name):
|
||||
contexts = _to_callables(context, trailer)
|
||||
|
||||
args = TreeArguments.create_cached(
|
||||
execution_context.evaluator,
|
||||
context=context,
|
||||
argument_node=trailer.children[1],
|
||||
trailer=trailer,
|
||||
)
|
||||
for c in contexts:
|
||||
yield c, args
|
||||
else:
|
||||
assert False
|
||||
|
||||
|
||||
def _goes_to_param_name(param_name, context, potential_name):
|
||||
if potential_name.type != 'name':
|
||||
return False
|
||||
from jedi.evaluate.names import TreeNameDefinition
|
||||
found = TreeNameDefinition(context, potential_name).goto()
|
||||
return any(param_name.parent_context == p.parent_context
|
||||
and param_name.start_pos == p.start_pos
|
||||
for p in found)
|
||||
|
||||
|
||||
def _to_callables(context, trailer):
|
||||
from jedi.evaluate.syntax_tree import eval_trailer
|
||||
|
||||
atom_expr = trailer.parent
|
||||
index = atom_expr.children[0] == 'await'
|
||||
# Eval atom first
|
||||
contexts = context.eval_node(atom_expr.children[index])
|
||||
for trailer2 in atom_expr.children[index + 1:]:
|
||||
if trailer == trailer2:
|
||||
break
|
||||
contexts = eval_trailer(context, contexts, trailer2)
|
||||
return contexts
|
||||
|
||||
|
||||
def _remove_given_params(arguments, param_names):
|
||||
count = 0
|
||||
used_keys = set()
|
||||
for key, _ in arguments.unpack():
|
||||
if key is None:
|
||||
count += 1
|
||||
else:
|
||||
used_keys.add(key)
|
||||
|
||||
for p in param_names:
|
||||
if count and p.maybe_positional_argument():
|
||||
count -= 1
|
||||
continue
|
||||
if p.string_name in used_keys and p.maybe_keyword_argument():
|
||||
continue
|
||||
yield p
|
||||
|
||||
|
||||
@to_list
|
||||
def process_params(param_names, star_count=3): # default means both * and **
|
||||
used_names = set()
|
||||
arg_callables = []
|
||||
kwarg_callables = []
|
||||
|
||||
kw_only_names = []
|
||||
kwarg_names = []
|
||||
arg_names = []
|
||||
original_arg_name = None
|
||||
original_kwarg_name = None
|
||||
for p in param_names:
|
||||
kind = p.get_kind()
|
||||
if kind == Parameter.VAR_POSITIONAL:
|
||||
if star_count & 1:
|
||||
arg_callables = _iter_nodes_for_param(p)
|
||||
original_arg_name = p
|
||||
elif p.get_kind() == Parameter.VAR_KEYWORD:
|
||||
if star_count & 2:
|
||||
kwarg_callables = list(_iter_nodes_for_param(p))
|
||||
original_kwarg_name = p
|
||||
elif kind == Parameter.KEYWORD_ONLY:
|
||||
if star_count & 2:
|
||||
kw_only_names.append(p)
|
||||
elif kind == Parameter.POSITIONAL_ONLY:
|
||||
if star_count & 1:
|
||||
yield p
|
||||
else:
|
||||
if star_count == 1:
|
||||
yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
|
||||
elif star_count == 2:
|
||||
kw_only_names.append(ParamNameFixedKind(p, Parameter.KEYWORD_ONLY))
|
||||
else:
|
||||
used_names.add(p.string_name)
|
||||
yield p
|
||||
|
||||
longest_param_names = ()
|
||||
found_arg_signature = False
|
||||
found_kwarg_signature = False
|
||||
for func_and_argument in arg_callables:
|
||||
func, arguments = func_and_argument
|
||||
new_star_count = star_count
|
||||
if func_and_argument in kwarg_callables:
|
||||
kwarg_callables.remove(func_and_argument)
|
||||
else:
|
||||
new_star_count = 1
|
||||
|
||||
for signature in func.get_signatures():
|
||||
found_arg_signature = True
|
||||
if new_star_count == 3:
|
||||
found_kwarg_signature = True
|
||||
args_for_this_func = []
|
||||
for p in process_params(
|
||||
list(_remove_given_params(
|
||||
arguments,
|
||||
signature.get_param_names(resolve_stars=False)
|
||||
)), new_star_count):
|
||||
if p.get_kind() == Parameter.VAR_KEYWORD:
|
||||
kwarg_names.append(p)
|
||||
elif p.get_kind() == Parameter.VAR_POSITIONAL:
|
||||
arg_names.append(p)
|
||||
elif p.get_kind() == Parameter.KEYWORD_ONLY:
|
||||
kw_only_names.append(p)
|
||||
else:
|
||||
args_for_this_func.append(p)
|
||||
if len(args_for_this_func) > len(longest_param_names):
|
||||
longest_param_names = args_for_this_func
|
||||
|
||||
for p in longest_param_names:
|
||||
if star_count == 1 and p.get_kind() != Parameter.VAR_POSITIONAL:
|
||||
yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
|
||||
else:
|
||||
if p.get_kind() == Parameter.POSITIONAL_OR_KEYWORD:
|
||||
used_names.add(p.string_name)
|
||||
yield p
|
||||
|
||||
if not found_arg_signature and original_arg_name is not None:
|
||||
yield original_arg_name
|
||||
elif arg_names:
|
||||
yield arg_names[0]
|
||||
|
||||
for p in kw_only_names:
|
||||
if p.string_name in used_names:
|
||||
continue
|
||||
yield p
|
||||
used_names.add(p.string_name)
|
||||
|
||||
for func, arguments in kwarg_callables:
|
||||
for signature in func.get_signatures():
|
||||
found_kwarg_signature = True
|
||||
for p in process_params(
|
||||
list(_remove_given_params(
|
||||
arguments,
|
||||
signature.get_param_names(resolve_stars=False)
|
||||
)), star_count=2):
|
||||
if p.get_kind() != Parameter.KEYWORD_ONLY or not kwarg_names:
|
||||
yield p
|
||||
|
||||
if not found_kwarg_signature and original_kwarg_name is not None:
|
||||
yield original_kwarg_name
|
||||
elif kwarg_names:
|
||||
yield kwarg_names[0]
|
||||
|
||||
|
||||
class ParamNameFixedKind(ParamNameWrapper):
|
||||
def __init__(self, param_name, new_kind):
|
||||
super(ParamNameFixedKind, self).__init__(param_name)
|
||||
self._new_kind = new_kind
|
||||
|
||||
def get_kind(self):
|
||||
return self._new_kind
|
||||
@@ -26,6 +26,8 @@ from jedi.evaluate.compiled.access import COMPARISON_OPERATORS
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.gradual.stub_context import VersionInfo
|
||||
from jedi.evaluate.gradual import annotation
|
||||
from jedi.evaluate.context.decorator import Decoratee
|
||||
from jedi.plugins import plugin_manager
|
||||
|
||||
|
||||
def _limit_context_infers(func):
|
||||
@@ -148,28 +150,28 @@ def eval_node(context, element):
|
||||
return eval_or_test(context, element)
|
||||
|
||||
|
||||
def eval_trailer(context, base_contexts, trailer):
|
||||
def eval_trailer(context, atom_contexts, trailer):
|
||||
trailer_op, node = trailer.children[:2]
|
||||
if node == ')': # `arglist` is optional.
|
||||
node = None
|
||||
|
||||
if trailer_op == '[':
|
||||
trailer_op, node, _ = trailer.children
|
||||
return base_contexts.get_item(
|
||||
return atom_contexts.get_item(
|
||||
eval_subscript_list(context.evaluator, context, node),
|
||||
ContextualizedNode(context, trailer)
|
||||
)
|
||||
else:
|
||||
debug.dbg('eval_trailer: %s in %s', trailer, base_contexts)
|
||||
debug.dbg('eval_trailer: %s in %s', trailer, atom_contexts)
|
||||
if trailer_op == '.':
|
||||
return base_contexts.py__getattribute__(
|
||||
return atom_contexts.py__getattribute__(
|
||||
name_context=context,
|
||||
name_or_str=node
|
||||
)
|
||||
else:
|
||||
assert trailer_op == '(', 'trailer_op is actually %s' % trailer_op
|
||||
args = arguments.TreeArguments(context.evaluator, context, node, trailer)
|
||||
return base_contexts.execute(args)
|
||||
return atom_contexts.execute(args)
|
||||
|
||||
|
||||
def eval_atom(context, atom):
|
||||
@@ -544,6 +546,7 @@ def _remove_statements(evaluator, context, stmt, name):
|
||||
return eval_expr_stmt(context, stmt, seek_name=name)
|
||||
|
||||
|
||||
@plugin_manager.decorate()
|
||||
def tree_name_to_contexts(evaluator, context, tree_name):
|
||||
context_set = NO_CONTEXTS
|
||||
module_node = context.get_root_context().tree_node
|
||||
@@ -666,6 +669,8 @@ def _apply_decorators(context, node):
|
||||
return initial
|
||||
|
||||
debug.dbg('decorator end %s', values, color="MAGENTA")
|
||||
if values != initial:
|
||||
return ContextSet([Decoratee(c, decoratee_context) for c in values])
|
||||
return values
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from weakref import WeakKeyDictionary
|
||||
|
||||
from parso.python import tree
|
||||
from parso.cache import parser_cache
|
||||
from parso import split_lines
|
||||
|
||||
from jedi._compatibility import literal_eval, force_unicode
|
||||
|
||||
@@ -278,3 +279,19 @@ def get_cached_code_lines(grammar, path):
|
||||
to do this, but we avoid splitting all the lines again.
|
||||
"""
|
||||
return parser_cache[grammar._hashed][path].lines
|
||||
|
||||
|
||||
def cut_value_at_position(leaf, position):
|
||||
"""
|
||||
Cuts of the value of the leaf at position
|
||||
"""
|
||||
lines = split_lines(leaf.value, keepends=True)[:position[0] - leaf.line + 1]
|
||||
column = position[1]
|
||||
if leaf.line == position[0]:
|
||||
column -= leaf.column
|
||||
lines[-1] = lines[-1][:column]
|
||||
return ''.join(lines)
|
||||
|
||||
|
||||
def get_string_quote(leaf):
|
||||
return re.match('\w*("""|\'{3}|"|\')', leaf.value).group(1)
|
||||
|
||||
@@ -1,37 +1,47 @@
|
||||
from jedi.plugins.stdlib import StdlibPlugin
|
||||
from jedi.plugins.flask import FlaskPlugin
|
||||
from functools import wraps
|
||||
|
||||
|
||||
class _PluginManager(object):
|
||||
def __init__(self, registered_plugin_classes=()):
|
||||
self._registered_plugin_classes = list(registered_plugin_classes)
|
||||
def __init__(self):
|
||||
self._registered_plugins = []
|
||||
self._cached_base_callbacks = {}
|
||||
self._built_functions = {}
|
||||
|
||||
def register(self, plugin_class):
|
||||
def register(self, *plugins):
|
||||
"""
|
||||
Makes it possible to register your plugin.
|
||||
"""
|
||||
self._registered_plugins.append(plugin_class)
|
||||
self._registered_plugins.extend(plugins)
|
||||
self._build_functions()
|
||||
|
||||
def _build_chain(self, evaluator):
|
||||
for plugin_class in self._registered_plugin_classes:
|
||||
yield plugin_class(evaluator)
|
||||
def decorate(self):
|
||||
def decorator(callback):
|
||||
@wraps(callback)
|
||||
def wrapper(*args, **kwargs):
|
||||
return built_functions[name](*args, **kwargs)
|
||||
|
||||
def get_callbacks(self, evaluator):
|
||||
return _PluginCallbacks(self._build_chain(evaluator))
|
||||
name = callback.__name__
|
||||
|
||||
assert name not in self._built_functions
|
||||
built_functions = self._built_functions
|
||||
built_functions[name] = callback
|
||||
self._cached_base_callbacks[name] = callback
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
def _build_functions(self):
|
||||
for name, callback in self._cached_base_callbacks.items():
|
||||
for plugin in reversed(self._registered_plugins):
|
||||
# Need to reverse so the first plugin is run first.
|
||||
try:
|
||||
func = getattr(plugin, name)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
callback = func(callback)
|
||||
self._built_functions[name] = callback
|
||||
|
||||
|
||||
class _PluginCallbacks(object):
|
||||
def __init__(self, plugins):
|
||||
self._plugins = list(plugins)
|
||||
|
||||
def decorate(self, name, callback):
|
||||
for plugin in reversed(self._plugins):
|
||||
# Need to reverse so the first plugin is run first.
|
||||
callback = getattr(plugin, name)(callback)
|
||||
return callback
|
||||
|
||||
|
||||
plugin_manager = _PluginManager([
|
||||
StdlibPlugin,
|
||||
FlaskPlugin,
|
||||
])
|
||||
plugin_manager = _PluginManager()
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
class BasePlugin(object):
|
||||
"""
|
||||
Plugins are created each time an evaluator is created.
|
||||
"""
|
||||
def __init__(self, evaluator):
|
||||
# In __init__ you can do some caching.
|
||||
self._evaluator = evaluator
|
||||
|
||||
def execute(self, callback):
|
||||
"""
|
||||
Decorates the execute(context, arguments) function.
|
||||
"""
|
||||
return callback
|
||||
|
||||
def import_module(self, callback):
|
||||
"""
|
||||
Decorates the
|
||||
import_module(evaluator, import_path, sys_path, add_error_callback)
|
||||
function.
|
||||
"""
|
||||
return callback
|
||||
@@ -1,25 +1,21 @@
|
||||
from jedi.plugins.base import BasePlugin
|
||||
|
||||
|
||||
class FlaskPlugin(BasePlugin):
|
||||
def import_module(self, callback):
|
||||
"""
|
||||
Handle "magic" Flask extension imports:
|
||||
``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
|
||||
"""
|
||||
def wrapper(evaluator, import_names, module_context, *args, **kwargs):
|
||||
if len(import_names) == 3 and import_names[:2] == ('flask', 'ext'):
|
||||
# New style.
|
||||
ipath = (u'flask_' + import_names[2]),
|
||||
context_set = callback(evaluator, ipath, None, *args, **kwargs)
|
||||
if context_set:
|
||||
return context_set
|
||||
context_set = callback(evaluator, (u'flaskext',), None, *args, **kwargs)
|
||||
return callback(
|
||||
evaluator,
|
||||
(u'flaskext', import_names[2]),
|
||||
next(iter(context_set)),
|
||||
*args, **kwargs
|
||||
)
|
||||
return callback(evaluator, import_names, module_context, *args, **kwargs)
|
||||
return wrapper
|
||||
def import_module(callback):
|
||||
"""
|
||||
Handle "magic" Flask extension imports:
|
||||
``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
|
||||
"""
|
||||
def wrapper(evaluator, import_names, module_context, *args, **kwargs):
|
||||
if len(import_names) == 3 and import_names[:2] == ('flask', 'ext'):
|
||||
# New style.
|
||||
ipath = (u'flask_' + import_names[2]),
|
||||
context_set = callback(evaluator, ipath, None, *args, **kwargs)
|
||||
if context_set:
|
||||
return context_set
|
||||
context_set = callback(evaluator, (u'flaskext',), None, *args, **kwargs)
|
||||
return callback(
|
||||
evaluator,
|
||||
(u'flaskext', import_names[2]),
|
||||
next(iter(context_set)),
|
||||
*args, **kwargs
|
||||
)
|
||||
return callback(evaluator, import_names, module_context, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
10
jedi/plugins/registry.py
Normal file
10
jedi/plugins/registry.py
Normal file
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
This is not a plugin, this is just the place were plugins are registered.
|
||||
"""
|
||||
|
||||
from jedi.plugins import stdlib
|
||||
from jedi.plugins import flask
|
||||
from jedi.plugins import plugin_manager
|
||||
|
||||
|
||||
plugin_manager.register(stdlib, flask)
|
||||
@@ -10,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.
|
||||
"""
|
||||
import parso
|
||||
import os
|
||||
|
||||
from jedi._compatibility import force_unicode
|
||||
from jedi.plugins.base import BasePlugin
|
||||
from jedi._compatibility import force_unicode, Parameter
|
||||
from jedi import debug
|
||||
from jedi.evaluate.utils import safe_property
|
||||
from jedi.evaluate.helpers import get_str_or_none
|
||||
from jedi.evaluate.arguments import ValuesArguments, \
|
||||
repack_with_argument_clinic, AbstractArguments, TreeArgumentsWrapper
|
||||
@@ -24,11 +25,16 @@ from jedi.evaluate.base_context import ContextualizedNode, \
|
||||
NO_CONTEXTS, ContextSet, ContextWrapper, LazyContextWrapper
|
||||
from jedi.evaluate.context import ClassContext, ModuleContext, \
|
||||
FunctionExecutionContext
|
||||
from jedi.evaluate.context.klass import ClassMixin
|
||||
from jedi.evaluate.context.function import FunctionMixin
|
||||
from jedi.evaluate.context import iterable
|
||||
from jedi.evaluate.lazy_context import LazyTreeContext, LazyKnownContext, \
|
||||
LazyKnownContexts
|
||||
from jedi.evaluate.names import ContextName, BaseTreeParamName
|
||||
from jedi.evaluate.syntax_tree import is_string
|
||||
from jedi.evaluate.filters import AttributeOverwrite, publish_method
|
||||
from jedi.evaluate.filters import AttributeOverwrite, publish_method, \
|
||||
ParserTreeFilter, DictFilter
|
||||
from jedi.evaluate.signature import AbstractSignature, SignatureWrapper
|
||||
|
||||
|
||||
# Copied from Python 3.6's stdlib.
|
||||
@@ -98,45 +104,48 @@ _NAMEDTUPLE_FIELD_TEMPLATE = '''\
|
||||
'''
|
||||
|
||||
|
||||
class StdlibPlugin(BasePlugin):
|
||||
def execute(self, callback):
|
||||
def wrapper(context, arguments):
|
||||
try:
|
||||
obj_name = context.name.string_name
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if context.parent_context == self._evaluator.builtins_module:
|
||||
module_name = 'builtins'
|
||||
elif context.parent_context is not None and context.parent_context.is_module():
|
||||
module_name = context.parent_context.py__name__()
|
||||
else:
|
||||
return callback(context, arguments=arguments)
|
||||
|
||||
if isinstance(context, BoundMethod):
|
||||
if module_name == 'builtins':
|
||||
if context.py__name__() == '__get__':
|
||||
if context.class_context.py__name__() == 'property':
|
||||
return builtins_property(
|
||||
context,
|
||||
arguments=arguments
|
||||
)
|
||||
elif context.py__name__() in ('deleter', 'getter', 'setter'):
|
||||
if context.class_context.py__name__() == 'property':
|
||||
return ContextSet([context.instance])
|
||||
|
||||
return callback(context, arguments=arguments)
|
||||
|
||||
# for now we just support builtin functions.
|
||||
try:
|
||||
func = _implemented[module_name][obj_name]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return func(context, arguments=arguments)
|
||||
def execute(callback):
|
||||
def wrapper(context, arguments):
|
||||
def call():
|
||||
return callback(context, arguments=arguments)
|
||||
|
||||
return wrapper
|
||||
try:
|
||||
obj_name = context.name.string_name
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if context.parent_context == context.evaluator.builtins_module:
|
||||
module_name = 'builtins'
|
||||
elif context.parent_context is not None and context.parent_context.is_module():
|
||||
module_name = context.parent_context.py__name__()
|
||||
else:
|
||||
return call()
|
||||
|
||||
if isinstance(context, BoundMethod):
|
||||
if module_name == 'builtins':
|
||||
if context.py__name__() == '__get__':
|
||||
if context.class_context.py__name__() == 'property':
|
||||
return builtins_property(
|
||||
context,
|
||||
arguments=arguments,
|
||||
callback=call,
|
||||
)
|
||||
elif context.py__name__() in ('deleter', 'getter', 'setter'):
|
||||
if context.class_context.py__name__() == 'property':
|
||||
return ContextSet([context.instance])
|
||||
|
||||
return call()
|
||||
|
||||
# for now we just support builtin functions.
|
||||
try:
|
||||
func = _implemented[module_name][obj_name]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return func(context, arguments=arguments, callback=call)
|
||||
return call()
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def _follow_param(evaluator, arguments, index):
|
||||
@@ -149,15 +158,18 @@ def _follow_param(evaluator, arguments, index):
|
||||
|
||||
|
||||
def argument_clinic(string, want_obj=False, want_context=False,
|
||||
want_arguments=False, want_evaluator=False):
|
||||
want_arguments=False, want_evaluator=False,
|
||||
want_callback=False):
|
||||
"""
|
||||
Works like Argument Clinic (PEP 436), to validate function params.
|
||||
"""
|
||||
|
||||
def f(func):
|
||||
@repack_with_argument_clinic(string, keep_arguments_param=True)
|
||||
@repack_with_argument_clinic(string, keep_arguments_param=True,
|
||||
keep_callback_param=True)
|
||||
def wrapper(obj, *args, **kwargs):
|
||||
arguments = kwargs.pop('arguments')
|
||||
callback = kwargs.pop('callback')
|
||||
assert not kwargs # Python 2...
|
||||
debug.dbg('builtin start %s' % obj, color='MAGENTA')
|
||||
result = NO_CONTEXTS
|
||||
@@ -169,6 +181,8 @@ def argument_clinic(string, want_obj=False, want_context=False,
|
||||
kwargs['evaluator'] = obj.evaluator
|
||||
if want_arguments:
|
||||
kwargs['arguments'] = arguments
|
||||
if want_callback:
|
||||
kwargs['callback'] = callback
|
||||
result = func(*args, **kwargs)
|
||||
debug.dbg('builtin end: %s', result, color='MAGENTA')
|
||||
return result
|
||||
@@ -378,6 +392,9 @@ class ClassMethodGet(AttributeOverwrite, ContextWrapper):
|
||||
self._class = klass
|
||||
self._function = function
|
||||
|
||||
def get_signatures(self):
|
||||
return self._function.get_signatures()
|
||||
|
||||
def get_object(self):
|
||||
return self._wrapped_context
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -428,14 +445,16 @@ def collections_namedtuple(obj, arguments):
|
||||
if not param_contexts:
|
||||
return NO_CONTEXTS
|
||||
_fields = list(param_contexts)[0]
|
||||
if isinstance(_fields, compiled.CompiledValue):
|
||||
fields = force_unicode(_fields.get_safe_value()).replace(',', ' ').split()
|
||||
string = get_str_or_none(_fields)
|
||||
if string is not None:
|
||||
fields = force_unicode(string).replace(',', ' ').split()
|
||||
elif isinstance(_fields, iterable.Sequence):
|
||||
fields = [
|
||||
force_unicode(v.get_safe_value())
|
||||
force_unicode(get_str_or_none(v))
|
||||
for lazy_context in _fields.py__iter__()
|
||||
for v in lazy_context.infer() if is_string(v)
|
||||
for v in lazy_context.infer()
|
||||
]
|
||||
fields = [f for f in fields if f is not None]
|
||||
else:
|
||||
return NO_CONTEXTS
|
||||
|
||||
@@ -471,17 +490,49 @@ class PartialObject(object):
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._actual_context, name)
|
||||
|
||||
def py__call__(self, arguments):
|
||||
key, lazy_context = next(self._arguments.unpack(), (None, None))
|
||||
def _get_function(self, unpacked_arguments):
|
||||
key, lazy_context = next(unpacked_arguments, (None, None))
|
||||
if key is not None or lazy_context is None:
|
||||
debug.warning("Partial should have a proper function %s", self._arguments)
|
||||
return None
|
||||
return lazy_context.infer()
|
||||
|
||||
def get_signatures(self):
|
||||
unpacked_arguments = self._arguments.unpack()
|
||||
func = self._get_function(unpacked_arguments)
|
||||
if func is None:
|
||||
return []
|
||||
|
||||
arg_count = 0
|
||||
keys = set()
|
||||
for key, _ in unpacked_arguments:
|
||||
if key is None:
|
||||
arg_count += 1
|
||||
else:
|
||||
keys.add(key)
|
||||
return [PartialSignature(s, arg_count, keys) for s in func.get_signatures()]
|
||||
|
||||
def py__call__(self, arguments):
|
||||
func = self._get_function(self._arguments.unpack())
|
||||
if func is None:
|
||||
return NO_CONTEXTS
|
||||
|
||||
return lazy_context.infer().execute(
|
||||
return func.execute(
|
||||
MergedPartialArguments(self._arguments, arguments)
|
||||
)
|
||||
|
||||
|
||||
class PartialSignature(SignatureWrapper):
|
||||
def __init__(self, wrapped_signature, skipped_arg_count, skipped_arg_set):
|
||||
super(PartialSignature, self).__init__(wrapped_signature)
|
||||
self._skipped_arg_count = skipped_arg_count
|
||||
self._skipped_arg_set = skipped_arg_set
|
||||
|
||||
def get_param_names(self, resolve_stars=False):
|
||||
names = self._wrapped_signature.get_param_names()[self._skipped_arg_count:]
|
||||
return [n for n in names if n.string_name not in self._skipped_arg_set]
|
||||
|
||||
|
||||
class MergedPartialArguments(AbstractArguments):
|
||||
def __init__(self, partial_arguments, call_arguments):
|
||||
self._partial_arguments = partial_arguments
|
||||
@@ -498,7 +549,7 @@ class MergedPartialArguments(AbstractArguments):
|
||||
yield key_lazy_context
|
||||
|
||||
|
||||
def functools_partial(obj, arguments):
|
||||
def functools_partial(obj, arguments, callback):
|
||||
return ContextSet(
|
||||
PartialObject(instance, arguments)
|
||||
for instance in obj.py__call__(arguments)
|
||||
@@ -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):
|
||||
def __init__(self, instance, args_context_set):
|
||||
super(ItemGetterCallable, self).__init__(instance)
|
||||
@@ -544,6 +655,33 @@ class ItemGetterCallable(ContextWrapper):
|
||||
return context_set
|
||||
|
||||
|
||||
@argument_clinic('func, /')
|
||||
def _functools_wraps(funcs):
|
||||
return ContextSet(WrapsCallable(func) for func in funcs)
|
||||
|
||||
|
||||
class WrapsCallable(ContextWrapper):
|
||||
# XXX this is not the correct wrapped context, it should be a weird
|
||||
# partials object, but it doesn't matter, because it's always used as a
|
||||
# decorator anyway.
|
||||
@repack_with_argument_clinic('func, /')
|
||||
def py__call__(self, funcs):
|
||||
return ContextSet({Wrapped(func, self._wrapped_context) for func in funcs})
|
||||
|
||||
|
||||
class Wrapped(ContextWrapper, FunctionMixin):
|
||||
def __init__(self, func, original_function):
|
||||
super(Wrapped, self).__init__(func)
|
||||
self._original_function = original_function
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._original_function.name
|
||||
|
||||
def get_signature_functions(self):
|
||||
return [self]
|
||||
|
||||
|
||||
@argument_clinic('*args, /', want_obj=True, want_arguments=True)
|
||||
def _operator_itemgetter(args_context_set, obj, arguments):
|
||||
return ContextSet([
|
||||
@@ -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 = {
|
||||
'builtins': {
|
||||
'getattr': builtins_getattr,
|
||||
@@ -569,15 +745,15 @@ _implemented = {
|
||||
'deepcopy': _return_first_param,
|
||||
},
|
||||
'json': {
|
||||
'load': lambda obj, arguments: NO_CONTEXTS,
|
||||
'loads': lambda obj, arguments: NO_CONTEXTS,
|
||||
'load': lambda obj, arguments, callback: NO_CONTEXTS,
|
||||
'loads': lambda obj, arguments, callback: NO_CONTEXTS,
|
||||
},
|
||||
'collections': {
|
||||
'namedtuple': collections_namedtuple,
|
||||
},
|
||||
'functools': {
|
||||
'partial': functools_partial,
|
||||
'wraps': _return_first_param,
|
||||
'wraps': _functools_wraps,
|
||||
},
|
||||
'_weakref': {
|
||||
'proxy': _return_first_param,
|
||||
@@ -597,10 +773,63 @@ _implemented = {
|
||||
# The _alias function just leads to some annoying type inference.
|
||||
# Therefore, just make it return nothing, which leads to the stubs
|
||||
# being used instead. This only matters for 3.7+.
|
||||
'_alias': lambda obj, arguments: NO_CONTEXTS,
|
||||
'_alias': lambda obj, arguments, callback: NO_CONTEXTS,
|
||||
},
|
||||
'dataclasses': {
|
||||
# For now this works at least better than Jedi trying to understand it.
|
||||
'dataclass': 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 sys
|
||||
|
||||
from parso import split_lines
|
||||
|
||||
from jedi import Interpreter
|
||||
from jedi.api.helpers import get_on_completion_name
|
||||
|
||||
|
||||
READLINE_DEBUG = False
|
||||
@@ -86,23 +83,18 @@ def setup_readline(namespace_module=__main__):
|
||||
logging.debug("Start REPL completion: " + repr(text))
|
||||
interpreter = Interpreter(text, [namespace_module.__dict__])
|
||||
|
||||
lines = split_lines(text)
|
||||
position = (len(lines), len(lines[-1]))
|
||||
name = get_on_completion_name(
|
||||
interpreter._module_node,
|
||||
lines,
|
||||
position
|
||||
)
|
||||
before = text[:len(text) - len(name)]
|
||||
completions = interpreter.completions()
|
||||
logging.debug("REPL completions: %s", completions)
|
||||
|
||||
self.matches = [
|
||||
text[:len(text) - c._like_name_length] + c.name_with_symbols
|
||||
for c in completions
|
||||
]
|
||||
except:
|
||||
logging.error("REPL Completion error:\n" + traceback.format_exc())
|
||||
raise
|
||||
finally:
|
||||
sys.path.pop(0)
|
||||
|
||||
self.matches = [before + c.name_with_symbols for c in completions]
|
||||
try:
|
||||
return self.matches[state]
|
||||
except IndexError:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import recurse_class2
|
||||
from . import recurse_class2
|
||||
|
||||
class C(recurse_class2.C):
|
||||
def a(self):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import recurse_class1
|
||||
from . import recurse_class1
|
||||
|
||||
class C(recurse_class1.C):
|
||||
pass
|
||||
|
||||
@@ -165,3 +165,19 @@ def keyword_only(a: str, *, b: str):
|
||||
a.startswi
|
||||
#? ['startswith']
|
||||
b.startswi
|
||||
|
||||
|
||||
def argskwargs(*args: int, **kwargs: float):
|
||||
"""
|
||||
This might be a bit confusing, but is part of the standard.
|
||||
args is changed to Tuple[int] in this case and kwargs to Dict[str, float],
|
||||
which makes sense if you think about it a bit.
|
||||
"""
|
||||
#? tuple()
|
||||
args
|
||||
#? int()
|
||||
args[0]
|
||||
#? str()
|
||||
next(iter(kwargs.keys()))
|
||||
#? float()
|
||||
kwargs['']
|
||||
|
||||
@@ -67,6 +67,18 @@ class X(): pass
|
||||
#? type
|
||||
type(X)
|
||||
|
||||
# -----------------
|
||||
# type() calls with multiple parameters
|
||||
# -----------------
|
||||
|
||||
X = type('X', (object,), dict(a=1))
|
||||
|
||||
# Doesn't work yet.
|
||||
#?
|
||||
X.a
|
||||
#?
|
||||
X
|
||||
|
||||
if os.path.isfile():
|
||||
#? ['abspath']
|
||||
fails = os.path.abspath
|
||||
@@ -290,3 +302,31 @@ class Test(metaclass=Meta):
|
||||
result = super(Test, self).test_function()
|
||||
#? []
|
||||
result.
|
||||
|
||||
# -----------------
|
||||
# Enum
|
||||
# -----------------
|
||||
|
||||
# python >= 3.4
|
||||
import enum
|
||||
|
||||
class X(enum.Enum):
|
||||
attr_x = 3
|
||||
attr_y = 2.0
|
||||
|
||||
#? ['mro']
|
||||
X.mro
|
||||
#? ['attr_x', 'attr_y']
|
||||
X.attr_
|
||||
#? str()
|
||||
X.attr_x.name
|
||||
#? int()
|
||||
X.attr_x.value
|
||||
#? str()
|
||||
X.attr_y.name
|
||||
#? float()
|
||||
X.attr_y.value
|
||||
#? str()
|
||||
X().name
|
||||
#? float()
|
||||
X().attr_x.attr_y.value
|
||||
|
||||
@@ -315,7 +315,8 @@ def test_signature_is_definition(Script):
|
||||
|
||||
# Now compare all the attributes that a CallSignature must also have.
|
||||
for attr_name in dir(definition):
|
||||
dont_scan = ['defined_names', 'parent', 'goto_assignments', 'infer', 'params']
|
||||
dont_scan = ['defined_names', 'parent', 'goto_assignments', 'infer',
|
||||
'params', 'get_signatures', 'execute']
|
||||
if attr_name.startswith('_') or attr_name in dont_scan:
|
||||
continue
|
||||
|
||||
|
||||
@@ -442,3 +442,20 @@ def test_builtin_module_with_path(Script):
|
||||
assert semlock.name == 'SemLock'
|
||||
assert semlock.line is None
|
||||
assert semlock.column is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'code, description', [
|
||||
('int', 'instance int'),
|
||||
('str.index', 'instance int'),
|
||||
('1', None),
|
||||
]
|
||||
)
|
||||
def test_execute(Script, code, description):
|
||||
definition, = Script(code).goto_assignments()
|
||||
definitions = definition.execute()
|
||||
if description is None:
|
||||
assert not definitions
|
||||
else:
|
||||
d, = definitions
|
||||
assert d.description == description
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import os
|
||||
from os.path import join, sep as s
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
from ..helpers import root_dir
|
||||
|
||||
|
||||
def test_in_whitespace(Script):
|
||||
@@ -69,8 +70,8 @@ def test_points_in_completion(Script):
|
||||
|
||||
def test_loading_unicode_files_with_bad_global_charset(Script, monkeypatch, tmpdir):
|
||||
dirname = str(tmpdir.mkdir('jedi-test'))
|
||||
filename1 = os.path.join(dirname, 'test1.py')
|
||||
filename2 = os.path.join(dirname, 'test2.py')
|
||||
filename1 = join(dirname, 'test1.py')
|
||||
filename2 = join(dirname, 'test2.py')
|
||||
if sys.version_info < (3, 0):
|
||||
data = "# coding: latin-1\nfoo = 'm\xf6p'\n"
|
||||
else:
|
||||
@@ -156,3 +157,111 @@ def test_with_stmt_error_recovery(Script):
|
||||
)
|
||||
def test_keyword_completion(Script, code, has_keywords):
|
||||
assert has_keywords == any(x.is_keyword for x in Script(code).completions())
|
||||
|
||||
|
||||
f1 = join(root_dir, 'example.py')
|
||||
f2 = join(root_dir, 'test', 'example.py')
|
||||
os_path = 'from os.path import *\n'
|
||||
# os.path.sep escaped
|
||||
se = s * 2 if s == '\\' else s
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'file, code, column, expected', [
|
||||
# General tests / relative paths
|
||||
(None, '"comp', None, ['ile', 'lex']), # No files like comp
|
||||
(None, '"test', None, [s]),
|
||||
(None, '"test', 4, ['t' + s]),
|
||||
('example.py', '"test%scomp' % s, None, ['letion' + s]),
|
||||
('example.py', 'r"comp"', None, "A LOT"),
|
||||
('example.py', 'r"tes"', None, "A LOT"),
|
||||
('example.py', 'r"tes"', 5, ['t' + s]),
|
||||
('example.py', 'r" tes"', 6, []),
|
||||
('test%sexample.py' % se, 'r"tes"', 5, ['t' + s]),
|
||||
('test%sexample.py' % se, 'r"test%scomp"' % s, 5, ['t' + s]),
|
||||
('test%sexample.py' % se, 'r"test%scomp"' % s, 11, ['letion' + s]),
|
||||
('test%sexample.py' % se, '"%s"' % join('test', 'completion', 'basi'), 21, ['c.py']),
|
||||
('example.py', 'rb"' + join('..', 'jedi', 'tes'), None, ['t' + s]),
|
||||
|
||||
# Absolute paths
|
||||
(None, '"' + join(root_dir, 'test', 'test_ca'), None, ['che.py"']),
|
||||
(None, '"%s"' % join(root_dir, 'test', 'test_ca'), len(root_dir) + 14, ['che.py']),
|
||||
|
||||
# Longer quotes
|
||||
('example.py', 'r"""test', None, [s]),
|
||||
('example.py', 'r"""\ntest', None, []),
|
||||
('example.py', 'u"""tes\n', (1, 7), ['t' + s]),
|
||||
('example.py', '"""test%stest_cache.p"""' % s, 20, ['y']),
|
||||
('example.py', '"""test%stest_cache.p"""' % s, 19, ['py"""']),
|
||||
|
||||
# Adding
|
||||
('example.py', '"test" + "%stest_cac' % se, None, ['he.py"']),
|
||||
('example.py', '"test" + "%s" + "test_cac' % se, None, ['he.py"']),
|
||||
('example.py', 'x = 1 + "test', None, []),
|
||||
('example.py', 'x = f("te" + "st)', 16, [s]),
|
||||
('example.py', 'x = f("te" + "st', 16, [s]),
|
||||
('example.py', 'x = f("te" + "st"', 16, [s]),
|
||||
('example.py', 'x = f("te" + "st")', 16, [s]),
|
||||
('example.py', 'x = f("t" + "est")', 16, [s]),
|
||||
# This is actually not correct, but for now leave it here, because of
|
||||
# Python 2.
|
||||
('example.py', 'x = f(b"t" + "est")', 17, [s]),
|
||||
('example.py', '"test" + "', None, [s]),
|
||||
|
||||
# __file__
|
||||
(f1, os_path + 'dirname(__file__) + "%stest' % s, None, [s]),
|
||||
(f2, os_path + 'dirname(__file__) + "%stest_ca' % se, None, ['che.py"']),
|
||||
(f2, os_path + 'dirname(abspath(__file__)) + sep + "test_ca', None, ['che.py"']),
|
||||
(f2, os_path + 'join(dirname(__file__), "completion") + sep + "basi', None, ['c.py"']),
|
||||
(f2, os_path + 'join("test", "completion") + sep + "basi', None, ['c.py"']),
|
||||
|
||||
# inside join
|
||||
(f2, os_path + 'join(dirname(__file__), "completion", "basi', None, ['c.py"']),
|
||||
(f2, os_path + 'join(dirname(__file__), "completion", "basi)', 43, ['c.py"']),
|
||||
(f2, os_path + 'join(dirname(__file__), "completion", "basi")', 43, ['c.py']),
|
||||
(f2, os_path + 'join(dirname(__file__), "completion", "basi)', 35, ['']),
|
||||
(f2, os_path + 'join(dirname(__file__), "completion", "basi)', 33, ['on"']),
|
||||
(f2, os_path + 'join(dirname(__file__), "completion", "basi")', 33, ['on"']),
|
||||
|
||||
# join with one argument. join will not get evaluated and the result is
|
||||
# that directories and in a slash. This is unfortunate, but doesn't
|
||||
# really matter.
|
||||
(f2, os_path + 'join("tes', 9, ['t"']),
|
||||
(f2, os_path + 'join(\'tes)', 9, ["t'"]),
|
||||
(f2, os_path + 'join(r"tes"', 10, ['t']),
|
||||
(f2, os_path + 'join("""tes""")', 11, ['t']),
|
||||
|
||||
# Almost like join but not really
|
||||
(f2, os_path + 'join["tes', 9, ['t' + s]),
|
||||
(f2, os_path + 'join["tes"', 9, ['t' + s]),
|
||||
(f2, os_path + 'join["tes"]', 9, ['t' + s]),
|
||||
(f2, os_path + 'join[dirname(__file__), "completi', 33, []),
|
||||
(f2, os_path + 'join[dirname(__file__), "completi"', 33, []),
|
||||
(f2, os_path + 'join[dirname(__file__), "completi"]', 33, []),
|
||||
|
||||
# With full paths
|
||||
(f2, 'import os\nos.path.join(os.path.dirname(__file__), "completi', 49, ['on"']),
|
||||
(f2, 'import os\nos.path.join(os.path.dirname(__file__), "completi"', 49, ['on']),
|
||||
(f2, 'import os\nos.path.join(os.path.dirname(__file__), "completi")', 49, ['on']),
|
||||
|
||||
# With alias
|
||||
(f2, 'import os.path as p as p\np.join(p.dirname(__file__), "completi', None, ['on"']),
|
||||
(f2, 'from os.path import dirname, join as j\nj(dirname(__file__), "completi',
|
||||
None, ['on"']),
|
||||
|
||||
# Trying to break it
|
||||
(f2, os_path + 'join(["tes', 10, ['t' + s]),
|
||||
(f2, os_path + 'join(["tes"]', 10, ['t' + s]),
|
||||
(f2, os_path + 'join(["tes"])', 10, ['t' + s]),
|
||||
(f2, os_path + 'join("test", "test_cac" + x,', 22, ['he.py']),
|
||||
]
|
||||
)
|
||||
def test_file_path_completions(Script, file, code, column, expected):
|
||||
line = None
|
||||
if isinstance(column, tuple):
|
||||
line, column = column
|
||||
comps = Script(code, path=file, line=line, column=column).completions()
|
||||
if expected == "A LOT":
|
||||
assert len(comps) > 100 # This is basically global completions.
|
||||
else:
|
||||
assert [c.complete for c in comps] == expected
|
||||
|
||||
@@ -7,8 +7,8 @@ import pytest
|
||||
|
||||
import jedi
|
||||
from jedi._compatibility import is_py3, py_version
|
||||
from jedi.evaluate.compiled import mixed
|
||||
|
||||
from jedi.evaluate.compiled import mixed, context
|
||||
from importlib import import_module
|
||||
|
||||
if py_version > 30:
|
||||
def exec_(source, global_map):
|
||||
@@ -197,7 +197,13 @@ def test_getitem_side_effects():
|
||||
_assert_interpreter_complete('foo["asdf"].upper', locals(), ['upper'])
|
||||
|
||||
|
||||
def test_property_error_oldstyle():
|
||||
@pytest.fixture(params=[False, True])
|
||||
def allow_descriptor_access_or_not(request, monkeypatch):
|
||||
monkeypatch.setattr(jedi.Interpreter, '_allow_descriptor_getattr_default', request.param)
|
||||
return request.param
|
||||
|
||||
|
||||
def test_property_error_oldstyle(allow_descriptor_access_or_not):
|
||||
lst = []
|
||||
class Foo3:
|
||||
@property
|
||||
@@ -209,11 +215,14 @@ def test_property_error_oldstyle():
|
||||
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
|
||||
_assert_interpreter_complete('foo.bar.baz', locals(), [])
|
||||
|
||||
# There should not be side effects
|
||||
assert lst == []
|
||||
if allow_descriptor_access_or_not:
|
||||
assert lst == [1, 1]
|
||||
else:
|
||||
# There should not be side effects
|
||||
assert lst == []
|
||||
|
||||
|
||||
def test_property_error_newstyle():
|
||||
def test_property_error_newstyle(allow_descriptor_access_or_not):
|
||||
lst = []
|
||||
class Foo3(object):
|
||||
@property
|
||||
@@ -225,10 +234,25 @@ def test_property_error_newstyle():
|
||||
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
|
||||
_assert_interpreter_complete('foo.bar.baz', locals(), [])
|
||||
|
||||
# There should not be side effects
|
||||
assert lst == []
|
||||
if allow_descriptor_access_or_not:
|
||||
assert lst == [1, 1]
|
||||
else:
|
||||
# There should not be side effects
|
||||
assert lst == []
|
||||
|
||||
|
||||
def test_property_content():
|
||||
class Foo3(object):
|
||||
@property
|
||||
def bar(self):
|
||||
return 1
|
||||
|
||||
foo = Foo3()
|
||||
def_, = jedi.Interpreter('foo.bar', [locals()]).goto_definitions()
|
||||
assert def_.name == 'int'
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
|
||||
def test_param_completion():
|
||||
def foo(bar):
|
||||
pass
|
||||
@@ -275,6 +299,7 @@ def test_completion_param_annotations():
|
||||
assert d.name == 'bytes'
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
|
||||
def test_keyword_argument():
|
||||
def f(some_keyword_argument):
|
||||
pass
|
||||
@@ -439,3 +464,44 @@ def test__wrapped__():
|
||||
c, = jedi.Interpreter('syslogs_to_df', [locals()]).completions()
|
||||
# Apparently the function starts on the line where the decorator starts.
|
||||
assert c.line == syslogs_to_df.__wrapped__.__code__.co_firstlineno + 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize('module_name', ['sys', 'time'])
|
||||
def test_core_module_completes(module_name):
|
||||
module = import_module(module_name)
|
||||
assert jedi.Interpreter(module_name + '.\n', [locals()]).completions()
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
|
||||
@pytest.mark.parametrize(
|
||||
'code, expected, index', [
|
||||
('a(', ['a', 'b', 'c'], 0),
|
||||
('b(', ['b', 'c'], 0),
|
||||
# Might or might not be correct, because c is given as a keyword
|
||||
# argument as well, but that is just what inspect.signature returns.
|
||||
('c(', ['b', 'c'], 0),
|
||||
]
|
||||
)
|
||||
def test_partial_signatures(code, expected, index):
|
||||
import functools
|
||||
|
||||
def func(a, b, c):
|
||||
pass
|
||||
|
||||
a = functools.partial(func)
|
||||
b = functools.partial(func, 1)
|
||||
c = functools.partial(func, 1, c=2)
|
||||
|
||||
sig, = jedi.Interpreter(code, [locals()]).call_signatures()
|
||||
assert sig.name == 'partial'
|
||||
assert [p.name for p in sig.params] == expected
|
||||
assert index == sig.index
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
|
||||
def test_type_var():
|
||||
"""This was an issue before, see Github #1369"""
|
||||
import typing
|
||||
x = typing.TypeVar('myvar')
|
||||
def_, = jedi.Interpreter('x', [locals()]).goto_definitions()
|
||||
assert def_.name == 'TypeVar'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
|
||||
from ..helpers import get_example_dir
|
||||
from ..helpers import get_example_dir, set_cwd, root_dir
|
||||
from jedi import Interpreter
|
||||
|
||||
|
||||
def test_django_default_project(Script):
|
||||
@@ -13,3 +14,11 @@ def test_django_default_project(Script):
|
||||
c, = script.completions()
|
||||
assert c.name == "SomeModel"
|
||||
assert script._evaluator.project._django is True
|
||||
|
||||
|
||||
def test_interpreter_project_path():
|
||||
# Run from anywhere it should be the cwd.
|
||||
dir = os.path.join(root_dir, 'test')
|
||||
with set_cwd(dir):
|
||||
project = Interpreter('', [locals()])._evaluator.project
|
||||
assert project._path == dir
|
||||
|
||||
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_, = Script('__name__').completions()
|
||||
assert def_.name == '__name__'
|
||||
@@ -5,3 +8,14 @@ def test_module_attributes(Script):
|
||||
assert def_.column is None
|
||||
str_, = def_.infer()
|
||||
assert str_.name == 'str'
|
||||
|
||||
|
||||
def test_module__file__(Script, environment):
|
||||
assert not Script('__file__').goto_definitions()
|
||||
def_, = Script('__file__', path='example.py').goto_definitions()
|
||||
value = force_unicode(def_._name._context.get_safe_value())
|
||||
assert value.endswith('example.py')
|
||||
|
||||
def_, = Script('import antigravity; antigravity.__file__').goto_definitions()
|
||||
value = force_unicode(def_._name._context.get_safe_value())
|
||||
assert value.endswith('.py')
|
||||
|
||||
@@ -6,6 +6,7 @@ from textwrap import dedent
|
||||
import jedi
|
||||
import pytest
|
||||
from ..helpers import unittest
|
||||
import sys
|
||||
|
||||
try:
|
||||
import numpydoc # NOQA
|
||||
@@ -21,6 +22,11 @@ except ImportError:
|
||||
else:
|
||||
numpy_unavailable = False
|
||||
|
||||
if sys.version_info.major == 2:
|
||||
# In Python 2 there's an issue with tox/docutils that makes the tests fail,
|
||||
# Python 2 is soon end-of-life, so just don't support numpydoc for it anymore.
|
||||
numpydoc_unavailable = True
|
||||
|
||||
|
||||
def test_function_doc(Script):
|
||||
defs = Script("""
|
||||
@@ -385,3 +391,26 @@ def test_numpy_comp_returns():
|
||||
)
|
||||
names = [c.name for c in jedi.Script(s).completions()]
|
||||
assert 'diagonal' in names
|
||||
|
||||
|
||||
def test_decorator(Script):
|
||||
code = dedent('''
|
||||
def decorator(name=None):
|
||||
def _decorate(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
"""wrapper docstring"""
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
return _decorate
|
||||
|
||||
|
||||
@decorator('testing')
|
||||
def check_user(f):
|
||||
"""Nice docstring"""
|
||||
pass
|
||||
|
||||
check_user''')
|
||||
|
||||
d, = Script(code).goto_definitions()
|
||||
assert d.docstring(raw=True) == 'Nice docstring'
|
||||
|
||||
@@ -472,6 +472,6 @@ def test_relative_import_star(Script):
|
||||
from . import *
|
||||
furl.c
|
||||
"""
|
||||
script = jedi.Script(source,3,len("furl.c"), 'export.py')
|
||||
script = jedi.Script(source, 3, len("furl.c"), 'export.py')
|
||||
|
||||
assert script.completions()
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import pytest
|
||||
from textwrap import dedent
|
||||
from operator import ge, lt
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from jedi.evaluate.gradual.conversion import _stub_to_python_context_set
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
classmethod_code = '''
|
||||
class X:
|
||||
@classmethod
|
||||
def x(cls, a, b):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def static(a, b):
|
||||
pass
|
||||
'''
|
||||
|
||||
|
||||
partial_code = '''
|
||||
import functools
|
||||
|
||||
def func(a, b, c):
|
||||
pass
|
||||
|
||||
a = functools.partial(func)
|
||||
b = functools.partial(func, 1)
|
||||
c = functools.partial(func, 1, c=2)
|
||||
d = functools.partial()
|
||||
'''
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'code, expected', [
|
||||
('def f(a, * args, x): pass\n f(', 'f(a, *args, x)'),
|
||||
@@ -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,/,y,* ,z): pass\n f(', 'f(x, /, y, *, z)'),
|
||||
('def f(a, /, *, x=3, **kwargs): pass\n f(', 'f(a, /, *, x=3, **kwargs)'),
|
||||
|
||||
(classmethod_code + 'X.x(', 'x(cls, a, b)'),
|
||||
(classmethod_code + 'X().x(', 'x(cls, a, b)'),
|
||||
(classmethod_code + 'X.static(', 'static(a, b)'),
|
||||
(classmethod_code + 'X().static(', 'static(a, b)'),
|
||||
|
||||
(partial_code + 'a(', 'func(a, b, c)'),
|
||||
(partial_code + 'b(', 'func(b, c)'),
|
||||
(partial_code + 'c(', 'func(b)'),
|
||||
(partial_code + 'd(', None),
|
||||
]
|
||||
)
|
||||
def test_tree_signature(Script, environment, code, expected):
|
||||
@@ -48,15 +86,183 @@ def test_tree_signature(Script, environment, code, expected):
|
||||
if environment.version_info < (3, 8):
|
||||
pytest.skip()
|
||||
|
||||
if expected is None:
|
||||
assert not Script(code).call_signatures()
|
||||
else:
|
||||
sig, = Script(code).call_signatures()
|
||||
assert expected == sig.to_string()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'combination, expected', [
|
||||
# Functions
|
||||
('full_redirect(simple)', 'b, *, c'),
|
||||
('full_redirect(simple4)', 'b, x: int'),
|
||||
('full_redirect(a)', 'b, *args'),
|
||||
('full_redirect(kw)', 'b, *, c, **kwargs'),
|
||||
('full_redirect(akw)', 'c, *args, **kwargs'),
|
||||
|
||||
# Non functions
|
||||
('full_redirect(lambda x, y: ...)', 'y'),
|
||||
('full_redirect()', '*args, **kwargs'),
|
||||
('full_redirect(1)', '*args, **kwargs'),
|
||||
|
||||
# Classes / inheritance
|
||||
('full_redirect(C)', 'z, *, c'),
|
||||
('full_redirect(C())', 'y'),
|
||||
('D', 'D(a, z, /)'),
|
||||
('D()', 'D(x, y)'),
|
||||
('D().foo', 'foo(a, *, bar, z, **kwargs)'),
|
||||
|
||||
# Merging
|
||||
('two_redirects(simple, simple)', 'a, b, *, c'),
|
||||
('two_redirects(simple2, simple2)', 'x'),
|
||||
('two_redirects(akw, kw)', 'a, c, *args, **kwargs'),
|
||||
('two_redirects(kw, akw)', 'a, b, *args, c, **kwargs'),
|
||||
|
||||
('combined_redirect(simple, simple2)', 'a, b, /, *, x'),
|
||||
('combined_redirect(simple, simple3)', 'a, b, /, *, a, x: int'),
|
||||
('combined_redirect(simple2, simple)', 'x, /, *, a, b, c'),
|
||||
('combined_redirect(simple3, simple)', 'a, x: int, /, *, a, b, c'),
|
||||
|
||||
('combined_redirect(simple, kw)', 'a, b, /, *, a, b, c, **kwargs'),
|
||||
('combined_redirect(kw, simple)', 'a, b, /, *, a, b, c'),
|
||||
|
||||
('combined_lot_of_args(kw, simple4)', '*, b'),
|
||||
('combined_lot_of_args(simple4, kw)', '*, b, c, **kwargs'),
|
||||
|
||||
('combined_redirect(combined_redirect(simple2, simple4), combined_redirect(kw, simple5))',
|
||||
'x, /, *, y'),
|
||||
('combined_redirect(combined_redirect(simple4, simple2), combined_redirect(simple5, kw))',
|
||||
'a, b, x: int, /, *, a, b, c, **kwargs'),
|
||||
('combined_redirect(combined_redirect(a, kw), combined_redirect(kw, simple5))',
|
||||
'a, b, /, *args, y'),
|
||||
|
||||
('no_redirect(kw)', '*args, **kwargs'),
|
||||
('no_redirect(akw)', '*args, **kwargs'),
|
||||
('no_redirect(simple)', '*args, **kwargs'),
|
||||
]
|
||||
)
|
||||
def test_nested_signatures(Script, environment, combination, expected, skip_pre_python35):
|
||||
code = dedent('''
|
||||
def simple(a, b, *, c): ...
|
||||
def simple2(x): ...
|
||||
def simple3(a, x: int): ...
|
||||
def simple4(a, b, x: int): ...
|
||||
def simple5(y): ...
|
||||
def a(a, b, *args): ...
|
||||
def kw(a, b, *, c, **kwargs): ...
|
||||
def akw(a, c, *args, **kwargs): ...
|
||||
|
||||
def no_redirect(func):
|
||||
return lambda *args, **kwargs: func(1)
|
||||
def full_redirect(func):
|
||||
return lambda *args, **kwargs: func(1, *args, **kwargs)
|
||||
def two_redirects(func1, func2):
|
||||
return lambda *args, **kwargs: func1(*args, **kwargs) + func2(1, *args, **kwargs)
|
||||
def combined_redirect(func1, func2):
|
||||
return lambda *args, **kwargs: func1(*args) + func2(**kwargs)
|
||||
def combined_lot_of_args(func1, func2):
|
||||
return lambda *args, **kwargs: func1(1, 2, 3, 4, *args) + func2(a=3, x=1, y=1, **kwargs)
|
||||
|
||||
class C:
|
||||
def __init__(self, a, z, *, c): ...
|
||||
def __call__(self, x, y): ...
|
||||
|
||||
def foo(self, bar, z, **kwargs): ...
|
||||
|
||||
class D(C):
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
|
||||
def foo(self, a, **kwargs):
|
||||
super().foo(**kwargs)
|
||||
''')
|
||||
code += 'z = ' + combination + '\nz('
|
||||
sig, = Script(code).call_signatures()
|
||||
assert expected == sig._signature.to_string()
|
||||
computed = sig.to_string()
|
||||
if not re.match(r'\w+\(', expected):
|
||||
expected = '<lambda>(' + expected + ')'
|
||||
assert expected == computed
|
||||
|
||||
|
||||
def test_pow_signature(Script):
|
||||
# See github #1357
|
||||
sigs = Script('pow(').call_signatures()
|
||||
strings = {sig._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',
|
||||
'pow(x: float, y: float, /) -> float',
|
||||
'pow(x: int, y: int, z: int, /) -> Any',
|
||||
'pow(x: int, y: int, /) -> Any'}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'code, signature', [
|
||||
[dedent('''
|
||||
import functools
|
||||
def f(x):
|
||||
pass
|
||||
def x(f):
|
||||
@functools.wraps(f)
|
||||
def wrapper(*args):
|
||||
# Have no arguments here, but because of wraps, the signature
|
||||
# should still be f's.
|
||||
return f(*args)
|
||||
return wrapper
|
||||
|
||||
x(f)('''), 'f(x, /)'],
|
||||
[dedent('''
|
||||
import functools
|
||||
def f(x):
|
||||
pass
|
||||
def x(f):
|
||||
@functools.wraps(f)
|
||||
def wrapper():
|
||||
# Have no arguments here, but because of wraps, the signature
|
||||
# should still be f's.
|
||||
return 1
|
||||
return wrapper
|
||||
|
||||
x(f)('''), 'f()'],
|
||||
]
|
||||
)
|
||||
def test_wraps_signature(Script, code, signature, skip_pre_python35):
|
||||
sigs = Script(code).call_signatures()
|
||||
assert {sig.to_string() for sig in sigs} == {signature}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'start, start_params', [
|
||||
['@dataclass\nclass X:', []],
|
||||
['@dataclass(eq=True)\nclass X:', []],
|
||||
[dedent('''
|
||||
class Y():
|
||||
y: int
|
||||
@dataclass
|
||||
class X(Y):'''), []],
|
||||
[dedent('''
|
||||
@dataclass
|
||||
class Y():
|
||||
y: int
|
||||
z = 5
|
||||
@dataclass
|
||||
class X(Y):'''), ['y']],
|
||||
]
|
||||
)
|
||||
def test_dataclass_signature(Script, skip_pre_python37, start, start_params):
|
||||
code = dedent('''
|
||||
name: str
|
||||
foo = 3
|
||||
price: float
|
||||
quantity: int = 0.0
|
||||
|
||||
X(''')
|
||||
|
||||
code = 'from dataclasses import dataclass\n' + start + code
|
||||
|
||||
sig, = Script(code).call_signatures()
|
||||
assert [p.name for p in sig.params] == start_params + ['name', 'price', 'quantity']
|
||||
quantity, = sig.params[-1].infer()
|
||||
assert quantity.name == 'int'
|
||||
price, = sig.params[-2].infer()
|
||||
assert price.name == 'float'
|
||||
|
||||
Reference in New Issue
Block a user