Goto didn't work well on imports in __init__.py files.

Fixes #956.
This commit is contained in:
Dave Halter
2017-09-11 21:48:37 +02:00
parent c05f1d3ccc
commit 619acbd2ca
10 changed files with 56 additions and 13 deletions

View File

@@ -223,9 +223,9 @@ class Script(object):
:rtype: list of :class:`classes.Definition` :rtype: list of :class:`classes.Definition`
""" """
def filter_follow_imports(names): def filter_follow_imports(names, follow_classes):
for name in names: for name in names:
if isinstance(name, (imports.ImportName, TreeNameDefinition)): if isinstance(name, follow_classes):
for context in name.infer(): for context in name.infer():
yield context.name yield context.name
else: else:
@@ -233,7 +233,13 @@ class Script(object):
names = self._goto() names = self._goto()
if follow_imports: if follow_imports:
names = filter_follow_imports(names) # TODO really, sure? TreeNameDefinition? Should probably not follow
# that.
follow_classes = (imports.ImportName, TreeNameDefinition)
else:
follow_classes = (imports.SubModuleName,)
names = filter_follow_imports(names, follow_classes)
defs = [classes.Definition(self._evaluator, d) for d in set(names)] defs = [classes.Definition(self._evaluator, d) for d in set(names)]
return helpers.sorted_definitions(defs) return helpers.sorted_definitions(defs)

View File

@@ -118,7 +118,7 @@ class Evaluator(object):
self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self) self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self)
def find_types(self, context, name_or_str, name_context, position=None, def find_types(self, context, name_or_str, name_context, position=None,
search_global=False, is_goto=False): search_global=False, is_goto=False, analysis_errors=True):
""" """
This is the search function. The most important part to debug. This is the search function. The most important part to debug.
`remove_statements` and `filter_statements` really are the core part of `remove_statements` and `filter_statements` really are the core part of
@@ -127,7 +127,8 @@ class Evaluator(object):
:param position: Position of the last statement -> tuple of line, column :param position: Position of the last statement -> tuple of line, column
:return: List of Names. Their parents are the types. :return: List of Names. Their parents are the types.
""" """
f = finder.NameFinder(self, context, name_context, name_or_str, position) f = finder.NameFinder(self, context, name_context, name_or_str,
position, analysis_errors=analysis_errors)
filters = f.get_filters(search_global) filters = f.get_filters(search_global)
if is_goto: if is_goto:
return f.filter_name(filters) return f.filter_name(filters)

View File

@@ -54,11 +54,13 @@ class Context(object):
@Python3Method @Python3Method
def py__getattribute__(self, name_or_str, name_context=None, position=None, def py__getattribute__(self, name_or_str, name_context=None, position=None,
search_global=False, is_goto=False): search_global=False, is_goto=False,
analysis_errors=True):
if name_context is None: if name_context is None:
name_context = self name_context = self
return self.evaluator.find_types( return self.evaluator.find_types(
self, name_or_str, name_context, position, search_global, is_goto) self, name_or_str, name_context, position, search_global, is_goto,
analysis_errors)
def create_context(self, node, node_is_context=False, node_is_object=False): def create_context(self, node, node_is_context=False, node_is_object=False):
return self.evaluator.create_context(self, node, node_is_context, node_is_object) return self.evaluator.create_context(self, node, node_is_context, node_is_object)

View File

@@ -30,13 +30,14 @@ from jedi.evaluate import analysis
from jedi.evaluate import flow_analysis from jedi.evaluate import flow_analysis
from jedi.evaluate import param from jedi.evaluate import param
from jedi.evaluate import helpers from jedi.evaluate import helpers
from jedi.evaluate.filters import get_global_filters from jedi.evaluate.filters import get_global_filters, TreeNameDefinition
from jedi.evaluate.context import ContextualizedName, ContextualizedNode from jedi.evaluate.context import ContextualizedName, ContextualizedNode
from jedi.parser_utils import is_scope, get_parent_scope from jedi.parser_utils import is_scope, get_parent_scope
class NameFinder(object): class NameFinder(object):
def __init__(self, evaluator, context, name_context, name_or_str, position=None): def __init__(self, evaluator, context, name_context, name_or_str,
position=None, analysis_errors=True):
self._evaluator = evaluator self._evaluator = evaluator
# Make sure that it's not just a syntax tree node. # Make sure that it's not just a syntax tree node.
self._context = context self._context = context
@@ -48,6 +49,7 @@ class NameFinder(object):
self._string_name = name_or_str self._string_name = name_or_str
self._position = position self._position = position
self._found_predefined_types = None self._found_predefined_types = None
self._analysis_errors = analysis_errors
@debug.increase_indent @debug.increase_indent
def find(self, filters, attribute_lookup): def find(self, filters, attribute_lookup):
@@ -65,7 +67,7 @@ class NameFinder(object):
types = self._names_to_types(names, attribute_lookup) types = self._names_to_types(names, attribute_lookup)
if not names and not types \ if not names and self._analysis_errors and not types \
and not (isinstance(self._name, tree.Name) and and not (isinstance(self._name, tree.Name) and
isinstance(self._name.parent.parent, tree.Param)): isinstance(self._name.parent.parent, tree.Param)):
if isinstance(self._name, tree.Name): if isinstance(self._name, tree.Name):
@@ -122,7 +124,19 @@ class NameFinder(object):
for filter in filters: for filter in filters:
names = filter.get(self._string_name) names = filter.get(self._string_name)
if names: if names:
if len(names) == 1:
n, = names
if isinstance(n, TreeNameDefinition):
# Something somewhere went terribly wrong. This
# typically happens when using goto on an import in an
# __init__ file. I think we need a better solution, but
# it's kind of hard, because for Jedi it's not clear
# that that name has not been defined, yet.
if n.tree_name == self._name:
if self._name.get_definition().type == 'import_from':
continue
break break
debug.dbg('finder.filter_name "%s" in (%s): %s@%s', self._string_name, debug.dbg('finder.filter_name "%s" in (%s): %s@%s', self._string_name,
self._context, names, self._position) self._context, names, self._position)
return list(names) return list(names)

View File

@@ -65,9 +65,10 @@ def infer_import(context, tree_name, is_goto=False):
if from_import_name is not None: if from_import_name is not None:
types = unite( types = unite(
t.py__getattribute__( t.py__getattribute__(
from_import_name.value if isinstance(from_import_name, tree.Name) else from_import_name, from_import_name,
name_context=context, name_context=context,
is_goto=is_goto is_goto=is_goto,
analysis_errors=False
) for t in types ) for t in types
) )

View File

@@ -2,7 +2,7 @@
addopts = --doctest-modules addopts = --doctest-modules
# Ignore broken files in blackbox test directories # Ignore broken files in blackbox test directories
norecursedirs = .* docs completion refactor absolute_import namespace_package scripts extensions speed static_analysis not_in_sys_path buildout_project sample_venvs init_extension_module norecursedirs = .* docs completion refactor absolute_import namespace_package scripts extensions speed static_analysis not_in_sys_path buildout_project sample_venvs init_extension_module simple_import
# Activate `clean_jedi_cache` fixture for all tests. This should be # Activate `clean_jedi_cache` fixture for all tests. This should be
# fine as long as we are using `clean_jedi_cache` as a session scoped # fine as long as we are using `clean_jedi_cache` as a session scoped

View File

@@ -0,0 +1,5 @@
from simple_import import module
def in_function():
from simple_import import module2

View File

View File

View File

@@ -2,6 +2,7 @@
Test all things related to the ``jedi.api`` module. Test all things related to the ``jedi.api`` module.
""" """
import os
from textwrap import dedent from textwrap import dedent
from jedi import api from jedi import api
@@ -205,3 +206,16 @@ def test_goto_assignments_follow_imports():
definition, = script.goto_assignments() definition, = script.goto_assignments()
assert (definition.line, definition.column) == start_pos assert (definition.line, definition.column) == start_pos
def test_goto_module():
def check(line, expected):
script = api.Script(path=path, line=line)
module, = script.goto_assignments()
assert module.module_path == expected
base_path = os.path.join(os.path.dirname(__file__), 'simple_import')
path = os.path.join(base_path, '__init__.py')
check(1, os.path.join(base_path, 'module.py'))
check(5, os.path.join(base_path, 'module2.py'))