Merge branch 'master' into relative-import

This commit is contained in:
Dave Halter
2020-12-12 12:15:13 +01:00
24 changed files with 223 additions and 44 deletions

View File

@@ -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)"

View File

@@ -160,7 +160,7 @@ class Script:
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,
@@ -196,6 +196,7 @@ class Script:
# 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
@@ -281,6 +282,11 @@ class Script:
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)

View File

@@ -749,6 +749,24 @@ class Completion(BaseName):
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())

View File

@@ -366,8 +366,11 @@ class Project:
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 OSError:
continue
return False

View File

@@ -324,8 +324,7 @@ class CompiledName(AbstractNameDefinition):
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 @@ class CompiledName(AbstractNameDefinition):
@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)

View File

@@ -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'

View File

@@ -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

View File

@@ -3,7 +3,7 @@ from pathlib import Path
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

View File

@@ -533,8 +533,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(

View File

@@ -7,6 +7,7 @@ from parso.tree import search_ancestor
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 @@ class TreeNameDefinition(AbstractTreeName):
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'):

View File

@@ -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 OSError:
continue
return None

View File

@@ -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 @@ class CompiledInstanceClassFilter(AbstractFilter):
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):
@@ -516,6 +501,18 @@ class SelfName(TreeNameDefinition):
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):

View File

@@ -819,7 +819,8 @@ def get_metaclass_filters(func):
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 @@ class EnumInstance(LazyValueWrapper):
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

View File

@@ -179,3 +179,22 @@ 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
#? int()
self.y
self.z: int
self.z = ''
#? str() int()
self.z
self.w: float
#? float()
self.w

View File

@@ -0,0 +1 @@
from .cq import selectors

View File

@@ -0,0 +1 @@
from . import selectors

View File

@@ -0,0 +1,3 @@
import cadquery_simple as cq
cq.

View File

@@ -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]

View File

@@ -513,10 +513,14 @@ def test_added_equals_to_params(Script):
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'

View File

@@ -6,6 +6,7 @@ import pytest
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

View File

@@ -206,6 +206,24 @@ def test_numpydoc_parameters_set_of_values():
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')

View File

@@ -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][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()

View File

@@ -489,3 +489,9 @@ def test_relative_imports_without_path_and_setup_py(
assert n.name == name
assert n.type == 'module'
assert n.line == 1
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()

View File

@@ -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()