From 6e0e8d1550ed1d8eedb8bf7247e9598803702ca5 Mon Sep 17 00:00:00 2001 From: sbaudoin Date: Thu, 9 Nov 2023 23:40:41 +0100 Subject: [PATCH] hyphen: Add min-spaces-after and check-scalars options This permits checking style when people want sequences to have a fixed number of spaces greater than 1 for example. We may also use these options to identify typos where we expect everything starting with an hyphen to be a sequence. --- tests/rules/test_hyphens.py | 57 +++++++++++++++++++- yamllint/rules/hyphens.py | 104 +++++++++++++++++++++++++++++++++--- 2 files changed, 152 insertions(+), 9 deletions(-) diff --git a/tests/rules/test_hyphens.py b/tests/rules/test_hyphens.py index a0ec5775..0e341451 100644 --- a/tests/rules/test_hyphens.py +++ b/tests/rules/test_hyphens.py @@ -15,12 +15,25 @@ from tests.common import RuleTestCase +from yamllint import config + class HyphenTestCase(RuleTestCase): rule_id = 'hyphens' def test_disabled(self): - conf = 'hyphens: disable' + self.run_disabled_test('hyphens: disable') + self.run_disabled_test('hyphens:\n' + ' max-spaces-after: 5\n' + ' min-spaces-after: -1\n') + self.run_disabled_test('hyphens:\n' + ' max-spaces-after: -1\n' + ' min-spaces-after: -1\n') + self.run_disabled_test('hyphens:\n' + ' max-spaces-after: -1\n' + ' min-spaces-after: 0\n') + + def run_disabled_test(self, conf): self.check('---\n' '- elem1\n' '- elem2\n', conf) @@ -51,6 +64,9 @@ def test_disabled(self): ' subobject:\n' ' - elem1\n' ' - elem2\n', conf) + self.check('---\n' + 'object:\n' + ' -elem2\n', conf) def test_enabled(self): conf = 'hyphens: {max-spaces-after: 1}' @@ -103,3 +119,42 @@ def test_max_3(self): ' b:\n' ' - elem1\n' ' - elem2\n', conf, problem1=(4, 9), problem2=(5, 9)) + + def test_invalid_spaces(self): + conf = 'hyphens: {max-spaces-after: 0}' + self.assertRaises(config.YamlLintConfigError, self.check, '', conf) + + conf = 'hyphens: {min-spaces-after: 3}' + self.assertRaises(config.YamlLintConfigError, self.check, '', conf) + + def test_min_space(self): + conf = 'hyphens: {max-spaces-after: 4, min-spaces-after: 3}' + self.check('---\n' + 'object:\n' + ' - elem1\n' + ' - elem2\n', conf) + self.check('---\n' + 'object:\n' + ' - elem1\n' + ' - elem2: -foo\n' + '-bar:\n', conf) + self.check('---\n' + 'object:\n' + ' - elem1\n' + ' - elem2\n', conf, problem1=(3, 6), problem2=(4, 6)) + + conf = ('hyphens:\n' + ' max-spaces-after: 4\n' + ' min-spaces-after: 3\n' + ' check-scalars: true\n') + self.check('---\n' + 'foo\n' + '-bar\n', conf) + self.check('---\n' + 'object:\n' + ' - elem1\n' + ' - elem2\n' + 'key: -value\n', conf, problem=(5, 6)) + self.check('---\n' + 'list:\n' + ' -value\n', conf, problem=(3, 3)) diff --git a/yamllint/rules/hyphens.py b/yamllint/rules/hyphens.py index 50e4d6d9..5ee6d149 100644 --- a/yamllint/rules/hyphens.py +++ b/yamllint/rules/hyphens.py @@ -19,7 +19,20 @@ .. rubric:: Options * ``max-spaces-after`` defines the maximal number of spaces allowed after - hyphens. + hyphens. Set to a negative integer if you want to allow any number of + spaces. +* ``min-spaces-after`` defines the minimal number of spaces expected after + hyphens. Set to a negative integer if you want to allow any number of + spaces. When set to a positive value, cannot be greater than + ``max-spaces-after``. +* YAMLLint will consider ``-xx`` as a scalar. However you may consider + that, in your context, such a syntax is a typo and is actually a sequence + and as a consequence there should be a space after the hyphen. As this is + not a standard behaviour, you explicitly need to enable this control by + setting the option ``check-scalars`` to ``true``. **Use with caution** + as all scalars will be checked and non-solvable false positive might be + identified. Has no effect when set to ``true`` but ``min-spaces-after`` + is disabled (< 0). .. rubric:: Default values (when enabled) @@ -28,6 +41,8 @@ rules: hyphens: max-spaces-after: 1 + min-spaces-after: -1 # Disabled + check-scalars: false .. rubric:: Examples @@ -72,24 +87,97 @@ - key - key2 - key42 + +#. With ``hyphens: {min-spaces-after: 3}`` + + the following code snippet would **PASS**: + :: + + list: + - key + - key2 + - key42 + -foo: # starter of a new sequence named "-foo"; + # without the colon, a syntax error will be raised. + + the following code snippet would **FAIL**: + :: + + - key + - key2 + - key42 + +#. With ``hyphens: {min-spaces-after: 3, check-scalars: true}`` + + the following code snippet would **PASS**: + :: + + list: + - key + - key2 + - key42 + key: -value + + the following code snippets would **FAIL**: + :: + + --- + -item0 + + :: + + sequence: + -key # Mind the spaces before the hyphen to enforce + # the sequence and avoid a syntax error """ import yaml +from yamllint.linter import LintProblem from yamllint.rules.common import spaces_after ID = 'hyphens' TYPE = 'token' -CONF = {'max-spaces-after': int} -DEFAULT = {'max-spaces-after': 1} +CONF = {'max-spaces-after': int, + 'min-spaces-after': int, + 'check-scalars': bool} +DEFAULT = {'max-spaces-after': 1, + 'min-spaces-after': -1, + 'check-scalars': False} + + +def VALIDATE(conf): + if conf['max-spaces-after'] == 0: + return '"max-spaces-after" cannot be set to 0' + if (conf['min-spaces-after'] > 0 and + conf['min-spaces-after'] > conf['max-spaces-after']): + return '"min-spaces-after" cannot be greater than "max-spaces-after"' def check(conf, token, prev, next, nextnext, context): if isinstance(token, yaml.BlockEntryToken): - problem = spaces_after(token, prev, next, - max=conf['max-spaces-after'], - max_desc='too many spaces after hyphen') - if problem is not None: - yield problem + if conf['max-spaces-after'] > 0: + problem = spaces_after(token, prev, next, + max=conf['max-spaces-after'], + max_desc='too many spaces after hyphen') + if problem is not None: + yield problem + + if conf['min-spaces-after'] > 0: + problem = spaces_after(token, prev, next, + min=conf['min-spaces-after'], + min_desc='too few spaces after hyphen') + if problem is not None: + yield problem + + if (conf['check-scalars'] and conf['min-spaces-after'] > 0 + and isinstance(token, yaml.ScalarToken)): + # Token identified as a scalar so there is no space after the + # hyphen: no need to count + if token.value.startswith('-'): + yield LintProblem( + token.start_mark.line + 1, + token.start_mark.column + 1, + 'too few spaces after hyphen')