mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 14:04:26 +08:00
Simplify usages. It should also work way better, now.
This commit is contained in:
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
50
jedi/evaluate/usages.py
Normal 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()
|
||||
@@ -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
|
||||
|
||||
# -----------------
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user