diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index cf1d6a6a..3a4d2d8d 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -223,9 +223,9 @@ class Script(object): :rtype: list of :class:`classes.Definition` """ - def filter_follow_imports(names): + def filter_follow_imports(names, follow_classes): for name in names: - if isinstance(name, (imports.ImportName, TreeNameDefinition)): + if isinstance(name, follow_classes): for context in name.infer(): yield context.name else: @@ -233,7 +233,13 @@ class Script(object): names = self._goto() 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)] return helpers.sorted_definitions(defs) diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index c0045497..2b2ffc52 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -118,7 +118,7 @@ class Evaluator(object): self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self) 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. `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 :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) if is_goto: return f.filter_name(filters) diff --git a/jedi/evaluate/context.py b/jedi/evaluate/context.py index a232be6b..d111ff24 100644 --- a/jedi/evaluate/context.py +++ b/jedi/evaluate/context.py @@ -54,11 +54,13 @@ class Context(object): @Python3Method 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: name_context = self 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): return self.evaluator.create_context(self, node, node_is_context, node_is_object) diff --git a/jedi/evaluate/finder.py b/jedi/evaluate/finder.py index 9c11ae5e..18e69348 100644 --- a/jedi/evaluate/finder.py +++ b/jedi/evaluate/finder.py @@ -30,13 +30,14 @@ from jedi.evaluate import analysis from jedi.evaluate import flow_analysis from jedi.evaluate import param 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.parser_utils import is_scope, get_parent_scope 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 # Make sure that it's not just a syntax tree node. self._context = context @@ -48,6 +49,7 @@ class NameFinder(object): self._string_name = name_or_str self._position = position self._found_predefined_types = None + self._analysis_errors = analysis_errors @debug.increase_indent def find(self, filters, attribute_lookup): @@ -65,7 +67,7 @@ class NameFinder(object): 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 isinstance(self._name.parent.parent, tree.Param)): if isinstance(self._name, tree.Name): @@ -122,7 +124,19 @@ class NameFinder(object): for filter in filters: names = filter.get(self._string_name) 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 + debug.dbg('finder.filter_name "%s" in (%s): %s@%s', self._string_name, self._context, names, self._position) return list(names) diff --git a/jedi/evaluate/imports.py b/jedi/evaluate/imports.py index 489fa085..0d6a4661 100644 --- a/jedi/evaluate/imports.py +++ b/jedi/evaluate/imports.py @@ -65,9 +65,10 @@ def infer_import(context, tree_name, is_goto=False): if from_import_name is not None: types = unite( t.py__getattribute__( - from_import_name.value if isinstance(from_import_name, tree.Name) else from_import_name, + from_import_name, name_context=context, - is_goto=is_goto + is_goto=is_goto, + analysis_errors=False ) for t in types ) diff --git a/pytest.ini b/pytest.ini index 6fafa946..323d03ed 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,7 +2,7 @@ addopts = --doctest-modules # 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 # fine as long as we are using `clean_jedi_cache` as a session scoped diff --git a/test/test_api/simple_import/__init__.py b/test/test_api/simple_import/__init__.py new file mode 100644 index 00000000..3a03a829 --- /dev/null +++ b/test/test_api/simple_import/__init__.py @@ -0,0 +1,5 @@ +from simple_import import module + + +def in_function(): + from simple_import import module2 diff --git a/test/test_api/simple_import/module.py b/test/test_api/simple_import/module.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test_api/simple_import/module2.py b/test/test_api/simple_import/module2.py new file mode 100644 index 00000000..e69de29b diff --git a/test/test_api/test_api.py b/test/test_api/test_api.py index a096e649..5674fd87 100644 --- a/test/test_api/test_api.py +++ b/test/test_api/test_api.py @@ -2,6 +2,7 @@ Test all things related to the ``jedi.api`` module. """ +import os from textwrap import dedent from jedi import api @@ -205,3 +206,16 @@ def test_goto_assignments_follow_imports(): definition, = script.goto_assignments() 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'))