mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 14:04:26 +08:00
Make it possible to use goto_definition on "broken" imports
This commit is contained in:
@@ -9,7 +9,7 @@ from jedi.api import classes
|
||||
from jedi.api import helpers
|
||||
from jedi.evaluate import imports
|
||||
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.parser_utils import get_statement_of_position
|
||||
|
||||
@@ -185,7 +185,7 @@ class Completion:
|
||||
# Also true for defining names as a class or function.
|
||||
return list(self._get_class_context_completions(is_function=True))
|
||||
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)
|
||||
completion_names += self._get_importer_names(
|
||||
@@ -240,26 +240,6 @@ class Completion:
|
||||
completion_names += filter.values()
|
||||
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):
|
||||
names = [n.value for n in names]
|
||||
i = imports.Importer(self._evaluator, names, self._module_context, level)
|
||||
|
||||
@@ -290,6 +290,16 @@ class Evaluator(object):
|
||||
if type_ in ('import_from', 'import_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)
|
||||
|
||||
def goto(self, context, name):
|
||||
|
||||
@@ -67,6 +67,7 @@ class SubModuleDictMixin(object):
|
||||
|
||||
return names
|
||||
|
||||
|
||||
class ModuleMixin(SubModuleDictMixin):
|
||||
def get_filters(self, search_global=False, until_position=None, origin_scope=None):
|
||||
yield MergedFilter(
|
||||
|
||||
@@ -239,3 +239,24 @@ def execute_evaluated(context, *value_list):
|
||||
from jedi.evaluate.base_context import ContextSet
|
||||
arguments = ValuesArguments([ContextSet([value]) for value in value_list])
|
||||
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
|
||||
|
||||
@@ -239,11 +239,6 @@ class Importer(object):
|
||||
self._evaluator = evaluator
|
||||
self.level = level
|
||||
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._inference_possible = True
|
||||
@@ -316,10 +311,16 @@ class Importer(object):
|
||||
+ sys_path.check_sys_path_modifications(self.module_context)
|
||||
)
|
||||
|
||||
if self.import_path and self.file_path is not None \
|
||||
and 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(self.file_path)))
|
||||
if self.import_path:
|
||||
try:
|
||||
file_path = self.module_context.py__file__()
|
||||
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
|
||||
|
||||
|
||||
@@ -120,6 +120,8 @@ import_tree.a
|
||||
|
||||
#! ['module mod1']
|
||||
import import_tree.mod1
|
||||
#! ['module mod1']
|
||||
from import_tree.mod1
|
||||
#! ['a = 1']
|
||||
import_tree.mod1.a
|
||||
|
||||
|
||||
@@ -114,6 +114,12 @@ def as_imports():
|
||||
bar.a
|
||||
|
||||
|
||||
def broken_import():
|
||||
import import_tree.mod1
|
||||
#? import_tree.mod1
|
||||
from import_tree.mod1
|
||||
|
||||
|
||||
def test_import_priorities():
|
||||
"""
|
||||
It's possible to overwrite import paths in an ``__init__.py`` file, by
|
||||
|
||||
Reference in New Issue
Block a user