Simplify usages. It should also work way better, now.

This commit is contained in:
Dave Halter
2017-10-08 20:13:24 +02:00
parent 4ddf7bf56d
commit bedff46735
5 changed files with 66 additions and 154 deletions

View File

@@ -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):
"""

View File

@@ -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

50
jedi/evaluate/usages.py Normal file
View File

@@ -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()

View File

@@ -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
# -----------------

View File

@@ -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))