Skip to content

Commit

Permalink
Goto didn't work well on imports in __init__.py files.
Browse files Browse the repository at this point in the history
Fixes #956.
  • Loading branch information
davidhalter committed Sep 11, 2017
1 parent c05f1d3 commit 619acbd
Show file tree
Hide file tree
Showing 10 changed files with 56 additions and 13 deletions.
12 changes: 9 additions & 3 deletions jedi/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,17 +223,23 @@ def goto_assignments(self, follow_imports=False):
: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:
yield name

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)
Expand Down
5 changes: 3 additions & 2 deletions jedi/evaluate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def reset_recursion_limitations(self):
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
Expand All @@ -127,7 +127,8 @@ def find_types(self, context, name_or_str, name_context, position=None,
: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)
Expand Down
6 changes: 4 additions & 2 deletions jedi/evaluate/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@ def eval_trailer(self, types, trailer):

@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)
Expand Down
20 changes: 17 additions & 3 deletions jedi/evaluate/finder.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@
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
Expand All @@ -48,6 +49,7 @@ def __init__(self, evaluator, context, name_context, name_or_str, position=None)
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):
Expand All @@ -65,7 +67,7 @@ def find(self, filters, 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
isinstance(self._name.parent.parent, tree.Param)):
if isinstance(self._name, tree.Name):
Expand Down Expand Up @@ -122,7 +124,19 @@ def filter_name(self, filters):
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)
Expand Down
5 changes: 3 additions & 2 deletions jedi/evaluate/imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
2 changes: 1 addition & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions test/test_api/simple_import/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from simple_import import module


def in_function():
from simple_import import module2
Empty file.
Empty file.
14 changes: 14 additions & 0 deletions test/test_api/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Test all things related to the ``jedi.api`` module.
"""

import os
from textwrap import dedent

from jedi import api
Expand Down Expand Up @@ -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'))

0 comments on commit 619acbd

Please sign in to comment.