diff --git a/conftest.py b/conftest.py index 20d6fd68..a9e31ad2 100644 --- a/conftest.py +++ b/conftest.py @@ -127,3 +127,11 @@ def skip_python2(environment): # This if is just needed to avoid that tests ever skip way more than # they should for all Python versions. pytest.skip() + + +@pytest.fixture() +def skip_pre_python38(environment): + if environment.version_info < (3, 8): + # This if is just needed to avoid that tests ever skip way more than + # they should for all Python versions. + pytest.skip() diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 2225bc57..3f9f9542 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -385,10 +385,7 @@ class Script(object): # TODO here we use stubs instead of the actual contexts. We should use # the signatures from stubs, but the actual contexts, probably?! - return [classes.CallSignature(self._evaluator, signature, - call_signature_details.bracket_leaf.start_pos, - call_signature_details.call_index, - call_signature_details.keyword_name_str) + return [classes.CallSignature(self._evaluator, signature, call_signature_details) for signature in definitions.get_signatures()] def _analysis(self): diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 4001a343..f983217a 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -604,11 +604,9 @@ 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, signature, bracket_start_pos, index, key_name_str): + def __init__(self, evaluator, signature, call_details): super(CallSignature, self).__init__(evaluator, signature.name) - self._index = index - self._key_name_str = key_name_str - self._bracket_start_pos = bracket_start_pos + self._call_details = call_details self._signature = signature @property @@ -617,9 +615,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_str is not None: + if self._call_details.keyword_name_str is not None: for i, param in enumerate(self.params): - if self._key_name_str == param.name: + if self._call_details.keyword_name_str == param.name: return i if self.params: param_name = self.params[-1]._name @@ -628,7 +626,7 @@ class CallSignature(Definition): return i return None - if self._index >= len(self.params): + if self._call_details.call_index >= len(self.params): for i, param in enumerate(self.params): tree_name = param._name.tree_name if tree_name is not None: @@ -636,7 +634,7 @@ class CallSignature(Definition): if tree_name.get_definition().star_count == 1: return i return None - return self._index + return self._call_details.call_index @property def params(self): @@ -648,7 +646,7 @@ class CallSignature(Definition): The indent of the bracket that is responsible for the last function call. """ - return self._bracket_start_pos + return self._call_details.bracket_leaf.start_pos @property def _params_str(self): diff --git a/test/test_api/test_call_signatures.py b/test/test_api/test_call_signatures.py index 4ea04422..6fdfa231 100644 --- a/test/test_api/test_call_signatures.py +++ b/test/test_api/test_call_signatures.py @@ -395,6 +395,82 @@ def test_keyword_argument_index(Script, environment): assert get(both + 'foo(a, b, c').index == 0 +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' + + +@pytest.mark.parametrize( + 'code, call, index', [ + # No *args, **kwargs + (code1, 'f(', 0), + (code1, 'f(a', 0), + (code1, 'f(a,', 1), + (code1, 'f(a,b', 1), + (code1, 'f(a,b,', 2), + (code1, 'f(a,b,c', None), + (code1, 'f(a,b,a', 2), + (code1, 'f(a,b,a=', None), + (code1, 'f(a,b,abc', 2), + (code1, 'f(a,b,abc=(', 2), + (code1, 'f(a,b,abc=(f,1,2,3', 2), + (code1, 'f(a,b,abd', 3), + (code1, 'f(a,b,x', 4), + (code1, 'f(a,b,xy', 4), + (code1, 'f(a,b,xyz=', 4), + (code1, 'f(a,b,xy=', None), + (code1, 'f(u=', None), + (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), + + # *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), + + # *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), + ] +) +def test_signature_index(skip_pre_python38, Script, code, call, index): + sig, = Script(code + '\n' + call).call_signatures() + print(call) + print('index', index) + assert index == sig.index + + @pytest.mark.skipif(sys.version_info[0] == 2, reason="Python 2 doesn't support __signature__") @pytest.mark.parametrize('code', ['foo', 'instance.foo']) def test_arg_defaults(Script, environment, code):