diff --git a/jedi/api/helpers.py b/jedi/api/helpers.py index 236deb02..9b2f2c07 100644 --- a/jedi/api/helpers.py +++ b/jedi/api/helpers.py @@ -330,17 +330,14 @@ def _get_call_signature_details_from_error_node(node, additional_children, posit def get_call_signature_details(module, position): leaf = module.get_leaf_for_position(position, include_prefixes=True) + # It's easier to deal with the previous token than the next one in this + # case. if leaf.start_pos >= position: # Whitespace / comments after the leaf count towards the previous leaf. leaf = leaf.get_previous_leaf() if leaf is None: return None - if leaf == ')': - # TODO is this ok? - if leaf.end_pos == position: - leaf = leaf.get_next_leaf() - # Now that we know where we are in the syntax tree, we start to look at # parents for possible function definitions. node = leaf.parent @@ -364,11 +361,17 @@ def get_call_signature_details(module, position): continue additional_children.insert(0, n) + # Find a valid trailer if node.type == 'trailer' and node.children[0] == '(': - leaf = node.get_previous_leaf() - if leaf is None: - return None - return CallDetails(node.children[0], node.children, position) + # Additionally we have to check that an ending parenthesis isn't + # interpreted wrong. There are two cases: + # 1. Cursor before paren -> The current signature is good + # 2. Cursor after paren -> We need to skip the current signature + if not (leaf is node.children[-1] and position >= leaf.end_pos): + leaf = node.get_previous_leaf() + if leaf is None: + return None + return CallDetails(node.children[0], node.children, position) node = node.parent diff --git a/jedi/inference/base_value.py b/jedi/inference/base_value.py index 57f74c1a..9c0d77ef 100644 --- a/jedi/inference/base_value.py +++ b/jedi/inference/base_value.py @@ -8,7 +8,7 @@ just one. """ from functools import reduce from operator import add -from parso.python.tree import ExprStmt, SyncCompFor, Name +from parso.python.tree import Name from jedi import debug from jedi._compatibility import zip_longest, unicode diff --git a/test/test_api/test_call_signatures.py b/test/test_api/test_call_signatures.py index 1e7bf0ea..ff797452 100644 --- a/test/test_api/test_call_signatures.py +++ b/test/test_api/test_call_signatures.py @@ -491,6 +491,13 @@ _calls = [ (code4, 'i(?b,*r,c', 1), (code4, 'i(?*', 0), (code4, 'i(?**', (0, 1)), + + # Random + (code4, 'i(()', 0), + (code4, 'i((),', 1), + (code4, 'i([(),', 0), + (code4, 'i([(,', 1), + (code4, 'i(x,()', 1), ] @@ -625,3 +632,36 @@ def test_call_magic_method(Script): assert [p.name for p in sig.params] == ['self', 'baz'] sig, = Script(code + 'X().__call__(').call_signatures() assert [p.name for p in sig.params] == ['baz'] + + +@pytest.mark.parametrize('column', [6, 9]) +def test_cursor_after_signature(Script, column): + source = dedent(""" + def foo(*args): + pass + foo() # _ + """) + + script = Script(source, 4, column) + + assert not script.call_signatures() + + +@pytest.mark.parametrize( + 'code, line, column, name, index', [ + ('abs(()\ndef foo(): pass', 1, None, 'abs', 0), + ('abs(chr() \ndef foo(): pass', 1, 10, 'abs', 0), + ('abs(chr()\ndef foo(): pass', 1, None, 'abs', 0), + ('abs(chr()\ndef foo(): pass', 1, 8, 'chr', 0), + ('abs(chr()\ndef foo(): pass', 1, 7, 'abs', 0), + ('abs(chr ( \nclass y: pass', 1, None, 'chr', 0), + ('abs(chr ( \nclass y: pass', 1, 8, 'abs', 0), + ('abs(chr ( \nclass y: pass', 1, 9, 'abs', 0), + ('abs(chr ( \nclass y: pass', 1, 10, 'chr', 0), + ] +) +def test_base_signatures(Script, code, line, column, name, index): + sig, = Script(code, line=line, column=column).call_signatures() + + assert sig.name == name + assert sig.index == index