Skip to content

Commit

Permalink
hyphen: Add min-spaces-after and check-scalars options
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sbaudoin committed Nov 9, 2023
1 parent 8713140 commit 15dbaed
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 9 deletions.
57 changes: 56 additions & 1 deletion tests/rules/test_hyphens.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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}'
Expand Down Expand Up @@ -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))
104 changes: 96 additions & 8 deletions yamllint/rules/hyphens.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -28,6 +41,8 @@
rules:
hyphens:
max-spaces-after: 1
min-spaces-after: -1 # Disabled
check-scalars: false
.. rubric:: Examples
Expand Down Expand Up @@ -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')

0 comments on commit 15dbaed

Please sign in to comment.