diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index 261848f4..5bb637a1 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -22,11 +22,11 @@ from jedi import settings from jedi import cache from jedi.api import classes from jedi.api import interpreter -from jedi.api import usages from jedi.api import helpers from jedi.api.completion import Completion from jedi.evaluate import Evaluator from jedi.evaluate import imports +from jedi.evaluate import usages from jedi.evaluate.project import Project from jedi.evaluate.arguments import try_iter_content from jedi.evaluate.helpers import get_module_names, evaluate_call_of_leaf @@ -205,7 +205,12 @@ class Script(object): else: yield name - names = self._goto() + tree_name = self._get_module_node().get_name_of_position(self._pos) + if tree_name is None: + return [] + context = self._evaluator.create_context(self._get_module(), tree_name) + names = list(self._evaluator.goto(context, tree_name)) + if follow_imports: def check(name): if isinstance(name, ModuleName): @@ -220,16 +225,6 @@ class Script(object): defs = [classes.Definition(self._evaluator, d) for d in set(names)] return helpers.sorted_definitions(defs) - def _goto(self): - """ - Used for goto_assignments and usages. - """ - name = self._get_module_node().get_name_of_position(self._pos) - if name is None: - return [] - context = self._evaluator.create_context(self._get_module(), name) - return list(self._evaluator.goto(context, name)) - def usages(self, additional_module_paths=()): """ Return :class:`classes.Definition` objects, which contain all @@ -241,31 +236,15 @@ class Script(object): :rtype: list of :class:`classes.Definition` """ - module_node = self._get_module_node() - user_stmt = get_statement_of_position(module_node, self._pos) - definition_names = self._goto() - if not definition_names and isinstance(user_stmt, tree.Import): - # For not defined imports (goto doesn't find something, we take - # the name as a definition. This is enough, because every name - # points to it. - name = user_stmt.get_name_of_position(self._pos) - if name is None: - # Must be syntax - return [] - definition_names = [TreeNameDefinition(self._get_module(), name)] - - if not definition_names: - # Without a definition for a name we cannot find references. + tree_name = self._get_module_node().get_name_of_position(self._pos) + if tree_name is None: + # Must be syntax return [] - definition_names = usages.resolve_potential_imports(self._evaluator, - definition_names) + names = usages.usages(self._evaluator, self._get_module(), tree_name) - modules = set([d.get_root_context() for d in definition_names]) - modules.add(self._get_module()) - definitions = usages.usages(self._evaluator, definition_names, modules) - - return helpers.sorted_definitions(set(definitions)) + definitions = [classes.Definition(self._evaluator, n) for n in names] + return helpers.sorted_definitions(definitions) def call_signatures(self): """ diff --git a/jedi/api/usages.py b/jedi/api/usages.py deleted file mode 100644 index 8b9af71c..00000000 --- a/jedi/api/usages.py +++ /dev/null @@ -1,75 +0,0 @@ -from jedi.api import classes -from parso.python import tree -from jedi.evaluate import imports -from jedi.evaluate.filters import TreeNameDefinition -from jedi.evaluate.context import ModuleContext - - -def compare_contexts(c1, c2): - return c1 == c2 or (c1[1] == c2[1] and c1[0].tree_node == c2[0].tree_node) - - -def usages(evaluator, definition_names, mods): - """ - :param definitions: list of Name - """ - def resolve_names(definition_names): - for name in definition_names: - if name.api_type == 'module': - found = False - for context in name.infer(): - if isinstance(context, ModuleContext): - found = True - yield context.name - if not found: - yield name - else: - yield name - - def compare_array(definition_names): - """ `definitions` are being compared by module/start_pos, because - sometimes the id's of the objects change (e.g. executions). - """ - return [ - (name.get_root_context(), name.start_pos) - for name in resolve_names(definition_names) - ] - - search_name = list(definition_names)[0].string_name - compare_definitions = compare_array(definition_names) - mods = mods | set([d.get_root_context() for d in definition_names]) - definition_names = set(resolve_names(definition_names)) - for m in imports.get_modules_containing_name(evaluator, mods, search_name): - if isinstance(m, ModuleContext): - for name_node in m.tree_node.get_used_names().get(search_name, []): - context = evaluator.create_context(m, name_node) - result = evaluator.goto(context, name_node) - if any(compare_contexts(c1, c2) - for c1 in compare_array(result) - for c2 in compare_definitions): - name = TreeNameDefinition(context, name_node) - definition_names.add(name) - # Previous definitions might be imports, so include them - # (because goto might return that import name). - compare_definitions += compare_array([name]) - else: - # compiled objects - definition_names.add(m.name) - - return [classes.Definition(evaluator, n) for n in definition_names] - - -def resolve_potential_imports(evaluator, definitions): - """ Adds the modules of the imports """ - new = set() - for d in definitions: - if isinstance(d, TreeNameDefinition): - imp_or_stmt = d.tree_name.get_definition() - if isinstance(imp_or_stmt, tree.Import): - new |= resolve_potential_imports( - evaluator, - set(imports.infer_import( - d.parent_context, d.tree_name, is_goto=True - )) - ) - return set(definitions) | new diff --git a/jedi/evaluate/usages.py b/jedi/evaluate/usages.py new file mode 100644 index 00000000..75ff1e5d --- /dev/null +++ b/jedi/evaluate/usages.py @@ -0,0 +1,50 @@ +from jedi.evaluate import imports +from jedi.evaluate.filters import TreeNameDefinition +from jedi.evaluate.context import ModuleContext + + +def usages(evaluator, module_context, tree_name): + """ + :param definitions: list of Name + """ + def resolve_names(definition_names, avoid_names=()): + for name in definition_names: + if name in avoid_names: + # Avoiding recursions here, because goto on a module name lands + # on the same module. + continue + + if not isinstance(name, imports.SubModuleName): + # SubModuleNames are not actually existing names but created + # names when importing something like `import foo.bar.baz`. + yield name + + if name.api_type == 'module': + for name in resolve_names(name.goto(), definition_names): + yield name + + def find_names(module_context, tree_name): + context = evaluator.create_context(module_context, tree_name) + name = TreeNameDefinition(context, tree_name) + found_names = set(name.goto()) + found_names.add(name) + return dcti(resolve_names(found_names)) + + def dcti(names): + return dict( + (n if n.tree_name is None else n.tree_name, n) + for n in names + ) + + search_name = tree_name.value + found_names = find_names(module_context, tree_name) + modules = set(d.get_root_context() for d in found_names.values()) + modules = set(m for m in modules if isinstance(m, ModuleContext)) + for m in imports.get_modules_containing_name(evaluator, modules, search_name): + for name_leaf in m.tree_node.get_used_names().get(search_name, []): + new = find_names(m, name_leaf) + for tree_name in new: + if tree_name in found_names: + found_names.update(new) + break + return found_names.values() diff --git a/test/completion/usages.py b/test/completion/usages.py index 55891b7f..88d909c0 100644 --- a/test/completion/usages.py +++ b/test/completion/usages.py @@ -188,7 +188,7 @@ class TestClass(Super): self.base_class #< (-20,13), (0,13) self.base_var - #< + #< (0, 18), TestClass.base_var @@ -242,7 +242,7 @@ def f(**kwargs): # No result # ----------------- if isinstance(j, int): - #< + #< (0, 4), j # ----------------- diff --git a/test/test_api/test_usages.py b/test/test_api/test_usages.py index e1852aa0..1325e412 100644 --- a/test/test_api/test_usages.py +++ b/test/test_api/test_usages.py @@ -1,48 +1,6 @@ import jedi -import os.path + def test_import_usage(): s = jedi.Script("from .. import foo", line=1, column=18, path="foo.py") assert [usage.line for usage in s.usages()] == [1] - - -def usages_with_additional_modules(script, additional_modules): - """ - Stripped down version of `jedi.api.Script.usages` that can take an - explicit set of additional modules. For use with `test_cross_module_usages`. - """ - - definition_names = jedi.api.usages.resolve_potential_imports(script._evaluator, - script._goto()) - modules = set([d.get_root_context() for d in definition_names]) - modules.add(script._get_module()) - for additional_module in additional_modules: - modules.add(additional_module._name.get_root_context()) - return jedi.api.usages.usages(script._evaluator, definition_names, modules) - - -def test_cross_module_usages(): - """ - This tests finding of usages between different modules. In - `jedi.api.usages.compare_contexts`, this exercises the case where - `c1 != c2`. This tests whether `jedi` can find the usage of - `import_tree_for_usages.b.bar` in `import_tree_for_usages.a` - """ - - def usages_script(): - source = 'import import_tree_for_usages.b; import_tree_for_usages.b.bar' - return jedi.api.Script(source=source, line=1, column=len(source), - sys_path=[os.path.dirname(os.path.abspath(__file__))]) - - def module_script(): - source = 'import import_tree_for_usages.a; import_tree_for_usages.a' - return jedi.api.Script(source=source, line=1, column=len(source), - sys_path=[os.path.dirname(os.path.abspath(__file__))]) - - module = module_script().goto_definitions()[0] - module_definition = module._name.get_root_context() - usages_list = usages_with_additional_modules(usages_script(), set([module])) - - assert any([elt for elt in usages_list if elt.module_name == 'a']), ( - "Did not find cross-module usage of :func:`b.bar` in :mod:`a`. Usages list was: {}" - .format(usages_list))