quoted-strings: Add options extra-required and extra-allowed
Add ability to: - require strings to be quoted if they match a pattern (PCRE regex) - allow quoted strings if they match a pattern, while `require: only-when-needed` is enforced. Co-Authored-By: Leo Feyer (https://github.com/adrienverge/yamllint/pull/246)
This commit is contained in:
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
from tests.common import RuleTestCase
|
from tests.common import RuleTestCase
|
||||||
|
|
||||||
|
from yamllint import config
|
||||||
|
|
||||||
|
|
||||||
class QuotedTestCase(RuleTestCase):
|
class QuotedTestCase(RuleTestCase):
|
||||||
rule_id = 'quoted-strings'
|
rule_id = 'quoted-strings'
|
||||||
@@ -357,3 +359,79 @@ class QuotedTestCase(RuleTestCase):
|
|||||||
'k5: :wq\n'
|
'k5: :wq\n'
|
||||||
'k6: ":wq"\n', # fails
|
'k6: ":wq"\n', # fails
|
||||||
conf, problem1=(3, 5), problem2=(5, 5), problem3=(7, 5))
|
conf, problem1=(3, 5), problem2=(5, 5), problem3=(7, 5))
|
||||||
|
|
||||||
|
def test_only_when_needed_extras(self):
|
||||||
|
conf = ('quoted-strings:\n'
|
||||||
|
' required: true\n'
|
||||||
|
' extra-allowed: [^http://]\n')
|
||||||
|
self.assertRaises(config.YamlLintConfigError, self.check, '', conf)
|
||||||
|
|
||||||
|
conf = ('quoted-strings:\n'
|
||||||
|
' required: true\n'
|
||||||
|
' extra-required: [^http://]\n')
|
||||||
|
self.assertRaises(config.YamlLintConfigError, self.check, '', conf)
|
||||||
|
|
||||||
|
conf = ('quoted-strings:\n'
|
||||||
|
' required: false\n'
|
||||||
|
' extra-allowed: [^http://]\n')
|
||||||
|
self.assertRaises(config.YamlLintConfigError, self.check, '', conf)
|
||||||
|
|
||||||
|
conf = ('quoted-strings:\n'
|
||||||
|
' required: true\n')
|
||||||
|
self.check('---\n'
|
||||||
|
'- 123\n'
|
||||||
|
'- "123"\n'
|
||||||
|
'- localhost\n' # fails
|
||||||
|
'- "localhost"\n'
|
||||||
|
'- http://localhost\n' # fails
|
||||||
|
'- "http://localhost"\n'
|
||||||
|
'- ftp://localhost\n' # fails
|
||||||
|
'- "ftp://localhost"\n',
|
||||||
|
conf, problem1=(4, 3), problem2=(6, 3), problem3=(8, 3))
|
||||||
|
|
||||||
|
conf = ('quoted-strings:\n'
|
||||||
|
' required: only-when-needed\n'
|
||||||
|
' extra-allowed: [^ftp://]\n'
|
||||||
|
' extra-required: [^http://]\n')
|
||||||
|
self.check('---\n'
|
||||||
|
'- 123\n'
|
||||||
|
'- "123"\n'
|
||||||
|
'- localhost\n'
|
||||||
|
'- "localhost"\n' # fails
|
||||||
|
'- http://localhost\n' # fails
|
||||||
|
'- "http://localhost"\n'
|
||||||
|
'- ftp://localhost\n'
|
||||||
|
'- "ftp://localhost"\n',
|
||||||
|
conf, problem1=(5, 3), problem2=(6, 3))
|
||||||
|
|
||||||
|
conf = ('quoted-strings:\n'
|
||||||
|
' required: false\n'
|
||||||
|
' extra-required: [^http://, ^ftp://]\n')
|
||||||
|
self.check('---\n'
|
||||||
|
'- 123\n'
|
||||||
|
'- "123"\n'
|
||||||
|
'- localhost\n'
|
||||||
|
'- "localhost"\n'
|
||||||
|
'- http://localhost\n' # fails
|
||||||
|
'- "http://localhost"\n'
|
||||||
|
'- ftp://localhost\n' # fails
|
||||||
|
'- "ftp://localhost"\n',
|
||||||
|
conf, problem1=(6, 3), problem2=(8, 3))
|
||||||
|
|
||||||
|
conf = ('quoted-strings:\n'
|
||||||
|
' required: only-when-needed\n'
|
||||||
|
' extra-allowed: [^ftp://, ";$", " "]\n')
|
||||||
|
self.check('---\n'
|
||||||
|
'- localhost\n'
|
||||||
|
'- "localhost"\n' # fails
|
||||||
|
'- ftp://localhost\n'
|
||||||
|
'- "ftp://localhost"\n'
|
||||||
|
'- i=i+1\n'
|
||||||
|
'- "i=i+1"\n' # fails
|
||||||
|
'- i=i+2;\n'
|
||||||
|
'- "i=i+2;"\n'
|
||||||
|
'- foo\n'
|
||||||
|
'- "foo"\n' # fails
|
||||||
|
'- foo bar\n'
|
||||||
|
'- "foo bar"\n',
|
||||||
|
conf, problem1=(3, 3), problem2=(7, 3), problem3=(11, 3))
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ used.
|
|||||||
* ``required`` defines whether using quotes in string values is required
|
* ``required`` defines whether using quotes in string values is required
|
||||||
(``true``, default) or not (``false``), or only allowed when really needed
|
(``true``, default) or not (``false``), or only allowed when really needed
|
||||||
(``only-when-needed``).
|
(``only-when-needed``).
|
||||||
|
* ``extra-required`` is a list of PCRE regexes to force string values to be
|
||||||
|
quoted, if they match any regex. This option can only be used with
|
||||||
|
``required: false`` and ``required: only-when-needed``.
|
||||||
|
* ``extra-allowed`` is a list of PCRE regexes to allow quoted string values,
|
||||||
|
even if ``required: only-when-needed`` is set.
|
||||||
|
|
||||||
**Note**: Multi-line strings (with ``|`` or ``>``) will not be checked.
|
**Note**: Multi-line strings (with ``|`` or ``>``) will not be checked.
|
||||||
|
|
||||||
@@ -63,8 +68,44 @@ used.
|
|||||||
::
|
::
|
||||||
|
|
||||||
foo: 'bar'
|
foo: 'bar'
|
||||||
|
|
||||||
|
#. With ``quoted-strings: {required: false, extra-required: [^http://,
|
||||||
|
^ftp://]}``
|
||||||
|
|
||||||
|
the following code snippet would **PASS**:
|
||||||
|
::
|
||||||
|
|
||||||
|
- localhost
|
||||||
|
- "localhost"
|
||||||
|
- "http://localhost"
|
||||||
|
- "ftp://localhost"
|
||||||
|
|
||||||
|
the following code snippet would **FAIL**:
|
||||||
|
::
|
||||||
|
|
||||||
|
- http://localhost
|
||||||
|
- ftp://localhost
|
||||||
|
|
||||||
|
#. With ``quoted-strings: {required: only-when-needed, extra-allowed:
|
||||||
|
[^http://, ^ftp://], extra-required: [QUOTED]}``
|
||||||
|
|
||||||
|
the following code snippet would **PASS**:
|
||||||
|
::
|
||||||
|
|
||||||
|
- localhost
|
||||||
|
- "http://localhost"
|
||||||
|
- "ftp://localhost"
|
||||||
|
- "this is a string that needs to be QUOTED"
|
||||||
|
|
||||||
|
the following code snippet would **FAIL**:
|
||||||
|
::
|
||||||
|
|
||||||
|
- "localhost"
|
||||||
|
- this is a string that needs to be QUOTED
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from yamllint.linter import LintProblem
|
from yamllint.linter import LintProblem
|
||||||
@@ -72,9 +113,23 @@ from yamllint.linter import LintProblem
|
|||||||
ID = 'quoted-strings'
|
ID = 'quoted-strings'
|
||||||
TYPE = 'token'
|
TYPE = 'token'
|
||||||
CONF = {'quote-type': ('any', 'single', 'double'),
|
CONF = {'quote-type': ('any', 'single', 'double'),
|
||||||
'required': (True, False, 'only-when-needed')}
|
'required': (True, False, 'only-when-needed'),
|
||||||
|
'extra-required': [str],
|
||||||
|
'extra-allowed': [str]}
|
||||||
DEFAULT = {'quote-type': 'any',
|
DEFAULT = {'quote-type': 'any',
|
||||||
'required': True}
|
'required': True,
|
||||||
|
'extra-required': [],
|
||||||
|
'extra-allowed': []}
|
||||||
|
|
||||||
|
|
||||||
|
def VALIDATE(conf):
|
||||||
|
if conf['required'] is True and len(conf['extra-allowed']) > 0:
|
||||||
|
return 'cannot use both "required: true" and "extra-allowed"'
|
||||||
|
if conf['required'] is True and len(conf['extra-required']) > 0:
|
||||||
|
return 'cannot use both "required: true" and "extra-required"'
|
||||||
|
if conf['required'] is False and len(conf['extra-allowed']) > 0:
|
||||||
|
return 'cannot use both "required: false" and "extra-allowed"'
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_SCALAR_TAG = u'tag:yaml.org,2002:str'
|
DEFAULT_SCALAR_TAG = u'tag:yaml.org,2002:str'
|
||||||
|
|
||||||
@@ -125,36 +180,48 @@ def check(conf, token, prev, next, nextnext, context):
|
|||||||
return
|
return
|
||||||
|
|
||||||
quote_type = conf['quote-type']
|
quote_type = conf['quote-type']
|
||||||
required = conf['required']
|
|
||||||
|
|
||||||
# Completely relaxed about quotes (same as the rule being disabled)
|
|
||||||
if required is False and quote_type == 'any':
|
|
||||||
return
|
|
||||||
|
|
||||||
msg = None
|
msg = None
|
||||||
if required is True:
|
if conf['required'] is True:
|
||||||
|
|
||||||
# Quotes are mandatory and need to match config
|
# Quotes are mandatory and need to match config
|
||||||
if token.style is None or not _quote_match(quote_type, token.style):
|
if token.style is None or not _quote_match(quote_type, token.style):
|
||||||
msg = "string value is not quoted with %s quotes" % (quote_type)
|
msg = "string value is not quoted with %s quotes" % quote_type
|
||||||
|
|
||||||
elif required is False:
|
elif conf['required'] is False:
|
||||||
|
|
||||||
# Quotes are not mandatory but when used need to match config
|
# Quotes are not mandatory but when used need to match config
|
||||||
if token.style and not _quote_match(quote_type, token.style):
|
if token.style and not _quote_match(quote_type, token.style):
|
||||||
msg = "string value is not quoted with %s quotes" % (quote_type)
|
msg = "string value is not quoted with %s quotes" % quote_type
|
||||||
|
|
||||||
elif not token.plain:
|
elif not token.style:
|
||||||
|
is_extra_required = any(re.search(r, token.value)
|
||||||
|
for r in conf['extra-required'])
|
||||||
|
if is_extra_required:
|
||||||
|
msg = "string value is not quoted"
|
||||||
|
|
||||||
# Quotes are disallowed when not needed
|
elif conf['required'] == 'only-when-needed':
|
||||||
if (tag == DEFAULT_SCALAR_TAG and token.value and
|
|
||||||
|
# Quotes are not strictly needed here
|
||||||
|
if (token.style and tag == DEFAULT_SCALAR_TAG and token.value and
|
||||||
not _quotes_are_needed(token.value)):
|
not _quotes_are_needed(token.value)):
|
||||||
|
is_extra_required = any(re.search(r, token.value)
|
||||||
|
for r in conf['extra-required'])
|
||||||
|
is_extra_allowed = any(re.search(r, token.value)
|
||||||
|
for r in conf['extra-allowed'])
|
||||||
|
if not (is_extra_required or is_extra_allowed):
|
||||||
msg = "string value is redundantly quoted with %s quotes" % (
|
msg = "string value is redundantly quoted with %s quotes" % (
|
||||||
quote_type)
|
quote_type)
|
||||||
|
|
||||||
# But when used need to match config
|
# But when used need to match config
|
||||||
elif token.style and not _quote_match(quote_type, token.style):
|
elif token.style and not _quote_match(quote_type, token.style):
|
||||||
msg = "string value is not quoted with %s quotes" % (quote_type)
|
msg = "string value is not quoted with %s quotes" % quote_type
|
||||||
|
|
||||||
|
elif not token.style:
|
||||||
|
is_extra_required = len(conf['extra-required']) and any(
|
||||||
|
re.search(r, token.value) for r in conf['extra-required'])
|
||||||
|
if is_extra_required:
|
||||||
|
msg = "string value is not quoted"
|
||||||
|
|
||||||
if msg is not None:
|
if msg is not None:
|
||||||
yield LintProblem(
|
yield LintProblem(
|
||||||
|
|||||||
Reference in New Issue
Block a user