Merge pull request #149 from tkf/definition-in-call

Fallback to callee definition when definition not found (fixes #131)
This commit is contained in:
David Halter
2013-03-10 21:14:15 -07:00
6 changed files with 184 additions and 66 deletions
+6 -1
View File
@@ -5,7 +5,12 @@ python:
- 2.7 - 2.7
- 3.2 - 3.2
install: install:
- if [[ $TRAVIS_PYTHON_VERSION == '2.5' ]]; then pip install --use-mirrors simplejson; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.5' ]]; then
pip install --use-mirrors simplejson unittest2;
fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then
pip install --use-mirrors unittest2;
fi
- pip install --use-mirrors nose - pip install --use-mirrors nose
script: script:
- cd test - cd test
+51 -28
View File
@@ -231,13 +231,37 @@ class Script(object):
goto_path = self._module.get_path_under_cursor() goto_path = self._module.get_path_under_cursor()
context = self._module.get_context() context = self._module.get_context()
scopes = set()
lower_priority_operators = ('()', '(', ',')
"""Operators that could hide callee."""
if next(context) in ('class', 'def'): if next(context) in ('class', 'def'):
scopes = set([self._module.parser.user_scope]) scopes = set([self._module.parser.user_scope])
elif not goto_path: elif not goto_path:
op = self._module.get_operator_under_cursor() op = self._module.get_operator_under_cursor()
scopes = set([keywords.get_operator(op, self.pos)] if op else []) if op and op not in lower_priority_operators:
else: scopes = set([keywords.get_operator(op, self.pos)])
scopes = set(self._prepare_goto(goto_path))
# Fetch definition of callee
if not goto_path:
(call, _) = self._func_call_and_param_index()
if call is not None:
while call.next is not None:
call = call.next
# reset cursor position:
(row, col) = call.name.end_pos
self.pos = (row, max(col - 1, 0))
self._module = modules.ModuleWithCursor(
self._source_path,
source=self.source,
position=self.pos)
# then try to find the path again
goto_path = self._module.get_path_under_cursor()
if not scopes:
if goto_path:
scopes = set(self._prepare_goto(goto_path))
elif op in lower_priority_operators:
scopes = set([keywords.get_operator(op, self.pos)])
scopes = resolve_import_paths(scopes) scopes = resolve_import_paths(scopes)
@@ -370,6 +394,25 @@ class Script(object):
:rtype: :class:`api_classes.CallDef` :rtype: :class:`api_classes.CallDef`
""" """
(call, index) = self._func_call_and_param_index()
if call is None:
return None
user_stmt = self._parser.user_stmt
with common.scale_speed_settings(settings.scale_function_definition):
_callable = lambda: evaluate.follow_call(call)
origins = cache.cache_function_definition(_callable, user_stmt)
debug.speed('func_call followed')
if len(origins) == 0:
return None
# just take entry zero, because we need just one.
executable = origins[0]
return api_classes.CallDef(executable, index, call)
def _func_call_and_param_index(self):
def check_user_stmt(user_stmt): def check_user_stmt(user_stmt):
if user_stmt is None \ if user_stmt is None \
or not isinstance(user_stmt, pr.Statement): or not isinstance(user_stmt, pr.Statement):
@@ -404,37 +447,17 @@ class Script(object):
if repr(old_call) == repr(call): if repr(old_call) == repr(call):
# return the index of the part_parser # return the index of the part_parser
return old_call, index return old_call, index
return None, 0 return None, 0
else:
raise NotFoundError()
debug.speed('func_call start') debug.speed('func_call start')
call = None call = None
index = 0
if settings.use_function_definition_cache: if settings.use_function_definition_cache:
try: call, index = check_cache()
call, index = check_cache()
except NotFoundError:
return None
user_stmt = self._parser.user_stmt
if call is None: if call is None:
# This is a backup, if the above is not successful. call, index = check_user_stmt(self._parser.user_stmt)
call, index = check_user_stmt(user_stmt)
if call is None:
return None
debug.speed('func_call parsed') debug.speed('func_call parsed')
return call, index
with common.scale_speed_settings(settings.scale_function_definition):
_callable = lambda: evaluate.follow_call(call)
origins = cache.cache_function_definition(_callable, user_stmt)
debug.speed('func_call followed')
if len(origins) == 0:
return None
# just take entry zero, because we need just one.
executable = origins[0]
return api_classes.CallDef(executable, index, call)
def _get_on_import_stmt(self, is_like_search=False): def _get_on_import_stmt(self, is_like_search=False):
""" Resolve the user statement, if it is an import. Only resolve the """ Resolve the user statement, if it is an import. Only resolve the
+4 -2
View File
@@ -1,7 +1,9 @@
import unittest
import time import time
import sys import sys
if sys.hexversion < 0x02070000:
import unittest2 as unittest
else:
import unittest
import os import os
from os.path import abspath, dirname from os.path import abspath, dirname
import functools import functools
+65
View File
@@ -0,0 +1,65 @@
"""
Fallback to callee definition when definition not found.
- https://github.com/davidhalter/jedi/issues/131
- https://github.com/davidhalter/jedi/pull/149
"""
"""Parenthesis closed at next line."""
#? isinstance
isinstance(
)
#? isinstance
isinstance(
)
#? isinstance
isinstance(None,
)
#? isinstance
isinstance(None,
)
"""Parenthesis closed at same line."""
# Note: len('isinstance(') == 11
#? 11 isinstance
isinstance()
# Note: len('isinstance(None,') == 16
##? 16 isinstance
isinstance(None,)
# Note: len('isinstance(None,') == 16
##? 16 isinstance
isinstance(None, )
# Note: len('isinstance(None, ') == 17
##? 17 isinstance
isinstance(None, )
# Note: len('isinstance( ') == 12
##? 12 isinstance
isinstance( )
"""Unclosed parenthesis."""
#? isinstance
isinstance(
def x(): pass # acts like EOF
##? isinstance
isinstance(
def x(): pass # acts like EOF
#? isinstance
isinstance(None,
def x(): pass # acts like EOF
#? isinstance
isinstance(None,
+53 -35
View File
@@ -5,17 +5,18 @@ Unit tests to avoid errors of the past. Makes use of Python's ``unittest``
module. module.
""" """
import unittest
import time import time
import functools import functools
import itertools import itertools
import os import os
import textwrap
from base import TestBase, cwd_at from base import TestBase, unittest, cwd_at
import jedi import jedi
from jedi._compatibility import is_py25, utf8, unicode from jedi._compatibility import is_py25, utf8, unicode
from jedi import api from jedi import api
from jedi import api_classes
#jedi.set_debug_function(jedi.debug.print_to_stdout) #jedi.set_debug_function(jedi.debug.print_to_stdout)
@@ -145,10 +146,15 @@ class TestRegression(TestBase):
assert self.complete("from datetime import")[0].word == 'import' assert self.complete("from datetime import")[0].word == 'import'
assert self.complete("from datetime import ") assert self.complete("from datetime import ")
def assert_call_def(self, call_def, name, index):
self.assertEqual(
{'call_name': getattr(call_def, 'call_name', None),
'index': getattr(call_def, 'index', None)},
{'call_name': name, 'index': index},
)
def test_function_definition(self): def test_function_definition(self):
def check(call_def, name, index): check = self.assert_call_def
return call_def and call_def.call_name == name \
and call_def.index == index
# simple # simple
s = "abs(a, str(" s = "abs(a, str("
@@ -161,13 +167,13 @@ class TestRegression(TestBase):
s7 = "str().upper().center(" s7 = "str().upper().center("
s8 = "str(int[zip(" s8 = "str(int[zip("
assert check(self.function_definition(s, (1, 4)), 'abs', 0) check(self.function_definition(s, (1, 4)), 'abs', 0)
assert check(self.function_definition(s, (1, 6)), 'abs', 1) check(self.function_definition(s, (1, 6)), 'abs', 1)
assert check(self.function_definition(s, (1, 7)), 'abs', 1) check(self.function_definition(s, (1, 7)), 'abs', 1)
assert check(self.function_definition(s, (1, 8)), 'abs', 1) check(self.function_definition(s, (1, 8)), 'abs', 1)
assert check(self.function_definition(s, (1, 11)), 'str', 0) check(self.function_definition(s, (1, 11)), 'str', 0)
assert check(self.function_definition(s2, (1, 4)), 'abs', 0) check(self.function_definition(s2, (1, 4)), 'abs', 0)
assert self.function_definition(s2, (1, 5)) is None assert self.function_definition(s2, (1, 5)) is None
assert self.function_definition(s2) is None assert self.function_definition(s2) is None
@@ -175,43 +181,41 @@ class TestRegression(TestBase):
assert self.function_definition(s3) is None assert self.function_definition(s3) is None
assert self.function_definition(s4, (1, 3)) is None assert self.function_definition(s4, (1, 3)) is None
assert check(self.function_definition(s4, (1, 4)), 'abs', 0) check(self.function_definition(s4, (1, 4)), 'abs', 0)
assert check(self.function_definition(s4, (1, 8)), 'zip', 0) check(self.function_definition(s4, (1, 8)), 'zip', 0)
assert check(self.function_definition(s4, (1, 9)), 'abs', 0) check(self.function_definition(s4, (1, 9)), 'abs', 0)
#assert check(self.function_definition(s4, (1, 10)), 'abs', 1) #check(self.function_definition(s4, (1, 10)), 'abs', 1)
assert check(self.function_definition(s5, (1, 4)), 'abs', 0) check(self.function_definition(s5, (1, 4)), 'abs', 0)
assert check(self.function_definition(s5, (1, 6)), 'abs', 1) check(self.function_definition(s5, (1, 6)), 'abs', 1)
assert check(self.function_definition(s6), 'center', 0) check(self.function_definition(s6), 'center', 0)
assert check(self.function_definition(s6, (1, 4)), 'str', 0) check(self.function_definition(s6, (1, 4)), 'str', 0)
assert check(self.function_definition(s7), 'center', 0) check(self.function_definition(s7), 'center', 0)
assert check(self.function_definition(s8), 'zip', 0) check(self.function_definition(s8), 'zip', 0)
assert check(self.function_definition(s8, (1, 8)), 'str', 0) check(self.function_definition(s8, (1, 8)), 'str', 0)
s = "import time; abc = time; abc.sleep(" s = "import time; abc = time; abc.sleep("
assert check(self.function_definition(s), 'sleep', 0) check(self.function_definition(s), 'sleep', 0)
# jedi-vim #9 # jedi-vim #9
s = "with open(" s = "with open("
assert check(self.function_definition(s), 'open', 0) check(self.function_definition(s), 'open', 0)
# jedi-vim #11 # jedi-vim #11
s1 = "for sorted(" s1 = "for sorted("
assert check(self.function_definition(s1), 'sorted', 0) check(self.function_definition(s1), 'sorted', 0)
s2 = "for s in sorted(" s2 = "for s in sorted("
assert check(self.function_definition(s2), 'sorted', 0) check(self.function_definition(s2), 'sorted', 0)
# jedi #57 # jedi #57
s = "def func(alpha, beta): pass\n" \ s = "def func(alpha, beta): pass\n" \
"func(alpha='101'," "func(alpha='101',"
assert check(self.function_definition(s, (2, 13)), 'func', 0) check(self.function_definition(s, (2, 13)), 'func', 0)
def test_function_definition_complex(self): def test_function_definition_complex(self):
def check(call_def, name, index): check = self.assert_call_def
return call_def and call_def.call_name == name \
and call_def.index == index
s = """ s = """
def abc(a,b): def abc(a,b):
@@ -223,18 +227,27 @@ class TestRegression(TestBase):
if 1: if 1:
pass pass
""" """
assert check(self.function_definition(s, (6, 24)), 'abc', 0) check(self.function_definition(s, (6, 24)), 'abc', 0)
s = """ s = """
import re import re
def huhu(it): def huhu(it):
re.compile( re.compile(
return it * 2 return it * 2
""" """
assert check(self.function_definition(s, (4, 31)), 'compile', 0) check(self.function_definition(s, (4, 31)), 'compile', 0)
# jedi-vim #70 # jedi-vim #70
s = """def foo(""" s = """def foo("""
assert self.function_definition(s) is None assert self.function_definition(s) is None
@unittest.skip("function_definition at ``f( |)`` does not work")
def test_function_definition_empty_paren_pre_space(self):
s = textwrap.dedent("""\
def f(a, b):
pass
f( )""")
call_def = self.function_definition(s, (3, 3))
self.assert_call_def(call_def, 'f', 0)
@cwd_at('jedi') @cwd_at('jedi')
def test_add_dynamic_mods(self): def test_add_dynamic_mods(self):
api.settings.additional_dynamic_modules = ['dynamic.py'] api.settings.additional_dynamic_modules = ['dynamic.py']
@@ -384,9 +397,14 @@ class TestFeature(TestBase):
""" feature request #61""" """ feature request #61"""
assert self.complete('import os; os.path.join')[0].full_name \ assert self.complete('import os; os.path.join')[0].full_name \
== 'os.path.join' == 'os.path.join'
# issue #94
defs = self.definition("""import os; os.path.join(""") def test_keyword_full_name_should_be_none(self):
assert defs[0].full_name is None """issue #94"""
# Using `from jedi.keywords import Keyword` here does NOT work
# in Python 3. This is due to the import hack jedi using.
Keyword = api_classes.keywords.Keyword
d = api_classes.Definition(Keyword('(', (0, 0)))
assert d.full_name is None
def test_full_name_builtin(self): def test_full_name_builtin(self):
self.assertEqual(self.complete('type')[0].full_name, 'type') self.assertEqual(self.complete('type')[0].full_name, 'type')
+5
View File
@@ -14,4 +14,9 @@ changedir = test
[testenv:py25] [testenv:py25]
deps = deps =
simplejson simplejson
unittest2
{[testenv]deps}
[testenv:py26]
deps =
unittest2
{[testenv]deps} {[testenv]deps}