1
0
forked from VimPlug/jedi

Compare commits

..

38 Commits
v0.18.0 ... tmp

Author SHA1 Message Date
Dave Halter
354dab9503 Debug 2021-01-02 13:48:54 +01:00
Dave Halter
ca2c732d66 PNGs are not text and should not be normalized 2021-01-02 12:27:24 +01:00
Dave Halter
2ec3d72151 Use "namespace" as a Name.type 2021-01-02 12:14:28 +01:00
Dave Halter
02d43caa5e Fix a wrong test about references 2021-01-02 01:17:38 +01:00
Dave Halter
55c7e4eb49 Stdlib modules should not be included in the get_references search, fixes davidhalter/jedi-vim#792 2021-01-02 00:58:50 +01:00
Dave Halter
7d160f96f6 Do not show signatures for properties, fixes #1695 2021-01-01 23:51:41 +01:00
Dave Halter
1ccc63e83d Make py__iter__ work as well for Interpreter 2021-01-01 17:58:31 +01:00
Dave Halter
971913be35 Make it possible to use __getitem__ in interpreter 2021-01-01 15:57:55 +01:00
Dave Halter
36ea6b3285 Change an import 2021-01-01 05:19:37 +01:00
Dave Halter
85f45771f1 Fix typing.NewType signature 2021-01-01 04:22:52 +01:00
Dave Halter
30e702de11 Generics don't have signatures 2021-01-01 04:09:49 +01:00
Dave Halter
778442a972 Type aliases should not have a signature 2021-01-01 03:59:28 +01:00
Dave Halter
4f34712858 Fix signatures for TypeVar and cast, fixes #1709 2021-01-01 03:59:12 +01:00
Dave Halter
d821451a64 Upgrade typeshed 2021-01-01 03:18:49 +01:00
Dave Halter
92d96ac336 actually use auto_import_modules correctly 2021-01-01 02:59:42 +01:00
Dave Halter
c64e33173a Fix an issue about properties, fixes #1705 2020-12-28 00:54:40 +01:00
Dave Halter
5d2aed34f4 Fix signatures if a decorator has no signatures, fixes #1705 2020-12-28 00:47:10 +01:00
Dave Halter
04c1c0f871 Fix an issue with api_name of class attributes, fixes #1688 2020-12-28 00:29:30 +01:00
Dave Halter
0f128c6deb Fix nested comprehension contexts, fixes #1691 2020-12-27 21:09:00 +01:00
Dave Halter
8373ef079f Remove an unnecessary comment 2020-12-26 22:43:47 +01:00
Dave Halter
227cbde169 Merge branch 'master' of github.com:davidhalter/jedi 2020-12-26 18:02:05 +01:00
Dave Halter
1f06e6f0c9 name the ci workflow in the hope that badges will then be displayed 2020-12-26 17:57:38 +01:00
Dave Halter
2d3b8ac8df Merge pull request #1715 from davidhalter/github-actions
Use GitHub Actions
2020-12-26 12:56:20 +01:00
Dave Halter
fa6072b4fa Change Python test order in CI 2020-12-26 12:39:37 +01:00
Dave Halter
aae2f7c49a Change badges from Travis/Appveyor to GitHub Actions 2020-12-26 12:37:04 +01:00
Dave Halter
52443daf12 Fix another Windows test on 3.8 2020-12-26 12:19:59 +01:00
Dave Halter
86d57edda4 Some Windows compatibility fixes 2020-12-26 11:52:47 +01:00
Dave Halter
7298350e76 Standardize line separator 2020-12-26 04:27:06 +01:00
Dave Halter
3184264b3b Try to fix windows 2020-12-26 04:16:32 +01:00
Dave Halter
d4a1657b2e Better error reporting 2020-12-26 04:03:19 +01:00
Dave Halter
bea401912f Hopefully fix Actions configuration 2020-12-26 03:42:33 +01:00
Dave Halter
3e4070bbb3 Enable Windows 2019 2020-12-26 03:35:28 +01:00
Dave Halter
3d7ad50f57 Remove travis and appveyor configs in favor of github action 2020-12-26 03:33:22 +01:00
Dave Halter
85ec94cf65 Fix pytest issues, fixes #1699 2020-12-26 03:32:17 +01:00
Dave Halter
0cc5c974f6 Try to improve GH Actions 2020-12-26 01:43:29 +01:00
Dave Halter
6f76bb945a GH actions, checkout recursive submodules 2020-12-26 01:14:17 +01:00
Dave Halter
239a3730a6 Try to add Github Actions 2020-12-26 01:03:03 +01:00
Dave Halter
8740ff2691 Ignore the mypy cache for searching folders 2020-12-25 17:35:28 +01:00
52 changed files with 414 additions and 268 deletions

10
.gitattributes vendored Normal file
View File

@@ -0,0 +1,10 @@
# all end-of-lines are normalized to LF when written to the repository
# https://git-scm.com/docs/gitattributes#_text
* text=auto
# force all text files on the working dir to have LF line endings
# https://git-scm.com/docs/gitattributes#_eol
* text eol=lf
# PNGs are not text and should not be normalized
*.png -text

76
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,76 @@
name: ci
on: push
jobs:
tests:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04, windows-2019]
python-version: [3.9, 3.8, 3.7, 3.6]
environment: ['3.8', '3.9', '3.7', '3.6', 'interpreter']
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- uses: actions/setup-python@v2
if: ${{ matrix.environment != 'interpreter' }}
with:
python-version: ${{ matrix.environment }}
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: 'pip install .[testing]'
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
- name: Run tests
run: python -m pytest
env:
JEDI_TEST_ENVIRONMENT: ${{ matrix.environment }}
code-quality:
runs-on: ubuntu-20.04
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Install dependencies
run: 'pip install .[qa]'
- name: Run tests
run: |
python -m flake8 jedi setup.py
python -m mypy jedi sith.py
coverage:
runs-on: ubuntu-20.04
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Install dependencies
run: 'pip install .[testing] coverage'
- name: Run tests
run: |
python -m coverage run --source jedi -m pytest
python -m coverage report
- name: Upload coverage data
run: |
pip install --quiet codecov coveralls
python -m coverage xml
python -m coverage report -m
bash <(curl -s https://codecov.io/bash) -X gcov -X coveragepy -X search -X fix -X xcode -f coverage.xml

View File

@@ -1,74 +0,0 @@
dist: xenial
language: python
python:
- 3.9-dev
- 3.8
- 3.7
- 3.6
env:
- JEDI_TEST_ENVIRONMENT=38
- JEDI_TEST_ENVIRONMENT=39
- JEDI_TEST_ENVIRONMENT=37
- JEDI_TEST_ENVIRONMENT=36
- JEDI_TEST_ENVIRONMENT=interpreter
matrix:
include:
- python: 3.8
script:
- 'pip install coverage'
- 'coverage run --source jedi -m pytest'
- 'coverage report'
after_script:
- |
pip install --quiet codecov coveralls
coverage xml
coverage report -m
coveralls
bash <(curl -s https://codecov.io/bash) -X gcov -X coveragepy -X search -X fix -X xcode -f coverage.xml
- python: 3.8
install:
- 'pip install .[qa]'
script:
- 'flake8 jedi setup.py'
- 'mypy jedi sith.py'
install:
- sudo apt-get -y install python3-venv
- pip install .[testing]
script:
- |
# Setup/install Python for $JEDI_TEST_ENVIRONMENT.
set -ex
test_env_version=${JEDI_TEST_ENVIRONMENT:0:1}.${JEDI_TEST_ENVIRONMENT:1:1}
if [ "$TRAVIS_PYTHON_VERSION" != "$test_env_version" ] && [ "$JEDI_TEST_ENVIRONMENT" != "interpreter" ]; then
python_bin=python$test_env_version
python_path="$(which $python_bin || true)"
if [ -z "$python_path" ]; then
# Only required for JEDI_TEST_ENVIRONMENT=38, because it's not always
# available.
download_name=python-$test_env_version
if [ "$JEDI_TEST_ENVIRONMENT" == "39" ]; then
wget https://storage.googleapis.com/travis-ci-language-archives/python/binaries/ubuntu/16.04/x86_64/python-3.9-dev.tar.bz2
sudo tar xjf python-3.9-dev.tar.bz2 --directory / opt/python
ln -s "/opt/python/3.9-dev/bin/python" /home/travis/bin/python3.9
else
wget https://s3.amazonaws.com/travis-python-archives/binaries/ubuntu/16.04/x86_64/$download_name.tar.bz2
sudo tar xjf $download_name.tar.bz2 --directory / opt/python
ln -s "/opt/python/${test_env_version}/bin/python" /home/travis/bin/$python_bin
fi
elif [ "${python_path#/opt/pyenv/shims}" != "$python_path" ]; then
# Activate pyenv version (required with JEDI_TEST_ENVIRONMENT=36).
pyenv_bin="$(pyenv whence --path "$python_bin" | head -n1)"
ln -s "$pyenv_bin" /home/travis/bin/$python_bin
fi
$python_bin --version
python_ver=$($python_bin -c 'import sys; print("%d%d" % sys.version_info[0:2])')
if [ "$JEDI_TEST_ENVIRONMENT" != "$python_ver" ]; then
echo "Unexpected Python version for $JEDI_TEST_ENVIRONMENT: $python_ver"
set +ex
exit 2
fi
fi
set +ex
- pytest

View File

@@ -6,6 +6,8 @@ Changelog
Unreleased Unreleased
++++++++++ ++++++++++
- Implict namespaces are now a separate types in ``Name().type``
0.18.0 (2020-12-25) 0.18.0 (2020-12-25)
+++++++++++++++++++ +++++++++++++++++++

View File

@@ -10,17 +10,9 @@ Jedi - an awesome autocompletion, static analysis and refactoring library for Py
:target: https://github.com/davidhalter/jedi/issues :target: https://github.com/davidhalter/jedi/issues
:alt: The resolution time is the median time an issue or pull request stays open. :alt: The resolution time is the median time an issue or pull request stays open.
.. image:: https://travis-ci.org/davidhalter/jedi.svg?branch=master .. image:: https://github.com/davidhalter/jedi/workflows/ci/badge.svg?branch=master
:target: https://travis-ci.org/davidhalter/jedi :target: https://github.com/davidhalter/jedi/actions
:alt: Linux Tests :alt: Tests
.. image:: https://ci.appveyor.com/api/projects/status/mgva3bbawyma1new/branch/master?svg=true
:target: https://ci.appveyor.com/project/davidhalter/jedi/branch/master
:alt: Windows Tests
.. image:: https://coveralls.io/repos/davidhalter/jedi/badge.svg?branch=master
:target: https://coveralls.io/r/davidhalter/jedi
:alt: Coverage status
.. image:: https://pepy.tech/badge/jedi .. image:: https://pepy.tech/badge/jedi
:target: https://pepy.tech/project/jedi :target: https://pepy.tech/project/jedi

View File

@@ -1,17 +0,0 @@
environment:
matrix:
- PYTHON_PATH: C:\Python37
JEDI_TEST_ENVIRONMENT: 37
- PYTHON_PATH: C:\Python37
JEDI_TEST_ENVIRONMENT: 36
- PYTHON_PATH: C:\Python36
JEDI_TEST_ENVIRONMENT: 37
- PYTHON_PATH: C:\Python36
JEDI_TEST_ENVIRONMENT: 36
install:
- git submodule update --init --recursive
- set PATH=%PYTHON_PATH%;%PYTHON_PATH%\Scripts;%PATH%
- pip install .[testing]
build_script:
- pytest

View File

@@ -100,7 +100,9 @@ def environment(request):
if request.config.option.interpreter_env or version == 'interpreter': if request.config.option.interpreter_env or version == 'interpreter':
return InterpreterEnvironment() return InterpreterEnvironment()
return get_system_environment(version[0] + '.' + version[1:]) if '.' not in version:
version = version[0] + '.' + version[1:]
return get_system_environment(version)
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
@@ -138,6 +140,11 @@ def goto_or_help_or_infer(request, Script):
return do return do
@pytest.fixture(scope='session', params=['goto', 'complete', 'help'])
def goto_or_complete(request, Script):
return lambda code, *args, **kwargs: getattr(Script(code), request.param)(*args, **kwargs)
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def has_django(environment): def has_django(environment):
script = jedi.Script('import django', environment=environment) script = jedi.Script('import django', environment=environment)

View File

@@ -12,8 +12,8 @@ easy as::
python3.8 -m pytest python3.8 -m pytest
Tests are also run automatically on `Travis CI Tests are also run automatically on `GitHub Actions
<https://travis-ci.org/davidhalter/jedi/>`_. <https://github.com/davidhalter/jedi/actions>`_.
You want to add a test for |jedi|? Great! We love that. Normally you should You want to add a test for |jedi|? Great! We love that. Normally you should
write your tests as :ref:`Blackbox Tests <blackbox>`. Most tests would write your tests as :ref:`Blackbox Tests <blackbox>`. Most tests would

View File

@@ -18,13 +18,9 @@ Jedi - an awesome autocompletion, static analysis and refactoring library for Py
:target: https://github.com/davidhalter/jedi/issues :target: https://github.com/davidhalter/jedi/issues
:alt: The resolution time is the median time an issue or pull request stays open. :alt: The resolution time is the median time an issue or pull request stays open.
.. image:: https://travis-ci.org/davidhalter/jedi.svg?branch=master .. image:: https://github.com/davidhalter/jedi/workflows/ci/badge.svg?branch=master
:target: https://travis-ci.org/davidhalter/jedi :target: https://github.com/davidhalter/jedi/actions
:alt: Linux Tests :alt: Tests
.. image:: https://ci.appveyor.com/api/projects/status/mgva3bbawyma1new/branch/master?svg=true
:target: https://ci.appveyor.com/project/davidhalter/jedi/branch/master
:alt: Windows Tests
.. image:: https://coveralls.io/repos/davidhalter/jedi/badge.svg?branch=master .. image:: https://coveralls.io/repos/davidhalter/jedi/badge.svg?branch=master
:target: https://coveralls.io/r/davidhalter/jedi :target: https://coveralls.io/r/davidhalter/jedi
@@ -73,5 +69,4 @@ mailing list: https://groups.google.com/g/jedi-announce. To subscribe you can
simply send an empty email to ``jedi-announce+subscribe@googlegroups.com``. simply send an empty email to ``jedi-announce+subscribe@googlegroups.com``.
- `Source Code on Github <https://github.com/davidhalter/jedi>`_ - `Source Code on Github <https://github.com/davidhalter/jedi>`_
- `Travis Testing <https://travis-ci.org/davidhalter/jedi>`_
- `Python Package Index <https://pypi.python.org/pypi/jedi/>`_ - `Python Package Index <https://pypi.python.org/pypi/jedi/>`_

View File

@@ -393,7 +393,7 @@ class Script:
quite hard to do for Jedi, if it is too complicated, Jedi will stop quite hard to do for Jedi, if it is too complicated, Jedi will stop
searching. searching.
:param include_builtins: Default ``True``. If ``False``, checks if a reference :param include_builtins: Default ``True``. If ``False``, checks if a definition
is a builtin (e.g. ``sys``) and in that case does not return it. is a builtin (e.g. ``sys``) and in that case does not return it.
:param scope: Default ``'project'``. If ``'file'``, include references in :param scope: Default ``'project'``. If ``'file'``, include references in
the current module only. the current module only.

View File

@@ -550,6 +550,8 @@ class BaseName:
return ''.join(lines[start_index:index + after + 1]) return ''.join(lines[start_index:index + after + 1])
def _get_signatures(self, for_docstring=False): def _get_signatures(self, for_docstring=False):
if self._name.api_type == 'property':
return []
if for_docstring and self._name.api_type == 'statement' and not self.is_stub(): if for_docstring and self._name.api_type == 'statement' and not self.is_stub():
# For docstrings we don't resolve signatures if they are simple # For docstrings we don't resolve signatures if they are simple
# statements and not stubs. This is a speed optimization. # statements and not stubs. This is a speed optimization.

View File

@@ -627,7 +627,7 @@ def search_in_module(inference_state, module_context, names, wanted_names,
new_names = [] new_names = []
for n in names: for n in names:
if s == n.string_name: if s == n.string_name:
if n.tree_name is not None and n.api_type == 'module' \ if n.tree_name is not None and n.api_type in ('module', 'namespace') \
and ignore_imports: and ignore_imports:
continue continue
new_names += complete_trailer( new_names += complete_trailer(

View File

@@ -439,5 +439,5 @@ def get_default_project(path=None):
def _remove_imports(names): def _remove_imports(names):
return [ return [
n for n in names n for n in names
if n.tree_name is None or n.api_type != 'module' if n.tree_name is None or n.api_type not in ('module', 'namespace')
] ]

View File

@@ -156,8 +156,8 @@ def rename(inference_state, definitions, new_name):
def inline(inference_state, names): def inline(inference_state, names):
if not names: if not names:
raise RefactoringError("There is no name under the cursor") raise RefactoringError("There is no name under the cursor")
if any(n.api_type == 'module' for n in names): if any(n.api_type in ('module', 'namespace') for n in names):
raise RefactoringError("Cannot inline imports or modules") raise RefactoringError("Cannot inline imports, modules or namespaces")
if any(n.tree_name is None for n in names): if any(n.tree_name is None for n in names):
raise RefactoringError("Cannot inline builtins/extensions") raise RefactoringError("Cannot inline builtins/extensions")

View File

@@ -255,8 +255,7 @@ class TreeContextMixin:
if scope_node.type in ('funcdef', 'lambdef', 'classdef'): if scope_node.type in ('funcdef', 'lambdef', 'classdef'):
return self.create_value(scope_node).as_context() return self.create_value(scope_node).as_context()
elif scope_node.type in ('comp_for', 'sync_comp_for'): elif scope_node.type in ('comp_for', 'sync_comp_for'):
parent_scope = parser_utils.get_parent_scope(scope_node) parent_context = from_scope_node(parent_scope(scope_node.parent))
parent_context = from_scope_node(parent_scope)
if node.start_pos >= scope_node.children[-1].start_pos: if node.start_pos >= scope_node.children[-1].start_pos:
return parent_context return parent_context
return CompForContext(parent_context, scope_node) return CompForContext(parent_context, scope_node)

View File

@@ -146,6 +146,9 @@ class DefineGenericBaseClass(LazyValueWrapper):
) for class_set1, class_set2 in zip(given_params1, given_params2) ) for class_set1, class_set2 in zip(given_params1, given_params2)
) )
def get_signatures(self):
return []
def __repr__(self): def __repr__(self):
return '<%s: %s%s>' % ( return '<%s: %s%s>' % (
self.__class__.__name__, self.__class__.__name__,
@@ -380,6 +383,9 @@ class BaseTypingValue(LazyValueWrapper):
def _get_wrapped_value(self): def _get_wrapped_value(self):
return _PseudoTreeNameClass(self.parent_context, self._tree_name) return _PseudoTreeNameClass(self.parent_context, self._tree_name)
def get_signatures(self):
return self._wrapped_value.get_signatures()
def __repr__(self): def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self._tree_name.value) return '%s(%s)' % (self.__class__.__name__, self._tree_name.value)

View File

@@ -3,7 +3,7 @@ from jedi.inference.base_value import ValueSet, NO_VALUES, ValueWrapper
from jedi.inference.gradual.base import BaseTypingValue from jedi.inference.gradual.base import BaseTypingValue
class TypeVarClass(BaseTypingValue): class TypeVarClass(ValueWrapper):
def py__call__(self, arguments): def py__call__(self, arguments):
unpacked = arguments.unpack() unpacked = arguments.unpack()
@@ -17,9 +17,9 @@ class TypeVarClass(BaseTypingValue):
return ValueSet([TypeVar.create_cached( return ValueSet([TypeVar.create_cached(
self.inference_state, self.inference_state,
self.parent_context, self.parent_context,
self._tree_name, tree_name=self.tree_node.name,
var_name, var_name=var_name,
unpacked unpacked_args=unpacked,
)]) )])
def _find_string_name(self, lazy_value): def _find_string_name(self, lazy_value):

View File

@@ -120,7 +120,7 @@ def import_module_decorator(func):
) )
inference_state.module_cache.add(import_names, python_value_set) inference_state.module_cache.add(import_names, python_value_set)
if not prefer_stubs: if not prefer_stubs or import_names[0] in settings.auto_import_modules:
return python_value_set return python_value_set
stub = try_to_load_stub_cached(inference_state, import_names, python_value_set, stub = try_to_load_stub_cached(inference_state, import_names, python_value_set,

View File

@@ -10,7 +10,7 @@ import itertools
from jedi import debug from jedi import debug
from jedi.inference.compiled import builtin_from_name, create_simple_object from jedi.inference.compiled import builtin_from_name, create_simple_object
from jedi.inference.base_value import ValueSet, NO_VALUES, Value, \ from jedi.inference.base_value import ValueSet, NO_VALUES, Value, \
LazyValueWrapper LazyValueWrapper, ValueWrapper
from jedi.inference.lazy_value import LazyKnownValues from jedi.inference.lazy_value import LazyKnownValues
from jedi.inference.arguments import repack_with_argument_clinic from jedi.inference.arguments import repack_with_argument_clinic
from jedi.inference.filters import FilterWrapper from jedi.inference.filters import FilterWrapper
@@ -63,8 +63,8 @@ class TypingModuleName(NameWrapper):
# have any effects there (because it's never executed). # have any effects there (because it's never executed).
return return
elif name == 'TypeVar': elif name == 'TypeVar':
yield TypeVarClass.create_cached( cls, = self._wrapped_name.infer()
inference_state, self.parent_context, self.tree_name) yield TypeVarClass.create_cached(inference_state, cls)
elif name == 'Any': elif name == 'Any':
yield AnyClass.create_cached( yield AnyClass.create_cached(
inference_state, self.parent_context, self.tree_name) inference_state, self.parent_context, self.tree_name)
@@ -76,21 +76,20 @@ class TypingModuleName(NameWrapper):
yield OverloadFunction.create_cached( yield OverloadFunction.create_cached(
inference_state, self.parent_context, self.tree_name) inference_state, self.parent_context, self.tree_name)
elif name == 'NewType': elif name == 'NewType':
yield NewTypeFunction.create_cached( v, = self._wrapped_name.infer()
inference_state, self.parent_context, self.tree_name) yield NewTypeFunction.create_cached(inference_state, v)
elif name == 'cast': elif name == 'cast':
yield CastFunction.create_cached( cast_fn, = self._wrapped_name.infer()
inference_state, self.parent_context, self.tree_name) yield CastFunction.create_cached(inference_state, cast_fn)
elif name == 'TypedDict': elif name == 'TypedDict':
# TODO doesn't even exist in typeshed/typing.py, yet. But will be # TODO doesn't even exist in typeshed/typing.py, yet. But will be
# added soon. # added soon.
yield TypedDictClass.create_cached( yield TypedDictClass.create_cached(
inference_state, self.parent_context, self.tree_name) inference_state, self.parent_context, self.tree_name)
elif name in ('no_type_check', 'no_type_check_decorator'):
# This is not necessary, as long as we are not doing type checking.
yield from self._wrapped_name.infer()
else: else:
# Everything else shouldn't be relevant for type checking. # Not necessary, as long as we are not doing type checking:
# no_type_check & no_type_check_decorator
# Everything else shouldn't be relevant...
yield from self._wrapped_name.infer() yield from self._wrapped_name.infer()
@@ -275,6 +274,9 @@ class TypeAlias(LazyValueWrapper):
def gather_annotation_classes(self): def gather_annotation_classes(self):
return ValueSet([self._get_wrapped_value()]) return ValueSet([self._get_wrapped_value()])
def get_signatures(self):
return []
class Callable(BaseTypingInstance): class Callable(BaseTypingInstance):
def py__call__(self, arguments): def py__call__(self, arguments):
@@ -395,7 +397,7 @@ class OverloadFunction(BaseTypingValue):
return func_value_set return func_value_set
class NewTypeFunction(BaseTypingValue): class NewTypeFunction(ValueWrapper):
def py__call__(self, arguments): def py__call__(self, arguments):
ordered_args = arguments.unpack() ordered_args = arguments.unpack()
next(ordered_args, (None, None)) next(ordered_args, (None, None))
@@ -430,7 +432,7 @@ class NewType(Value):
return CompiledValueName(self, 'NewType') return CompiledValueName(self, 'NewType')
class CastFunction(BaseTypingValue): class CastFunction(ValueWrapper):
@repack_with_argument_clinic('type, object, /') @repack_with_argument_clinic('type, object, /')
def py__call__(self, type_value_set, object_value_set): def py__call__(self, type_value_set, object_value_set):
return type_value_set.execute_annotation() return type_value_set.execute_annotation()

View File

@@ -339,7 +339,7 @@ class Importer:
values = self.follow() values = self.follow()
for value in values: for value in values:
# Non-modules are not completable. # Non-modules are not completable.
if value.api_type != 'module': # not a module if value.api_type not in ('module', 'namespace'): # not a module
continue continue
if not value.is_compiled(): if not value.is_compiled():
# sub_modules_dict is not implemented for compiled modules. # sub_modules_dict is not implemented for compiled modules.

View File

@@ -340,7 +340,7 @@ class TreeNameDefinition(AbstractTreeName):
@inference_state_method_cache(default='') @inference_state_method_cache(default='')
def py__doc__(self): def py__doc__(self):
api_type = self.api_type api_type = self.api_type
if api_type in ('function', 'class'): if api_type in ('function', 'class', 'property'):
# Make sure the names are not TreeNameDefinitions anymore. # Make sure the names are not TreeNameDefinitions anymore.
return clean_scope_docstring(self.tree_name.get_definition()) return clean_scope_docstring(self.tree_name.get_definition())

View File

@@ -4,13 +4,13 @@ import re
from parso import python_bytes_to_unicode from parso import python_bytes_to_unicode
from jedi.debug import dbg from jedi.debug import dbg
from jedi.file_io import KnownContentFileIO from jedi.file_io import KnownContentFileIO, FolderIO
from jedi.inference.names import SubModuleName from jedi.inference.names import SubModuleName
from jedi.inference.imports import load_module_from_path from jedi.inference.imports import load_module_from_path
from jedi.inference.filters import ParserTreeFilter from jedi.inference.filters import ParserTreeFilter
from jedi.inference.gradual.conversion import convert_names from jedi.inference.gradual.conversion import convert_names
_IGNORE_FOLDERS = ('.tox', '.venv', 'venv', '__pycache__') _IGNORE_FOLDERS = ('.tox', '.venv', '.mypy_cache', 'venv', '__pycache__')
_OPENED_FILE_LIMIT = 2000 _OPENED_FILE_LIMIT = 2000
""" """
@@ -127,10 +127,10 @@ def find_references(module_context, tree_name, only_in_module=False):
module_contexts = [module_context] module_contexts = [module_context]
if not only_in_module: if not only_in_module:
module_contexts.extend( for m in set(d.get_root_context() for d in found_names):
m for m in set(d.get_root_context() for d in found_names) if m != module_context and m.tree_node is not None \
if m != module_context and m.tree_node is not None and inf.project.path in m.py__file__().parents:
) module_contexts.append(m)
# For param no search for other modules is necessary. # For param no search for other modules is necessary.
if only_in_module or any(n.api_type == 'param' for n in found_names): if only_in_module or any(n.api_type == 'param' for n in found_names):
potential_modules = module_contexts potential_modules = module_contexts
@@ -250,6 +250,11 @@ def _find_python_files_in_sys_path(inference_state, module_contexts):
folder_io = folder_io.get_parent_folder() folder_io = folder_io.get_parent_folder()
def _find_project_modules(inference_state, module_contexts):
except_ = [m.py__file__() for m in module_contexts]
yield from recurse_find_python_files(FolderIO(inference_state.project.path), except_)
def get_module_contexts_containing_name(inference_state, module_contexts, name, def get_module_contexts_containing_name(inference_state, module_contexts, name,
limit_reduction=1): limit_reduction=1):
""" """
@@ -269,7 +274,10 @@ def get_module_contexts_containing_name(inference_state, module_contexts, name,
if len(name) <= 2: if len(name) <= 2:
return return
file_io_iterator = _find_python_files_in_sys_path(inference_state, module_contexts) # Currently not used, because there's only `scope=project` and `scope=file`
# At the moment there is no such thing as `scope=sys.path`.
# file_io_iterator = _find_python_files_in_sys_path(inference_state, module_contexts)
file_io_iterator = _find_project_modules(inference_state, module_contexts)
yield from search_in_file_ios(inference_state, file_io_iterator, name, yield from search_in_file_ios(inference_state, file_io_iterator, name,
limit_reduction=limit_reduction) limit_reduction=limit_reduction)

View File

@@ -19,3 +19,16 @@ class Decoratee(ValueWrapper):
Decoratee(v, self._original_value) Decoratee(v, self._original_value)
for v in self._wrapped_value.py__get__(instance, class_value) for v in self._wrapped_value.py__get__(instance, class_value)
) )
def get_signatures(self):
signatures = self._wrapped_value.get_signatures()
if signatures:
return signatures
# Fallback to signatures of the original function/class if the
# decorator has no signature or it is not inferrable.
#
# __get__ means that it's a descriptor. In that case we don't return
# signatures, because they are usually properties.
if not self._wrapped_value.py__getattribute__('__get__'):
return self._original_value.get_signatures()
return []

View File

@@ -121,7 +121,13 @@ class AbstractInstanceValue(Value):
return [s.bind(self) for s in call_funcs.get_signatures()] return [s.bind(self) for s in call_funcs.get_signatures()]
def get_function_slot_names(self, name): def get_function_slot_names(self, name):
# Searches for Python functions in classes. # Python classes don't look at the dictionary of the instance when
# looking up `__call__`. This is something that has to do with Python's
# internal slot system (note: not __slots__, but C slots).
for filter in self.get_filters(include_self_names=False):
names = filter.get(name)
if names:
return names
return [] return []
def execute_function_slots(self, names, *inferred_args): def execute_function_slots(self, names, *inferred_args):
@@ -133,6 +139,27 @@ class AbstractInstanceValue(Value):
def get_type_hint(self, add_class_info=True): def get_type_hint(self, add_class_info=True):
return self.py__name__() return self.py__name__()
def py__getitem__(self, index_value_set, contextualized_node):
names = self.get_function_slot_names('__getitem__')
if not names:
return super().py__getitem__(
index_value_set,
contextualized_node,
)
args = ValuesArguments([index_value_set])
return ValueSet.from_sets(name.infer().execute(args) for name in names)
def py__iter__(self, contextualized_node=None):
iter_slot_names = self.get_function_slot_names('__iter__')
if not iter_slot_names:
return super().py__iter__(contextualized_node)
def iterate():
for generator in self.execute_function_slots(iter_slot_names):
yield from generator.py__next__(contextualized_node)
return iterate()
def __repr__(self): def __repr__(self):
return "<%s of %s>" % (self.__class__.__name__, self.class_value) return "<%s of %s>" % (self.__class__.__name__, self.class_value)
@@ -237,27 +264,6 @@ class _BaseTreeInstance(AbstractInstanceValue):
or self.get_function_slot_names('__getattribute__')) or self.get_function_slot_names('__getattribute__'))
return self.execute_function_slots(names, name) return self.execute_function_slots(names, name)
def py__getitem__(self, index_value_set, contextualized_node):
names = self.get_function_slot_names('__getitem__')
if not names:
return super().py__getitem__(
index_value_set,
contextualized_node,
)
args = ValuesArguments([index_value_set])
return ValueSet.from_sets(name.infer().execute(args) for name in names)
def py__iter__(self, contextualized_node=None):
iter_slot_names = self.get_function_slot_names('__iter__')
if not iter_slot_names:
return super().py__iter__(contextualized_node)
def iterate():
for generator in self.execute_function_slots(iter_slot_names):
yield from generator.py__next__(contextualized_node)
return iterate()
def py__next__(self, contextualized_node=None): def py__next__(self, contextualized_node=None):
name = u'__next__' name = u'__next__'
next_slot_names = self.get_function_slot_names(name) next_slot_names = self.get_function_slot_names(name)
@@ -295,16 +301,6 @@ class _BaseTreeInstance(AbstractInstanceValue):
else: else:
return ValueSet([self]) return ValueSet([self])
def get_function_slot_names(self, name):
# Python classes don't look at the dictionary of the instance when
# looking up `__call__`. This is something that has to do with Python's
# internal slot system (note: not __slots__, but C slots).
for filter in self.get_filters(include_self_names=False):
names = filter.get(name)
if names:
return names
return []
class TreeInstance(_BaseTreeInstance): class TreeInstance(_BaseTreeInstance):
def __init__(self, inference_state, parent_context, class_value, arguments): def __init__(self, inference_state, parent_context, class_value, arguments):

View File

@@ -75,9 +75,9 @@ class ClassName(TreeNameDefinition):
@property @property
def api_type(self): def api_type(self):
if self.tree_name is not None: type_ = super().api_type
if type_ == 'function':
definition = self.tree_name.get_definition() definition = self.tree_name.get_definition()
if definition.type == 'funcdef':
if function_is_property(definition): if function_is_property(definition):
# This essentially checks if there is an @property before # This essentially checks if there is an @property before
# the function. @property could be something different, but # the function. @property could be something different, but
@@ -85,7 +85,7 @@ class ClassName(TreeNameDefinition):
# is not really a property anymore, should be shot. (i.e. # is not really a property anymore, should be shot. (i.e.
# this is a heuristic). # this is a heuristic).
return 'property' return 'property'
return super().api_type return type_
class ClassFilter(ParserTreeFilter): class ClassFilter(ParserTreeFilter):

View File

@@ -20,10 +20,7 @@ class ImplicitNamespaceValue(Value, SubModuleDictMixin):
""" """
Provides support for implicit namespace packages Provides support for implicit namespace packages
""" """
# Is a module like every other module, because if you import an empty api_type = 'namespace'
# folder foobar it will be available as an object:
# <module 'foobar' (namespace)>.
api_type = 'module'
parent_context = None parent_context = None
def __init__(self, inference_state, string_names, paths): def __init__(self, inference_state, string_names, paths):

View File

@@ -306,7 +306,7 @@ def expr_is_dotted(node):
return node.type == 'name' return node.type == 'name'
def _function_is_x_method(method_name): def _function_is_x_method(*method_names):
def wrapper(function_node): def wrapper(function_node):
""" """
This is a heuristic. It will not hold ALL the times, but it will be This is a heuristic. It will not hold ALL the times, but it will be
@@ -316,7 +316,7 @@ def _function_is_x_method(method_name):
""" """
for decorator in function_node.get_decorators(): for decorator in function_node.get_decorators():
dotted_name = decorator.children[1] dotted_name = decorator.children[1]
if dotted_name.get_code() == method_name: if dotted_name.get_code() in method_names:
return True return True
return False return False
return wrapper return wrapper
@@ -324,4 +324,4 @@ def _function_is_x_method(method_name):
function_is_staticmethod = _function_is_x_method('staticmethod') function_is_staticmethod = _function_is_x_method('staticmethod')
function_is_classmethod = _function_is_x_method('classmethod') function_is_classmethod = _function_is_x_method('classmethod')
function_is_property = _function_is_x_method('property') function_is_property = _function_is_x_method('property', 'cached_property')

View File

@@ -5,6 +5,7 @@ from jedi.inference.cache import inference_state_method_cache
from jedi.inference.imports import load_module_from_path from jedi.inference.imports import load_module_from_path
from jedi.inference.filters import ParserTreeFilter from jedi.inference.filters import ParserTreeFilter
from jedi.inference.base_value import NO_VALUES, ValueSet from jedi.inference.base_value import NO_VALUES, ValueSet
from jedi.inference.helpers import infer_call_of_leaf
_PYTEST_FIXTURE_MODULES = [ _PYTEST_FIXTURE_MODULES = [
('_pytest', 'monkeypatch'), ('_pytest', 'monkeypatch'),
@@ -147,18 +148,35 @@ class FixtureFilter(ParserTreeFilter):
def _filter(self, names): def _filter(self, names):
for name in super()._filter(names): for name in super()._filter(names):
funcdef = name.parent funcdef = name.parent
if funcdef.type == 'funcdef':
# Class fixtures are not supported # Class fixtures are not supported
if funcdef.type == 'funcdef':
decorated = funcdef.parent decorated = funcdef.parent
if decorated.type == 'decorated' and self._is_fixture(decorated): if decorated.type == 'decorated' and self._is_fixture(decorated):
yield name yield name
def _is_fixture(self, decorated): def _is_fixture(self, decorated):
for decorator in decorated.children: decorators = decorated.children[0]
if decorators.type == 'decorators':
decorators = decorators.children
else:
decorators = [decorators]
for decorator in decorators:
dotted_name = decorator.children[1] dotted_name = decorator.children[1]
# A heuristic, this makes it faster. # A heuristic, this makes it faster.
if 'fixture' in dotted_name.get_code(): if 'fixture' in dotted_name.get_code():
for value in self.parent_context.infer_node(dotted_name): if dotted_name.type == 'atom_expr':
# Since Python3.9 a decorator does not have dotted names
# anymore.
last_trailer = dotted_name.children[-1]
last_leaf = last_trailer.get_last_leaf()
if last_leaf == ')':
values = infer_call_of_leaf(
self.parent_context, last_leaf, cut_own_trailer=True)
else:
values = self.parent_context.infer_node(dotted_name)
else:
values = self.parent_context.infer_node(dotted_name)
for value in values:
if value.name.get_qualified_names(include_module_names=True) \ if value.name.get_qualified_names(include_module_names=True) \
== ('_pytest', 'fixtures', 'fixture'): == ('_pytest', 'fixtures', 'fixture'):
return True return True

View File

@@ -214,6 +214,20 @@ f
#? str() #? str()
g g
# -----------------
# setitem
# -----------------
class F:
setitem_x = [1,2]
setitem_x[0] = 3
#? ['setitem_x']
F().setitem_x
#? list()
F().setitem_x
# ----------------- # -----------------
# dicts # dicts
# ----------------- # -----------------

View File

@@ -202,6 +202,10 @@ from keyword import not_existing1, not_existing2
from tokenize import io from tokenize import io
tokenize.generate_tokens tokenize.generate_tokens
import socket
#? 14 ['SocketIO']
socket.SocketIO
# ----------------- # -----------------
# builtins # builtins
# ----------------- # -----------------

View File

@@ -54,7 +54,7 @@ a
#? int() #? int()
(3 ** 3) (3 ** 3)
#? int() str() #? int()
(3 ** 'a') (3 ** 'a')
#? int() #? int()
(3 + 'a') (3 + 'a')

View File

@@ -310,6 +310,13 @@ z = 3
#< 10 (0,1), (0,10) #< 10 (0,1), (0,10)
{z:1 for z in something} {z:1 for z in something}
#< 8 (0,6), (0, 40)
[[x + nested_loopv2 for x in bar()] for nested_loopv2 in baz()]
#< 25 (0,20), (0, 65)
(("*" if abs(foo(x, nested_loopv1)) else " " for x in bar()) for nested_loopv1 in baz())
def whatever_func(): def whatever_func():
zzz = 3 zzz = 3
if UNDEFINED: if UNDEFINED:
@@ -376,3 +383,12 @@ usage_definition = 1
if False: if False:
#< 8 (-3, 0), (0, 4), ('import_tree.references', 1, 21), ('import_tree.references', 5, 4) #< 8 (-3, 0), (0, 4), ('import_tree.references', 1, 21), ('import_tree.references', 5, 4)
usage_definition() usage_definition()
# -----------------
# stdlib stuff
# -----------------
import socket
#< (1, 21), (0, 7), ('socket', ..., 6), ('stub:socket', ..., 4), ('imports', ..., 7)
socket.SocketIO
some_socket = socket.SocketIO()

View File

@@ -82,9 +82,9 @@ def _collect_file_tests(code, path, lines_to_execute):
yield RefactoringCase(name, first, line_nr, index, path, kwargs, type_, second) yield RefactoringCase(name, first, line_nr, index, path, kwargs, type_, second)
if match is None: if match is None:
raise Exception("Didn't match any test") raise Exception(f"Didn't match any test for {path}, {code!r}")
if match.end() != len(code): if match.end() != len(code):
raise Exception("Didn't match until the end of the file in %s" % path) raise Exception(f"Didn't match until the end of the file in {path}")
def collect_dir_tests(base_dir, test_files): def collect_dir_tests(base_dir, test_files):

View File

@@ -68,7 +68,7 @@ from import_tree import inline_mod
#? 11 error #? 11 error
test(inline_mod) test(inline_mod)
# ++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot inline imports or modules Cannot inline imports, modules or namespaces
# -------------------------------------------------- module-works # -------------------------------------------------- module-works
from import_tree import inline_mod from import_tree import inline_mod
#? 22 #? 22

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
""" """
|jedi| is mostly being tested by what I would call "integration tests". These |jedi| is mostly being tested by what I would call "integration tests". These
tests are testing type inference with the public API. This makes a tests are testing type inference with the public API. This makes a
@@ -107,6 +107,7 @@ import operator
from ast import literal_eval from ast import literal_eval
from io import StringIO from io import StringIO
from functools import reduce from functools import reduce
from unittest.mock import ANY
import parso import parso
from _pytest.outcomes import Skipped from _pytest.outcomes import Skipped
@@ -209,6 +210,9 @@ class IntegrationTestCase(BaseTestCase):
# import cProfile; cProfile.run('...') # import cProfile; cProfile.run('...')
comp_str = {c.name for c in completions} comp_str = {c.name for c in completions}
for r in completions:
# Test if this access raises an error
assert isinstance(r.type, str)
return compare_cb(self, comp_str, set(literal_eval(self.correct))) return compare_cb(self, comp_str, set(literal_eval(self.correct)))
def run_inference(self, compare_cb, environment): def run_inference(self, compare_cb, environment):
@@ -244,6 +248,9 @@ class IntegrationTestCase(BaseTestCase):
should = definition(self.correct, self.start, script.path) should = definition(self.correct, self.start, script.path)
result = script.infer(self.line_nr, self.column) result = script.infer(self.line_nr, self.column)
is_str = set(comparison(r) for r in result) is_str = set(comparison(r) for r in result)
for r in result:
# Test if this access raises an error
assert isinstance(r.type, str)
return compare_cb(self, is_str, should) return compare_cb(self, is_str, should)
def run_goto(self, compare_cb, environment): def run_goto(self, compare_cb, environment):
@@ -269,6 +276,8 @@ class IntegrationTestCase(BaseTestCase):
for pos_tup in positions: for pos_tup in positions:
if type(pos_tup[0]) == str: if type(pos_tup[0]) == str:
# this means that there is a module specified # this means that there is a module specified
if pos_tup[1] == ...:
pos_tup = pos_tup[0], ANY, pos_tup[2]
wanted.append(pos_tup) wanted.append(pos_tup)
else: else:
line = pos_tup[0] line = pos_tup[0]

View File

@@ -1,2 +0,0 @@
with open() as fin:
fin.read()

View File

@@ -169,10 +169,11 @@ def test_reference_description(Script):
def test_get_line_code(Script): def test_get_line_code(Script):
def get_line_code(source, line=None, **kwargs): def get_line_code(source, line=None, **kwargs):
return Script(source).complete(line=line)[0].get_line_code(**kwargs) # On Windows replace \r
return Script(source).complete(line=line)[0].get_line_code(**kwargs).replace('\r', '')
# On builtin # On builtin
assert get_line_code('abs') == 'def abs(__n: SupportsAbs[_T]) -> _T: ...\n' assert get_line_code('abs') == 'def abs(__x: SupportsAbs[_T]) -> _T: ...\n'
# On custom code # On custom code
first_line = 'def foo():\n' first_line = 'def foo():\n'

View File

@@ -39,12 +39,11 @@ class TestSignatures(TestCase):
run = self._run_simple run = self._run_simple
# simple # simple
s1 = "sorted(a, bool(" s1 = "tuple(a, bool("
run(s1, 'sorted', 0, 7) run(s1, 'tuple', 0, 6)
run(s1, 'sorted', 1, 9) run(s1, 'tuple', None, 8)
run(s1, 'sorted', 1, 10) run(s1, 'tuple', None, 9)
run(s1, 'sorted', None, 11) run(s1, 'bool', 0, 14)
run(s1, 'bool', 0, 15)
s2 = "abs(), " s2 = "abs(), "
run(s2, 'abs', 0, 4) run(s2, 'abs', 0, 4)
@@ -65,9 +64,9 @@ class TestSignatures(TestCase):
run(s4, 'abs', 0, 10) run(s4, 'abs', 0, 10)
run(s4, 'abs', None, 11) run(s4, 'abs', None, 11)
s5 = "sorted(1,\nif 2:\n def a():" s5 = "tuple(1,\nif 2:\n def a():"
run(s5, 'sorted', 0, 7) run(s5, 'tuple', 0, 6)
run(s5, 'sorted', 1, 9) run(s5, 'tuple', None, 8)
s6 = "bool().__eq__(" s6 = "bool().__eq__("
run(s6, '__eq__', 0) run(s6, '__eq__', 0)
@@ -89,8 +88,8 @@ class TestSignatures(TestCase):
def test_for(self): def test_for(self):
# jedi-vim #11 # jedi-vim #11
self._run_simple("for sorted(", 'sorted', 0) self._run_simple("for tuple(", 'tuple', 0)
self._run_simple("for s in sorted(", 'sorted', 0) self._run_simple("for s in tuple(", 'tuple', 0)
def test_with(Script): def test_with(Script):
@@ -272,7 +271,7 @@ def test_pow_params(Script):
# See Github #1357. # See Github #1357.
for sig in Script('pow(').get_signatures(): for sig in Script('pow(').get_signatures():
param_names = [p.name for p in sig.params] param_names = [p.name for p in sig.params]
assert param_names in (['x', 'y'], ['x', 'y', 'z']) assert param_names in (['base', 'exp'], ['base', 'exp', 'mod'])
def test_param_name(Script): def test_param_name(Script):

View File

@@ -195,7 +195,7 @@ def test_hashlib_params(Script, environment):
script = Script('from hashlib import sha256') script = Script('from hashlib import sha256')
c, = script.complete() c, = script.complete()
sig, = c.get_signatures() sig, = c.get_signatures()
assert [p.name for p in sig.params] == ['arg'] assert [p.name for p in sig.params] == ['string']
def test_signature_params(Script): def test_signature_params(Script):
@@ -619,7 +619,7 @@ def test_definition_goto_follow_imports(Script):
('n = next; n', 'Union[next(__i: Iterator[_T]) -> _T, ' ('n = next; n', 'Union[next(__i: Iterator[_T]) -> _T, '
'next(__i: Iterator[_T], default: _VT) -> Union[_T, _VT]]'), 'next(__i: Iterator[_T], default: _VT) -> Union[_T, _VT]]'),
('abs', 'abs(__n: SupportsAbs[_T]) -> _T'), ('abs', 'abs(__x: SupportsAbs[_T]) -> _T'),
('def foo(x, y): return x if xxxx else y\nfoo(str(), 1)\nfoo', ('def foo(x, y): return x if xxxx else y\nfoo(str(), 1)\nfoo',
'foo(x: str, y: int) -> Union[int, str]'), 'foo(x: str, y: int) -> Union[int, str]'),
('def foo(x, y = None): return x if xxxx else y\nfoo(str(), 1)\nfoo', ('def foo(x, y = None): return x if xxxx else y\nfoo(str(), 1)\nfoo',

View File

@@ -150,19 +150,6 @@ def test_async(Script, environment):
assert 'hey' in names assert 'hey' in names
def test_method_doc_with_signature(Script):
code = 'f = open("")\nf.writelin'
c, = Script(code).complete()
assert c.name == 'writelines'
assert c.docstring() == 'writelines(lines: Iterable[AnyStr]) -> None'
def test_method_doc_with_signature2(Script):
code = 'f = open("")\nf.writelines'
d, = Script(code).goto()
assert d.docstring() == 'writelines(lines: Iterable[AnyStr]) -> None'
def test_with_stmt_error_recovery(Script): def test_with_stmt_error_recovery(Script):
assert Script('with open('') as foo: foo.\na').complete(line=1) assert Script('with open('') as foo: foo.\na').complete(line=1)

View File

@@ -95,7 +95,7 @@ def test_builtin_docstring(goto_or_help_or_infer):
d, = goto_or_help_or_infer('open') d, = goto_or_help_or_infer('open')
doc = d.docstring() doc = d.docstring()
assert doc.startswith('open(file: Union[') assert doc.startswith('open(file: ')
assert 'Open file' in doc assert 'Open file' in doc

View File

@@ -599,6 +599,34 @@ def test_dict_getitem(code, types):
assert [c.name for c in comps] == types assert [c.name for c in comps] == types
@pytest.mark.parametrize('class_is_findable', [False, True])
@pytest.mark.parametrize(
'code, expected', [
('DunderCls()[0]', 'int'),
('next(DunderCls())', 'float'),
('for x in DunderCls(): x', 'str'),
]
)
def test_dunders(class_is_findable, code, expected):
from typing import Iterator
class DunderCls:
def __getitem__(self, key) -> int:
pass
def __iter__(self, key) -> Iterator[str]:
pass
def __next__(self, key) -> float:
pass
if not class_is_findable:
DunderCls.__name__ = 'asdf'
n, = jedi.Interpreter(code, [locals()]).infer()
assert n.name == expected
def foo(): def foo():
raise KeyError raise KeyError

View File

@@ -10,16 +10,15 @@ def test_import_references(Script):
def test_exclude_builtin_modules(Script): def test_exclude_builtin_modules(Script):
def get(include): def get(include):
from jedi.api.project import Project references = Script(source).get_references(include_builtins=include)
script = Script(source, project=Project('', sys_path=[], smart_sys_path=False))
references = script.get_references(column=8, include_builtins=include)
return [(d.line, d.column) for d in references] return [(d.line, d.column) for d in references]
source = '''import sys\nprint(sys.path)''' source = '''import sys\nsys.setprofile'''
places = get(include=True) places = get(include=True)
assert len(places) > 2 # Includes stubs assert len(places) >= 3 # Includes stubs, the reference itself and the builtin
places = get(include=False) places = get(include=False)
assert places == [(1, 7), (2, 6)] # Just the reference
assert places == [(2, 4)]
@pytest.mark.parametrize('code, places', [ @pytest.mark.parametrize('code, places', [

View File

@@ -15,4 +15,4 @@ def test_module__file__(Script, environment):
def_, = Script('import antigravity; antigravity.__file__').infer() def_, = Script('import antigravity; antigravity.__file__').infer()
value = def_._name._value.get_safe_value() value = def_._name._value.get_safe_value()
assert value.endswith('.py') assert value.endswith('.pyi')

View File

@@ -507,3 +507,35 @@ def test_doctest_function_start(Script):
return return
''') ''')
assert Script(code).complete(7, 8) assert Script(code).complete(7, 8)
@pytest.mark.parametrize(
"name, docstring", [
('prop1', 'Returns prop1.'),
('prop2', 'Returns None or ...'),
('prop3', 'Non-sense property.'),
('prop4', 'Django like property'),
]
)
def test_property(name, docstring, goto_or_complete):
code = dedent('''
from typing import Optional
class Test:
@property
def prop1(self) -> int:
"""Returns prop1."""
@property
def prop2(self) -> Optional[int]:
"""Returns None or ..."""
@property
def prop3(self) -> None:
"""Non-sense property."""
@cached_property # Not imported, but Jedi uses a heuristic
def prop4(self) -> None:
"""Django like property"""
''')
n, = goto_or_complete(code + 'Test().' + name)
assert n.docstring() == docstring

View File

@@ -13,17 +13,15 @@ def test_completions(Script):
assert len(s.complete()) >= 15 assert len(s.complete()) >= 15
def test_get_signatures_extension(Script): def test_get_signatures_extension(Script, environment):
if os.name == 'nt': if os.name == 'nt':
func = 'LoadLibrary' func = 'LoadLibrary'
params = 1
else: else:
func = 'dlopen' func = 'dlopen'
params = 2
s = Script('import _ctypes; _ctypes.%s(' % (func,)) s = Script('import _ctypes; _ctypes.%s(' % (func,))
sigs = s.get_signatures() sigs = s.get_signatures()
assert len(sigs) == 1 assert len(sigs) == 1
assert len(sigs[0].params) == params assert len(sigs[0].params) in (1, 2)
def test_get_signatures_stdlib(Script): def test_get_signatures_stdlib(Script):

View File

@@ -69,11 +69,12 @@ def test_stub_get_line_code(Script):
code = 'from abc import ABC; ABC' code = 'from abc import ABC; ABC'
script = Script(code) script = Script(code)
d, = script.goto(only_stubs=True) d, = script.goto(only_stubs=True)
assert d.get_line_code() == 'class ABC(metaclass=ABCMeta): ...\n' # Replace \r for tests on Windows
assert d.get_line_code().replace('\r', '') == 'class ABC(metaclass=ABCMeta): ...\n'
del parser_cache[script._inference_state.latest_grammar._hashed][d.module_path] del parser_cache[script._inference_state.latest_grammar._hashed][d.module_path]
d, = Script(path=d.module_path).goto(d.line, d.column, only_stubs=True) d, = Script(path=d.module_path).goto(d.line, d.column, only_stubs=True)
assert d.is_stub() assert d.is_stub()
assert d.get_line_code() == 'class ABC(metaclass=ABCMeta): ...\n' assert d.get_line_code().replace('\r', '') == 'class ABC(metaclass=ABCMeta): ...\n'
def test_os_stat_result(Script): def test_os_stat_result(Script):

View File

@@ -21,10 +21,10 @@ def test_get_typeshed_directories():
def transform(set_): def transform(set_):
return {x.replace('/', os.path.sep) for x in set_} return {x.replace('/', os.path.sep) for x in set_}
dirs = get_dirs(PythonVersionInfo(3, 6)) dirs = get_dirs(PythonVersionInfo(3, 7))
assert dirs == transform({'stdlib/2and3', 'stdlib/3', assert dirs == transform({'stdlib/2and3', 'stdlib/3', 'stdlib/3.7',
'stdlib/3.6', 'third_party/2and3', 'third_party/2and3',
'third_party/3', 'third_party/3.6'}) 'third_party/3', 'third_party/3.7'})
def test_get_stub_files(): def test_get_stub_files():
@@ -92,7 +92,7 @@ def test_sys_exc_info(Script):
# It's an optional. # It's an optional.
assert def_.name == 'BaseException' assert def_.name == 'BaseException'
assert def_.module_path == typeshed.TYPESHED_PATH.joinpath( assert def_.module_path == typeshed.TYPESHED_PATH.joinpath(
'stdlib', '2and3', 'builtins.pyi' 'stdlib', '3', 'builtins.pyi'
) )
assert def_.type == 'instance' assert def_.type == 'instance'
assert none.name == 'NoneType' assert none.name == 'NoneType'

View File

@@ -55,7 +55,7 @@ def test_implicit_nested_namespace_package(Script):
assert len(result) == 1 assert len(result) == 1
implicit_pkg, = Script(code, project=project).infer(column=10) implicit_pkg, = Script(code, project=project).infer(column=10)
assert implicit_pkg.type == 'module' assert implicit_pkg.type == 'namespace'
assert implicit_pkg.module_path is None assert implicit_pkg.module_path is None

View File

@@ -102,6 +102,25 @@ class X:
(partialmethod_code + 'X().d(', None), (partialmethod_code + 'X().d(', None),
(partialmethod_code + 'X.c(', 'func(a, b)'), (partialmethod_code + 'X.c(', 'func(a, b)'),
(partialmethod_code + 'X.d(', None), (partialmethod_code + 'X.d(', None),
('import contextlib\n@contextlib.contextmanager\ndef f(x): pass\nf(', 'f(x)'),
# typing lib
('from typing import cast\ncast(', {
'cast(typ: object, val: Any) -> Any',
'cast(typ: str, val: Any) -> Any',
'cast(typ: Type[_T], val: Any) -> _T'}),
('from typing import TypeVar\nTypeVar(',
'TypeVar(name: str, *constraints: Type[Any], bound: Union[None, Type[Any], str]=..., '
'covariant: bool=..., contravariant: bool=...)'),
('from typing import List\nList(', None),
('from typing import List\nList[int](', None),
('from typing import Tuple\nTuple(', None),
('from typing import Tuple\nTuple[int](', None),
('from typing import Optional\nOptional(', None),
('from typing import Optional\nOptional[int](', None),
('from typing import Any\nAny(', None),
('from typing import NewType\nNewType(', 'NewType(name: str, tp: Type[_T]) -> Type[_T]'),
] ]
) )
def test_tree_signature(Script, environment, code, expected): def test_tree_signature(Script, environment, code, expected):
@@ -112,8 +131,10 @@ def test_tree_signature(Script, environment, code, expected):
if expected is None: if expected is None:
assert not Script(code).get_signatures() assert not Script(code).get_signatures()
else: else:
sig, = Script(code).get_signatures() actual = {sig.to_string() for sig in Script(code).get_signatures()}
assert expected == sig.to_string() if not isinstance(expected, set):
expected = {expected}
assert expected == actual
@pytest.mark.parametrize( @pytest.mark.parametrize(
@@ -134,7 +155,7 @@ def test_tree_signature(Script, environment, code, expected):
('full_redirect(C)', 'z, *, c'), ('full_redirect(C)', 'z, *, c'),
('full_redirect(C())', 'y'), ('full_redirect(C())', 'y'),
('full_redirect(G)', 't: T'), ('full_redirect(G)', 't: T'),
('full_redirect(G[str])', 't: T'), ('full_redirect(G[str])', '*args, **kwargs'),
('D', 'D(a, z, /)'), ('D', 'D(a, z, /)'),
('D()', 'D(x, y)'), ('D()', 'D(x, y)'),
('D().foo', 'foo(a, *, bar, z, **kwargs)'), ('D().foo', 'foo(a, *, bar, z, **kwargs)'),
@@ -225,14 +246,22 @@ def test_nested_signatures(Script, environment, combination, expected):
assert expected == computed assert expected == computed
def test_pow_signature(Script): def test_pow_signature(Script, environment):
# See github #1357 # See github #1357
sigs = Script('pow(').get_signatures() sigs = Script('pow(').get_signatures()
strings = {sig.to_string() for sig in sigs} strings = {sig.to_string() for sig in sigs}
assert strings == {'pow(x: float, y: float, z: float, /) -> float', if environment.version_info < (3, 8):
'pow(x: float, y: float, /) -> float', assert strings == {'pow(base: _SupportsPow2[_E, _T_co], exp: _E, /) -> _T_co',
'pow(x: int, y: int, z: int, /) -> Any', 'pow(base: _SupportsPow3[_E, _M, _T_co], exp: _E, mod: _M, /) -> _T_co',
'pow(x: int, y: int, /) -> Any'} 'pow(base: float, exp: float, mod: None=..., /) -> float',
'pow(base: int, exp: int, mod: None=..., /) -> Any',
'pow(base: int, exp: int, mod: int, /) -> int'}
else:
assert strings == {'pow(base: _SupportsPow2[_E, _T_co], exp: _E) -> _T_co',
'pow(base: _SupportsPow3[_E, _M, _T_co], exp: _E, mod: _M) -> _T_co',
'pow(base: float, exp: float, mod: None=...) -> float',
'pow(base: int, exp: int, mod: None=...) -> Any',
'pow(base: int, exp: int, mod: int) -> int'}
@pytest.mark.parametrize( @pytest.mark.parametrize(

View File

@@ -1,9 +1,8 @@
import pytest import pytest
from jedi import settings from jedi import settings
from jedi.inference.names import ValueName
from jedi.inference.compiled import CompiledValueName from jedi.inference.compiled import CompiledValueName
from jedi.inference.gradual.typeshed import StubModuleValue from jedi.inference.compiled.value import CompiledModule
@pytest.fixture() @pytest.fixture()
@@ -13,9 +12,9 @@ def auto_import_json(monkeypatch):
def test_base_auto_import_modules(auto_import_json, Script): def test_base_auto_import_modules(auto_import_json, Script):
loads, = Script('import json; json.loads').infer() loads, = Script('import json; json.loads').infer()
assert isinstance(loads._name, ValueName) assert isinstance(loads._name, CompiledValueName)
value, = loads._name.infer() value, = loads._name.infer()
assert isinstance(value.parent_context._value, StubModuleValue) assert isinstance(value.parent_context._value, CompiledModule)
def test_auto_import_modules_imports(auto_import_json, Script): def test_auto_import_modules_imports(auto_import_json, Script):