diff --git a/tests/rules/test_quoted_strings.py b/tests/rules/test_quoted_strings.py index 1bcb6f8f..31b392a9 100644 --- a/tests/rules/test_quoted_strings.py +++ b/tests/rules/test_quoted_strings.py @@ -14,6 +14,7 @@ # along with this program. If not, see . from tests.common import RuleTestCase +import yaml.reader from yamllint import config @@ -475,6 +476,30 @@ def test_only_when_needed_extras(self): '- "foo bar"\n', conf, problem1=(3, 3), problem2=(7, 3), problem3=(11, 3)) + def test_only_when_needed_special_characters(self): + conf = 'quoted-strings: {required: only-when-needed}\n' + self.check('---\n' + 'k1: "\\u001b"\n', + conf) + self.check('---\n' + "k1: '\\u001b'\n", + conf, problem=(2, 5)) + self.check('---\n' + 'k1: \\u001b\n', + conf) + self.assertRaises(yaml.reader.ReaderError, self.check, + '---\n' + 'k1: "\u001b"\n', + conf) + self.assertRaises(yaml.reader.ReaderError, self.check, + '---\n' + "k1: '\u001b'\n", + conf) + self.assertRaises(yaml.reader.ReaderError, self.check, + '---\n' + 'k1: \u001b\n', + conf) + def test_octal_values(self): conf = 'quoted-strings: {required: true}\n' diff --git a/yamllint/rules/quoted_strings.py b/yamllint/rules/quoted_strings.py index b38f6edf..a013dc6b 100644 --- a/yamllint/rules/quoted_strings.py +++ b/yamllint/rules/quoted_strings.py @@ -204,11 +204,19 @@ def _quote_match(quote_type, token_style): (quote_type == 'double' and token_style == '"')) -def _quotes_are_needed(string, is_inside_a_flow): +def _quotes_are_needed(string, style, is_inside_a_flow): # Quotes needed on strings containing flow tokens if is_inside_a_flow and set(string) & {',', '[', ']', '{', '}'}: return True + if style == '"': + try: + yaml.reader.Reader('').check_printable('key: ' + string) + except yaml.reader.ReaderError: + # Special characters in a double-quoted string + # are assumed to have been backslash-escaped + return True + loader = yaml.BaseLoader('key: ' + string) # Remove the 5 first tokens corresponding to 'key: ' (StreamStartToken, # BlockMappingStartToken, KeyToken, ScalarToken(value=key), ValueToken) @@ -299,6 +307,7 @@ def check(conf, token, prev, next, nextnext, context): # Quotes are not strictly needed here if (token.style and tag == DEFAULT_SCALAR_TAG and token.value and not _quotes_are_needed(token.value, + token.style, context['flow_nest_count'] > 0)): is_extra_required = any(re.search(r, token.value) for r in conf['extra-required'])