diff --git a/jedi/inference/__init__.py b/jedi/inference/__init__.py index e8b788d3..3e5414c9 100644 --- a/jedi/inference/__init__.py +++ b/jedi/inference/__init__.py @@ -69,7 +69,6 @@ from jedi.file_io import FileIO from jedi import debug from jedi import parser_utils -from jedi.inference.utils import unite from jedi.inference import imports from jedi.inference import recursion from jedi.inference.cache import inference_state_function_cache @@ -79,8 +78,9 @@ from jedi.inference.base_value import ContextualizedNode, \ ValueSet, NO_VALUES, iterate_values from jedi.inference.value import ClassValue, FunctionValue from jedi.inference.context import CompForContext -from jedi.inference.syntax_tree import infer_trailer, infer_expr_stmt, \ +from jedi.inference.syntax_tree import infer_expr_stmt, \ infer_node, check_tuple_assignments +from jedi.inference.imports import follow_error_node_imports_if_possible from jedi.plugins import plugin_manager @@ -261,106 +261,12 @@ class InferenceState(object): if type_ in ('import_from', 'import_name'): return imports.infer_import(context, name) else: - result = self._follow_error_node_imports_if_possible(context, name) + result = follow_error_node_imports_if_possible(context, name) if result is not None: return result return helpers.infer_call_of_leaf(context, name) - def _follow_error_node_imports_if_possible(self, context, name): - error_node = tree.search_ancestor(name, 'error_node') - if error_node is not None: - # Get the first command start of a started simple_stmt. The error - # node is sometimes a small_stmt and sometimes a simple_stmt. Check - # for ; leaves that start a new statements. - start_index = 0 - for index, n in enumerate(error_node.children): - if n.start_pos > name.start_pos: - break - if n == ';': - start_index = index + 1 - nodes = error_node.children[start_index:] - first_name = nodes[0].get_first_leaf().value - - # Make it possible to infer stuff like `import foo.` or - # `from foo.bar`. - if first_name in ('from', 'import'): - is_import_from = first_name == 'from' - level, names = helpers.parse_dotted_names( - nodes, - is_import_from=is_import_from, - until_node=name, - ) - return imports.Importer(self, names, context.get_root_context(), level).follow() - return None - - def goto(self, context, name): - definition = name.get_definition(import_name_always=True) - if definition is not None: - type_ = definition.type - if type_ == 'expr_stmt': - # Only take the parent, because if it's more complicated than just - # a name it's something you can "goto" again. - is_simple_name = name.parent.type not in ('power', 'trailer') - if is_simple_name: - return [TreeNameDefinition(context, name)] - elif type_ in ('import_from', 'import_name'): - module_names = imports.goto_import(context, name) - return module_names - else: - return [TreeNameDefinition(context, name)] - else: - values = self._follow_error_node_imports_if_possible(context, name) - if values is not None: - return [value.name for value in values] - - par = name.parent - node_type = par.type - if node_type == 'argument' and par.children[1] == '=' and par.children[0] == name: - # Named param goto. - trailer = par.parent - if trailer.type == 'arglist': - trailer = trailer.parent - if trailer.type != 'classdef': - if trailer.type == 'decorator': - value_set = context.infer_node(trailer.children[1]) - else: - i = trailer.parent.children.index(trailer) - to_infer = trailer.parent.children[:i] - if to_infer[0] == 'await': - to_infer.pop(0) - value_set = context.infer_node(to_infer[0]) - for trailer in to_infer[1:]: - value_set = infer_trailer(context, value_set, trailer) - param_names = [] - for value in value_set: - for signature in value.get_signatures(): - for param_name in signature.get_param_names(): - if param_name.string_name == name.value: - param_names.append(param_name) - return param_names - elif node_type == 'dotted_name': # Is a decorator. - index = par.children.index(name) - if index > 0: - new_dotted = helpers.deep_ast_copy(par) - new_dotted.children[index - 1:] = [] - values = context.infer_node(new_dotted) - return unite( - value.goto(name, name_context=value.as_context()) - for value in values - ) - - if node_type == 'trailer' and par.children[0] == '.': - values = helpers.infer_call_of_leaf(context, name, cut_own_trailer=True) - return values.goto(name, name_context=context) - else: - stmt = tree.search_ancestor( - name, 'expr_stmt', 'lambdef' - ) or name - if stmt.type == 'lambdef': - stmt = name - return context.goto(name, position=stmt.start_pos) - def parse_and_get_code(self, code=None, path=None, encoding='utf-8', use_latest_grammar=False, file_io=None, **kwargs): if self.allow_different_encoding: diff --git a/jedi/inference/imports.py b/jedi/inference/imports.py index ce45ed5b..0292d45b 100644 --- a/jedi/inference/imports.py +++ b/jedi/inference/imports.py @@ -577,3 +577,33 @@ def get_module_contexts_containing_name(inference_state, module_contexts, name): m = check_fs(file_io, base_names) if m is not None: yield m + + +def follow_error_node_imports_if_possible(context, name): + error_node = tree.search_ancestor(name, 'error_node') + if error_node is not None: + # Get the first command start of a started simple_stmt. The error + # node is sometimes a small_stmt and sometimes a simple_stmt. Check + # for ; leaves that start a new statements. + start_index = 0 + for index, n in enumerate(error_node.children): + if n.start_pos > name.start_pos: + break + if n == ';': + start_index = index + 1 + nodes = error_node.children[start_index:] + first_name = nodes[0].get_first_leaf().value + + # Make it possible to infer stuff like `import foo.` or + # `from foo.bar`. + if first_name in ('from', 'import'): + is_import_from = first_name == 'from' + level, names = helpers.parse_dotted_names( + nodes, + is_import_from=is_import_from, + until_node=name, + ) + return Importer( + context.inference_state, names, context.get_root_context(), level).follow() + return None + diff --git a/jedi/inference/names.py b/jedi/inference/names.py index 3c34e9f9..b2a3ff99 100644 --- a/jedi/inference/names.py +++ b/jedi/inference/names.py @@ -3,9 +3,11 @@ from abc import abstractmethod from parso.tree import search_ancestor from jedi._compatibility import Parameter +from jedi.inference.utils import unite from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference import docstrings from jedi.cache import memoize_method +from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf class AbstractNameDefinition(object): @@ -106,10 +108,77 @@ class AbstractTreeName(AbstractNameDefinition): return None return parent_names + (self.tree_name.value,) - def goto(self, **kwargs): - return self.parent_context.inference_state.goto( - self.parent_context, self.tree_name, **kwargs - ) + def goto(self): + context = self.parent_context + name = self.tree_name + definition = name.get_definition(import_name_always=True) + if definition is not None: + type_ = definition.type + if type_ == 'expr_stmt': + # Only take the parent, because if it's more complicated than just + # a name it's something you can "goto" again. + is_simple_name = name.parent.type not in ('power', 'trailer') + if is_simple_name: + return [TreeNameDefinition(context, name)] + elif type_ in ('import_from', 'import_name'): + from jedi.inference.imports import goto_import + module_names = goto_import(context, name) + return module_names + else: + return [TreeNameDefinition(context, name)] + else: + from jedi.inference.imports import follow_error_node_imports_if_possible + values = follow_error_node_imports_if_possible(context, name) + if values is not None: + return [value.name for value in values] + + par = name.parent + node_type = par.type + if node_type == 'argument' and par.children[1] == '=' and par.children[0] == name: + # Named param goto. + trailer = par.parent + if trailer.type == 'arglist': + trailer = trailer.parent + if trailer.type != 'classdef': + if trailer.type == 'decorator': + value_set = context.infer_node(trailer.children[1]) + else: + i = trailer.parent.children.index(trailer) + to_infer = trailer.parent.children[:i] + if to_infer[0] == 'await': + to_infer.pop(0) + value_set = context.infer_node(to_infer[0]) + from jedi.inference.syntax_tree import infer_trailer + for trailer in to_infer[1:]: + value_set = infer_trailer(context, value_set, trailer) + param_names = [] + for value in value_set: + for signature in value.get_signatures(): + for param_name in signature.get_param_names(): + if param_name.string_name == name.value: + param_names.append(param_name) + return param_names + elif node_type == 'dotted_name': # Is a decorator. + index = par.children.index(name) + if index > 0: + new_dotted = deep_ast_copy(par) + new_dotted.children[index - 1:] = [] + values = context.infer_node(new_dotted) + return unite( + value.goto(name, name_context=value.as_context()) + for value in values + ) + + if node_type == 'trailer' and par.children[0] == '.': + values = infer_call_of_leaf(context, name, cut_own_trailer=True) + return values.goto(name, name_context=context) + else: + stmt = search_ancestor( + name, 'expr_stmt', 'lambdef' + ) or name + if stmt.type == 'lambdef': + stmt = name + return context.goto(name, position=stmt.start_pos) def is_import(self): imp = search_ancestor(self.tree_name, 'import_from', 'import_name') @@ -216,6 +285,7 @@ class TreeNameDefinition(AbstractTreeName): node = node.parent return indexes + class _ParamMixin(object): def maybe_positional_argument(self, include_star=True): options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]