From ee4d163ff831b17103001fc450b9ea1470b51e29 Mon Sep 17 00:00:00 2001 From: Rusty Geldmacher Date: Tue, 15 Dec 2020 05:52:21 -0500 Subject: [PATCH] Allow only non-empty brackets/braces We'd like to disallow brackets and braces in our YAML, but there's a catch: the only way to describe an empty array or hash in YAML is to supply an empty one (`[]` or `{}`). Otherwise, the value will be null. This commit adds a `non-empty` option to `forbid` for brackets and braces. When it is set, all flow and sequence mappings will cause errors _except_ for empty ones. --- tests/rules/test_braces.py | 24 ++++++++++++++++++++++++ tests/rules/test_brackets.py | 23 +++++++++++++++++++++++ yamllint/rules/braces.py | 27 ++++++++++++++++++++++++--- yamllint/rules/brackets.py | 27 ++++++++++++++++++++++++--- 4 files changed, 95 insertions(+), 6 deletions(-) diff --git a/tests/rules/test_braces.py b/tests/rules/test_braces.py index 69c5461..cae7014 100644 --- a/tests/rules/test_braces.py +++ b/tests/rules/test_braces.py @@ -61,6 +61,30 @@ class ColonTestCase(RuleTestCase): ' a: 1\n' '}\n', conf, problem=(2, 8)) + conf = ('braces:\n' + ' forbid: non-empty\n') + self.check('---\n' + 'dict:\n' + ' a: 1\n', conf) + self.check('---\n' + 'dict: {}\n', conf) + self.check('---\n' + 'dict: {\n' + '}\n', conf) + self.check('---\n' + 'dict: {\n' + '# commented: value\n' + '# another: value2\n' + '}\n', conf) + self.check('---\n' + 'dict: {a}\n', conf, problem=(2, 8)) + self.check('---\n' + 'dict: {a: 1}\n', conf, problem=(2, 8)) + self.check('---\n' + 'dict: {\n' + ' a: 1\n' + '}\n', conf, problem=(2, 8)) + def test_min_spaces(self): conf = ('braces:\n' ' max-spaces-inside: -1\n' diff --git a/tests/rules/test_brackets.py b/tests/rules/test_brackets.py index a35eff9..41f20a7 100644 --- a/tests/rules/test_brackets.py +++ b/tests/rules/test_brackets.py @@ -60,6 +60,29 @@ class ColonTestCase(RuleTestCase): ' b\n' ']\n', conf, problem=(2, 9)) + conf = ('brackets:\n' + ' forbid: non-empty\n') + self.check('---\n' + 'array:\n' + ' - a\n' + ' - b\n', conf) + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [\n\n' + ']\n', conf) + self.check('---\n' + 'array: [\n' + '# a comment\n' + ']\n', conf) + self.check('---\n' + 'array: [a, b]\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [\n' + ' a,\n' + ' b\n' + ']\n', conf, problem=(2, 9)) + def test_min_spaces(self): conf = ('brackets:\n' ' max-spaces-inside: -1\n' diff --git a/yamllint/rules/braces.py b/yamllint/rules/braces.py index 759306e..d3c03fb 100644 --- a/yamllint/rules/braces.py +++ b/yamllint/rules/braces.py @@ -22,7 +22,8 @@ braces (``{`` and ``}``). * ``forbid`` is used to forbid the use of flow mappings which are denoted by surrounding braces (``{`` and ``}``). Use ``true`` to forbid the use of flow - mappings completely. + mappings completely. Use ``non-empty`` to forbid the use of all flow + mappings except for empty ones. * ``min-spaces-inside`` defines the minimal number of spaces required inside braces. * ``max-spaces-inside`` defines the maximal number of spaces allowed inside @@ -60,6 +61,18 @@ braces (``{`` and ``}``). object: { key1: 4, key2: 8 } +#. With ``braces: {forbid: non-empty}`` + + the following code snippet would **PASS**: + :: + + object: {} + + the following code snippet would **FAIL**: + :: + + object: { key1: 4, key2: 8 } + #. With ``braces: {min-spaces-inside: 0, max-spaces-inside: 0}`` the following code snippet would **PASS**: @@ -128,7 +141,7 @@ from yamllint.rules.common import spaces_after, spaces_before ID = 'braces' TYPE = 'token' -CONF = {'forbid': bool, +CONF = {'forbid': (bool, 'non-empty'), 'min-spaces-inside': int, 'max-spaces-inside': int, 'min-spaces-inside-empty': int, @@ -141,7 +154,15 @@ DEFAULT = {'forbid': False, def check(conf, token, prev, next, nextnext, context): - if conf['forbid'] and isinstance(token, yaml.FlowMappingStartToken): + if (conf['forbid'] is True and + isinstance(token, yaml.FlowMappingStartToken)): + yield LintProblem(token.start_mark.line + 1, + token.end_mark.column + 1, + 'forbidden flow mapping') + + elif (conf['forbid'] == 'non-empty' and + isinstance(token, yaml.FlowMappingStartToken) and + not isinstance(next, yaml.FlowMappingEndToken)): yield LintProblem(token.start_mark.line + 1, token.end_mark.column + 1, 'forbidden flow mapping') diff --git a/yamllint/rules/brackets.py b/yamllint/rules/brackets.py index 6ab02df..01fee87 100644 --- a/yamllint/rules/brackets.py +++ b/yamllint/rules/brackets.py @@ -22,7 +22,8 @@ inside brackets (``[`` and ``]``). * ``forbid`` is used to forbid the use of flow sequences which are denoted by surrounding brackets (``[`` and ``]``). Use ``true`` to forbid the use of - flow sequences completely. + flow sequences completely. Use ``non-empty`` to forbid the use of all flow + sequences except for empty ones. * ``min-spaces-inside`` defines the minimal number of spaces required inside brackets. * ``max-spaces-inside`` defines the maximal number of spaces allowed inside @@ -61,6 +62,18 @@ inside brackets (``[`` and ``]``). object: [ 1, 2, abc ] +#. With ``brackets: {forbid: non-empty}`` + + the following code snippet would **PASS**: + :: + + object: [] + + the following code snippet would **FAIL**: + :: + + object: [ 1, 2, abc ] + #. With ``brackets: {min-spaces-inside: 0, max-spaces-inside: 0}`` the following code snippet would **PASS**: @@ -129,7 +142,7 @@ from yamllint.rules.common import spaces_after, spaces_before ID = 'brackets' TYPE = 'token' -CONF = {'forbid': bool, +CONF = {'forbid': (bool, 'non-empty'), 'min-spaces-inside': int, 'max-spaces-inside': int, 'min-spaces-inside-empty': int, @@ -142,7 +155,15 @@ DEFAULT = {'forbid': False, def check(conf, token, prev, next, nextnext, context): - if conf['forbid'] and isinstance(token, yaml.FlowSequenceStartToken): + if (conf['forbid'] is True and + isinstance(token, yaml.FlowSequenceStartToken)): + yield LintProblem(token.start_mark.line + 1, + token.end_mark.column + 1, + 'forbidden flow sequence') + + elif (conf['forbid'] == 'non-empty' and + isinstance(token, yaml.FlowSequenceStartToken) and + not isinstance(next, yaml.FlowSequenceEndToken)): yield LintProblem(token.start_mark.line + 1, token.end_mark.column + 1, 'forbidden flow sequence')