mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-07 14:34:31 +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 import cache
|
||||||
from jedi.api import classes
|
from jedi.api import classes
|
||||||
from jedi.api import interpreter
|
from jedi.api import interpreter
|
||||||
from jedi.api import usages
|
|
||||||
from jedi.api import helpers
|
from jedi.api import helpers
|
||||||
from jedi.api.completion import Completion
|
from jedi.api.completion import Completion
|
||||||
from jedi.evaluate import Evaluator
|
from jedi.evaluate import Evaluator
|
||||||
from jedi.evaluate import imports
|
from jedi.evaluate import imports
|
||||||
|
from jedi.evaluate import usages
|
||||||
from jedi.evaluate.project import Project
|
from jedi.evaluate.project import Project
|
||||||
from jedi.evaluate.arguments import try_iter_content
|
from jedi.evaluate.arguments import try_iter_content
|
||||||
from jedi.evaluate.helpers import get_module_names, evaluate_call_of_leaf
|
from jedi.evaluate.helpers import get_module_names, evaluate_call_of_leaf
|
||||||
@@ -205,7 +205,12 @@ class Script(object):
|
|||||||
else:
|
else:
|
||||||
yield name
|
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:
|
if follow_imports:
|
||||||
def check(name):
|
def check(name):
|
||||||
if isinstance(name, ModuleName):
|
if isinstance(name, ModuleName):
|
||||||
@@ -220,16 +225,6 @@ class Script(object):
|
|||||||
defs = [classes.Definition(self._evaluator, d) for d in set(names)]
|
defs = [classes.Definition(self._evaluator, d) for d in set(names)]
|
||||||
return helpers.sorted_definitions(defs)
|
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=()):
|
def usages(self, additional_module_paths=()):
|
||||||
"""
|
"""
|
||||||
Return :class:`classes.Definition` objects, which contain all
|
Return :class:`classes.Definition` objects, which contain all
|
||||||
@@ -241,31 +236,15 @@ class Script(object):
|
|||||||
|
|
||||||
:rtype: list of :class:`classes.Definition`
|
:rtype: list of :class:`classes.Definition`
|
||||||
"""
|
"""
|
||||||
module_node = self._get_module_node()
|
tree_name = self._get_module_node().get_name_of_position(self._pos)
|
||||||
user_stmt = get_statement_of_position(module_node, self._pos)
|
if tree_name is None:
|
||||||
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
|
# Must be syntax
|
||||||
return []
|
return []
|
||||||
definition_names = [TreeNameDefinition(self._get_module(), name)]
|
|
||||||
|
|
||||||
if not definition_names:
|
names = usages.usages(self._evaluator, self._get_module(), tree_name)
|
||||||
# Without a definition for a name we cannot find references.
|
|
||||||
return []
|
|
||||||
|
|
||||||
definition_names = usages.resolve_potential_imports(self._evaluator,
|
definitions = [classes.Definition(self._evaluator, n) for n in names]
|
||||||
definition_names)
|
return helpers.sorted_definitions(definitions)
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
def call_signatures(self):
|
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
|
self.base_class
|
||||||
#< (-20,13), (0,13)
|
#< (-20,13), (0,13)
|
||||||
self.base_var
|
self.base_var
|
||||||
#<
|
#< (0, 18),
|
||||||
TestClass.base_var
|
TestClass.base_var
|
||||||
|
|
||||||
|
|
||||||
@@ -242,7 +242,7 @@ def f(**kwargs):
|
|||||||
# No result
|
# No result
|
||||||
# -----------------
|
# -----------------
|
||||||
if isinstance(j, int):
|
if isinstance(j, int):
|
||||||
#<
|
#< (0, 4),
|
||||||
j
|
j
|
||||||
|
|
||||||
# -----------------
|
# -----------------
|
||||||
|
|||||||
@@ -1,48 +1,6 @@
|
|||||||
import jedi
|
import jedi
|
||||||
import os.path
|
|
||||||
|
|
||||||
def test_import_usage():
|
def test_import_usage():
|
||||||
s = jedi.Script("from .. import foo", line=1, column=18, path="foo.py")
|
s = jedi.Script("from .. import foo", line=1, column=18, path="foo.py")
|
||||||
assert [usage.line for usage in s.usages()] == [1]
|
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