Make it possible to use goto_definition on "broken" imports

This commit is contained in:
Dave Halter
2019-03-27 00:39:51 +01:00
parent 993567ca56
commit f4c17e578c
7 changed files with 52 additions and 31 deletions

View File

@@ -9,7 +9,7 @@ from jedi.api import classes
from jedi.api import helpers from jedi.api import helpers
from jedi.evaluate import imports from jedi.evaluate import imports
from jedi.api import keywords from jedi.api import keywords
from jedi.evaluate.helpers import evaluate_call_of_leaf from jedi.evaluate.helpers import evaluate_call_of_leaf, parse_dotted_names
from jedi.evaluate.filters import get_global_filters from jedi.evaluate.filters import get_global_filters
from jedi.parser_utils import get_statement_of_position from jedi.parser_utils import get_statement_of_position
@@ -185,7 +185,7 @@ class Completion:
# Also true for defining names as a class or function. # Also true for defining names as a class or function.
return list(self._get_class_context_completions(is_function=True)) return list(self._get_class_context_completions(is_function=True))
elif "import_stmt" in nonterminals: elif "import_stmt" in nonterminals:
level, names = self._parse_dotted_names(nodes, "import_from" in nonterminals) level, names = parse_dotted_names(nodes, "import_from" in nonterminals)
only_modules = not ("import_from" in nonterminals and 'import' in nodes) only_modules = not ("import_from" in nonterminals and 'import' in nodes)
completion_names += self._get_importer_names( completion_names += self._get_importer_names(
@@ -240,26 +240,6 @@ class Completion:
completion_names += filter.values() completion_names += filter.values()
return completion_names return completion_names
def _parse_dotted_names(self, nodes, is_import_from):
level = 0
names = []
for node in nodes[1:]:
if node in ('.', '...'):
if not names:
level += len(node.value)
elif node.type == 'dotted_name':
names += node.children[::2]
elif node.type == 'name':
names.append(node)
elif node == ',':
if not is_import_from:
names = []
else:
# Here if the keyword `import` comes along it stops checking
# for names.
break
return level, names
def _get_importer_names(self, names, level=0, only_modules=True): def _get_importer_names(self, names, level=0, only_modules=True):
names = [n.value for n in names] names = [n.value for n in names]
i = imports.Importer(self._evaluator, names, self._module_context, level) i = imports.Importer(self._evaluator, names, self._module_context, level)

View File

@@ -290,6 +290,16 @@ class Evaluator(object):
if type_ in ('import_from', 'import_name'): if type_ in ('import_from', 'import_name'):
return imports.infer_import(context, name) return imports.infer_import(context, name)
error_node = tree.search_ancestor(name, 'error_node')
if error_node is not None:
first_name = error_node.get_first_leaf().value
if first_name == 'from':
level, names = helpers.parse_dotted_names(
error_node.children,
is_import_from=True
)
return imports.Importer(self, names, context.get_root_context(), level).follow()
return helpers.evaluate_call_of_leaf(context, name) return helpers.evaluate_call_of_leaf(context, name)
def goto(self, context, name): def goto(self, context, name):

View File

@@ -67,6 +67,7 @@ class SubModuleDictMixin(object):
return names return names
class ModuleMixin(SubModuleDictMixin): class ModuleMixin(SubModuleDictMixin):
def get_filters(self, search_global=False, until_position=None, origin_scope=None): def get_filters(self, search_global=False, until_position=None, origin_scope=None):
yield MergedFilter( yield MergedFilter(

View File

@@ -239,3 +239,24 @@ def execute_evaluated(context, *value_list):
from jedi.evaluate.base_context import ContextSet from jedi.evaluate.base_context import ContextSet
arguments = ValuesArguments([ContextSet([value]) for value in value_list]) arguments = ValuesArguments([ContextSet([value]) for value in value_list])
return context.evaluator.execute(context, arguments) return context.evaluator.execute(context, arguments)
def parse_dotted_names(nodes, is_import_from):
level = 0
names = []
for node in nodes[1:]:
if node in ('.', '...'):
if not names:
level += len(node.value)
elif node.type == 'dotted_name':
names += node.children[::2]
elif node.type == 'name':
names.append(node)
elif node == ',':
if not is_import_from:
names = []
else:
# Here if the keyword `import` comes along it stops checking
# for names.
break
return level, names

View File

@@ -239,11 +239,6 @@ class Importer(object):
self._evaluator = evaluator self._evaluator = evaluator
self.level = level self.level = level
self.module_context = module_context self.module_context = module_context
try:
self.file_path = module_context.py__file__()
except AttributeError:
# Can be None for certain compiled modules like 'builtins'.
self.file_path = None
self._fixed_sys_path = None self._fixed_sys_path = None
self._inference_possible = True self._inference_possible = True
@@ -316,10 +311,16 @@ class Importer(object):
+ sys_path.check_sys_path_modifications(self.module_context) + sys_path.check_sys_path_modifications(self.module_context)
) )
if self.import_path and self.file_path is not None \ if self.import_path:
and self._evaluator.environment.version_info.major == 2: try:
# Python2 uses an old strange way of importing relative imports. file_path = self.module_context.py__file__()
sys_path_mod.append(force_unicode(os.path.dirname(self.file_path))) except AttributeError:
# Can be None for certain compiled modules like 'builtins'.
file_path = None
else:
if self._evaluator.environment.version_info.major == 2:
# Python2 uses an old strange way of importing relative imports.
sys_path_mod.append(force_unicode(os.path.dirname(file_path)))
return sys_path_mod return sys_path_mod

View File

@@ -120,6 +120,8 @@ import_tree.a
#! ['module mod1'] #! ['module mod1']
import import_tree.mod1 import import_tree.mod1
#! ['module mod1']
from import_tree.mod1
#! ['a = 1'] #! ['a = 1']
import_tree.mod1.a import_tree.mod1.a

View File

@@ -114,6 +114,12 @@ def as_imports():
bar.a bar.a
def broken_import():
import import_tree.mod1
#? import_tree.mod1
from import_tree.mod1
def test_import_priorities(): def test_import_priorities():
""" """
It's possible to overwrite import paths in an ``__init__.py`` file, by It's possible to overwrite import paths in an ``__init__.py`` file, by