1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-02-04 08:50:05 +08:00
SpaceVim/bundle/jedi-vim/pythonx/jedi/test/test_api/test_classes.py
2022-10-23 15:41:52 +08:00

669 lines
20 KiB
Python

""" Test all things related to the ``jedi.api_classes`` module.
"""
from textwrap import dedent
from inspect import cleandoc
import pytest
import jedi
from jedi import __doc__ as jedi_doc
from jedi.inference.compiled import CompiledValueName
from ..helpers import get_example_dir
def test_is_keyword(Script):
results = Script('str', path=None).infer(1, 1)
assert len(results) == 1 and results[0].is_keyword is False
def test_basedefinition_type(Script, get_names):
def make_definitions():
"""
Return a list of definitions for parametrized tests.
:rtype: [jedi.api_classes.BaseName]
"""
source = dedent("""
import sys
class C:
pass
x = C()
def f():
pass
def g():
yield
h = lambda: None
""")
definitions = []
definitions += get_names(source)
source += dedent("""
variable = sys or C or x or f or g or g() or h""")
lines = source.splitlines()
script = Script(source, path=None)
definitions += script.infer(len(lines), len('variable'))
script2 = Script(source, path=None)
definitions += script2.get_references(4, len('class C'))
source_param = "def f(a): return a"
script_param = Script(source_param, path=None)
definitions += script_param.goto(1, len(source_param))
return definitions
for definition in make_definitions():
assert definition.type in ('module', 'class', 'instance', 'function',
'generator', 'statement', 'import', 'param')
@pytest.mark.parametrize(
('src', 'expected_result', 'column'), [
# import one level
('import t', 'module', None),
('import ', 'module', None),
('import datetime; datetime', 'module', None),
# from
('from datetime import timedelta', 'class', None),
('from datetime import timedelta; timedelta', 'class', None),
('from json import tool', 'module', None),
('from json import tool; tool', 'module', None),
# import two levels
('import json.tool; json', 'module', None),
('import json.tool; json.tool', 'module', None),
('import json.tool; json.tool.main', 'function', None),
('import json.tool', 'module', None),
('import json.tool', 'module', 9),
]
)
def test_basedefinition_type_import(Script, src, expected_result, column):
types = {t.type for t in Script(src).complete(column=column)}
assert types == {expected_result}
def test_function_signature_in_doc(Script):
defs = Script("""
def f(x, y=1, z='a'):
pass
f""").infer()
doc = defs[0].docstring()
assert "f(x, y=1, z='a')" in str(doc)
def test_param_docstring(get_names):
param = get_names("def test(parameter): pass", all_scopes=True)[1]
assert param.name == 'parameter'
assert param.docstring() == ''
def test_class_signature(Script):
defs = Script("""
class Foo:
def __init__(self, x, y=1, z='a'):
pass
Foo""").infer()
doc = defs[0].docstring()
assert doc == "Foo(x, y=1, z='a')"
def test_position_none_if_builtin(Script):
gotos = Script('import sys; sys.path').goto()
assert gotos[0].in_builtin_module()
assert gotos[0].line is not None
assert gotos[0].column is not None
def test_completion_docstring(Script, jedi_path):
"""
Jedi should follow imports in certain conditions
"""
def docstr(src, result):
c = Script(src, project=project).complete()[0]
assert c.docstring(raw=True, fast=False) == cleandoc(result)
project = jedi.Project('.', sys_path=[jedi_path])
c = Script('import jedi\njed', project=project).complete()[0]
assert c.docstring(fast=False) == cleandoc(jedi_doc)
docstr('import jedi\njedi.Scr', cleandoc(jedi.Script.__doc__))
docstr('abcd=3;abcd', '')
docstr('"hello"\nabcd=3\nabcd', '')
docstr(
dedent('''
def x():
"hello"
0
x'''),
'hello'
)
docstr(
dedent('''
def x():
"hello";0
x'''),
'hello'
)
# Shouldn't work with a tuple.
docstr(
dedent('''
def x():
"hello",0
x'''),
''
)
# Should also not work if we rename something.
docstr(
dedent('''
def x():
"hello"
y = x
y'''),
''
)
def test_completion_params(Script):
c = Script('import string; string.capwords').complete()[0]
assert [p.name for p in c.get_signatures()[0].params] == ['s', 'sep']
def test_functions_should_have_params(Script):
for c in Script('bool.').complete():
if c.type == 'function':
if c.name in ('denominator', 'numerator', 'imag', 'real', '__class__'):
# Properties
assert not c.get_signatures()
else:
assert c.get_signatures()
def test_hashlib_params(Script, environment):
if environment.version_info < (3,):
pytest.skip()
script = Script('from hashlib import sha256')
c, = script.complete()
sig, = c.get_signatures()
assert [p.name for p in sig.params] == ['string']
def test_signature_params(Script):
def check(defs):
signature, = defs[0].get_signatures()
assert len(signature.params) == 1
assert signature.params[0].name == 'bar'
s = dedent('''
def foo(bar):
pass
foo''')
check(Script(s).infer())
check(Script(s).goto())
check(Script(s + '\nbar=foo\nbar').goto())
def test_param_endings(Script):
"""
Params should be represented without the comma and whitespace they have
around them.
"""
sig, = Script('def x(a, b=5, c=""): pass\n x(').get_signatures()
assert [p.description for p in sig.params] == ['param a', 'param b=5', 'param c=""']
@pytest.mark.parametrize(
'code, index, name, is_definition', [
('name', 0, 'name', False),
('a = f(x)', 0, 'a', True),
('a = f(x)', 1, 'f', False),
('a = f(x)', 2, 'x', False),
]
)
def test_is_definition(get_names, code, index, name, is_definition):
d = get_names(
dedent(code),
references=True,
all_scopes=True,
)[index]
assert d.name == name
assert d.is_definition() == is_definition
@pytest.mark.parametrize(
'code, expected', (
('import x as a', [False, True]),
('from x import y', [False, True]),
('from x.z import y', [False, False, True]),
)
)
def test_is_definition_import(get_names, code, expected):
ns = get_names(dedent(code), references=True, all_scopes=True)
# Assure that names are definitely sorted.
ns = sorted(ns, key=lambda name: (name.line, name.column))
assert [name.is_definition() for name in ns] == expected
def test_parent(Script):
def _parent(source, line=None, column=None):
def_, = Script(dedent(source)).goto(line, column)
return def_.parent()
parent = _parent('foo=1\nfoo')
assert parent.type == 'module'
parent = _parent('''
def spam():
if 1:
y=1
y''')
assert parent.name == 'spam'
assert parent.parent().type == 'module'
def test_parent_on_function(Script):
code = 'def spam():\n pass'
def_, = Script(code).goto(line=1, column=len('def spam'))
parent = def_.parent()
assert parent.name == '__main__'
assert parent.type == 'module'
def test_parent_on_completion_and_else(Script):
script = Script(dedent('''\
class Foo():
def bar(name): name
Foo().bar'''))
bar, = script.complete()
parent = bar.parent()
assert parent.name == 'Foo'
assert parent.type == 'class'
param, name, = [d for d in script.get_names(all_scopes=True, references=True)
if d.name == 'name']
parent = name.parent()
assert parent.name == 'bar'
assert parent.type == 'function'
parent = name.parent().parent()
assert parent.name == 'Foo'
assert parent.type == 'class'
parent = param.parent()
assert parent.name == 'bar'
assert parent.type == 'function'
parent = param.parent().parent()
assert parent.name == 'Foo'
assert parent.type == 'class'
parent = Script('str.join').complete()[0].parent()
assert parent.name == 'str'
assert parent.type == 'class'
def test_parent_on_closure(Script):
script = Script(dedent('''\
class Foo():
def bar(name):
def inner(): foo
return inner'''))
names = script.get_names(all_scopes=True, references=True)
inner_func, inner_reference = filter(lambda d: d.name == 'inner', names)
foo, = filter(lambda d: d.name == 'foo', names)
assert foo.parent().name == 'inner'
assert foo.parent().parent().name == 'bar'
assert foo.parent().parent().parent().name == 'Foo'
assert foo.parent().parent().parent().parent().name == '__main__'
assert inner_func.parent().name == 'bar'
assert inner_func.parent().parent().name == 'Foo'
assert inner_reference.parent().name == 'bar'
assert inner_reference.parent().parent().name == 'Foo'
def test_parent_on_comprehension(Script):
ns = Script('''\
def spam():
return [i for i in range(5)]
''').get_names(all_scopes=True)
assert [name.name for name in ns] == ['spam', 'i']
assert ns[0].parent().name == '__main__'
assert ns[0].parent().type == 'module'
assert ns[1].parent().name == 'spam'
assert ns[1].parent().type == 'function'
def test_type(Script):
for c in Script('a = [str()]; a[0].').complete():
if c.name == '__class__' and False: # TODO fix.
assert c.type == 'class'
else:
assert c.type in ('function', 'statement')
for c in Script('list.').complete():
assert c.type
# Github issue #397, type should never raise an error.
for c in Script('import os; os.path.').complete():
assert c.type
def test_type_II(Script):
"""
GitHub Issue #833, `keyword`s are seen as `module`s
"""
for c in Script('f').complete():
if c.name == 'for':
assert c.type == 'keyword'
@pytest.mark.parametrize(
'added_code, expected_type, expected_infer_type', [
('Foo().x', 'property', 'instance'),
('Foo.x', 'property', 'property'),
('Foo().y', 'function', 'function'),
('Foo.y', 'function', 'function'),
('Foo().z', 'function', 'function'),
('Foo.z', 'function', 'function'),
]
)
def test_class_types(goto_or_help_or_infer, added_code, expected_type,
expected_infer_type):
code = dedent('''\
class Foo:
@property
def x(self): return 1
@staticmethod
def y(self): ...
@classmethod
def z(self): ...
''')
d, = goto_or_help_or_infer(code + added_code)
if goto_or_help_or_infer.type == 'infer':
assert d.type == expected_infer_type
else:
assert d.type == expected_type
"""
This tests the BaseName.goto function, not the jedi
function. They are not really different in functionality, but really
different as an implementation.
"""
def test_goto_repetition(get_names):
defs = get_names('a = 1; a', references=True, definitions=False)
# Repeat on the same variable. Shouldn't change once we're on a
# definition.
for _ in range(3):
assert len(defs) == 1
ass = defs[0].goto()
assert ass[0].description == 'a = 1'
def test_goto_named_params(get_names):
src = """\
def foo(a=1, bar=2):
pass
foo(bar=1)
"""
bar = get_names(dedent(src), references=True)[-1]
param = bar.goto()[0]
assert (param.line, param.column) == (1, 13)
assert param.type == 'param'
def test_class_call(get_names):
src = 'from threading import Thread; Thread(group=1)'
n = get_names(src, references=True)[-1]
assert n.name == 'group'
param_def = n.goto()[0]
assert param_def.name == 'group'
assert param_def.type == 'param'
def test_parentheses(get_names):
n = get_names('("").upper', references=True)[-1]
assert n.goto()[0].name == 'upper'
def test_import(get_names):
nms = get_names('from json import load', references=True)
assert nms[0].name == 'json'
assert nms[0].type == 'module'
n = nms[0].goto()[0]
assert n.name == 'json'
assert n.type == 'module'
assert nms[1].name == 'load'
assert nms[1].type == 'function'
n = nms[1].goto()[0]
assert n.name == 'load'
assert n.type == 'function'
nms = get_names('import os; os.path', references=True)
assert nms[0].name == 'os'
assert nms[0].type == 'module'
n = nms[0].goto()[0]
assert n.name == 'os'
assert n.type == 'module'
nms = nms[2].goto()
assert nms
assert all(n.type == 'module' for n in nms)
assert 'posixpath' in {n.name for n in nms}
nms = get_names('import os.path', references=True)
n = nms[0].goto()[0]
assert n.name == 'os'
assert n.type == 'module'
n = nms[1].goto()[0]
# This is very special, normally the name doesn't change, but since
# os.path is a sys.modules hack, it does.
assert n.name in ('macpath', 'ntpath', 'posixpath', 'os2emxpath')
assert n.type == 'module'
def test_import_alias(get_names):
nms = get_names('import json as foo', references=True)
assert nms[0].name == 'json'
assert nms[0].type == 'module'
assert nms[0]._name.tree_name.parent.type == 'dotted_as_name'
n = nms[0].goto()[0]
assert n.name == 'json'
assert n.type == 'module'
assert n._name._value.tree_node.type == 'file_input'
assert nms[1].name == 'foo'
assert nms[1].type == 'module'
assert nms[1]._name.tree_name.parent.type == 'dotted_as_name'
ass = nms[1].goto()
assert len(ass) == 1
assert ass[0].name == 'json'
assert ass[0].type == 'module'
assert ass[0]._name._value.tree_node.type == 'file_input'
def test_added_equals_to_params(Script):
def run(rest_source):
source = dedent("""
def foo(bar, baz):
pass
""")
results = Script(source + rest_source).complete()
assert len(results) == 1
return results[0]
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'
def test_builtin_module_with_path(Script):
"""
This test simply tests if a module from /usr/lib/python3.8/lib-dynload/ has
a path or not. It shouldn't have a module_path, because that is just
confusing.
"""
semlock, = Script('from _multiprocessing import SemLock').infer()
assert isinstance(semlock._name, CompiledValueName)
assert semlock.module_path is None
assert semlock.in_builtin_module() is True
assert semlock.name == 'SemLock'
assert semlock.line is None
assert semlock.column is None
@pytest.mark.parametrize(
'code, description', [
('int', 'instance int'),
('str.index', 'instance int'),
('1', None),
]
)
def test_execute(Script, code, description):
definition, = Script(code).goto()
definitions = definition.execute()
if description is None:
assert not definitions
else:
d, = definitions
assert d.description == description
@pytest.mark.parametrize('goto', [False, True, None])
@pytest.mark.parametrize(
'code, name, file_name', [
('from pkg import Foo; Foo.foo', 'foo', '__init__.py'),
('from pkg import Foo; Foo().foo', 'foo', '__init__.py'),
('from pkg import Foo; Foo.bar', 'bar', 'module.py'),
('from pkg import Foo; Foo().bar', 'bar', 'module.py'),
])
def test_inheritance_module_path(Script, goto, code, name, file_name):
base_path = get_example_dir('inheritance', 'pkg')
whatever_path = base_path.joinpath('NOT_EXISTING.py')
script = Script(code, path=whatever_path)
if goto is None:
func, = script.infer()
else:
func, = script.goto(follow_imports=goto)
assert func.type == 'function'
assert func.name == name
assert func.module_path == base_path.joinpath(file_name)
def test_definition_goto_follow_imports(Script):
dumps = Script('from json import dumps\ndumps').get_names(references=True)[-1]
assert dumps.description == 'dumps'
no_follow, = dumps.goto()
assert no_follow.description == 'def dumps'
assert no_follow.line == 1
assert no_follow.column == 17
assert no_follow.module_name == '__main__'
follow, = dumps.goto(follow_imports=True)
assert follow.description == 'def dumps'
assert follow.line != 1
assert follow.module_name == 'json'
@pytest.mark.parametrize(
'code, expected', [
('1', 'int'),
('x = None; x', 'None'),
('n: Optional[str]; n', 'Optional[str]'),
('n = None if xxxxx else ""; n', 'Optional[str]'),
('n = None if xxxxx else str(); n', 'Optional[str]'),
('n = None if xxxxx else str; n', 'Optional[Type[str]]'),
('class Foo: pass\nFoo', 'Type[Foo]'),
('class Foo: pass\nFoo()', 'Foo'),
('n: Type[List[int]]; n', 'Type[List[int]]'),
('n: Type[List]; n', 'Type[list]'),
('n: List; n', 'list'),
('n: List[int]; n', 'List[int]'),
('n: Iterable[int]; n', 'Iterable[int]'),
('n = [1]; n', 'List[int]'),
('n = [1, ""]; n', 'List[Union[int, str]]'),
('n = [1, str(), None]; n', 'List[Optional[Union[int, str]]]'),
('n = {1, str()}; n', 'Set[Union[int, str]]'),
('n = (1,); n', 'Tuple[int]'),
('n = {1: ""}; n', 'Dict[int, str]'),
('n = {1: "", 1.0: b""}; n', 'Dict[Union[float, int], Union[bytes, str]]'),
('n = next; n', 'Union[next(__i: Iterator[_T]) -> _T, '
'next(__i: Iterator[_T], default: _VT) -> Union[_T, _VT]]'),
('abs', 'abs(__x: SupportsAbs[_T]) -> _T'),
('def foo(x, y): return x if xxxx else y\nfoo(str(), 1)\nfoo',
'foo(x: str, y: int) -> Union[int, str]'),
('def foo(x, y = None): return x if xxxx else y\nfoo(str(), 1)\nfoo',
'foo(x: str, y: int=None) -> Union[int, str]'),
]
)
def test_get_type_hint(Script, code, expected):
code = 'from typing import *\n' + code
d, = Script(code).goto()
assert d.get_type_hint() == expected
def test_pseudotreenameclass_type(Script):
assert Script('from typing import Any\n').get_names()[0].type == 'class'
cls_code = '''\
class AClass:
"""my class"""
@staticmethod
def hello():
func_var = 1
return func_var
'''
@pytest.mark.parametrize(
'code, pos, start, end', [
('def a_func():\n return "bar"\n', (1, 4), (1, 0), (2, 16)),
('var1 = 12', (1, 0), (1, 0), (1, 9)),
('var1 + 1', (1, 0), (1, 0), (1, 4)),
('class AClass: pass', (1, 6), (1, 0), (1, 18)),
('class AClass: pass\n', (1, 6), (1, 0), (1, 18)),
(cls_code, (1, 6), (1, 0), (6, 23)),
(cls_code, (4, 8), (4, 4), (6, 23)),
(cls_code, (5, 8), (5, 8), (5, 20)),
]
)
def test_definition_start_end_position(Script, code, pos, start, end):
'''Tests for definition_start_position and definition_end_position'''
name = next(
n for n in Script(code=code).get_names(all_scopes=True, references=True)
if n._name.tree_name.start_pos <= pos <= n._name.tree_name.end_pos
)
assert name.get_definition_start_position() == start
assert name.get_definition_end_position() == end