diff --git a/jedi/api/completion.py b/jedi/api/completion.py index 31d0a479..74faf5fd 100644 --- a/jedi/api/completion.py +++ b/jedi/api/completion.py @@ -85,7 +85,7 @@ def get_flow_scope_node(module_node, position): class Completion: - def __init__(self, evaluator, module, code_lines, position, call_signatures_method): + def __init__(self, evaluator, module, code_lines, position, call_signatures_callback): self._evaluator = evaluator self._module_context = module self._module_node = module.tree_node @@ -96,14 +96,15 @@ class Completion: # The actual cursor position is not what we need to calculate # everything. We want the start of the name we're on. self._position = position[0], position[1] - len(self._like_name) - self._call_signatures_method = call_signatures_method + self._call_signatures_callback = call_signatures_callback def completions(self): leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True) string, start_leaf = _extract_string_while_in_string(leaf, self._position) if string is not None: completions = list(file_name_completions( - self._evaluator, self._module_context, start_leaf, string, self._like_name + self._evaluator, self._module_context, start_leaf, string, + self._like_name, self._call_signatures_callback )) if completions: return completions @@ -223,7 +224,7 @@ class Completion: completion_names += self._get_class_context_completions(is_function=False) if 'trailer' in nonterminals: - call_signatures = self._call_signatures_method() + call_signatures = self._call_signatures_callback() completion_names += get_call_signature_param_names(call_signatures) return completion_names diff --git a/jedi/api/file_name.py b/jedi/api/file_name.py index 4949c79e..3e21de49 100644 --- a/jedi/api/file_name.py +++ b/jedi/api/file_name.py @@ -5,10 +5,10 @@ from jedi.evaluate.names import AbstractArbitraryName from jedi.api import classes from jedi.evaluate.helpers import get_str_or_none from jedi.parser_utils import get_string_quote -from jedi.evaluate.syntax_tree import eval_trailer -def file_name_completions(evaluator, module_context, start_leaf, string, like_name): +def file_name_completions(evaluator, module_context, start_leaf, string, + like_name, call_signatures_callback): # First we want to find out what can actually be changed as a name. like_name_length = len(os.path.basename(string) + like_name) @@ -22,7 +22,14 @@ def file_name_completions(evaluator, module_context, start_leaf, string, like_na must_start_with = os.path.basename(string) + like_name string = os.path.dirname(string) - string, is_in_os_path_join = _maybe_add_os_path_join(module_context, start_leaf, string) + sigs = call_signatures_callback() + is_in_os_path_join = sigs and all(s.full_name == 'os.path.join' for s in sigs) + if is_in_os_path_join: + to_be_added = _add_os_path_join(module_context, start_leaf, sigs[0].bracket_start) + if to_be_added is None: + is_in_os_path_join = False + else: + string = to_be_added + string base_path = os.path.join(evaluator.project._path, string) try: listed = os.listdir(base_path) @@ -92,29 +99,15 @@ class FileName(AbstractArbitraryName): is_context_name = False -def _maybe_add_os_path_join(module_context, start_leaf, string): - def check_for_power(atom_or_trailer, nodes): - trailers = [] - if atom_or_trailer.type not in ('name', 'atom', 'trailer'): - return string, False - while atom_or_trailer.type == 'trailer': - trailers.insert(0, atom_or_trailer) - atom_or_trailer = atom_or_trailer.get_previous_sibling() +def _add_os_path_join(module_context, start_leaf, bracket_start): + def check(maybe_bracket, nodes): + if maybe_bracket.start_pos != bracket_start: + return None - context = module_context.create_context(atom_or_trailer) - contexts = context.eval_node(atom_or_trailer) - for trailer in trailers: - contexts = eval_trailer(context, contexts, trailer) - if any([c.name.get_qualified_names(include_module_names=True) - != ('os', 'path', 'join') for c in contexts]): - return string, False - return _add_strings(context, nodes, add_slash=True) or string, True - - def check_trailer(trailer, arglist_nodes): - if trailer.children[0] == '(': - atom_or_trailer = trailer.get_previous_sibling() - return check_for_power(atom_or_trailer, arglist_nodes) - return string, False + if not nodes: + return '' + context = module_context.create_context(nodes[0]) + return _add_strings(context, nodes, add_slash=True) or '' # Maybe an arglist or some weird error case. Therefore checked below. arglist = start_leaf.parent @@ -132,25 +125,20 @@ def _maybe_add_os_path_join(module_context, start_leaf, string): else: arglist_nodes = [] - if error_node.children[index + 1] == '(': - atom_or_trailer = error_node.children[index] - return check_for_power(atom_or_trailer, arglist_nodes[::2]) + return check(error_node.children[index + 1], arglist_nodes[::2]) elif arglist.type == 'arglist': trailer = arglist.parent if trailer.type == 'error_node': trailer_index = trailer.children.index(arglist) assert trailer_index >= 2 assert trailer.children[trailer_index - 1] == '(' - atom_or_trailer = trailer.children[trailer_index - 2] - return check_for_power(atom_or_trailer, arglist_nodes[::2]) + return check(trailer.children[trailer_index - 1], arglist_nodes[::2]) elif trailer.type == 'trailer': - return check_trailer(trailer, arglist_nodes[::2]) + return check(trailer.children[0], arglist_nodes[::2]) elif arglist.type == 'trailer': - return check_trailer(arglist, []) + return check(arglist.children[0], []) elif arglist.type == 'error_node': # Stuff like `join(""` - if len(arglist_nodes) >= 2 and arglist_nodes[-1] == '(': - atom_or_trailer = arglist_nodes[-2] - return check_for_power(atom_or_trailer, []) + return check(arglist_nodes[-1], []) - return string, False + return None diff --git a/test/test_api/test_completion.py b/test/test_api/test_completion.py index e4d68e5e..95803c53 100644 --- a/test/test_api/test_completion.py +++ b/test/test_api/test_completion.py @@ -242,6 +242,11 @@ os_path = 'from os.path import *\n' (f2, 'import os.path as p as p\np.join(p.dirname(__file__), "completi', None, ['on"']), (f2, 'from os.path import dirname, join as j\nj(dirname(__file__), "completi', None, ['on"']), + + # Trying to break it + (f2, os_path + 'join(["tes', 10, ['t' + s]), + (f2, os_path + 'join(["tes"]', 10, ['t' + s]), + (f2, os_path + 'join(["tes"])', 10, ['t' + s]), ] ) def test_file_path_completions(Script, file, code, column, expected):