1
0
forked from VimPlug/jedi

Compare commits

..

71 Commits

Author SHA1 Message Date
Dave Halter
eaab706038 Prepare the release of 0.18.2 2022-11-21 23:23:46 +01:00
Dave Halter
41455480be Better search for venvs 2022-11-21 23:06:26 +01:00
Dave Halter
0a670d10dd Merge branch 'master' of github.com:davidhalter/jedi 2022-11-21 22:59:48 +01:00
Dave Halter
6b73d5c1bf Probably using the 3.10 grammar is better for stubs for now 2022-11-21 21:07:33 +01:00
Dave Halter
a3fed3b6a6 Remove a TODO that was already implemented 2022-11-14 08:39:11 +01:00
Dave Halter
66c52b4bc7 Try to fix a test for Windows 2022-11-13 23:48:43 +01:00
Dave Halter
89f9a3a7f1 Fix a Django test 2022-11-13 23:38:22 +01:00
Dave Halter
3a30008cc4 Fix keyword argument completion, fixes #1856 2022-11-13 20:26:00 +01:00
Dave Halter
b0d5fc2bd0 Fix errors around docs of namespace packages, fixes #1890, fixes #1822 2022-11-13 19:50:08 +01:00
Dave Halter
6e5db3f479 Fix a weird AttributeError, fixes #1765 2022-11-13 18:26:01 +01:00
Dave Halter
85780111e0 Use the latest grammar from parso for stubs, probably fixes #1864 2022-11-13 17:59:22 +01:00
Dave Halter
0ba48bbb9d Fix an issue with creatin a diff, fixes #1757 2022-11-13 17:51:54 +01:00
Dave Halter
26f7878d97 Revert some of the logic around ClassVar completions, see #1847 2022-11-12 23:15:16 +01:00
Dave Halter
8027e1b162 Remove the ClassVar filter, see also #1847 2022-11-12 22:58:00 +01:00
Dave Halter
78a53bf005 Change a test slightly 2022-11-12 13:59:07 +01:00
Dave Halter
8485df416d Finally fix a Django test 2022-11-11 18:00:17 +01:00
Dave Halter
94e78340e1 Fix a formatting issue in CI 2022-11-11 17:54:57 +01:00
Dave Halter
f454989859 Now that ClassVars work differently fix a Django test 2022-11-11 17:52:35 +01:00
Dave Halter
e779f23ac7 Another small change towards tests 2022-11-11 17:50:05 +01:00
Dave Halter
3c40363a39 Remove another test that depends on specific pytest versions and is well covered by other tests 2022-11-11 17:47:02 +01:00
Dave Halter
a6cf2c338a Remove part of a test that is annoying to develop 2022-11-11 17:44:49 +01:00
Dave Halter
2a7311c1a0 Remove some unrelated things from .gitignore again 2022-11-11 17:15:46 +01:00
Dave Halter
81427e4408 Add a note about pytest entrypoints in CHANGELOG 2022-11-11 17:01:11 +01:00
Dave Halter
804e4b0ca2 Merge pull request #1861 from qmmp123/master
Fix: #1847
2022-11-11 16:00:39 +00:00
Dave Halter
3475ccfbd3 Merge pull request #1870 from Presburger/master
fix autocomplete crash in ycmd
2022-11-11 15:50:10 +00:00
Dave Halter
9723a0eed0 Merge pull request #1879 from marciomazza/find-external-pytest-fixtures
Find external pytest fixtures
2022-11-11 15:46:40 +00:00
Dave Halter
658f80fa1e Just pin all documentation generation dependencies 2022-11-11 16:36:23 +01:00
Dave Halter
31c2c508c3 Try to get jedi.readthedocs.org running again 2022-11-11 16:15:37 +01:00
Dave Halter
6c9cab2f8e Merge pull request #1889 from AndrewAmmerlaan/master
python3.11 compatibility
2022-10-20 19:08:52 +00:00
Andrew Ammerlaan
0a6ad1010c inference/compiled/subprocess/functions.py: Skip python3.11's frozen imports
Bug: https://github.com/davidhalter/jedi/issues/1858
Signed-off-by: Andrew Ammerlaan <andrewammerlaan@gentoo.org>
2022-10-19 16:53:17 +02:00
Dave Halter
3a60943f6e Merge pull request #1885 from asford/attrs_support
Extend dataclass constructor hinting to attrs next-gen apis.
2022-10-13 19:12:59 +00:00
Alex Ford
4d1e00c3ab Skip if attrs not in target environment.
Add check for attrs in test environment and skip if not installed.
This is patterned off the existing django tests.
2022-10-13 00:43:29 -07:00
Alex Ford
e15f51ecc1 Remove mutable from attrs signature tests 2022-10-11 17:55:57 -07:00
Alex Ford
eaa66b3dbb Update setup.py 2022-10-11 17:40:31 -07:00
Alex Ford
239d9e0b22 Add note to changelog 2022-10-11 17:40:31 -07:00
Alex Ford
40e1e3f560 Extend dataclass constructor hinting to attrs next-gen apis.
Trivially extends dataclass constructor hinting to attrs next-gen APIs.

This will stumble in cases where attrs extends beyond the standard
dataclasses API, such as complex use of defaults, converters, et al.
However, it likely covers the vast majority of cases which fall solidly
in the intersection of the two APIs.

Extension beyond these cases could use [PEP0681 dataclass_transforms],
however this is definitely a problem for another day.

[PEP0681 dataclass_transforms]: https://peps.python.org/pep-0681/

https://github.com/davidhalter/jedi/issues/1835
2022-10-11 17:40:31 -07:00
Marcio Mazza
c243608ac6 Add your name to AUTHORS.txt 2022-09-05 17:31:14 -03:00
Marcio Mazza
e25750ecef Make code compatible with python < 3.8 2022-09-05 17:05:11 -03:00
Marcio Mazza
1a306fddbf Fix check pytest fixture from import on the right context 2022-09-04 13:12:13 -03:00
Marcio Mazza
ec425ed2af Add tests to find pytest fixtures from external plugins 2022-09-03 17:16:32 -03:00
Marcio Mazza
fa1e9ce9a7 Simplify entry points enumeration 2022-09-03 17:16:32 -03:00
Marcio Mazza
8447d7f3e4 Discard imports of modules as pytest fixtures 2022-09-03 17:16:32 -03:00
Marcio Mazza
27e13e4072 Allow for multiple returns from goto_import 2022-09-03 17:16:32 -03:00
Marcio Mazza
9fd4aab5da Find pytest fixtures from external plugins registered via setuptools entry points
Using setuptools entry points is probably the main pytest mechanism of
plugin discovery.

See https://docs.pytest.org/en/stable/how-to/writing_plugins.html#setuptools-entry-points

This extends the functionality of #791
and maybe eliminates the need for #1786.
2022-09-03 17:16:32 -03:00
Dave Halter
8b0d391ac1 Merge pull request #1876 from marciomazza/fix-skipped-tests-due-to-python-symlinks
Fix skipped collection of pytest integration test files
2022-09-03 12:36:01 +00:00
Marcio Mazza
fa0c064841 Fix skipped collection of pytest integration test files
On integration tests file collection,
the value of `environment.executable` can also be a symlink
(e.g. in a virtualenv) with a different name than,
but pointing to the same as `sys.executable`
(e.g. .../bin/python3.10 and .../bin/python, respectively).

That causes skipping the collection of `completion/pytest.py`
and `completion/conftest.py` a lot of times, depending on the environment.
(e.g. "60 skipped" before x "23 skipped" after, in a local virtualenv)
2022-09-02 14:23:38 -03:00
Dave Halter
9e2089ef1e Merge pull request #1875 from marciomazza/fix-test-home-is-potential-project
Fix test where home could be a potential project
2022-09-02 09:19:52 +00:00
Marcio Mazza
85c7f14562 Fix test where home could be a potential project 2022-09-01 13:01:27 -03:00
Dave Halter
695f0832b4 Merge pull request #1871 from xzz53/fix-gitignore
Improve .gitignore handling
2022-08-22 09:59:53 +00:00
Mikhail Rudenko
cfb7e300af Improve .gitignore handling
At present, .gitignore patterns not starting with '/' are classified
as "ignored names" (opposing to "ignored paths") and not used for
filtering directories. But, according to the spec [1], the situation
is a bit different: all patterns apply to directories (and those
ending with '/' apply to directories only). Besides that, there two
kinds of patterns: those that match only w.r.t the directory where
defining .gitignore is located (they must contain a '/' in the
beginning or in the middle), which we call "absolute", and those that
also match in all subdirectories under the directory where defining
.gitignore is located (they must not contain '/' or contain only
trailing '/'), which we call "relative".

This commit implements handling of both "absolute" and "relative"
.gitignore patterns according to the spec. "Absolute" patterns are
handled mostly like `ignored_paths` were handled in the previous
implementation. "Relative" patterns are collected into a distinct set
containing `(defining_gitignore_dir, pattern)` tuples. For each
traversed `root_folder_io`, all applicable "relative" patterns are
expanded into a set of plain paths, which are then used for filtering
`folder_io`s.

While at it, also fix some minor issues. Explicitly ignore negative
and wildcard patterns, since we don't handle them correctly
anyway. Also, use '/' as a path separator instead of `os.path.sep`
when dealing with .gitignore, since the spec explicitly says that '/'
must be used on all platforms.

[1] https://git-scm.com/docs/gitignore
2022-08-21 21:50:29 +03:00
Yusheng.Ma
f5faca014f fix autocomplete crash in ycmd
Signed-off-by: Yusheng.Ma <Yusheng.Ma@zilliz.com>
2022-08-17 07:53:35 +00:00
Dave Halter
7ff0d2d595 Merge pull request #1867 from timgates42/bugfix_typos
docs: Fix a few typos
2022-07-15 07:36:27 +00:00
Tim Gates
c28b337278 docs: Fix a few typos
There are small typos in:
- jedi/api/exceptions.py
- jedi/inference/base_value.py
- jedi/inference/compiled/mixed.py
- jedi/inference/value/dynamic_arrays.py

Fixes:
- Should read `usually` rather than `ususally`.
- Should read `modifications` rather than `modfications`.
- Should read `interpreters` rather than `interpreteters`.
- Should read `inferred` rather than `inferrined`.
- Should read `completable` rather than `completeable`.

Signed-off-by: Tim Gates <tim.gates@iress.com>
2022-07-15 17:29:02 +10:00
nedilmark
128695bd8e remove debug changes 2022-07-03 09:42:29 +08:00
nedilmark
e194ab5951 Fix: #1847 2022-06-18 06:13:07 +08:00
Dave Halter
c0ac341750 Replace some type comments with annotations
This was necessary, back when we supported Python 3.5
2022-05-26 23:09:28 +02:00
Dave Halter
486695d479 Merge pull request #1851 from GalaxySnail/pep604
Add a naive implementation for PEP 604
2022-05-13 12:31:54 +02:00
GalaxySnail
8cb1b76ea4 Fix typo 2022-04-14 04:02:20 +08:00
GalaxySnail
e7755651a4 Add some tests for PEP 604 2022-04-14 03:32:43 +08:00
GalaxySnail
0c7384edc3 A naive implementation for PEP 604 2022-04-14 03:32:12 +08:00
Dave Halter
8f15f38949 Revert a change for Python 2.7 compatibility (see also e267f63657) 2021-12-25 14:08:44 +01:00
Dave Halter
96af7e4077 The Python 3.6 requirement is now the lowest supported version 2021-12-25 13:37:35 +01:00
Dave Halter
929fa9b452 Fix a small issue in overload tests 2021-12-25 13:18:58 +01:00
Dave Halter
08c5ab821f Merge pull request #1826 from PeterJCLaw/fix-1801-typed-decorator-on-instance-method
Make typed decorators work for instance methods
2021-12-13 02:05:55 +01:00
Peter Law
b6f761f13c Make typed decorators work for instance methods
This feels incomplete when compared to FunctionMixin.py__get__,
however seems to work at least in the cut-down reported.

Fixes https://github.com/davidhalter/jedi/issues/1801.
2021-12-12 18:18:55 +00:00
Peter Law
72cf41f4c9 Lambdas in comprehensions need parentheses in Python > 3.8
Fixes https://github.com/davidhalter/jedi/issues/1824.
2021-12-12 18:17:53 +00:00
Dave Halter
3602c10916 Merge pull request #1821 from tomaarsen/patch-1
Typo in docstring of `extract_variable`
2021-11-17 13:44:08 +01:00
Dave Halter
601bfb3493 The readthedocs option submodules should not be part of the Python option 2021-11-17 13:39:21 +01:00
Dave Halter
021f081d8a Submodules should be part of the readthedocs build 2021-11-17 13:38:03 +01:00
Dave Halter
54af6fa86d Try to fix docs dependencies
Docs were not building on read the docs, see also: https://github.com/sphinx-doc/sphinx/issues/9788
2021-11-17 13:33:41 +01:00
Tom Aarsen
f193ae67e9 typo: "statemenet" -> "statement" 2021-11-17 12:59:13 +01:00
49 changed files with 497 additions and 183 deletions

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@ record.json
/.pytest_cache
/.mypy_cache
/venv/
.nvimrc

View File

@@ -1,2 +1,11 @@
version: 2
python:
pip_install: true
install:
- method: pip
path: .
extra_requirements:
- docs
submodules:
include: all

View File

@@ -1,4 +1,4 @@
Main Authors
Main Authors
------------
- David Halter (@davidhalter) <davidhalter88@gmail.com>
@@ -62,6 +62,7 @@ Code Contributors
- Andrii Kolomoiets (@muffinmad)
- Leo Ryu (@Leo-Ryu)
- Joseph Birkner (@josephbirkner)
- Márcio Mazza (@marciomazza)
And a few more "anonymous" contributors.

View File

@@ -6,6 +6,16 @@ Changelog
Unreleased
++++++++++
0.18.2 (2022-11-21)
+++++++++++++++++++
- Added dataclass-equivalent for attrs.define
- Find fixtures from Pytest entrypoints; Examples of pytest plugins installed
like this are pytest-django, pytest-sugar and Faker.
- Fixed Project.search, when a venv was involved, which is why for example
`:Pyimport django.db` did not work in some cases in jedi-vim.
- And many smaller bugfixes
0.18.1 (2021-11-17)
+++++++++++++++++++

View File

@@ -27,7 +27,7 @@ ad
load
"""
__version__ = '0.18.1'
__version__ = '0.18.2'
from jedi.api import Script, Interpreter, set_debug_function, preload_module
from jedi import settings

View File

@@ -581,7 +581,7 @@ class Script:
@validate_line_column
def extract_variable(self, line, column, *, new_name, until_line=None, until_column=None):
"""
Moves an expression to a new statemenet.
Moves an expression to a new statement.
For example if you have the cursor on ``foo`` and provide a
``new_name`` called ``bar``::

View File

@@ -23,7 +23,7 @@ class RefactoringError(_JediError):
Refactorings can fail for various reasons. So if you work with refactorings
like :meth:`.Script.rename`, :meth:`.Script.inline`,
:meth:`.Script.extract_variable` and :meth:`.Script.extract_function`, make
sure to catch these. The descriptions in the errors are ususally valuable
sure to catch these. The descriptions in the errors are usually valuable
for end users.
A typical ``RefactoringError`` would tell the user that inlining is not

View File

@@ -280,7 +280,7 @@ class CallDetails:
def count_positional_arguments(self):
count = 0
for star_count, key_start, had_equal in self._list_arguments()[:-1]:
if star_count:
if star_count or key_start:
break
count += 1
return count
@@ -306,7 +306,7 @@ def _iter_arguments(nodes, position):
first = node.children[0]
second = node.children[1]
if second == '=':
if second.start_pos < position:
if second.start_pos < position and first.type == 'name':
yield 0, first.value, True
else:
yield 0, remove_after_pos(first), False

View File

@@ -352,9 +352,8 @@ class Project:
# 3. Search for modules on sys.path
sys_path = [
p for p in self._get_sys_path(inference_state)
# Exclude folders that are handled by recursing of the Python
# folders.
if not p.startswith(str(self._path))
# Exclude the current folder which is handled by recursing the folders.
if p != self._path
]
names = list(iter_module_names(inference_state, empty_module_context, sys_path))
yield from search_in_module(
@@ -433,7 +432,6 @@ def get_default_project(path=None):
probable_path = dir
if probable_path is not None:
# TODO search for setup.py etc
return Project(probable_path)
if first_no_init_file is not None:

View File

@@ -42,11 +42,17 @@ class ChangedFile:
if self._from_path is None:
from_p = ''
else:
from_p = self._from_path.relative_to(project_path)
try:
from_p = self._from_path.relative_to(project_path)
except ValueError: # Happens it the path is not on th project_path
from_p = self._from_path
if self._to_path is None:
to_p = ''
else:
to_p = self._to_path.relative_to(project_path)
try:
to_p = self._to_path.relative_to(project_path)
except ValueError:
to_p = self._to_path
diff = difflib.unified_diff(
old_lines, new_lines,
fromfile=str(from_p),

View File

@@ -90,7 +90,7 @@ class InferenceState:
self.compiled_subprocess = environment.get_inference_state_subprocess(self)
self.grammar = environment.get_grammar()
self.latest_grammar = parso.load_grammar(version='3.7')
self.latest_grammar = parso.load_grammar(version='3.10')
self.memoize_cache = {} # for memoize decorators
self.module_cache = imports.ModuleCache() # does the job of `sys.modules`.
self.stub_module_cache = {} # Dict[Tuple[str, ...], Optional[ModuleValue]]

View File

@@ -297,7 +297,7 @@ class Value(HelperValueMixin):
just the `_T` generic parameter.
`value_set`: represents the actual argument passed to the parameter
we're inferrined for, or (for recursive calls) their types. In the
we're inferred for, or (for recursive calls) their types. In the
above example this would first be the representation of the list
`[1]` and then, when recursing, just of `1`.
"""

View File

@@ -34,7 +34,7 @@ class MixedObject(ValueWrapper):
This combined logic makes it possible to provide more powerful REPL
completion. It allows side effects that are not noticable with the default
parser structure to still be completeable.
parser structure to still be completable.
The biggest difference from CompiledValue to MixedObject is that we are
generally dealing with Python code and not with C code. This will generate
@@ -267,7 +267,7 @@ def _find_syntax_node_name(inference_state, python_object):
@inference_state_function_cache()
def _create(inference_state, compiled_value, module_context):
# TODO accessing this is bad, but it probably doesn't matter that much,
# because we're working with interpreteters only here.
# because we're working with interpreters only here.
python_object = compiled_value.access_handle.access._obj
result = _find_syntax_node_name(inference_state, python_object)
if result is None:

View File

@@ -151,7 +151,11 @@ def _find_module(string, path=None, full_name=None, is_global_search=True):
spec = find_spec(string, p)
if spec is not None:
if spec.origin == "frozen":
continue
loader = spec.loader
if loader is None and not spec.has_location:
# This is a namespace package.
full_name = string if not path else full_name

View File

@@ -86,6 +86,8 @@ class StubFilter(ParserTreeFilter):
# Imports in stub files are only public if they have an "as"
# export.
definition = name.get_definition()
if definition is None:
return False
if definition.type in ('import_from', 'import_name'):
if name.parent.type not in ('import_as_name', 'dotted_as_name'):
return False

View File

@@ -294,6 +294,9 @@ class Callable(BaseTypingInstance):
from jedi.inference.gradual.annotation import infer_return_for_callable
return infer_return_for_callable(arguments, param_values, result_values)
def py__get__(self, instance, class_value):
return ValueSet([self])
class Tuple(BaseTypingInstance):
def _is_homogenous(self):

View File

@@ -248,7 +248,7 @@ class ValueNameMixin:
def get_defining_qualified_value(self):
context = self.parent_context
if context.is_module() or context.is_class():
if context is not None and (context.is_module() or context.is_class()):
return self.parent_context.get_value() # Might be None
return None

View File

@@ -180,26 +180,34 @@ def _check_fs(inference_state, file_io, regex):
return m.as_context()
def gitignored_lines(folder_io, file_io):
ignored_paths = set()
ignored_names = set()
def gitignored_paths(folder_io, file_io):
ignored_paths_abs = set()
ignored_paths_rel = set()
for l in file_io.read().splitlines():
if not l or l.startswith(b'#'):
if not l or l.startswith(b'#') or l.startswith(b'!') or b'*' in l:
continue
p = l.decode('utf-8', 'ignore')
if p.startswith('/'):
name = p[1:]
if name.endswith(os.path.sep):
name = name[:-1]
ignored_paths.add(os.path.join(folder_io.path, name))
p = l.decode('utf-8', 'ignore').rstrip('/')
if '/' in p:
name = p.lstrip('/')
ignored_paths_abs.add(os.path.join(folder_io.path, name))
else:
ignored_names.add(p)
return ignored_paths, ignored_names
name = p
ignored_paths_rel.add((folder_io.path, name))
return ignored_paths_abs, ignored_paths_rel
def expand_relative_ignore_paths(folder_io, relative_paths):
curr_path = folder_io.path
return {os.path.join(curr_path, p[1]) for p in relative_paths if curr_path.startswith(p[0])}
def recurse_find_python_folders_and_files(folder_io, except_paths=()):
except_paths = set(except_paths)
except_paths_relative = set()
for root_folder_io, folder_ios, file_ios in folder_io.walk():
# Delete folders that we don't want to iterate over.
for file_io in file_ios:
@@ -209,14 +217,21 @@ def recurse_find_python_folders_and_files(folder_io, except_paths=()):
yield None, file_io
if path.name == '.gitignore':
ignored_paths, ignored_names = \
gitignored_lines(root_folder_io, file_io)
except_paths |= ignored_paths
ignored_paths_abs, ignored_paths_rel = gitignored_paths(
root_folder_io, file_io
)
except_paths |= ignored_paths_abs
except_paths_relative |= ignored_paths_rel
except_paths_relative_expanded = expand_relative_ignore_paths(
root_folder_io, except_paths_relative
)
folder_ios[:] = [
folder_io
for folder_io in folder_ios
if folder_io.path not in except_paths
and folder_io.path not in except_paths_relative_expanded
and folder_io.get_base_name() not in _IGNORE_FOLDERS
]
for folder_io in folder_ios:

View File

@@ -2,6 +2,7 @@
Functions inferring the syntax tree.
"""
import copy
import itertools
from parso.python import tree
@@ -515,10 +516,20 @@ def _literals_to_types(inference_state, result):
def _infer_comparison(context, left_values, operator, right_values):
state = context.inference_state
if isinstance(operator, str):
operator_str = operator
else:
operator_str = str(operator.value)
if not left_values or not right_values:
# illegal slices e.g. cause left/right_result to be None
result = (left_values or NO_VALUES) | (right_values or NO_VALUES)
return _literals_to_types(state, result)
elif operator_str == "|" and all(
value.is_class() or value.is_compiled()
for value in itertools.chain(left_values, right_values)
):
# ^^^ A naive hack for PEP 604
return ValueSet.from_sets((left_values, right_values))
else:
# I don't think there's a reasonable chance that a string
# operation is still correct, once we pass something like six

View File

@@ -16,7 +16,7 @@ settings will stop this process.
It is important to note that:
1. Array modfications work only in the current module.
1. Array modifications work only in the current module.
2. Jedi only checks Array additions; ``list.pop``, etc are ignored.
"""
from jedi import debug

View File

@@ -78,6 +78,8 @@ class ClassName(TreeNameDefinition):
type_ = super().api_type
if type_ == 'function':
definition = self.tree_name.get_definition()
if definition is None:
return type_
if function_is_property(definition):
# This essentially checks if there is an @property before
# the function. @property could be something different, but
@@ -118,21 +120,6 @@ class ClassFilter(ParserTreeFilter):
return False
def _access_possible(self, name):
# Filter for ClassVar variables
# TODO this is not properly done, yet. It just checks for the string
# ClassVar in the annotation, which can be quite imprecise. If we
# wanted to do this correct, we would have to infer the ClassVar.
if not self._is_instance:
expr_stmt = name.get_definition()
if expr_stmt is not None and expr_stmt.type == 'expr_stmt':
annassign = expr_stmt.children[1]
if annassign.type == 'annassign':
# If there is an =, the variable is obviously also
# defined on the class.
if 'ClassVar' not in annassign.children[1].get_code() \
and '=' not in annassign.children:
return False
# Filter for name mangling of private variables like __foo
return not name.value.startswith('__') or name.value.endswith('__') \
or self._equals_origin_scope()

View File

@@ -2,7 +2,7 @@ from pathlib import Path
from parso.tree import search_ancestor
from jedi.inference.cache import inference_state_method_cache
from jedi.inference.imports import load_module_from_path
from jedi.inference.imports import goto_import, load_module_from_path
from jedi.inference.filters import ParserTreeFilter
from jedi.inference.base_value import NO_VALUES, ValueSet
from jedi.inference.helpers import infer_call_of_leaf
@@ -131,6 +131,17 @@ def _is_pytest_func(func_name, decorator_nodes):
or any('fixture' in n.get_code() for n in decorator_nodes)
def _find_pytest_plugin_modules():
"""
Finds pytest plugin modules hooked by setuptools entry points
See https://docs.pytest.org/en/stable/how-to/writing_plugins.html#setuptools-entry-points
"""
from pkg_resources import iter_entry_points
return [ep.module_name.split(".") for ep in iter_entry_points(group="pytest11")]
@inference_state_method_cache()
def _iter_pytest_modules(module_context, skip_own_module=False):
if not skip_own_module:
@@ -159,7 +170,7 @@ def _iter_pytest_modules(module_context, skip_own_module=False):
break
last_folder = folder # keep track of the last found parent name
for names in _PYTEST_FIXTURE_MODULES:
for names in _PYTEST_FIXTURE_MODULES + _find_pytest_plugin_modules():
for module_value in module_context.inference_state.import_module(names):
yield module_value.as_context()
@@ -167,14 +178,28 @@ def _iter_pytest_modules(module_context, skip_own_module=False):
class FixtureFilter(ParserTreeFilter):
def _filter(self, names):
for name in super()._filter(names):
funcdef = name.parent
# Class fixtures are not supported
if funcdef.type == 'funcdef':
decorated = funcdef.parent
if decorated.type == 'decorated' and self._is_fixture(decorated):
# look for fixture definitions of imported names
if name.parent.type == "import_from":
imported_names = goto_import(self.parent_context, name)
if any(
self._is_fixture(iname.parent_context, iname.tree_name)
for iname in imported_names
# discard imports of whole modules, that have no tree_name
if iname.tree_name
):
yield name
def _is_fixture(self, decorated):
elif self._is_fixture(self.parent_context, name):
yield name
def _is_fixture(self, context, name):
funcdef = name.parent
# Class fixtures are not supported
if funcdef.type != "funcdef":
return False
decorated = funcdef.parent
if decorated.type != "decorated":
return False
decorators = decorated.children[0]
if decorators.type == 'decorators':
decorators = decorators.children
@@ -191,11 +216,12 @@ class FixtureFilter(ParserTreeFilter):
last_leaf = last_trailer.get_last_leaf()
if last_leaf == ')':
values = infer_call_of_leaf(
self.parent_context, last_leaf, cut_own_trailer=True)
context, last_leaf, cut_own_trailer=True
)
else:
values = self.parent_context.infer_node(dotted_name)
values = context.infer_node(dotted_name)
else:
values = self.parent_context.infer_node(dotted_name)
values = context.infer_node(dotted_name)
for value in values:
if value.name.get_qualified_names(include_module_names=True) \
== ('_pytest', 'fixtures', 'fixture'):

View File

@@ -803,6 +803,15 @@ _implemented = {
# For now this works at least better than Jedi trying to understand it.
'dataclass': _dataclass
},
# attrs exposes declaration interface roughly compatible with dataclasses
# via attrs.define, attrs.frozen and attrs.mutable
# https://www.attrs.org/en/stable/names.html
'attr': {
'define': _dataclass,
},
'attrs': {
'define': _dataclass,
},
'os.path': {
'dirname': _create_string_input_function(os.path.dirname),
'abspath': _create_string_input_function(os.path.abspath),

View File

@@ -41,11 +41,40 @@ setup(name='jedi',
# coloroma for colored debug output
'colorama',
'Django<3.1', # For now pin this.
'attrs',
],
'qa': [
'flake8==3.8.3',
'mypy==0.782',
],
'docs': [
# Just pin all of these.
'Jinja2==2.11.3',
'MarkupSafe==1.1.1',
'Pygments==2.8.1',
'alabaster==0.7.12',
'babel==2.9.1',
'chardet==4.0.0',
'commonmark==0.8.1',
'docutils==0.17.1',
'future==0.18.2',
'idna==2.10',
'imagesize==1.2.0',
'mock==1.0.1',
'packaging==20.9',
'pyparsing==2.4.7',
'pytz==2021.1',
'readthedocs-sphinx-ext==2.1.4',
'recommonmark==0.5.0',
'requests==2.25.1',
'six==1.15.0',
'snowballstemmer==2.1.0',
'sphinx==1.8.5',
'sphinx-rtd-theme==0.4.3',
'sphinxcontrib-serializinghtml==1.1.4',
'sphinxcontrib-websupport==1.2.4',
'urllib3==1.26.4',
],
},
package_data={'jedi': ['*.pyi', 'third_party/typeshed/LICENSE',
'third_party/typeshed/README']},

View File

@@ -47,8 +47,6 @@ async def awaitable_test():
#? str()
foo
# python >= 3.6
async def asgen():
yield 1
await asyncio.sleep(0)

View File

@@ -23,7 +23,7 @@ def inheritance_fixture():
@pytest.fixture
def testdir(testdir):
#? ['chdir']
testdir.chdir
return testdir
def capsysbinary(capsysbinary):
#? ['close']
capsysbinary.clos
return capsysbinary

View File

@@ -5,6 +5,7 @@ import uuid
from django.db import models
from django.contrib.auth.models import User
from django.db.models.query_utils import DeferredAttribute
from django.db.models.manager import BaseManager
class TagManager(models.Manager):

View File

@@ -1,5 +1,3 @@
# python >= 3.6
class Foo:
bar = 1

View File

@@ -110,4 +110,4 @@ class Test(object):
# nocond lambdas make no sense at all.
#? int()
[a for a in [1,2] if lambda: 3][0]
[a for a in [1,2] if (lambda: 3)][0]

View File

@@ -0,0 +1,50 @@
""" Pep-0484 type hinted decorators """
from typing import Callable
def decorator(func):
def wrapper(*a, **k):
return str(func(*a, **k))
return wrapper
def typed_decorator(func: Callable[..., int]) -> Callable[..., str]:
...
# Functions
@decorator
def plain_func() -> int:
return 4
#? str()
plain_func()
@typed_decorator
def typed_func() -> int:
return 4
#? str()
typed_func()
# Methods
class X:
@decorator
def plain_method(self) -> int:
return 4
@typed_decorator
def typed_method(self) -> int:
return 4
inst = X()
#? str()
inst.plain_method()
#? str()
inst.typed_method()

View File

@@ -27,13 +27,13 @@ class PlainClass(object):
tpl = ("1", 2)
tpl_typed = ("2", 3) # type: Tuple[str, int]
tpl_typed: Tuple[str, int] = ("2", 3)
collection = {"a": 1}
collection_typed = {"a": 1} # type: Dict[str, int]
collection_typed: Dict[str, int] = {"a": 1}
list_of_ints = [42] # type: List[int]
list_of_funcs = [foo] # type: List[Callable[[T], T]]
list_of_ints: List[int] = [42]
list_of_funcs: List[Callable[[T], T]] = [foo]
custom_generic = CustomGeneric(123.45)

View File

@@ -19,12 +19,12 @@ T_co = TypeVar('T_co', covariant=True)
V = TypeVar('V')
just_float = 42. # type: float
optional_float = 42. # type: Optional[float]
list_of_ints = [42] # type: List[int]
list_of_floats = [42.] # type: List[float]
list_of_optional_floats = [x or None for x in list_of_floats] # type: List[Optional[float]]
list_of_ints_and_strs = [42, 'abc'] # type: List[Union[int, str]]
just_float: float = 42.
optional_float: Optional[float] = 42.
list_of_ints: List[int] = [42]
list_of_floats: List[float] = [42.]
list_of_optional_floats: List[Optional[float]] = [x or None for x in list_of_floats]
list_of_ints_and_strs: List[Union[int, str]] = [42, 'abc']
# Test that simple parameters are handled
def list_t_to_list_t(the_list: List[T]) -> List[T]:
@@ -48,7 +48,7 @@ for z in list_t_to_list_t(list_of_ints_and_strs):
z
list_of_int_type = [int] # type: List[Type[int]]
list_of_int_type: List[Type[int]] = [int]
# Test that nested parameters are handled
def list_optional_t_to_list_t(the_list: List[Optional[T]]) -> List[T]:
@@ -85,7 +85,7 @@ def optional_list_t_to_list_t(x: Optional[List[T]]) -> List[T]:
return x if x is not None else []
optional_list_float = None # type: Optional[List[float]]
optional_list_float: Optional[List[float]] = None
for xc in optional_list_t_to_list_t(optional_list_float):
#? float()
xc
@@ -134,7 +134,7 @@ def list_tuple_t_to_tuple_list_t(the_list: List[Tuple[T]]) -> Tuple[List[T], ...
return tuple(list(x) for x in the_list)
list_of_int_tuples = [(x,) for x in list_of_ints] # type: List[Tuple[int]]
list_of_int_tuples: List[Tuple[int]] = [(x,) for x in list_of_ints]
for b in list_tuple_t_to_tuple_list_t(list_of_int_tuples):
#? int()
@@ -145,7 +145,7 @@ def list_tuple_t_elipsis_to_tuple_list_t(the_list: List[Tuple[T, ...]]) -> Tuple
return tuple(list(x) for x in the_list)
list_of_int_tuple_elipsis = [tuple(list_of_ints)] # type: List[Tuple[int, ...]]
list_of_int_tuple_elipsis: List[Tuple[int, ...]] = [tuple(list_of_ints)]
for b in list_tuple_t_elipsis_to_tuple_list_t(list_of_int_tuple_elipsis):
#? int()
@@ -157,7 +157,7 @@ def foo(x: int) -> int:
return x
list_of_funcs = [foo] # type: List[Callable[[int], int]]
list_of_funcs: List[Callable[[int], int]] = [foo]
def list_func_t_to_list_func_type_t(the_list: List[Callable[[T], T]]) -> List[Callable[[Type[T]], T]]:
def adapt(func: Callable[[T], T]) -> Callable[[Type[T]], T]:
@@ -176,7 +176,7 @@ def bar(*a, **k) -> int:
return len(a) + len(k)
list_of_funcs_2 = [bar] # type: List[Callable[..., int]]
list_of_funcs_2: List[Callable[..., int]] = [bar]
def list_func_t_passthrough(the_list: List[Callable[..., T]]) -> List[Callable[..., T]]:
return the_list
@@ -187,7 +187,7 @@ for b in list_func_t_passthrough(list_of_funcs_2):
b(None, x="x")
mapping_int_str = {42: 'a'} # type: Dict[int, str]
mapping_int_str: Dict[int, str] = {42: 'a'}
# Test that mappings (that have more than one parameter) are handled
def invert_mapping(mapping: Mapping[K, V]) -> Mapping[V, K]:
@@ -210,11 +210,11 @@ first(mapping_int_str)
#? str()
first("abc")
some_str = NotImplemented # type: str
some_str: str = NotImplemented
#? str()
first(some_str)
annotated = [len] # type: List[ Callable[[Sequence[float]], int] ]
annotated: List[ Callable[[Sequence[float]], int] ] = [len]
#? int()
first(annotated)()
@@ -237,7 +237,7 @@ for b in values(mapping_int_str):
#
# Tests that user-defined generic types are handled
#
list_ints = [42] # type: List[int]
list_ints: List[int] = [42]
class CustomGeneric(Generic[T_co]):
def __init__(self, val: T_co) -> None:
@@ -248,7 +248,7 @@ class CustomGeneric(Generic[T_co]):
def custom(x: CustomGeneric[T]) -> T:
return x.val
custom_instance = CustomGeneric(42) # type: CustomGeneric[int]
custom_instance: CustomGeneric[int] = CustomGeneric(42)
#? int()
custom(custom_instance)
@@ -275,7 +275,7 @@ for x5 in wrap_custom(list_ints):
# Test extraction of type from a nested custom generic type
list_custom_instances = [CustomGeneric(42)] # type: List[CustomGeneric[int]]
list_custom_instances: List[CustomGeneric[int]] = [CustomGeneric(42)]
def unwrap_custom(iterable: Iterable[CustomGeneric[T]]) -> List[T]:
return [x.val for x in iterable]
@@ -303,7 +303,7 @@ for xg in unwrap_custom(CustomGeneric(s) for s in 'abc'):
# Test extraction of type from type parameer nested within a custom generic type
custom_instance_list_int = CustomGeneric([42]) # type: CustomGeneric[List[int]]
custom_instance_list_int: CustomGeneric[List[int]] = CustomGeneric([42])
def unwrap_custom2(instance: CustomGeneric[Iterable[T]]) -> List[T]:
return list(instance.val)
@@ -326,7 +326,7 @@ class Specialised(Mapping[int, str]):
pass
specialised_instance = NotImplemented # type: Specialised
specialised_instance: Specialised = NotImplemented
#? int()
first(specialised_instance)
@@ -341,7 +341,7 @@ class ChildOfSpecialised(Specialised):
pass
child_of_specialised_instance = NotImplemented # type: ChildOfSpecialised
child_of_specialised_instance: ChildOfSpecialised = NotImplemented
#? int()
first(child_of_specialised_instance)
@@ -355,13 +355,13 @@ class CustomPartialGeneric1(Mapping[str, T]):
pass
custom_partial1_instance = NotImplemented # type: CustomPartialGeneric1[int]
custom_partial1_instance: CustomPartialGeneric1[int] = NotImplemented
#? str()
first(custom_partial1_instance)
custom_partial1_unbound_instance = NotImplemented # type: CustomPartialGeneric1
custom_partial1_unbound_instance: CustomPartialGeneric1 = NotImplemented
#? str()
first(custom_partial1_unbound_instance)
@@ -371,7 +371,7 @@ class CustomPartialGeneric2(Mapping[T, str]):
pass
custom_partial2_instance = NotImplemented # type: CustomPartialGeneric2[int]
custom_partial2_instance: CustomPartialGeneric2[int] = NotImplemented
#? int()
first(custom_partial2_instance)
@@ -380,7 +380,7 @@ first(custom_partial2_instance)
values(custom_partial2_instance)[0]
custom_partial2_unbound_instance = NotImplemented # type: CustomPartialGeneric2
custom_partial2_unbound_instance: CustomPartialGeneric2 = NotImplemented
#? []
first(custom_partial2_unbound_instance)

View File

@@ -19,16 +19,16 @@ TTypeAny = TypeVar('TTypeAny', bound=Type[Any])
TCallable = TypeVar('TCallable', bound=Callable[..., Any])
untyped_list_str = ['abc', 'def']
typed_list_str = ['abc', 'def'] # type: List[str]
typed_list_str: List[str] = ['abc', 'def']
untyped_tuple_str = ('abc',)
typed_tuple_str = ('abc',) # type: Tuple[str]
typed_tuple_str: Tuple[str] = ('abc',)
untyped_tuple_str_int = ('abc', 4)
typed_tuple_str_int = ('abc', 4) # type: Tuple[str, int]
typed_tuple_str_int: Tuple[str, int] = ('abc', 4)
variadic_tuple_str = ('abc',) # type: Tuple[str, ...]
variadic_tuple_str_int = ('abc', 4) # type: Tuple[Union[str, int], ...]
variadic_tuple_str: Tuple[str, ...] = ('abc',)
variadic_tuple_str_int: Tuple[Union[str, int], ...] = ('abc', 4)
def untyped_passthrough(x):

View File

@@ -1,10 +1,9 @@
# python >= 3.6
from typing import List, Dict, overload, Tuple, TypeVar
lst: list
list_alias: List
list_str: List[str]
list_str: List[int]
list_int: List[int]
# -------------------------
# With base classes

View File

@@ -2,18 +2,14 @@
Test the typing library, with docstrings and annotations
"""
import typing
from typing import Sequence, MutableSequence, List, Iterable, Iterator, \
AbstractSet, Tuple, Mapping, Dict, Union, Optional
class B:
pass
def we_can_has_sequence(p, q, r, s, t, u):
"""
:type p: typing.Sequence[int]
:type q: typing.Sequence[B]
:type r: typing.Sequence[int]
:type s: typing.Sequence["int"]
:type t: typing.MutableSequence[dict]
:type u: typing.List[float]
"""
def we_can_has_sequence(p: Sequence[int], q: Sequence[B], r: Sequence[int],
s: Sequence["int"], t: MutableSequence[dict], u: List[float]):
#? ["count"]
p.c
#? int()
@@ -43,13 +39,8 @@ def we_can_has_sequence(p, q, r, s, t, u):
#? float()
u[1]
def iterators(ps, qs, rs, ts):
"""
:type ps: typing.Iterable[int]
:type qs: typing.Iterator[str]
:type rs: typing.Sequence["ForwardReference"]
:type ts: typing.AbstractSet["float"]
"""
def iterators(ps: Iterable[int], qs: Iterator[str], rs:
Sequence["ForwardReference"], ts: AbstractSet["float"]):
for p in ps:
#? int()
p
@@ -79,22 +70,13 @@ def iterators(ps, qs, rs, ts):
#? float()
t
def sets(p, q):
"""
:type p: typing.AbstractSet[int]
:type q: typing.MutableSet[float]
"""
def sets(p: AbstractSet[int], q: typing.MutableSet[float]):
#? []
p.a
#? ["add"]
q.a
def tuple(p, q, r):
"""
:type p: typing.Tuple[int]
:type q: typing.Tuple[int, str, float]
:type r: typing.Tuple[B, ...]
"""
def tuple(p: Tuple[int], q: Tuple[int, str, float], r: Tuple[B, ...]):
#? int()
p[0]
#? ['index']
@@ -127,16 +109,14 @@ class Key:
class Value:
pass
def mapping(p, q, d, dd, r, s, t):
"""
:type p: typing.Mapping[Key, Value]
:type q: typing.MutableMapping[Key, Value]
:type d: typing.Dict[Key, Value]
:type dd: typing.DefaultDict[Key, Value]
:type r: typing.KeysView[Key]
:type s: typing.ValuesView[Value]
:type t: typing.ItemsView[Key, Value]
"""
def mapping(
p: Mapping[Key, Value],
q: typing.MutableMapping[Key, Value],
d: Dict[Key, Value],
dd: typing.DefaultDict[Key, Value],
r: typing.KeysView[Key],
s: typing.ValuesView[Value],
t: typing.ItemsView[Key, Value]):
#? []
p.setd
#? ["setdefault"]
@@ -198,14 +178,12 @@ def mapping(p, q, d, dd, r, s, t):
#? Value()
value
def union(p, q, r, s, t):
"""
:type p: typing.Union[int]
:type q: typing.Union[int, int]
:type r: typing.Union[int, str, "int"]
:type s: typing.Union[int, typing.Union[str, "typing.Union['float', 'dict']"]]
:type t: typing.Union[int, None]
"""
def union(
p: Union[int],
q: Union[int, int],
r: Union[int, str, "int"],
s: Union[int, typing.Union[str, "typing.Union['float', 'dict']"]],
t: Union[int, None]):
#? int()
p
#? int()
@@ -217,9 +195,8 @@ def union(p, q, r, s, t):
#? int() None
t
def optional(p):
def optional(p: Optional[int]):
"""
:type p: typing.Optional[int]
Optional does not do anything special. However it should be recognised
as being of that type. Jedi doesn't do anything with the extra into that
it can be None as well
@@ -234,10 +211,7 @@ class TestDict(typing.Dict[str, int]):
def setdud(self):
pass
def testdict(x):
"""
:type x: TestDict
"""
def testdict(x: TestDict):
#? ["setdud", "setdefault"]
x.setd
for key in x.keys():
@@ -262,10 +236,7 @@ y = WrappingType(0) # Per https://github.com/davidhalter/jedi/issues/1015#issuec
#? str()
y
def testnewtype(y):
"""
:type y: WrappingType
"""
def testnewtype(y: WrappingType):
#? str()
y
#? ["upper"]
@@ -273,10 +244,7 @@ def testnewtype(y):
WrappingType2 = typing.NewType()
def testnewtype2(y):
"""
:type y: WrappingType2
"""
def testnewtype2(y: WrappingType2):
#?
y
#? []
@@ -297,10 +265,7 @@ class TestDefaultDict(typing.DefaultDict[str, int]):
def setdud(self):
pass
def testdict(x):
"""
:type x: TestDefaultDict
"""
def testdict(x: TestDefaultDict):
#? ["setdud", "setdefault"]
x.setd
for key in x.keys():

View File

@@ -1,9 +1,6 @@
"""
PEP 526 introduced a new way of using type annotations on variables. It was
introduced in Python 3.6.
PEP 526 introduced a way of using type annotations on variables.
"""
# python >= 3.6
import typing
asdf = ''
@@ -47,7 +44,7 @@ class Foo():
baz: typing.ClassVar[str]
#?
#? int()
Foo.bar
#? int()
Foo().bar
@@ -61,6 +58,7 @@ class VarClass:
var_instance2: float
var_class1: typing.ClassVar[str] = 1
var_class2: typing.ClassVar[bytes]
var_class3 = None
def __init__(self):
#? int()
@@ -73,15 +71,21 @@ class VarClass:
d.var_class2
#? []
d.int
#? ['var_class1', 'var_class2', 'var_instance1', 'var_instance2']
#? ['var_class1', 'var_class2', 'var_instance1', 'var_instance2', 'var_class3']
self.var_
class VarClass2(VarClass):
var_class3: typing.ClassVar[int]
#? ['var_class1', 'var_class2', 'var_instance1']
def __init__(self):
#? int()
self.var_class3
#? ['var_class1', 'var_class2', 'var_instance1', 'var_class3', 'var_instance2']
VarClass.var_
#? int()
VarClass.var_instance1
#?
#? float()
VarClass.var_instance2
#? str()
VarClass.var_class1
@@ -91,7 +95,7 @@ VarClass.var_class2
VarClass.int
d = VarClass()
#? ['var_class1', 'var_class2', 'var_instance1', 'var_instance2']
#? ['var_class1', 'var_class2', 'var_class3', 'var_instance1', 'var_instance2']
d.var_
#? int()
d.var_instance1

View File

@@ -0,0 +1,46 @@
from pep0484_generic_parameters import list_t_to_list_t
list_of_ints_and_strs: list[int | str]
# Test that unions are handled
x2 = list_t_to_list_t(list_of_ints_and_strs)[0]
#? int() str()
x2
for z in list_t_to_list_t(list_of_ints_and_strs):
#? int() str()
z
from pep0484_generic_passthroughs import (
typed_variadic_tuple_generic_passthrough,
)
variadic_tuple_str_int: tuple[int | str, ...]
for m in typed_variadic_tuple_generic_passthrough(variadic_tuple_str_int):
#? str() int()
m
def func_returns_byteslike() -> bytes | bytearray:
pass
#? bytes() bytearray()
func_returns_byteslike()
pep604_optional_1: int | str | None
pep604_optional_2: None | bytes
#? int() str() None
pep604_optional_1
#? None bytes()
pep604_optional_2
pep604_in_str: "int | bytes"
#? int() bytes()
pep604_in_str

View File

@@ -139,9 +139,6 @@ def test_p(monkeypatch):
#? ['capsysbinary']
def test_p(capsysbin
#? ['tmpdir', 'tmpdir_factory']
def test_p(tmpdi
def close_parens():
pass
@@ -183,3 +180,28 @@ def with_annot() -> Generator[float, None, None]:
def test_with_annot(inheritance_fixture, with_annot):
#? float()
with_annot
# -----------------
# pytest external plugins
# -----------------
#? ['admin_user', 'admin_client']
def test_z(admin
#! 15 ['def admin_client']
def test_p(admin_client):
#? ['login', 'logout']
admin_client.log
@pytest.fixture
@some_decorator
#? ['admin_user']
def bla(admin_u
return
@pytest.fixture
@some_decorator
#! 12 ['def admin_user']
def bla(admin_user):
pass

View File

@@ -0,0 +1,6 @@
from pytest import fixture
@fixture()
def admin_user():
pass

View File

@@ -0,0 +1,16 @@
import pytest
from .fixtures import admin_user # noqa
@pytest.fixture()
def admin_client():
return Client()
class Client:
def login(self, **credentials):
...
def logout(self):
...

View File

@@ -209,7 +209,7 @@ class IntegrationTestCase(BaseTestCase):
TEST_REFERENCES: self.run_get_references,
}
if (self.path.endswith('pytest.py') or self.path.endswith('conftest.py')) \
and environment.executable != os.path.realpath(sys.executable):
and os.path.realpath(environment.executable) != os.path.realpath(sys.executable):
# It's not guarantueed that pytest is installed in test
# environments, if we're not running in the same environment that
# we're already in, so just skip that case.

View File

@@ -650,6 +650,7 @@ def test_cursor_after_signature(Script, column):
('abs(chr ( \nclass y: pass', 1, 8, 'abs', 0),
('abs(chr ( \nclass y: pass', 1, 9, 'abs', 0),
('abs(chr ( \nclass y: pass', 1, 10, 'chr', 0),
('abs(foo.bar=3)', 1, 13, 'abs', 0),
]
)
def test_base_signatures(Script, code, line, column, name, index):

View File

@@ -739,3 +739,18 @@ def test_param_infer_default():
param, = abs_sig.params
assert param.name == 'x'
assert param.infer_default() == []
@pytest.mark.parametrize(
'code, expected', [
("random.triangular(", ['high=', 'low=', 'mode=']),
("random.triangular(low=1, ", ['high=', 'mode=']),
("random.triangular(high=1, ", ['low=', 'mode=']),
("random.triangular(low=1, high=2, ", ['mode=']),
("random.triangular(low=1, mode=2, ", ['high=']),
],
)
def test_keyword_param_completion(code, expected):
import random
completions = jedi.Interpreter(code, [locals()]).complete()
assert expected == [c.name for c in completions if c.name.endswith('=')]

View File

@@ -178,7 +178,7 @@ def test_is_potential_project(path, expected):
if expected is None:
try:
expected = _CONTAINS_POTENTIAL_PROJECT in os.listdir(path)
expected = bool(set(_CONTAINS_POTENTIAL_PROJECT) & set(os.listdir(path)))
except OSError:
expected = False

View File

@@ -1,6 +1,7 @@
import os
from textwrap import dedent
from pathlib import Path
import platform
import pytest
@@ -70,3 +71,23 @@ def test_diff_without_ending_newline(Script):
-a
+c
''')
def test_diff_path_outside_of_project(Script):
if platform.system().lower() == 'windows':
abs_path = r'D:\unknown_dir\file.py'
else:
abs_path = '/unknown_dir/file.py'
script = Script(
code='foo = 1',
path=abs_path,
project=jedi.get_default_project()
)
diff = script.rename(line=1, column=0, new_name='bar').get_diff()
assert diff == dedent(f'''\
--- {abs_path}
+++ {abs_path}
@@ -1 +1 @@
-foo = 1
+bar = 1
''')

View File

@@ -43,6 +43,9 @@ def test_implicit_namespace_package(Script):
solution = "foo = '%s'" % solution
assert completion.description == solution
c, = script_with_path('import pkg').complete()
assert c.docstring() == ""
def test_implicit_nested_namespace_package(Script):
code = 'from implicit_nested_namespaces.namespace.pkg.module import CONST'

View File

@@ -297,7 +297,6 @@ def test_os_issues(Script):
# Github issue #759
s = 'import os, s'
assert 'sys' in import_names(s)
assert 'path' not in import_names(s, column=len(s) - 1)
assert 'os' in import_names(s, column=len(s) - 3)
# Some more checks

View File

@@ -355,6 +355,48 @@ def test_dataclass_signature(Script, skip_pre_python37, start, start_params):
price, = sig.params[-2].infer()
assert price.name == 'float'
@pytest.mark.parametrize(
'start, start_params', [
['@define\nclass X:', []],
['@frozen\nclass X:', []],
['@define(eq=True)\nclass X:', []],
[dedent('''
class Y():
y: int
@define
class X(Y):'''), []],
[dedent('''
@define
class Y():
y: int
z = 5
@define
class X(Y):'''), ['y']],
]
)
def test_attrs_signature(Script, skip_pre_python37, start, start_params):
has_attrs = bool(Script('import attrs').infer())
if not has_attrs:
raise pytest.skip("attrs needed in target environment to run this test")
code = dedent('''
name: str
foo = 3
price: float
quantity: int = 0.0
X(''')
# attrs exposes two namespaces
code = 'from attrs import define, frozen\n' + start + code
sig, = Script(code).get_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'
@pytest.mark.parametrize(
'stmt, expected', [

View File

@@ -1,4 +1,5 @@
import os
from collections import namedtuple
import pytest
@@ -42,6 +43,22 @@ def test_completion(case, monkeypatch, environment, has_django):
if (not has_django) and case.path.endswith('django.py'):
pytest.skip('Needs django to be installed to run this test.')
if case.path.endswith("pytest.py"):
# to test finding pytest fixtures from external plugins
# add a stub pytest plugin to the project sys_path...
pytest_plugin_dir = str(helpers.get_example_dir("pytest_plugin_package"))
case._project.added_sys_path = [pytest_plugin_dir]
# ... and mock setuptools entry points to include it
# see https://docs.pytest.org/en/stable/how-to/writing_plugins.html#setuptools-entry-points
def mock_iter_entry_points(group):
assert group == "pytest11"
EntryPoint = namedtuple("EntryPoint", ["module_name"])
return [EntryPoint("pytest_plugin.plugin")]
monkeypatch.setattr("pkg_resources.iter_entry_points", mock_iter_entry_points)
repo_root = helpers.root_dir
monkeypatch.chdir(os.path.join(repo_root, 'jedi'))
case.run(assert_case_equal, environment)