Skip to content

Commit

Permalink
Add check-keys option to quoted-strings rule
Browse files Browse the repository at this point in the history
  • Loading branch information
HenryGessau committed Nov 27, 2023
1 parent 0dcf5f4 commit 16cc4d7
Show file tree
Hide file tree
Showing 3 changed files with 375 additions and 9 deletions.
337 changes: 336 additions & 1 deletion tests/rules/test_quoted_strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from yamllint import config


class QuotedTestCase(RuleTestCase):
class QuotedValuesTestCase(RuleTestCase):
rule_id = 'quoted-strings'

def test_disabled(self):
Expand Down Expand Up @@ -595,3 +595,338 @@ def test_allow_quoted_quotes(self):
"foo1: '[barbaz]'\n"
"foo2: '[bar\"baz]'\n",
conf)


class QuotedKeysTestCase(RuleTestCase):
rule_id = 'quoted-strings'

def conf(self, options):
conf_with_options = (f"{self.rule_id}:\n"
" check-keys: true\n")
for option in options:
conf_with_options += f" {option}\n"
return conf_with_options

key_strings = ('---\n'
'true: 2\n'
'123: 3\n'
'foo1: 4\n'
'"foo2": 5\n'
'"false": 6\n'
'"234": 7\n'
'\'bar\': 8\n'
'!!str generic_string: 9\n'
'!!str 456: 10\n'
'!!str "quoted_generic_string": 11\n'
'!!binary binstring: 12\n'
'!!int int_string: 13\n'
'!!bool bool_string: 14\n'
'!!bool "quoted_bool_string": 15\n'
# Sequences and mappings
'? - 16\n'
' - 17\n'
': 18\n'
'[119, 219]: 19\n'
'? a: 20\n'
' "b": 21\n'
': 22\n'
'{a: 123, "b": 223}: 23\n'
# Multiline strings
'? |\n'
' line 1\n'
' line 2\n'
': 27\n'
'? >\n'
' line 1\n'
' line 2\n'
': 31\n'
'?\n'
' line 1\n'
' line 2\n'
': 35\n'
'?\n'
' "line 1\\\n'
' line 2"\n'
': 39\n')

def test_disabled(self):
conf_disabled = f"{self.rule_id}: {{}}"
self.check(self.key_strings, conf_disabled)

def test_default(self):
# Default configuration, but with check-keys
conf_default = (f"{self.rule_id}:\n"
" check-keys: true\n")
self.check(self.key_strings, conf_default, problem1=(4, 1),
problem3=(20, 3), problem4=(23, 2), problem5=(33, 3))

def test_quote_type_any(self):
conf = self.conf(['quote-type: any'])

self.check(self.key_strings, conf,
problem1=(4, 1), problem2=(20, 3), problem3=(23, 2),
problem4=(33, 3))

def test_quote_type_single(self):
conf = self.conf(['quote-type: single'])

self.check(self.key_strings, conf,
problem1=(4, 1), problem2=(5, 1), problem3=(6, 1),
problem4=(7, 1), problem5=(20, 3), problem6=(21, 3),
problem7=(23, 2), problem8=(23, 10), problem9=(33, 3),
problem10=(37, 3))

def test_quote_type_double(self):
conf = self.conf(['quote-type: double'])

self.check(self.key_strings, conf,
problem1=(4, 1), problem2=(8, 1), problem3=(20, 3),
problem4=(23, 2), problem5=(33, 3))

def test_any_quotes_not_required(self):
conf = self.conf(['quote-type: any', 'required: false'])

self.check(self.key_strings, conf)

def test_single_quotes_not_required(self):
conf = self.conf(['quote-type: single', 'required: false'])

self.check(self.key_strings, conf,
problem1=(5, 1), problem2=(6, 1), problem3=(7, 1),
problem4=(21, 3), problem5=(23, 10), problem6=(37, 3))

def test_only_when_needed(self):
conf = self.conf(['required: only-when-needed'])

self.check(self.key_strings, conf,
problem1=(5, 1), problem2=(8, 1), problem3=(21, 3),
problem4=(23, 10), problem5=(37, 3))

def test_only_when_needed_single_quotes(self):
conf = self.conf(['quote-type: single', 'required: only-when-needed'])

self.check(self.key_strings, conf,
problem1=(5, 1), problem2=(6, 1), problem3=(7, 1),
problem4=(8, 1), problem5=(21, 3), problem6=(23, 10),
problem7=(37, 3))

def test_only_when_needed_corner_cases(self):
conf = self.conf(['required: only-when-needed'])

self.check('---\n'
'"": 2\n'
'"- item": 3\n'
'"key: value": 4\n'
'"%H:%M:%S": 5\n'
'"%wheel ALL=(ALL) NOPASSWD: ALL": 6\n'
'\'"quoted"\': 7\n'
'"\'foo\' == \'bar\'": 8\n'
'"\'Mac\' in ansible_facts.product_name": 9\n'
'\'foo # bar\': 10\n',
conf)
self.check('---\n'
'"": 2\n'
'"- item": 3\n'
'"key: value": 4\n'
'"%H:%M:%S": 5\n'
'"%wheel ALL=(ALL) NOPASSWD: ALL": 6\n'
'\'"quoted"\': 7\n'
'"\'foo\' == \'bar\'": 8\n'
'"\'Mac\' in ansible_facts.product_name": 9\n',
conf)

self.check('---\n'
'---: 2\n'
'"----": 3\n' # fails
'---------: 4\n'
'"----------": 5\n' # fails
':wq: 6\n'
'":cw": 7\n', # fails
conf, problem1=(3, 1), problem2=(5, 1), problem3=(7, 1))

def test_only_when_needed_extras(self):
conf = self.conf(['required: true', 'extra-allowed: [^http://]'])
self.assertRaises(config.YamlLintConfigError, self.check, '', conf)

conf = self.conf(['required: true', 'extra-required: [^http://]'])
self.assertRaises(config.YamlLintConfigError, self.check, '', conf)

conf = self.conf(['required: false', 'extra-allowed: [^http://]'])
self.assertRaises(config.YamlLintConfigError, self.check, '', conf)

conf = self.conf(['required: true'])
self.check('---\n'
'123: 2\n'
'"234": 3\n'
'localhost: 4\n' # fails
'"host.local": 5\n'
'http://localhost: 6\n' # fails
'"http://host.local": 7\n'
'ftp://localhost: 8\n' # fails
'"ftp://host.local": 9\n',
conf, problem1=(4, 1), problem2=(6, 1), problem3=(8, 1))

conf = self.conf(['required: only-when-needed',
'extra-allowed: [^ftp://]',
'extra-required: [^http://]'])
self.check('---\n'
'123: 2\n'
'"234": 3\n'
'localhost: 4\n'
'"host.local": 5\n' # fails
'http://localhost: 6\n' # fails
'"http://host.local": 7\n'
'ftp://localhost: 8\n'
'"ftp://host.local": 9\n',
conf, problem1=(5, 1), problem2=(6, 1))

conf = self.conf(['required: false',
'extra-required: [^http://, ^ftp://]'])
self.check('---\n'
'123: 2\n'
'"234": 3\n'
'localhost: 4\n'
'"host.local": 5\n'
'http://localhost: 6\n' # fails
'"http://host.local": 7\n'
'ftp://localhost: 8\n' # fails
'"ftp://host.local": 9\n',
conf, problem1=(6, 1), problem2=(8, 1))

conf = self.conf(['required: only-when-needed',
'extra-allowed: [^ftp://, ";$", " "]'])
self.check('---\n'
'localhost: 2\n'
'"host.local": 3\n' # fails
'ftp://localhost: 4\n'
'"ftp://host.local": 5\n'
'i=i+1: 6\n'
'"i=i+2": 7\n' # fails
'i=i+3;: 8\n'
'"i=i+4;": 9\n'
'foo1: 10\n'
'"foo2": 11\n' # fails
'foo bar1: 12\n'
'"foo bar2": 13\n',
conf, problem1=(3, 1), problem2=(7, 1), problem3=(11, 1))

def test_octal_values(self):
conf = self.conf(['required: true'])

self.check('---\n'
'100: 2\n'
'0100: 3\n'
'0o100: 4\n'
'777: 5\n'
'0777: 6\n'
'0o777: 7\n'
'800: 8\n'
'0800: 9\n' # fails
'0o800: 10\n' # fails
'"0900": 11\n'
'"0o900": 12\n',
conf,
problem1=(9, 1), problem2=(10, 1))

def test_allow_quoted_quotes(self):
conf = self.conf(['quote-type: single',
'required: false',
'allow-quoted-quotes: false'])
self.check('---\n'
'"[barbaz]": 2\n' # fails
'"[bar\'baz]": 3\n', # fails
conf, problem1=(2, 1), problem2=(3, 1))

conf = self.conf(['quote-type: single',
'required: false',
'allow-quoted-quotes: true'])
self.check('---\n'
'"[barbaz]": 2\n' # fails
'"[bar\'baz]": 3\n',
conf, problem1=(2, 1))

conf = self.conf(['quote-type: single',
'required: true',
'allow-quoted-quotes: false'])
self.check('---\n'
'"[barbaz]": 2\n' # fails
'"[bar\'baz]": 3\n', # fails
conf, problem1=(2, 1), problem2=(3, 1))

conf = self.conf(['quote-type: single',
'required: true',
'allow-quoted-quotes: true'])
self.check('---\n'
'"[barbaz]": 2\n' # fails
'"[bar\'baz]": 3\n',
conf, problem1=(2, 1))

conf = self.conf(['quote-type: single',
'required: only-when-needed',
'allow-quoted-quotes: false'])
self.check('---\n'
'"[barbaz]": 2\n' # fails
'"[bar\'baz]": 3\n', # fails
conf, problem1=(2, 1), problem2=(3, 1))

conf = self.conf(['quote-type: single',
'required: only-when-needed',
'allow-quoted-quotes: true'])
self.check('---\n'
'"[barbaz]": 2\n' # fails
'"[bar\'baz]": 3\n',
conf, problem1=(2, 1))

conf = self.conf(['quote-type: double',
'required: false',
'allow-quoted-quotes: false'])
self.check("---\n"
"'[barbaz]': 2\n" # fails
"'[bar\"baz]': 3\n", # fails
conf, problem1=(2, 1), problem2=(3, 1))

conf = self.conf(['quote-type: double',
'required: false',
'allow-quoted-quotes: true'])
self.check("---\n"
"'[barbaz]': 2\n" # fails
"'[bar\"baz]': 3\n",
conf, problem1=(2, 1))

conf = self.conf(['quote-type: double',
'required: true',
'allow-quoted-quotes: false'])
self.check("---\n"
"'[barbaz]': 2\n" # fails
"'[bar\"baz]': 3\n", # fails
conf, problem1=(2, 1), problem2=(3, 1))

conf = self.conf(['quote-type: double',
'required: true',
'allow-quoted-quotes: true'])
self.check("---\n"
"'[barbaz]': 2\n" # fails
"'[bar\"baz]': 3\n",
conf, problem1=(2, 1))

conf = self.conf(['quote-type: double',
'required: only-when-needed',
'allow-quoted-quotes: false'])
self.check("---\n"
"'[barbaz]': 2\n" # fails
"'[bar\"baz]': 3\n", # fails
conf, problem1=(2, 1), problem2=(3, 1))

conf = self.conf(['quote-type: double',
'required: only-when-needed',
'allow-quoted-quotes: true'])
self.check("---\n"
"'[barbaz]': 2\n" # fails
"'[bar\"baz]': 3\n",
conf, problem1=(2, 1))

conf = self.conf(['quote-type: any'])
self.check("---\n"
"'[barbaz]': 2\n"
"'[bar\"baz]': 3\n",
conf)
4 changes: 4 additions & 0 deletions yamllint/rules/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,7 @@ def is_explicit_key(token):
# : v
return (token.start_mark.pointer < token.end_mark.pointer and
token.start_mark.buffer[token.start_mark.pointer] == '?')


def is_key(token):
return token and isinstance(token, yaml.KeyToken)
Loading

0 comments on commit 16cc4d7

Please sign in to comment.