mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 05:54:25 +08:00
Fix CallSignature index for a looot of cases, fixes #1364,#1363
This commit is contained in:
@@ -615,6 +615,7 @@ class CallSignature(Definition):
|
||||
The Param index of the current call.
|
||||
Returns None if the index cannot be found in the curent call.
|
||||
"""
|
||||
return self._call_details.calculate_index(self._signature.get_param_names())
|
||||
if self._call_details.keyword_name_str is not None:
|
||||
for i, param in enumerate(self.params):
|
||||
if self._call_details.keyword_name_str == param.name:
|
||||
|
||||
@@ -8,7 +8,7 @@ from textwrap import dedent
|
||||
from parso.python.parser import Parser
|
||||
from parso.python import tree
|
||||
|
||||
from jedi._compatibility import u
|
||||
from jedi._compatibility import u, Parameter
|
||||
from jedi.evaluate.base_context import NO_CONTEXTS
|
||||
from jedi.evaluate.syntax_tree import eval_atom
|
||||
from jedi.evaluate.helpers import evaluate_call_of_leaf
|
||||
@@ -174,6 +174,104 @@ class CallDetails(object):
|
||||
def keyword_name_str(self):
|
||||
return _get_index_and_key(self._children, self._position)[1]
|
||||
|
||||
def calculate_index(self, param_names):
|
||||
positional_count = 0
|
||||
used_names = set()
|
||||
star_count = -1
|
||||
args = list(_iter_arguments(self._children, self._position))
|
||||
if not args:
|
||||
if param_names:
|
||||
return 0
|
||||
else:
|
||||
return None
|
||||
|
||||
is_kwarg = False
|
||||
for i, (star_count, key_start, had_equal) in enumerate(args):
|
||||
is_kwarg |= had_equal | (star_count == 2)
|
||||
if star_count:
|
||||
pass # For now do nothing, we don't know what's in there here.
|
||||
elif had_equal:
|
||||
if i + 1 != len(args):
|
||||
used_names.add(key_start)
|
||||
else:
|
||||
positional_count += 1
|
||||
|
||||
for i, param_name in enumerate(param_names):
|
||||
kind = param_name.get_kind()
|
||||
|
||||
if not is_kwarg:
|
||||
if kind == Parameter.VAR_POSITIONAL:
|
||||
return i
|
||||
if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
|
||||
if i + 1 == positional_count:
|
||||
return i
|
||||
|
||||
if key_start is not None:
|
||||
if param_name.string_name not in used_names \
|
||||
and (kind == Parameter.KEYWORD_ONLY
|
||||
or kind == Parameter.POSITIONAL_OR_KEYWORD
|
||||
and positional_count < i + 1):
|
||||
if had_equal:
|
||||
if param_name.string_name == key_start:
|
||||
return i
|
||||
else:
|
||||
if param_name.string_name.startswith(key_start):
|
||||
return i
|
||||
|
||||
if kind == Parameter.VAR_KEYWORD:
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
def _iter_arguments(nodes, position):
|
||||
def remove_after_pos(name):
|
||||
return name.value[:position[1] - name.start_pos[1]]
|
||||
|
||||
# Returns Generator[Tuple[star_count, Optional[key_start: str], had_equal]]
|
||||
nodes_before = [c for c in nodes if c.start_pos < position]
|
||||
if nodes_before[-1].type == 'arglist':
|
||||
for x in _iter_arguments(nodes_before[-1].children, position):
|
||||
yield x # Python 2 :(
|
||||
return
|
||||
|
||||
previous_node_yielded = False
|
||||
for i, node in enumerate(nodes_before):
|
||||
if node.type == 'argument':
|
||||
previous_node_yielded = True
|
||||
first = node.children[0]
|
||||
second = node.children[1]
|
||||
if second == '=':
|
||||
if second.start_pos < position:
|
||||
yield 0, first.value, True
|
||||
else:
|
||||
yield 0, remove_after_pos(first), True
|
||||
elif first in ('*', '**'):
|
||||
yield len(first), remove_after_pos(second), ''
|
||||
else:
|
||||
# Must be a Comprehension
|
||||
first_leaf = node.get_first_leaf()
|
||||
if first_leaf.type == 'name' and first_leaf.start_pos >= position:
|
||||
yield 0, remove_after_pos(first_leaf), False
|
||||
else:
|
||||
yield 0, None, False
|
||||
elif node == ',':
|
||||
if not previous_node_yielded:
|
||||
yield 0, '', False
|
||||
previous_node_yielded = False
|
||||
elif node == '=' and nodes_before[-1]:
|
||||
previous_node_yielded = True
|
||||
before = nodes_before[i - 1]
|
||||
if before.type == 'name':
|
||||
yield 0, before.value, True
|
||||
else:
|
||||
yield 0, None, False
|
||||
|
||||
if not previous_node_yielded:
|
||||
if nodes_before[-1].type == 'name':
|
||||
yield 0, remove_after_pos(nodes_before[-1]), False
|
||||
else:
|
||||
yield 0, '', False
|
||||
|
||||
|
||||
def _get_index_and_key(nodes, position):
|
||||
"""
|
||||
@@ -185,14 +283,13 @@ def _get_index_and_key(nodes, position):
|
||||
|
||||
key_str = None
|
||||
|
||||
if nodes_before:
|
||||
last = nodes_before[-1]
|
||||
if last.type == 'argument' and last.children[1] == '=' \
|
||||
and last.children[1].end_pos <= position:
|
||||
# Checked if the argument
|
||||
key_str = last.children[0].value
|
||||
elif last == '=':
|
||||
key_str = nodes_before[-2].value
|
||||
last = nodes_before[-1]
|
||||
if last.type == 'argument' and last.children[1] == '=' \
|
||||
and last.children[1].end_pos <= position:
|
||||
# Checked if the argument
|
||||
key_str = last.children[0].value
|
||||
elif last == '=':
|
||||
key_str = nodes_before[-2].value
|
||||
|
||||
return nodes_before.count(','), key_str
|
||||
|
||||
|
||||
@@ -396,13 +396,13 @@ def test_keyword_argument_index(Script, environment):
|
||||
|
||||
|
||||
code1 = 'def f(u, /, v=3, *, abc, abd, xyz): pass'
|
||||
code2 = 'def f(u, /, v=3, *, abc, abd, xyz, **kwargs): pass'
|
||||
code3 = 'def f(u, /, v, *args, x=1, y): pass'
|
||||
code4 = 'def f(u, /, v, *args, x=1, y, **kwargs): pass'
|
||||
code2 = 'def g(u, /, v=3, *, abc, abd, xyz, **kwargs): pass'
|
||||
code3 = 'def h(u, /, v, *args, x=1, y): pass'
|
||||
code4 = 'def i(u, /, v, *args, x=1, y, **kwargs): pass'
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'code, call, index', [
|
||||
'code, call, expected_index', [
|
||||
# No *args, **kwargs
|
||||
(code1, 'f(', 0),
|
||||
(code1, 'f(a', 0),
|
||||
@@ -424,51 +424,52 @@ code4 = 'def f(u, /, v, *args, x=1, y, **kwargs): pass'
|
||||
(code1, 'f(v=', 1),
|
||||
|
||||
# **kwargs
|
||||
(code2, 'f(a,b,a', 2),
|
||||
(code2, 'f(a,b,abd', 2),
|
||||
(code2, 'f(a,b,arr', 5),
|
||||
(code2, 'f(a,b,xy', 4),
|
||||
(code2, 'f(a,b,xy=', 4),
|
||||
(code2, 'f(a,b,abc=1,abd=4,', 5),
|
||||
(code2, 'f(a,b,abc=1,abd=4,lala', 5),
|
||||
(code2, 'f(a,b,abc=1,abd=4,lala=', 5),
|
||||
(code2, 'f(a,b,kw', 5),
|
||||
(code2, 'f(a,b,kwargs=', 5),
|
||||
(code2, 'f(u=', 5),
|
||||
(code2, 'f(v=', 1),
|
||||
(code2, 'g(a,b,a', 2),
|
||||
(code2, 'g(a,b,abc', 2),
|
||||
(code2, 'g(a,b,abd', 3),
|
||||
(code2, 'g(a,b,arr', 5),
|
||||
(code2, 'g(a,b,xy', 4),
|
||||
(code2, 'g(a,b,xyz=', 4),
|
||||
(code2, 'g(a,b,xy=', 5),
|
||||
(code2, 'g(a,b,abc=1,abd=4,', 4),
|
||||
(code2, 'g(a,b,abc=1,xyz=3,abd=4,', 5),
|
||||
(code2, 'g(a,b,abc=1,abd=4,lala', 5),
|
||||
(code2, 'g(a,b,abc=1,abd=4,lala=', 5),
|
||||
(code2, 'g(a,b,abc=1,abd=4,abd=', 5),
|
||||
(code2, 'g(a,b,kw', 5),
|
||||
(code2, 'g(a,b,kwargs=', 5),
|
||||
(code2, 'g(u=', 5),
|
||||
(code2, 'g(v=', 1),
|
||||
|
||||
# *args
|
||||
(code3, 'f(a,b,c', 2),
|
||||
(code3, 'f(a,b,c,', 2),
|
||||
(code3, 'f(a,b,c,d', 2),
|
||||
(code3, 'f(a,b,c,d[', 2),
|
||||
(code3, 'f(a,b,c,d(3,', 2),
|
||||
(code3, 'f(a,b,c,(3,)', 2),
|
||||
(code3, 'f(a,b,args=', None),
|
||||
(code3, 'f(a,b=', 1),
|
||||
(code3, 'f(a=', None),
|
||||
(code3, 'h(a,b,c', 2),
|
||||
(code3, 'h(a,b,c,', 2),
|
||||
(code3, 'h(a,b,c,d', 2),
|
||||
(code3, 'h(a,b,c,d[', 2),
|
||||
(code3, 'h(a,b,c,(3,', 2),
|
||||
(code3, 'h(a,b,c,(3,)', 2),
|
||||
(code3, 'h(a,b,args=', None),
|
||||
(code3, 'h(u,v=', 1),
|
||||
(code3, 'h(u=', None),
|
||||
|
||||
# *args, **kwargs
|
||||
(code4, 'f(a,b,c,d', 2),
|
||||
(code4, 'f(a,b,c,d,e', 2),
|
||||
(code4, 'f(a,b,c,d,e=', 5),
|
||||
(code4, 'f(a,b,c,d,e=3', 5),
|
||||
(code4, 'f(a,b,c,d=,', 3),
|
||||
(code4, 'f(a,b,c,d=,x=', 3),
|
||||
(code4, 'f(a,b,c,d=5,x=4', 3),
|
||||
(code4, 'f(a,b,c,d=5,x=4,y', 4),
|
||||
(code4, 'f(a,b,c,d=5,x=4,y=3,', 5),
|
||||
(code4, 'f(a,b,c,d=5,y=4,x=3', 4),
|
||||
(code4, 'f(a,b,c,d=5,y=4,x=3,', 5),
|
||||
(code4, 'f(a,b,c,d=4,', 5),
|
||||
(code4, 'f(a,b,c,d=,', 5),
|
||||
(code4, 'i(a,b,c,d', 2),
|
||||
(code4, 'i(a,b,c,d,e', 2),
|
||||
(code4, 'i(a,b,c,d,e=', 5),
|
||||
(code4, 'i(a,b,c,d,e=3', 5),
|
||||
#(code4, 'i(a,b,c,d=,x=', 3),
|
||||
(code4, 'i(a,b,c,d=5,x=4', 3),
|
||||
(code4, 'i(a,b,c,d=5,x=4,y', 4),
|
||||
(code4, 'i(a,b,c,d=5,x=4,y=3,', 5),
|
||||
(code4, 'i(a,b,c,d=5,y=4,x=3,', 5),
|
||||
(code4, 'i(a,b,c,d=4,', 3),
|
||||
(code4, 'i(a,b,c,x=1,d=2,', 4),
|
||||
]
|
||||
)
|
||||
def test_signature_index(skip_pre_python38, Script, code, call, index):
|
||||
def test_signature_index(skip_pre_python38, Script, code, call, expected_index):
|
||||
sig, = Script(code + '\n' + call).call_signatures()
|
||||
print(call)
|
||||
print('index', index)
|
||||
assert index == sig.index
|
||||
index = sig.index
|
||||
assert expected_index == index
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Python 2 doesn't support __signature__")
|
||||
|
||||
Reference in New Issue
Block a user