From a5a36a049c77a50f78ba6a0d139378d43b1a67ab Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 23 Oct 2020 19:08:41 +0200 Subject: [PATCH 01/18] Fix an infer issue on literals after brackets, fixes #1657 --- jedi/api/__init__.py | 5 +++++ test/test_api/test_api.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index a0430e90d..a47e66db6 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -281,6 +281,11 @@ def infer(self, line=None, column=None, *, only_stubs=False, prefer_stubs=False) leaf = self._module_node.get_leaf_for_position(pos) if leaf is None or leaf.type == 'string': return [] + if leaf.end_pos == (line, column) and leaf.type == 'operator': + next_ = leaf.get_next_leaf() + if next_.start_pos == leaf.end_pos \ + and next_.type in ('number', 'string', 'keyword'): + leaf = next_ context = self._get_module_context().create_context(leaf) diff --git a/test/test_api/test_api.py b/test/test_api/test_api.py index 1829e30f9..51c0da3be 100644 --- a/test/test_api/test_api.py +++ b/test/test_api/test_api.py @@ -370,3 +370,35 @@ def test_multi_goto(Script): y, = script.goto(line=4) assert x.line == 1 assert y.line == 2 + + +@pytest.mark.parametrize( + 'code, column, expected', [ + ('str() ', 3, 'str'), + ('str() ', 4, 'str'), + ('str() ', 5, 'str'), + ('str() ', 6, None), + ('str( ) ', 6, None), + (' 1', 1, None), + ('str(1) ', 3, 'str'), + ('str(1) ', 4, 'int'), + ('str(1) ', 5, 'int'), + ('str(1) ', 6, 'str'), + ('str(1) ', 7, None), + ('str( 1) ', 4, 'str'), + ('str( 1) ', 5, 'int'), + ('str(+1) ', 4, 'str'), + ('str(+1) ', 5, 'int'), + ('str(1, 1.) ', 3, 'str'), + ('str(1, 1.) ', 4, 'int'), + ('str(1, 1.) ', 5, 'int'), + ('str(1, 1.) ', 6, None), + ('str(1, 1.) ', 7, 'float'), + ] +) +def test_infer_after_parentheses(Script, code, column, expected): + completions = Script(code).infer(column=column) + if expected is None: + assert completions == [] + else: + assert [c.name for c in completions] == [expected] From e671a0cb6dd994ac1939681f297179bf6f801a85 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 23 Oct 2020 20:25:00 +0200 Subject: [PATCH 02/18] Fix an error with enums, fixes #1675 --- jedi/plugins/stdlib.py | 11 ++++++++++- test/test_inference/test_signature.py | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index fddde91f7..17f1df3bc 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -819,7 +819,8 @@ def wrapper(cls, metaclasses, is_instance): and metaclass.get_root_context().py__name__() == 'enum': filter_ = ParserTreeFilter(parent_context=cls.as_context()) return [DictFilter({ - name.string_name: EnumInstance(cls, name).name for name in filter_.values() + name.string_name: EnumInstance(cls, name).name + for name in filter_.values() })] return func(cls, metaclasses, is_instance) return wrapper @@ -837,6 +838,14 @@ def name(self): return ValueName(self, self._name.tree_name) def _get_wrapped_value(self): + n = self._name.string_name + if n.startswith('__') and n.endswith('__') or self._name.api_type == 'function': + inferred = self._name.infer() + if inferred: + return next(iter(inferred)) + o, = self.inference_state.builtins_module.py__getattribute__('object') + return o + value, = self._cls.execute_with_values() return value diff --git a/test/test_inference/test_signature.py b/test/test_inference/test_signature.py index 500759bc5..02f119a8b 100644 --- a/test/test_inference/test_signature.py +++ b/test/test_inference/test_signature.py @@ -340,3 +340,20 @@ def test_overload(Script, code): x1, x2 = Script(code, path=os.path.join(dir_, 'foo.py')).get_signatures() assert x1.to_string() == 'with_overload(x: int, y: int) -> float' assert x2.to_string() == 'with_overload(x: str, y: list) -> float' + + +def test_enum(Script): + script = Script('''\ + from enum import Enum + + class Planet(Enum): + MERCURY = (3.303e+23, 2.4397e6) + VENUS = (4.869e+24, 6.0518e6) + + def __init__(self, mass, radius): + self.mass = mass # in kilograms + self.radius = radius # in meters + + Planet.MERCURY''') + completion, = script.complete() + assert not completion.get_signatures() From bf310c780c7d9f58f4e4f2e6688ccbdfd0b9d834 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 23 Oct 2020 21:04:36 +0200 Subject: [PATCH 03/18] Fix a recursion on imports, fixes #1677 --- jedi/inference/names.py | 7 +++++++ test/examples/import-recursion/cadquery_simple/__init__.py | 1 + test/examples/import-recursion/cadquery_simple/cq.py | 1 + test/examples/import-recursion/cq_example.py | 3 +++ test/test_inference/test_imports.py | 6 ++++++ 5 files changed, 18 insertions(+) create mode 100644 test/examples/import-recursion/cadquery_simple/__init__.py create mode 100644 test/examples/import-recursion/cadquery_simple/cq.py create mode 100644 test/examples/import-recursion/cq_example.py diff --git a/jedi/inference/names.py b/jedi/inference/names.py index 38be6f22f..2be1427c4 100644 --- a/jedi/inference/names.py +++ b/jedi/inference/names.py @@ -7,6 +7,7 @@ from jedi.parser_utils import find_statement_documentation, clean_scope_docstring from jedi.inference.utils import unite from jedi.inference.base_value import ValueSet, NO_VALUES +from jedi.inference.cache import inference_state_method_cache from jedi.inference import docstrings from jedi.cache import memoize_method from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf @@ -331,6 +332,12 @@ def assignment_indexes(self): node = node.parent return indexes + @property + def inference_state(self): + # Used by the cache function below + return self.parent_context.inference_state + + @inference_state_method_cache(default='') def py__doc__(self): api_type = self.api_type if api_type in ('function', 'class'): diff --git a/test/examples/import-recursion/cadquery_simple/__init__.py b/test/examples/import-recursion/cadquery_simple/__init__.py new file mode 100644 index 000000000..e87c66328 --- /dev/null +++ b/test/examples/import-recursion/cadquery_simple/__init__.py @@ -0,0 +1 @@ +from .cq import selectors diff --git a/test/examples/import-recursion/cadquery_simple/cq.py b/test/examples/import-recursion/cadquery_simple/cq.py new file mode 100644 index 000000000..90bb9ac75 --- /dev/null +++ b/test/examples/import-recursion/cadquery_simple/cq.py @@ -0,0 +1 @@ +from . import selectors diff --git a/test/examples/import-recursion/cq_example.py b/test/examples/import-recursion/cq_example.py new file mode 100644 index 000000000..00a571762 --- /dev/null +++ b/test/examples/import-recursion/cq_example.py @@ -0,0 +1,3 @@ +import cadquery_simple as cq + +cq. diff --git a/test/test_inference/test_imports.py b/test/test_inference/test_imports.py index f1eb3f575..8e49244f2 100644 --- a/test/test_inference/test_imports.py +++ b/test/test_inference/test_imports.py @@ -468,3 +468,9 @@ def test_relative_import_star(Script): script = Script(source, path='export.py') assert script.complete(3, len("furl.c")) + + +def test_import_recursion(Script): + path = get_example_dir('import-recursion', "cq_example.py") + for c in Script(path=path).complete(3, 3): + c.docstring() From 49e35497ae6246cd26592e14628d06f1cdd59389 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 23 Oct 2020 21:28:08 +0200 Subject: [PATCH 04/18] Stop subclassing CompiledName, potentially fixes #1667 --- jedi/inference/value/instance.py | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/jedi/inference/value/instance.py b/jedi/inference/value/instance.py index 9514e01de..ac0574c7c 100644 --- a/jedi/inference/value/instance.py +++ b/jedi/inference/value/instance.py @@ -401,21 +401,10 @@ class AnonymousInstance(_BaseTreeInstance): _arguments = None -class CompiledInstanceName(compiled.CompiledName): - def __init__(self, inference_state, instance, klass, name): - parent_value = klass.parent_context.get_value() - assert parent_value is not None, "How? Please reproduce and report" - super().__init__( - inference_state, - parent_value, - name.string_name - ) - self._instance = instance - self._class_member_name = name - +class CompiledInstanceName(NameWrapper): @iterator_to_value_set def infer(self): - for result_value in self._class_member_name.infer(): + for result_value in self._wrapped_name.infer(): if result_value.api_type == 'function': yield CompiledBoundMethod(result_value) else: @@ -434,11 +423,7 @@ def values(self): return self._convert(self._class_filter.values()) def _convert(self, names): - klass = self._class_filter.compiled_value - return [ - CompiledInstanceName(self._instance.inference_state, self._instance, klass, n) - for n in names - ] + return [CompiledInstanceName(n) for n in names] class BoundMethod(FunctionMixin, ValueWrapper): From a4f45993f80df69cc9e5dcb37e113d3f1d441828 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 23 Oct 2020 21:38:39 +0200 Subject: [PATCH 05/18] Simplify some things, so something like #1678 does not happen again --- jedi/inference/compiled/value.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/jedi/inference/compiled/value.py b/jedi/inference/compiled/value.py index 1b34b123a..88732f011 100644 --- a/jedi/inference/compiled/value.py +++ b/jedi/inference/compiled/value.py @@ -324,8 +324,7 @@ def __init__(self, inference_state, parent_value, name): self.string_name = name def py__doc__(self): - value, = self.infer() - return value.py__doc__() + return self.infer_compiled_value().py__doc__() def _get_qualified_names(self): parent_qualified_names = self.parent_context.get_qualified_names() @@ -349,16 +348,12 @@ def __repr__(self): @property def api_type(self): - api = self.infer() - # If we can't find the type, assume it is an instance variable - if not api: - return "instance" - return next(iter(api)).api_type + return self.infer_compiled_value().api_type - @memoize_method def infer(self): return ValueSet([self.infer_compiled_value()]) + @memoize_method def infer_compiled_value(self): return create_from_name(self._inference_state, self._parent_value, self.string_name) From 6eabde1519c07a102ff8d96f117e7c5086c5f9dd Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 23 Oct 2020 23:26:07 +0200 Subject: [PATCH 06/18] Fix annotations on self attributes, fixes #1681 --- jedi/inference/value/instance.py | 12 ++++++++++++ test/completion/pep0484_basic.py | 10 ++++++++++ 2 files changed, 22 insertions(+) diff --git a/jedi/inference/value/instance.py b/jedi/inference/value/instance.py index ac0574c7c..c2c203591 100644 --- a/jedi/inference/value/instance.py +++ b/jedi/inference/value/instance.py @@ -501,6 +501,18 @@ def parent_context(self): def get_defining_qualified_value(self): return self._instance + def infer(self): + stmt = search_ancestor(self.tree_name, 'expr_stmt') + if stmt is not None: + if stmt.children[1].type == "annassign": + from jedi.inference.gradual.annotation import infer_annotation + values = infer_annotation( + self.parent_context, stmt.children[1].children[1] + ).execute_annotation() + if values: + return values + return super().infer() + class LazyInstanceClassName(NameWrapper): def __init__(self, instance, class_member_name): diff --git a/test/completion/pep0484_basic.py b/test/completion/pep0484_basic.py index 54ec87701..19c4cfa4f 100644 --- a/test/completion/pep0484_basic.py +++ b/test/completion/pep0484_basic.py @@ -179,3 +179,13 @@ def argskwargs(*args: int, **kwargs: float): next(iter(kwargs.keys())) #? float() kwargs[''] + + +class NotCalledClass: + def __init__(self, x): + self.x: int = x + self.y: int = '' + #? int() + self.x + #? int() + self.y From 98d0a55a02194522f7f0951b3cfdbea7d9b03032 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 23 Oct 2020 23:32:28 +0200 Subject: [PATCH 07/18] Add a few more tests for annotations on self --- test/completion/pep0484_basic.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/completion/pep0484_basic.py b/test/completion/pep0484_basic.py index 19c4cfa4f..e20fe4408 100644 --- a/test/completion/pep0484_basic.py +++ b/test/completion/pep0484_basic.py @@ -189,3 +189,12 @@ def __init__(self, x): self.x #? int() self.y + #? int() + self.y + self.z: int + self.z = '' + #? str() int() + self.z + self.w: float + #? float() + self.w From 6094e7b39a6382edac8de940816414502ed1f5fe Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 24 Oct 2020 10:12:21 +0200 Subject: [PATCH 08/18] Fix get_line_code for stubs --- jedi/api/__init__.py | 2 +- test/test_inference/test_gradual/test_conversion.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index a47e66db6..a1c3c5a65 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -160,7 +160,7 @@ def __init__(self, code=None, line=None, column=None, path=None, self._module_node, code = self._inference_state.parse_and_get_code( code=code, path=self.path, - use_latest_grammar=path and path.suffix == 'pyi', + use_latest_grammar=path and path.suffix == '.pyi', cache=False, # No disk cache, because the current script often changes. diff_cache=settings.fast_parser, cache_path=settings.cache_directory, diff --git a/test/test_inference/test_gradual/test_conversion.py b/test/test_inference/test_gradual/test_conversion.py index fd201aee0..809f87ae4 100644 --- a/test/test_inference/test_gradual/test_conversion.py +++ b/test/test_inference/test_gradual/test_conversion.py @@ -1,4 +1,5 @@ import os +from parso.cache import parser_cache from test.helpers import root_dir from jedi.api.project import Project @@ -64,6 +65,17 @@ def test_goto_import(Script): assert not d.is_stub() +def test_stub_get_line_code(Script): + code = 'from abc import ABC; ABC' + script = Script(code) + d, = script.goto(only_stubs=True) + assert d.get_line_code() == 'class ABC(metaclass=ABCMeta): ...\n' + del parser_cache[script._inference_state.latest_grammar._hashed][str(d.module_path)] + d, = Script(path=d.module_path).goto(d.line, d.column, only_stubs=True) + assert d.is_stub() + assert d.get_line_code() == 'class ABC(metaclass=ABCMeta): ...\n' + + def test_os_stat_result(Script): d, = Script('import os; os.stat_result').goto() assert d.is_stub() From a03a093e2ccff736d3c856bd4c25bd8366f1b1d5 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 24 Oct 2020 10:19:20 +0200 Subject: [PATCH 09/18] change the create_stub_module stuff a bit --- jedi/api/__init__.py | 1 + jedi/inference/gradual/typeshed.py | 9 +++++---- jedi/inference/gradual/utils.py | 5 +++-- jedi/inference/imports.py | 4 ++-- test/test_inference/test_gradual/test_conversion.py | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index a1c3c5a65..34718881d 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -196,6 +196,7 @@ def _get_module(self): # We are in a stub file. Try to load the stub properly. stub_module = load_proper_stub_module( self._inference_state, + self._inference_state.latest_grammar, file_io, names, self._module_node diff --git a/jedi/inference/gradual/typeshed.py b/jedi/inference/gradual/typeshed.py index 6295294d7..a4b287868 100644 --- a/jedi/inference/gradual/typeshed.py +++ b/jedi/inference/gradual/typeshed.py @@ -279,8 +279,8 @@ def _try_to_load_stub_from_file(inference_state, python_value_set, file_io, impo return None else: return create_stub_module( - inference_state, python_value_set, stub_module_node, file_io, - import_names + inference_state, inference_state.latest_grammar, python_value_set, + stub_module_node, file_io, import_names ) @@ -294,7 +294,8 @@ def parse_stub_module(inference_state, file_io): ) -def create_stub_module(inference_state, python_value_set, stub_module_node, file_io, import_names): +def create_stub_module(inference_state, grammar, python_value_set, + stub_module_node, file_io, import_names): if import_names == ('typing',): module_cls = TypingModuleWrapper else: @@ -306,7 +307,7 @@ def create_stub_module(inference_state, python_value_set, stub_module_node, file string_names=import_names, # The code was loaded with latest_grammar, so use # that. - code_lines=get_cached_code_lines(inference_state.latest_grammar, file_io.path), + code_lines=get_cached_code_lines(grammar, file_io.path), is_package=file_name == '__init__.pyi', ) return stub_module_value diff --git a/jedi/inference/gradual/utils.py b/jedi/inference/gradual/utils.py index 1fc90e7f3..af3703c7a 100644 --- a/jedi/inference/gradual/utils.py +++ b/jedi/inference/gradual/utils.py @@ -3,7 +3,7 @@ from jedi.inference.gradual.typeshed import TYPESHED_PATH, create_stub_module -def load_proper_stub_module(inference_state, file_io, import_names, module_node): +def load_proper_stub_module(inference_state, grammar, file_io, import_names, module_node): """ This function is given a random .pyi file and should return the proper module. @@ -27,7 +27,8 @@ def load_proper_stub_module(inference_state, file_io, import_names, module_node) actual_value_set = inference_state.import_module(import_names, prefer_stubs=False) stub = create_stub_module( - inference_state, actual_value_set, module_node, file_io, import_names + inference_state, grammar, actual_value_set, + module_node, file_io, import_names ) inference_state.stub_module_cache[import_names] = stub return stub diff --git a/jedi/inference/imports.py b/jedi/inference/imports.py index 5ae5819fa..f464b08b8 100644 --- a/jedi/inference/imports.py +++ b/jedi/inference/imports.py @@ -498,8 +498,8 @@ def load_module_from_path(inference_state, file_io, import_names=None, is_packag values = NO_VALUES return create_stub_module( - inference_state, values, parse_stub_module(inference_state, file_io), - file_io, import_names + inference_state, inference_state.latest_grammar, values, + parse_stub_module(inference_state, file_io), file_io, import_names ) else: module = _load_python_module( diff --git a/test/test_inference/test_gradual/test_conversion.py b/test/test_inference/test_gradual/test_conversion.py index 809f87ae4..c77a06b03 100644 --- a/test/test_inference/test_gradual/test_conversion.py +++ b/test/test_inference/test_gradual/test_conversion.py @@ -70,7 +70,7 @@ def test_stub_get_line_code(Script): script = Script(code) d, = script.goto(only_stubs=True) assert d.get_line_code() == 'class ABC(metaclass=ABCMeta): ...\n' - del parser_cache[script._inference_state.latest_grammar._hashed][str(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) assert d.is_stub() assert d.get_line_code() == 'class ABC(metaclass=ABCMeta): ...\n' From 69750b9bf0c41c3a202a77621c14994f1f2dbcba Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 24 Oct 2020 13:40:19 +0200 Subject: [PATCH 10/18] Add Python 3.9 to the testsed environments --- .travis.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1588ee1b7..e16e26897 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ python: env: - JEDI_TEST_ENVIRONMENT=38 + - JEDI_TEST_ENVIRONMENT=39 - JEDI_TEST_ENVIRONMENT=37 - JEDI_TEST_ENVIRONMENT=36 - JEDI_TEST_ENVIRONMENT=interpreter @@ -47,9 +48,15 @@ script: # Only required for JEDI_TEST_ENVIRONMENT=38, because it's not always # available. download_name=python-$test_env_version - 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 + 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)" From 83d4ec9e847920e4b6a199a934468f467f9b9e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Labb=C3=A9?= Date: Sat, 5 Dec 2020 21:00:28 -0300 Subject: [PATCH 11/18] Catch 'PermissionError' for unreadable directories --- jedi/api/project.py | 7 +++++-- jedi/inference/sys_path.py | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/jedi/api/project.py b/jedi/api/project.py index 415543144..4ba6b7d08 100644 --- a/jedi/api/project.py +++ b/jedi/api/project.py @@ -366,8 +366,11 @@ def __repr__(self): def _is_potential_project(path): for name in _CONTAINS_POTENTIAL_PROJECT: - if path.joinpath(name).exists(): - return True + try: + if path.joinpath(name).exists(): + return True + except PermissionError: + continue return False diff --git a/jedi/inference/sys_path.py b/jedi/inference/sys_path.py index 48b9ac897..9dd588dbe 100644 --- a/jedi/inference/sys_path.py +++ b/jedi/inference/sys_path.py @@ -171,8 +171,11 @@ def _get_paths_from_buildout_script(inference_state, buildout_script_path): def _get_parent_dir_with_file(path: Path, filename): for parent in path.parents: - if parent.joinpath(filename).is_file(): - return parent + try: + if parent.joinpath(filename).is_file(): + return parent + except PermissionError: + continue return None From ccdf7eddf461db1d78b34391d92e0f3c234d645e Mon Sep 17 00:00:00 2001 From: Yoni Weill Date: Sat, 5 Dec 2020 21:11:32 +0200 Subject: [PATCH 12/18] add Completion.get_completion_prefix_length fixes #1687 --- jedi/api/classes.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/jedi/api/classes.py b/jedi/api/classes.py index a8d9fb19c..42aac726c 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -749,6 +749,24 @@ def type(self): return super().type + def get_completion_prefix_length(self): + """ + Returns the length of the prefix being completed. + For example, completing ``isinstance``:: + + isinstan# <-- Cursor is here + + would return 8, because len('isinstan') == 8. + + Assuming the following function definition:: + + def foo(param=0): + pass + + completing ``foo(par`` would return 3. + """ + return self._like_name_length + def __repr__(self): return '<%s: %s>' % (type(self).__name__, self._name.get_public_name()) From 12a2d10595fe7b9f7b88c76b75c05fad75cb531b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Labb=C3=A9?= Date: Sun, 6 Dec 2020 15:25:46 -0300 Subject: [PATCH 13/18] Catch 'OSError' instead of just 'PermissionError' --- jedi/api/project.py | 2 +- jedi/inference/sys_path.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jedi/api/project.py b/jedi/api/project.py index 4ba6b7d08..8c438f26e 100644 --- a/jedi/api/project.py +++ b/jedi/api/project.py @@ -369,7 +369,7 @@ def _is_potential_project(path): try: if path.joinpath(name).exists(): return True - except PermissionError: + except OSError: continue return False diff --git a/jedi/inference/sys_path.py b/jedi/inference/sys_path.py index 9dd588dbe..e701686f5 100644 --- a/jedi/inference/sys_path.py +++ b/jedi/inference/sys_path.py @@ -174,7 +174,7 @@ def _get_parent_dir_with_file(path: Path, filename): try: if parent.joinpath(filename).is_file(): return parent - except PermissionError: + except OSError: continue return None From 47e60107b29c8c16dfcfe1fd906518f868521e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Labb=C3=A9?= Date: Sun, 6 Dec 2020 15:26:20 -0300 Subject: [PATCH 14/18] Add tests for 'test_get_parent_dir_with_file' and 'test_is_potential_project' --- test/test_api/test_project.py | 19 +++++++++++++++++++ test/test_inference/test_sys_path.py | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/test/test_api/test_project.py b/test/test_api/test_project.py index c8533618d..9ca4686ca 100644 --- a/test/test_api/test_project.py +++ b/test/test_api/test_project.py @@ -6,6 +6,7 @@ from ..helpers import get_example_dir, set_cwd, root_dir, test_dir from jedi import Interpreter from jedi.api import Project, get_default_project +from jedi.api.project import _is_potential_project, _CONTAINS_POTENTIAL_PROJECT def test_django_default_project(Script): @@ -160,3 +161,21 @@ def test_complete_search(Script, string, completions, all_scopes): project = Project(test_dir) defs = project.complete_search(string, all_scopes=all_scopes) assert [d.complete for d in defs] == completions + + +@pytest.mark.parametrize( + 'path,expected', [ + (Path(__file__).parents[2], True), # The path of the project + (Path(__file__).parents[1], False), # The path of the tests, not a project + (Path.home(), None) + ] +) +def test_is_potential_project(path, expected): + + if expected is None: + try: + expected = _CONTAINS_POTENTIAL_PROJECT in os.listdir(path) + except OSError: + expected = False + + assert _is_potential_project(path) == expected diff --git a/test/test_inference/test_sys_path.py b/test/test_inference/test_sys_path.py index 2fa0e4df8..d22cc2e03 100644 --- a/test/test_inference/test_sys_path.py +++ b/test/test_inference/test_sys_path.py @@ -108,3 +108,13 @@ def test_transform_path_to_dotted(sys_path_, module_path, expected, is_package): module_path = os.path.abspath(module_path) assert sys_path.transform_path_to_dotted(sys_path_, Path(module_path)) \ == (expected, is_package) + + +@pytest.mark.parametrize( + 'path,filename,expected', [ + (Path(__file__).parents[1], "setup.py", Path(__file__).parents[2]), + (Path(__file__).parents[2], os.path.basename(__file__), None) + ] +) +def test_get_parent_dir_with_file(path, filename, expected): + assert sys_path._get_parent_dir_with_file(path, filename) == expected From 10958200060a546d5ed8250809fee5ae7e94ae0f Mon Sep 17 00:00:00 2001 From: Yoni Weill Date: Sun, 6 Dec 2020 21:09:03 +0200 Subject: [PATCH 15/18] add tests for get_completion_prefix_length --- test/test_api/test_classes.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_api/test_classes.py b/test/test_api/test_classes.py index 6add83f61..4a6f7323c 100644 --- a/test/test_api/test_classes.py +++ b/test/test_api/test_classes.py @@ -513,10 +513,14 @@ def foo(bar, baz): assert run('foo(bar').name_with_symbols == 'bar=' assert run('foo(bar').complete == '=' + assert run('foo(bar').get_completion_prefix_length() == 3 assert run('foo(bar, baz').complete == '=' + assert run('foo(bar, baz').get_completion_prefix_length() == 3 assert run(' bar').name_with_symbols == 'bar' assert run(' bar').complete == '' + assert run(' bar').get_completion_prefix_length() == 3 x = run('foo(bar=isins').name_with_symbols + assert run('foo(bar=isins').get_completion_prefix_length() == 5 assert x == 'isinstance' From 06d6776422c4c06f0c312cfef3f2dacedfbda808 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sun, 6 Dec 2020 18:07:19 -0800 Subject: [PATCH 16/18] Add tests for #1702, for a rare numpydoc syntax. It looks like numpydoc, and things like masked array docstrings use a syntax that make jedi crash: fill_value : {var}, optional Value used internally for the masked values. If ``fill_value`` is not None, it supersedes ``endwith``. Here we add a test that we do not crash jedi. --- test/test_inference/test_docstring.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/test_inference/test_docstring.py b/test/test_inference/test_docstring.py index 3fc54eb19..62d36683b 100644 --- a/test/test_inference/test_docstring.py +++ b/test/test_inference/test_docstring.py @@ -206,6 +206,24 @@ def foobar(x, y): assert 'capitalize' in names assert 'numerator' in names +@pytest.mark.skipif(numpydoc_unavailable, + reason='numpydoc module is unavailable') +def test_numpydoc_parameters_set_single_value(): + """ + This is found in numpy masked-array I'm not too sure what this means but should not crash + """ + s = dedent(''' + def foobar(x, y): + """ + Parameters + ---------- + x : {var}, optional + """ + x.''') + names = [c.name for c in jedi.Script(s).complete()] + # just don't crash + assert names == [] + @pytest.mark.skipif(numpydoc_unavailable, reason='numpydoc module is unavailable') From 4740178bdf24ae111c50fb114e1d1d668b2c1a18 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sun, 6 Dec 2020 18:03:20 -0800 Subject: [PATCH 17/18] Not all nodes have children, protect agaisnt it. --- jedi/inference/docstrings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jedi/inference/docstrings.py b/jedi/inference/docstrings.py index ee7a8d895..80afbecfa 100644 --- a/jedi/inference/docstrings.py +++ b/jedi/inference/docstrings.py @@ -113,7 +113,7 @@ def _expand_typestr(type_str): elif type_str.startswith('{'): node = parse(type_str, version='3.7').children[0] if node.type == 'atom': - for leaf in node.children[1].children: + for leaf in getattr(node.children[1], "children", []): if leaf.type == 'number': if '.' in leaf.value: yield 'float' From 6dcae857a7a1ad333508400bda6d3c218e70d5b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Labb=C3=A9?= Date: Mon, 7 Dec 2020 14:50:04 -0300 Subject: [PATCH 18/18] Remove 'test_get_parent_dir_with_file' --- test/test_inference/test_sys_path.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/test_inference/test_sys_path.py b/test/test_inference/test_sys_path.py index d22cc2e03..2fa0e4df8 100644 --- a/test/test_inference/test_sys_path.py +++ b/test/test_inference/test_sys_path.py @@ -108,13 +108,3 @@ def test_transform_path_to_dotted(sys_path_, module_path, expected, is_package): module_path = os.path.abspath(module_path) assert sys_path.transform_path_to_dotted(sys_path_, Path(module_path)) \ == (expected, is_package) - - -@pytest.mark.parametrize( - 'path,filename,expected', [ - (Path(__file__).parents[1], "setup.py", Path(__file__).parents[2]), - (Path(__file__).parents[2], os.path.basename(__file__), None) - ] -) -def test_get_parent_dir_with_file(path, filename, expected): - assert sys_path._get_parent_dir_with_file(path, filename) == expected