From 7ddc9c9c78449f8bc2549b61df021f4c4fcc03dd Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Fri, 17 Jun 2016 17:03:34 +0200 Subject: [PATCH] Fix all call signature tests. --- jedi/api/__init__.py | 6 ++--- jedi/api/classes.py | 8 +++---- jedi/api/helpers.py | 33 ++++++++++++++++++++++----- test/test_api/test_call_signatures.py | 19 +++++++-------- 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index bfc59245..48d7f304 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -291,14 +291,14 @@ class Script(object): # self.source, self._pos) definitions = helpers.evaluate_goto_definition( self._evaluator, - call_signature_details.leaf + call_signature_details.bracket_leaf.get_previous_leaf() ) debug.speed('func_call followed') 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.keyword_name) + call_signature_details.keyword_name_str) for d in definitions if hasattr(d, 'py__call__')] def _analysis(self): diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 367eaea4..7bc99fa7 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -629,10 +629,10 @@ class CallSignature(Definition): It knows what functions you are currently in. e.g. `isinstance(` would 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) self._index = index - self._key_name = key_name + self._key_name_str = key_name_str self._bracket_start_pos = bracket_start_pos @property @@ -641,9 +641,9 @@ class CallSignature(Definition): The Param index of the current 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): - if self._key_name == param.name: + if self._key_name_str == param.name: return i if self.params and self.params[-1]._name.get_definition().stars == 2: return i diff --git a/jedi/api/helpers.py b/jedi/api/helpers.py index 70ec8bdb..452b8648 100644 --- a/jedi/api/helpers.py +++ b/jedi/api/helpers.py @@ -192,7 +192,7 @@ def evaluate_goto_definition(evaluator, leaf): CallSignatureDetails = namedtuple( '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: name = element.get_previous_leaf() if name.type == 'name': - nodes_before = [c for c in node.children[index:] if c.start_pos < position] - return CallSignatureDetails(name, nodes_before.count(','), None) + if node.children[-1].type == 'arglist': + 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): @@ -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 # parents for possible function definitions. node = leaf.parent - name = 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: if n.start_pos < position and n.type == 'error_node': result = _get_call_signature_details_from_error_node(n, position) @@ -223,9 +244,9 @@ def get_call_signature_details(module, position): return result 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] - return CallSignatureDetails(name, nodes_before.count(','), None) + return CallSignatureDetails(node.children[0], nodes_before.count(','), None) node = node.parent diff --git a/test/test_api/test_call_signatures.py b/test/test_api/test_call_signatures.py index a0758112..22c84a4c 100644 --- a/test/test_api/test_call_signatures.py +++ b/test/test_api/test_call_signatures.py @@ -15,7 +15,7 @@ class TestCallSignatures(TestCase): if not signatures: assert expected_name is None, \ - 'There are no signatures, but %s expected.' % expected_name + 'There are no signatures, but `%s` expected.' % expected_name else: assert signatures[0].name == expected_name assert signatures[0].index == expected_index @@ -73,10 +73,11 @@ class TestCallSignatures(TestCase): run("import time; abc = time; abc.sleep(", 'sleep', 0) + def test_issue_57(self): # jedi #57 s = "def func(alpha, beta): pass\n" \ "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): # jedi-vim #9 @@ -178,9 +179,7 @@ class TestCallSignatures(TestCase): def test_whitespace_before_bracket(self): self._run('str (', 'str', 0) self._run('str (";', 'str', 0) - # TODO this is not actually valid Python, the newline token should be - # ignored. - self._run('str\n(', 'str', 0) + self._run('str\n(', None) def test_brackets_in_string_literals(self): self._run('str (" (', 'str', 0) @@ -191,7 +190,8 @@ class TestCallSignatures(TestCase): Function definitions (and other tokens that cannot exist within call 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): assert not Script('if (1').call_signatures() @@ -316,11 +316,12 @@ def test_completion_interference(): assert Script('open(').call_signatures() -def test_signature_index(): - def get(source): - return Script(source).call_signatures()[0] +def test_keyword_argument_index(): + def get(source, column=None): + return Script(source, column=column).call_signatures()[0] assert get('sorted([], key=a').index == 2 + assert get('sorted([], key=').index == 2 assert get('sorted([], no_key=a').index is None args_func = 'def foo(*kwargs): pass\n'