Fix all call signature tests.

This commit is contained in:
Dave Halter
2016-06-17 17:03:34 +02:00
parent 32346c6da8
commit 7ddc9c9c78
4 changed files with 44 additions and 22 deletions

View File

@@ -291,14 +291,14 @@ class Script(object):
# self.source, self._pos) # self.source, self._pos)
definitions = helpers.evaluate_goto_definition( definitions = helpers.evaluate_goto_definition(
self._evaluator, self._evaluator,
call_signature_details.leaf call_signature_details.bracket_leaf.get_previous_leaf()
) )
debug.speed('func_call followed') debug.speed('func_call followed')
return [classes.CallSignature(self._evaluator, d.name, return [classes.CallSignature(self._evaluator, d.name,
call_signature_details.leaf.start_pos, call_signature_details.bracket_leaf.start_pos,
call_signature_details.call_index, call_signature_details.call_index,
call_signature_details.keyword_name) call_signature_details.keyword_name_str)
for d in definitions if hasattr(d, 'py__call__')] for d in definitions if hasattr(d, 'py__call__')]
def _analysis(self): def _analysis(self):

View File

@@ -629,10 +629,10 @@ class CallSignature(Definition):
It knows what functions you are currently in. e.g. `isinstance(` would It knows what functions you are currently in. e.g. `isinstance(` would
return the `isinstance` function. without `(` it would return nothing. return the `isinstance` function. without `(` it would return nothing.
""" """
def __init__(self, evaluator, executable_name, bracket_start_pos, index, key_name): def __init__(self, evaluator, executable_name, bracket_start_pos, index, key_name_str):
super(CallSignature, self).__init__(evaluator, executable_name) super(CallSignature, self).__init__(evaluator, executable_name)
self._index = index self._index = index
self._key_name = key_name self._key_name_str = key_name_str
self._bracket_start_pos = bracket_start_pos self._bracket_start_pos = bracket_start_pos
@property @property
@@ -641,9 +641,9 @@ class CallSignature(Definition):
The Param index of the current call. The Param index of the current call.
Returns None if the index cannot be found in the curent call. Returns None if the index cannot be found in the curent call.
""" """
if self._key_name is not None: if self._key_name_str is not None:
for i, param in enumerate(self.params): for i, param in enumerate(self.params):
if self._key_name == param.name: if self._key_name_str == param.name:
return i return i
if self.params and self.params[-1]._name.get_definition().stars == 2: if self.params and self.params[-1]._name.get_definition().stars == 2:
return i return i

View File

@@ -192,7 +192,7 @@ def evaluate_goto_definition(evaluator, leaf):
CallSignatureDetails = namedtuple( CallSignatureDetails = namedtuple(
'CallSignatureDetails', 'CallSignatureDetails',
['leaf', 'call_index', 'keyword_name'] ['bracket_leaf', 'call_index', 'keyword_name_str']
) )
@@ -202,8 +202,25 @@ def _get_call_signature_details_from_error_node(node, position):
if element == '(' and element.end_pos <= position and index > 0: if element == '(' and element.end_pos <= position and index > 0:
name = element.get_previous_leaf() name = element.get_previous_leaf()
if name.type == 'name': if name.type == 'name':
nodes_before = [c for c in node.children[index:] if c.start_pos < position] if node.children[-1].type == 'arglist':
return CallSignatureDetails(name, nodes_before.count(','), None) node = node.children[-1]
children = node.children
else:
# It's an error node, we don't want to match too much, just
# until the parentheses is enough.
children = node.children[index:]
nodes_before = [c for c in children if c.start_pos < position]
key_str = None
if nodes_before:
if nodes_before[-1].type == 'argument':
key_str = nodes_before[-1].children[0].value
elif nodes_before[-1] == '=':
key_str = nodes_before[-2].value
return CallSignatureDetails(
element,
nodes_before.count(','),
key_str
)
def get_call_signature_details(module, position): def get_call_signature_details(module, position):
@@ -214,8 +231,12 @@ def get_call_signature_details(module, position):
# Now that we know where we are in the syntax tree, we start to look at # Now that we know where we are in the syntax tree, we start to look at
# parents for possible function definitions. # parents for possible function definitions.
node = leaf.parent node = leaf.parent
name = None
while node is not None: while node is not None:
if node.type in ('funcdef', 'classdef'):
# Don't show call signatures if there's stuff before it that just
# makes it feel strange to have a call signature.
return None
for n in node.children: for n in node.children:
if n.start_pos < position and n.type == 'error_node': if n.start_pos < position and n.type == 'error_node':
result = _get_call_signature_details_from_error_node(n, position) result = _get_call_signature_details_from_error_node(n, position)
@@ -223,9 +244,9 @@ def get_call_signature_details(module, position):
return result return result
if node.type == 'trailer' and node.children[0] == '(': if node.type == 'trailer' and node.children[0] == '(':
name = node.get_previous_sibling() leaf = node.get_previous_leaf()
nodes_before = [c for c in node.children if c.start_pos < position] nodes_before = [c for c in node.children if c.start_pos < position]
return CallSignatureDetails(name, nodes_before.count(','), None) return CallSignatureDetails(node.children[0], nodes_before.count(','), None)
node = node.parent node = node.parent

View File

@@ -15,7 +15,7 @@ class TestCallSignatures(TestCase):
if not signatures: if not signatures:
assert expected_name is None, \ assert expected_name is None, \
'There are no signatures, but %s expected.' % expected_name 'There are no signatures, but `%s` expected.' % expected_name
else: else:
assert signatures[0].name == expected_name assert signatures[0].name == expected_name
assert signatures[0].index == expected_index assert signatures[0].index == expected_index
@@ -73,10 +73,11 @@ class TestCallSignatures(TestCase):
run("import time; abc = time; abc.sleep(", 'sleep', 0) run("import time; abc = time; abc.sleep(", 'sleep', 0)
def test_issue_57(self):
# jedi #57 # jedi #57
s = "def func(alpha, beta): pass\n" \ s = "def func(alpha, beta): pass\n" \
"func(alpha='101'," "func(alpha='101',"
run(s, 'func', 0, column=13, line=2) self._run_simple(s, 'func', 0, column=13, line=2)
def test_flows(self): def test_flows(self):
# jedi-vim #9 # jedi-vim #9
@@ -178,9 +179,7 @@ class TestCallSignatures(TestCase):
def test_whitespace_before_bracket(self): def test_whitespace_before_bracket(self):
self._run('str (', 'str', 0) self._run('str (', 'str', 0)
self._run('str (";', 'str', 0) self._run('str (";', 'str', 0)
# TODO this is not actually valid Python, the newline token should be self._run('str\n(', None)
# ignored.
self._run('str\n(', 'str', 0)
def test_brackets_in_string_literals(self): def test_brackets_in_string_literals(self):
self._run('str (" (', 'str', 0) self._run('str (" (', 'str', 0)
@@ -191,7 +190,8 @@ class TestCallSignatures(TestCase):
Function definitions (and other tokens that cannot exist within call Function definitions (and other tokens that cannot exist within call
signatures) should break and not be able to return a call signature. signatures) should break and not be able to return a call signature.
""" """
assert not Script('str(\ndef x').call_signatures() assert not self._run('str(\ndef x', 'str', 0)
assert not Script('str(\ndef x(): pass').call_signatures()
def test_flow_call(self): def test_flow_call(self):
assert not Script('if (1').call_signatures() assert not Script('if (1').call_signatures()
@@ -316,11 +316,12 @@ def test_completion_interference():
assert Script('open(').call_signatures() assert Script('open(').call_signatures()
def test_signature_index(): def test_keyword_argument_index():
def get(source): def get(source, column=None):
return Script(source).call_signatures()[0] return Script(source, column=column).call_signatures()[0]
assert get('sorted([], key=a').index == 2 assert get('sorted([], key=a').index == 2
assert get('sorted([], key=').index == 2
assert get('sorted([], no_key=a').index is None assert get('sorted([], no_key=a').index is None
args_func = 'def foo(*kwargs): pass\n' args_func = 'def foo(*kwargs): pass\n'