From 4b2b57aa32d99b29458492a68172e066744c590e Mon Sep 17 00:00:00 2001 From: Jim Riggs Date: Fri, 17 Feb 2017 13:58:38 -0600 Subject: [PATCH] Rules: Add min-spaces-inside-empty and max-spaces-inside-empty Add min-spaces-inside-empty and max-spaces-inside-empty to braces and brackets to allow separate handling for empty and non-empty objects. --- tests/rules/test_braces.py | 195 +++++++++++++++++++++++++++++++++-- tests/rules/test_brackets.py | 195 +++++++++++++++++++++++++++++++++-- yamllint/conf/default.yaml | 4 + yamllint/rules/braces.py | 48 ++++++++- yamllint/rules/brackets.py | 49 ++++++++- 5 files changed, 471 insertions(+), 20 deletions(-) diff --git a/tests/rules/test_braces.py b/tests/rules/test_braces.py index 0262ae0..308715b 100644 --- a/tests/rules/test_braces.py +++ b/tests/rules/test_braces.py @@ -32,11 +32,19 @@ class ColonTestCase(RuleTestCase): 'dict7: { a: 1, b, c: 3 }\n', conf) def test_min_spaces(self): - conf = 'braces: {max-spaces-inside: -1, min-spaces-inside: 0}' + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: {}\n', conf) - conf = 'braces: {max-spaces-inside: -1, min-spaces-inside: 1}' + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: {}\n', conf, problem=(2, 8)) self.check('---\n' @@ -52,7 +60,11 @@ class ColonTestCase(RuleTestCase): ' b\n' '}\n', conf) - conf = 'braces: {max-spaces-inside: -1, min-spaces-inside: 3}' + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 3\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: { a: 1, b }\n', conf, problem1=(2, 9), problem2=(2, 17)) @@ -60,7 +72,11 @@ class ColonTestCase(RuleTestCase): 'dict: { a: 1, b }\n', conf) def test_max_spaces(self): - conf = 'braces: {max-spaces-inside: 0, min-spaces-inside: -1}' + conf = ('braces:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: {}\n', conf) self.check('---\n' @@ -79,7 +95,11 @@ class ColonTestCase(RuleTestCase): ' b\n' '}\n', conf) - conf = 'braces: {max-spaces-inside: 3, min-spaces-inside: -1}' + conf = ('braces:\n' + ' max-spaces-inside: 3\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: { a: 1, b }\n', conf) self.check('---\n' @@ -87,7 +107,11 @@ class ColonTestCase(RuleTestCase): problem1=(2, 11), problem2=(2, 23)) def test_min_and_max_spaces(self): - conf = 'braces: {max-spaces-inside: 0, min-spaces-inside: 0}' + conf = ('braces:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: {}\n', conf) self.check('---\n' @@ -95,14 +119,169 @@ class ColonTestCase(RuleTestCase): self.check('---\n' 'dict: { a: 1, b}\n', conf, problem=(2, 10)) - conf = 'braces: {max-spaces-inside: 1, min-spaces-inside: 1}' + conf = ('braces:\n' + ' max-spaces-inside: 1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: {a: 1, b, c: 3 }\n', conf, problem=(2, 8)) - conf = 'braces: {max-spaces-inside: 2, min-spaces-inside: 0}' + conf = ('braces:\n' + ' max-spaces-inside: 2\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'dict: {a: 1, b, c: 3 }\n', conf) self.check('---\n' 'dict: { a: 1, b, c: 3 }\n', conf) self.check('---\n' 'dict: { a: 1, b, c: 3 }\n', conf, problem=(2, 10)) + + def test_min_spaces_empty(self): + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: 0\n') + self.check('---\n' + 'array: {}\n', conf) + + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: {}\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) + + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: 3\n') + self.check('---\n' + 'array: {}\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) + + def test_max_spaces_empty(self): + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: {}\n', conf) + self.check('---\n' + 'array: { }\n', conf, problem=(2, 9)) + + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: {}\n', conf) + self.check('---\n' + 'array: { }\n', conf) + self.check('---\n' + 'array: { }\n', conf, problem=(2, 10)) + + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 3\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: {}\n', conf) + self.check('---\n' + 'array: { }\n', conf) + self.check('---\n' + 'array: { }\n', conf, problem=(2, 12)) + + def test_min_and_max_spaces_empty(self): + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 2\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: {}\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) + self.check('---\n' + 'array: { }\n', conf) + self.check('---\n' + 'array: { }\n', conf, problem=(2, 11)) + + def test_mixed_empty_nonempty(self): + conf = ('braces:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: 0\n') + self.check('---\n' + 'array: { a: 1, b }\n', conf) + self.check('---\n' + 'array: {a: 1, b}\n', conf, + problem1=(2, 9), problem2=(2, 16)) + self.check('---\n' + 'array: {}\n', conf) + self.check('---\n' + 'array: { }\n', conf, + problem1=(2, 9)) + + conf = ('braces:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: { a: 1, b }\n', conf, + problem1=(2, 9), problem2=(2, 17)) + self.check('---\n' + 'array: {a: 1, b}\n', conf) + self.check('---\n' + 'array: {}\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) + + conf = ('braces:\n' + ' max-spaces-inside: 2\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: { a: 1, b }\n', conf) + self.check('---\n' + 'array: {a: 1, b }\n', conf, + problem1=(2, 9), problem2=(2, 18)) + self.check('---\n' + 'array: {}\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) + self.check('---\n' + 'array: { }\n', conf, + problem1=(2, 11)) + + conf = ('braces:\n' + ' max-spaces-inside: 1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: { a: 1, b }\n', conf) + self.check('---\n' + 'array: {a: 1, b}\n', conf, + problem1=(2, 9), problem2=(2, 16)) + self.check('---\n' + 'array: {}\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: { }\n', conf) diff --git a/tests/rules/test_brackets.py b/tests/rules/test_brackets.py index 8fb8006..273f1a7 100644 --- a/tests/rules/test_brackets.py +++ b/tests/rules/test_brackets.py @@ -32,11 +32,19 @@ class ColonTestCase(RuleTestCase): 'array7: [ a, b, c ]\n', conf) def test_min_spaces(self): - conf = 'brackets: {max-spaces-inside: -1, min-spaces-inside: 0}' + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: []\n', conf) - conf = 'brackets: {max-spaces-inside: -1, min-spaces-inside: 1}' + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: []\n', conf, problem=(2, 9)) self.check('---\n' @@ -51,7 +59,11 @@ class ColonTestCase(RuleTestCase): ' b\n' ']\n', conf) - conf = 'brackets: {max-spaces-inside: -1, min-spaces-inside: 3}' + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 3\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: [ a, b ]\n', conf, problem1=(2, 10), problem2=(2, 15)) @@ -59,7 +71,11 @@ class ColonTestCase(RuleTestCase): 'array: [ a, b ]\n', conf) def test_max_spaces(self): - conf = 'brackets: {max-spaces-inside: 0, min-spaces-inside: -1}' + conf = ('brackets:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: []\n', conf) self.check('---\n' @@ -78,7 +94,11 @@ class ColonTestCase(RuleTestCase): ' b\n' ']\n', conf) - conf = 'brackets: {max-spaces-inside: 3, min-spaces-inside: -1}' + conf = ('brackets:\n' + ' max-spaces-inside: 3\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: [ a, b ]\n', conf) self.check('---\n' @@ -86,7 +106,11 @@ class ColonTestCase(RuleTestCase): problem1=(2, 12), problem2=(2, 21)) def test_min_and_max_spaces(self): - conf = 'brackets: {max-spaces-inside: 0, min-spaces-inside: 0}' + conf = ('brackets:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: []\n', conf) self.check('---\n' @@ -94,14 +118,169 @@ class ColonTestCase(RuleTestCase): self.check('---\n' 'array: [ a, b]\n', conf, problem=(2, 11)) - conf = 'brackets: {max-spaces-inside: 1, min-spaces-inside: 1}' + conf = ('brackets:\n' + ' max-spaces-inside: 1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: [a, b, c ]\n', conf, problem=(2, 9)) - conf = 'brackets: {max-spaces-inside: 2, min-spaces-inside: 0}' + conf = ('brackets:\n' + ' max-spaces-inside: 2\n' + ' min-spaces-inside: 0\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: -1\n') self.check('---\n' 'array: [a, b, c ]\n', conf) self.check('---\n' 'array: [ a, b, c ]\n', conf) self.check('---\n' 'array: [ a, b, c ]\n', conf, problem=(2, 11)) + + def test_min_spaces_empty(self): + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: 0\n') + self.check('---\n' + 'array: []\n', conf) + + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: []\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: -1\n' + ' min-spaces-inside-empty: 3\n') + self.check('---\n' + 'array: []\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + + def test_max_spaces_empty(self): + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, problem=(2, 9)) + + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, problem=(2, 10)) + + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 3\n' + ' min-spaces-inside-empty: -1\n') + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, problem=(2, 12)) + + def test_min_and_max_spaces_empty(self): + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 2\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: []\n', conf, problem=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, problem=(2, 11)) + + def test_mixed_empty_nonempty(self): + conf = ('brackets:\n' + ' max-spaces-inside: -1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 0\n' + ' min-spaces-inside-empty: 0\n') + self.check('---\n' + 'array: [ a, b ]\n', conf) + self.check('---\n' + 'array: [a, b]\n', conf, + problem1=(2, 9), problem2=(2, 13)) + self.check('---\n' + 'array: []\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, + problem1=(2, 9)) + + conf = ('brackets:\n' + ' max-spaces-inside: 0\n' + ' min-spaces-inside: -1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: [ a, b ]\n', conf, + problem1=(2, 9), problem2=(2, 14)) + self.check('---\n' + 'array: [a, b]\n', conf) + self.check('---\n' + 'array: []\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + + conf = ('brackets:\n' + ' max-spaces-inside: 2\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: [ a, b ]\n', conf) + self.check('---\n' + 'array: [a, b ]\n', conf, + problem1=(2, 9), problem2=(2, 15)) + self.check('---\n' + 'array: []\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) + self.check('---\n' + 'array: [ ]\n', conf, + problem1=(2, 11)) + + conf = ('brackets:\n' + ' max-spaces-inside: 1\n' + ' min-spaces-inside: 1\n' + ' max-spaces-inside-empty: 1\n' + ' min-spaces-inside-empty: 1\n') + self.check('---\n' + 'array: [ a, b ]\n', conf) + self.check('---\n' + 'array: [a, b]\n', conf, + problem1=(2, 9), problem2=(2, 13)) + self.check('---\n' + 'array: []\n', conf, + problem1=(2, 9)) + self.check('---\n' + 'array: [ ]\n', conf) diff --git a/yamllint/conf/default.yaml b/yamllint/conf/default.yaml index 0a22e81..c7c4da4 100644 --- a/yamllint/conf/default.yaml +++ b/yamllint/conf/default.yaml @@ -4,9 +4,13 @@ rules: braces: min-spaces-inside: 0 max-spaces-inside: 0 + min-spaces-inside-empty: -1 + max-spaces-inside-empty: -1 brackets: min-spaces-inside: 0 max-spaces-inside: 0 + min-spaces-inside-empty: -1 + max-spaces-inside-empty: -1 colons: max-spaces-before: 0 max-spaces-after: 1 diff --git a/yamllint/rules/braces.py b/yamllint/rules/braces.py index 55f465e..59d9558 100644 --- a/yamllint/rules/braces.py +++ b/yamllint/rules/braces.py @@ -23,6 +23,10 @@ Use this rule to control the number of spaces inside braces (``{`` and ``}``). braces. * ``max-spaces-inside`` defines the maximal number of spaces allowed inside braces. +* ``min-spaces-inside-empty`` defines the minimal number of spaces required + inside empty braces. +* ``max-spaces-inside-empty`` defines the maximal number of spaces allowed + inside empty braces. .. rubric:: Examples @@ -59,6 +63,30 @@ Use this rule to control the number of spaces inside braces (``{`` and ``}``). :: object: {key1: 4, key2: 8 } + +#. With ``braces: {min-spaces-inside-empty: 0, max-spaces-inside-empty: 0}`` + + the following code snippet would **PASS**: + :: + + object: {} + + the following code snippet would **FAIL**: + :: + + object: { } + +#. With ``braces: {min-spaces-inside-empty: 1, max-spaces-inside-empty: -1}`` + + the following code snippet would **PASS**: + :: + + object: { } + + the following code snippet would **FAIL**: + :: + + object: {} """ @@ -70,11 +98,27 @@ from yamllint.rules.common import spaces_after, spaces_before ID = 'braces' TYPE = 'token' CONF = {'min-spaces-inside': int, - 'max-spaces-inside': int} + 'max-spaces-inside': int, + 'min-spaces-inside-empty': int, + 'max-spaces-inside-empty': int} def check(conf, token, prev, next, nextnext, context): - if isinstance(token, yaml.FlowMappingStartToken): + if (isinstance(token, yaml.FlowMappingStartToken) and + isinstance(next, yaml.FlowMappingEndToken)): + problem = spaces_after(token, prev, next, + min=(conf['min-spaces-inside-empty'] + if conf['min-spaces-inside-empty'] != -1 + else conf['min-spaces-inside']), + max=(conf['max-spaces-inside-empty'] + if conf['max-spaces-inside-empty'] != -1 + else conf['max-spaces-inside']), + min_desc='too few spaces inside empty braces', + max_desc='too many spaces inside empty braces') + if problem is not None: + yield problem + + elif isinstance(token, yaml.FlowMappingStartToken): problem = spaces_after(token, prev, next, min=conf['min-spaces-inside'], max=conf['max-spaces-inside'], diff --git a/yamllint/rules/brackets.py b/yamllint/rules/brackets.py index ae0e744..33bdaa9 100644 --- a/yamllint/rules/brackets.py +++ b/yamllint/rules/brackets.py @@ -24,6 +24,10 @@ Use this rule to control the number of spaces inside brackets (``[`` and brackets. * ``max-spaces-inside`` defines the maximal number of spaces allowed inside brackets. +* ``min-spaces-inside-empty`` defines the minimal number of spaces required + inside empty brackets. +* ``max-spaces-inside-empty`` defines the maximal number of spaces allowed + inside empty brackets. .. rubric:: Examples @@ -60,6 +64,30 @@ Use this rule to control the number of spaces inside brackets (``[`` and :: object: [1, 2, abc ] + +#. With ``brackets: {min-spaces-inside-empty: 0, max-spaces-inside-empty: 0}`` + + the following code snippet would **PASS**: + :: + + object: [] + + the following code snippet would **FAIL**: + :: + + object: [ ] + +#. With ``brackets: {min-spaces-inside-empty: 1, max-spaces-inside-empty: -1}`` + + the following code snippet would **PASS**: + :: + + object: [ ] + + the following code snippet would **FAIL**: + :: + + object: [] """ @@ -71,11 +99,28 @@ from yamllint.rules.common import spaces_after, spaces_before ID = 'brackets' TYPE = 'token' CONF = {'min-spaces-inside': int, - 'max-spaces-inside': int} + 'max-spaces-inside': int, + 'min-spaces-inside-empty': int, + 'max-spaces-inside-empty': int} def check(conf, token, prev, next, nextnext, context): - if isinstance(token, yaml.FlowSequenceStartToken): + if (isinstance(token, yaml.FlowSequenceStartToken) and + isinstance(next, yaml.FlowSequenceEndToken)): + problem = spaces_after(token, prev, next, + min=(conf['min-spaces-inside-empty'] + if conf['min-spaces-inside-empty'] != -1 + else conf['min-spaces-inside']), + max=(conf['max-spaces-inside-empty'] + if conf['max-spaces-inside-empty'] != -1 + else conf['max-spaces-inside']), + min_desc='too few spaces inside empty brackets', + max_desc=('too many spaces inside empty ' + 'brackets')) + if problem is not None: + yield problem + + elif isinstance(token, yaml.FlowSequenceStartToken): problem = spaces_after(token, prev, next, min=conf['min-spaces-inside'], max=conf['max-spaces-inside'],