Fix some more dict tests

This commit is contained in:
Dave Halter
2019-12-30 00:29:55 +01:00
parent c7296ade68
commit 7e769b87f3
5 changed files with 91 additions and 37 deletions

View File

@@ -10,7 +10,7 @@ from jedi import settings
from jedi.api import classes from jedi.api import classes
from jedi.api import helpers from jedi.api import helpers
from jedi.api import keywords from jedi.api import keywords
from jedi.api.strings import completions_for_dicts from jedi.api.strings import complete_dict
from jedi.api.file_name import complete_file_name from jedi.api.file_name import complete_file_name
from jedi.inference import imports from jedi.inference import imports
from jedi.inference.base_value import ValueSet from jedi.inference.base_value import ValueSet
@@ -105,21 +105,13 @@ class Completion:
leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True) leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
string, start_leaf = _extract_string_while_in_string(leaf, self._position) string, start_leaf = _extract_string_while_in_string(leaf, self._position)
prefixed_completions = [] prefixed_completions = complete_dict(
#if string is None: self._module_context,
#string = '' leaf,
bracket_leaf = leaf self._original_position,
#if bracket_leaf.type in ('number', 'error_leaf'): string,
#string = bracket_leaf.value fuzzy=fuzzy,
#bracket_leaf = bracket_leaf.get_previous_leaf() )
if bracket_leaf == '[':
context = self._module_context.create_context(bracket_leaf)
before_bracket_leaf = bracket_leaf.get_previous_leaf()
if before_bracket_leaf.type in ('atom', 'trailer', 'name'):
values = infer_call_of_leaf(context, before_bracket_leaf)
prefixed_completions += completions_for_dicts(
self._inference_state, values, string, fuzzy=fuzzy)
if string is not None and not prefixed_completions: if string is not None and not prefixed_completions:
prefixed_completions = list(complete_file_name( prefixed_completions = list(complete_file_name(
@@ -128,8 +120,6 @@ class Completion:
self._code_lines, self._original_position, self._code_lines, self._original_position,
fuzzy fuzzy
)) ))
if prefixed_completions:
return prefixed_completions
if string is not None: if string is not None:
return prefixed_completions return prefixed_completions

View File

@@ -7,9 +7,13 @@ It however does the same for numbers. The difference between string completions
and other completions is mostly that this module doesn't return defined and other completions is mostly that this module doesn't return defined
names in a module, but pretty much an arbitrary string. names in a module, but pretty much an arbitrary string.
""" """
import re
from jedi._compatibility import unicode
from jedi.inference.names import AbstractArbitraryName from jedi.inference.names import AbstractArbitraryName
from jedi.inference.helpers import infer_call_of_leaf
from jedi.api.classes import Completion from jedi.api.classes import Completion
from jedi.parser_utils import get_string_quote from jedi.parser_utils import cut_value_at_position
_sentinel = object() _sentinel = object()
@@ -19,11 +23,46 @@ class StringName(AbstractArbitraryName):
is_value_name = False is_value_name = False
def completions_for_dicts(inference_state, dicts, literal_string, fuzzy): def complete_dict(module_context, leaf, position, string, fuzzy):
for dict_key in sorted(_get_python_keys(dicts)): if string is None:
dict_key_str = repr(dict_key) string = ''
bracket_leaf = leaf
end_quote = ''
if bracket_leaf.type in ('number', 'error_leaf'):
string = cut_value_at_position(bracket_leaf, position)
if bracket_leaf.end_pos > position:
end_quote = _get_string_quote(string) or ''
if end_quote:
ending = cut_value_at_position(
bracket_leaf,
(position[0], position[1] + len(end_quote))
)
if not ending.endswith(end_quote):
end_quote = ''
bracket_leaf = bracket_leaf.get_previous_leaf()
if bracket_leaf == '[':
context = module_context.create_context(bracket_leaf)
before_bracket_leaf = bracket_leaf.get_previous_leaf()
if before_bracket_leaf.type in ('atom', 'trailer', 'name'):
values = infer_call_of_leaf(context, before_bracket_leaf)
return list(_completions_for_dicts(
module_context.inference_state,
values,
'' if string is None else string,
end_quote,
fuzzy=fuzzy,
))
return []
def _completions_for_dicts(inference_state, dicts, literal_string, end_quote, fuzzy):
for dict_key in sorted(_get_python_keys(dicts), key=lambda x: repr(x)):
dict_key_str = _create_repr_string(literal_string, dict_key)
if dict_key_str.startswith(literal_string): if dict_key_str.startswith(literal_string):
name = StringName(inference_state, dict_key_str[len(literal_string):]) n = dict_key_str[len(literal_string):-len(end_quote) or None]
name = StringName(inference_state, n)
yield Completion( yield Completion(
inference_state, inference_state,
name, name,
@@ -33,6 +72,17 @@ def completions_for_dicts(inference_state, dicts, literal_string, fuzzy):
) )
def _create_repr_string(literal_string, dict_key):
if not isinstance(dict_key, (unicode, bytes)) or not literal_string:
return repr(dict_key)
r = repr(dict_key)
prefix, quote = _get_string_prefix_and_quote(literal_string)
if quote == r[0]:
return prefix + r
return prefix + quote + r[1:-1] + quote
def _get_python_keys(dicts): def _get_python_keys(dicts):
for dct in dicts: for dct in dicts:
if dct.array_type == 'dict': if dct.array_type == 'dict':
@@ -42,9 +92,20 @@ def _get_python_keys(dicts):
yield dict_key yield dict_key
def _get_string_prefix_and_quote(string):
match = re.match(r'(\w*)("""|\'{3}|"|\')', string)
if match is None:
return None, None
return match.group(1), match.group(2)
def _get_string_quote(string):
return _get_string_prefix_and_quote(string)[1]
def get_quote_ending(start_leaf, code_lines, position): def get_quote_ending(start_leaf, code_lines, position):
if start_leaf.type == 'string': if start_leaf.type == 'string':
quote = get_string_quote(start_leaf) quote = _get_string_quote(start_leaf)
else: else:
assert start_leaf.type == 'error_leaf' assert start_leaf.type == 'error_leaf'
quote = start_leaf.value quote = start_leaf.value

View File

@@ -354,6 +354,9 @@ class TreeInstance(_BaseTreeInstance):
def get_annotated_class_object(self): def get_annotated_class_object(self):
return self._get_annotated_class_object() or self.class_value return self._get_annotated_class_object() or self.class_value
def get_key_values(self):
return NO_VALUES
def py__simple_getitem__(self, index): def py__simple_getitem__(self, index):
if self.array_type == 'dict': if self.array_type == 'dict':
# Logic for dict({'foo': bar}) and dict(foo=bar) # Logic for dict({'foo': bar}) and dict(foo=bar)

View File

@@ -293,10 +293,6 @@ def cut_value_at_position(leaf, position):
return ''.join(lines) return ''.join(lines)
def get_string_quote(leaf):
return re.match(r'\w*("""|\'{3}|"|\')', leaf.value).group(1)
def _function_is_x_method(method_name): def _function_is_x_method(method_name):
def wrapper(function_node): def wrapper(function_node):
""" """

View File

@@ -175,7 +175,7 @@ current_dirname = os.path.basename(dirname(dirname(dirname(__file__))))
@pytest.mark.parametrize( @pytest.mark.parametrize(
'file, code, column, expected', [ 'file, code, column, expected', [
# General tests / relative paths # General tests / relative paths
(None, '"comp', None, ['ile', 'lex']), # No files like comp (None, '"comp', None, []), # No files like comp
(None, '"test', None, [s]), (None, '"test', None, [s]),
(None, '"test', 4, ['t' + s]), (None, '"test', 4, ['t' + s]),
('example.py', '"test%scomp' % s, None, ['letion' + s]), ('example.py', '"test%scomp' % s, None, ['letion' + s]),
@@ -273,8 +273,7 @@ def test_file_path_completions(Script, file, code, column, expected):
assert [c.complete for c in comps] == expected assert [c.complete for c in comps] == expected
@pytest.mark.parametrize( _dict_keys_completion_tests = [
'added_code, column, expected', [
('ints[', 5, ['1', '50', Ellipsis]), ('ints[', 5, ['1', '50', Ellipsis]),
('ints[]', 5, ['1', '50', Ellipsis]), ('ints[]', 5, ['1', '50', Ellipsis]),
('ints[1]', 5, ['1', '50', Ellipsis]), ('ints[1]', 5, ['1', '50', Ellipsis]),
@@ -286,19 +285,20 @@ def test_file_path_completions(Script, file, code, column, expected):
('ints[5]', 6, ['0']), ('ints[5]', 6, ['0']),
('ints[50', 5, ['1', '50', Ellipsis]), ('ints[50', 5, ['1', '50', Ellipsis]),
('ints[5', 6, ['0']), ('ints[5', 6, ['0']),
('ints[50', 6, ['']),#TODO ['0']), ('ints[50', 6, ['0']),
('ints[50', 7, ['']), ('ints[50', 7, ['']),
('strs[', 5, ["'asdf'", "'fbar'", "'foo'", Ellipsis]), ('strs[', 5, ["'asdf'", "'fbar'", "'foo'", Ellipsis]),
('strs[]', 5, ["'asdf'", "'fbar'", "'foo'", Ellipsis]), ('strs[]', 5, ["'asdf'", "'fbar'", "'foo'", Ellipsis]),
("strs['", 6, ["asdf'", "fbar'", "foo'"]),
("strs[']", 6, ["asdf'", "fbar'", "foo'"]), ("strs[']", 6, ["asdf'", "fbar'", "foo'"]),
('strs["]', 6, ['asdf"', 'fbar"', 'foo"']), ('strs["]', 6, ['asdf"', 'fbar"', 'foo"']),
('strs["""]', 6, ['asdf', 'bar', 'foo']), ('strs["""]', 6, ['asdf', 'fbar', 'foo']),
('strs["""]', 8, ['asdf"""', 'fbar"""', 'foo"""']), ('strs["""]', 8, ['asdf"""', 'fbar"""', 'foo"""']),
('strs[b"]', 8, []), ('strs[b"]', 8, []),
('strs[r"asd', 11, ['f"']), ('strs[r"asd', 10, ['f"']),
('strs[R"asd', 11, ['f"']), ('strs[R"asd', 10, ['f"']),
('strs[f"asd', 11, ['f"']), ('strs[f"asd', 10, ['f"']),
('strs["f', 7, ['oo"]']), ('strs["f', 7, ['oo"]']),
('strs["f"', 7, ['oo']), ('strs["f"', 7, ['oo']),
@@ -310,7 +310,11 @@ def test_file_path_completions(Script, file, code, column, expected):
('casted["f', 9, ['3"', 'bar"', 'oo"']), ('casted["f', 9, ['3"', 'bar"', 'oo"']),
('casted_mod["f', 13, ['3"', 'bar"', 'oo"', 'uuu"', 'ull"']), ('casted_mod["f', 13, ['3"', 'bar"', 'oo"', 'uuu"', 'ull"']),
] ]
@pytest.mark.parametrize(
'added_code, column, expected', _dict_keys_completion_tests
) )
def test_dict_keys_completions(Script, added_code, column, expected, skip_pre_python35): def test_dict_keys_completions(Script, added_code, column, expected, skip_pre_python35):
code = dedent(r''' code = dedent(r'''