diff --git a/docs/rules.rst b/docs/rules.rst index dab310d..24318c7 100644 --- a/docs/rules.rst +++ b/docs/rules.rst @@ -59,6 +59,11 @@ empty-lines .. automodule:: yamllint.rules.empty_lines +empty-values +------------ + +.. automodule:: yamllint.rules.empty_values + hyphens ------- diff --git a/tests/rules/test_empty_values.py b/tests/rules/test_empty_values.py new file mode 100644 index 0000000..1cc4f3e --- /dev/null +++ b/tests/rules/test_empty_values.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2017 Greg Dubicki +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from tests.common import RuleTestCase + + +class EmptyValuesTestCase(RuleTestCase): + rule_id = 'empty-values' + + def test_disabled_globally(self): + conf = 'empty-values: disable' + self.check('---\n' + 'foo:\n', conf) + + self.check('---\n' + 'foo:\n' + ' bar:\n', conf) + + def test_disabled_forbid_in_block_mappings(self): + conf = 'empty-values: {forbid-in-block-mappings: false}' + self.check('---\n' + 'foo:\n', conf) + + self.check('---\n' + 'foo:\n' + 'bar: aaa\n', conf) + + def test_single_line(self): + conf = 'empty-values: {forbid-in-block-mappings: true}\n' + self.check('---\n' + 'implicitly-null:\n', conf, problem1=(2, 17)) + + self.check('---\n' + 'implicitly-null:with-colons:in-key:\n', conf, + problem1=(2, 36)) + + self.check('---\n' + 'implicitly-null:with-colons:in-key2:\n', conf, + problem1=(2, 37)) + + def test_enabled_all_lines(self): + conf = 'empty-values: {forbid-in-block-mappings: true}\n' + self.check('---\n' + 'foo:\n' + 'bar:\n' + 'foobar:\n', conf, problem1=(2, 5), + problem2=(3, 5), problem3=(4, 8)) + + def test_enabled_explicit_end_of_document(self): + conf = 'empty-values: {forbid-in-block-mappings: true}\n' + self.check('---\n' + 'foo:\n' + '...\n', conf, problem1=(2, 5)) + + def test_enabled_not_end_of_document(self): + conf = 'empty-values: {forbid-in-block-mappings: true}\n' + self.check('---\n' + 'foo:\n' + 'bar:\n' + ' aaa\n', conf, problem1=(2, 5)) + + def test_enabled_different_level(self): + conf = 'empty-values: {forbid-in-block-mappings: true}\n' + self.check('---\n' + 'foo:\n' + ' bar:\n' + 'aaa: bbb\n', conf, problem1=(3, 6)) + + def test_enabled_empty_flow_mapping(self): + conf = 'empty-values: {forbid-in-block-mappings: true}\n' + self.check('---\n' + 'foo: {a:}\n', conf) + + def test_enabled_empty_block_sequence(self): + conf = 'empty-values: {forbid-in-block-mappings: true}\n' + self.check('---\n' + 'foo:\n' + ' -\n', conf) + + def test_enabled_not_empty_or_explicit_null(self): + conf = 'empty-values: {forbid-in-block-mappings: true}\n' + self.check('---\n' + 'foo:\n' + ' bar:\n' + ' aaa\n', conf) + + self.check('---\n' + 'explicitly-null: null\n', conf) + + self.check('---\n' + 'explicitly-null:with-colons:in-key: null\n', conf) + + self.check('---\n' + 'false-null: nulL\n', conf) + + self.check('---\n' + 'empty-string: \'\'\n', conf) + + self.check('---\n' + 'nullable-boolean: false\n', conf) + + self.check('---\n' + 'nullable-int: 0\n', conf) + + self.check('---\n' + 'First occurrence: &anchor Foo\n' + 'Second occurrence: *anchor\n', conf) + + def test_enabled_various_explicit_null(self): + conf = 'empty-values: {forbid-in-block-mappings: true}\n' + self.check('---\n' + 'null-key1: {?: val}\n', conf) + + self.check('---\n' + 'null-alias: ~\n', conf) + + self.check('---\n' + 'null-key2: {? !!null "": val}\n', conf) diff --git a/yamllint/conf/default.yaml b/yamllint/conf/default.yaml index 57cff64..8542d03 100644 --- a/yamllint/conf/default.yaml +++ b/yamllint/conf/default.yaml @@ -32,6 +32,8 @@ rules: max: 2 max-start: 0 max-end: 0 + empty-values: + forbid-in-block-mappings: false hyphens: max-spaces-after: 1 indentation: diff --git a/yamllint/rules/__init__.py b/yamllint/rules/__init__.py index 83dca76..189ec69 100644 --- a/yamllint/rules/__init__.py +++ b/yamllint/rules/__init__.py @@ -24,6 +24,7 @@ from yamllint.rules import ( document_end, document_start, empty_lines, + empty_values, hyphens, indentation, key_duplicates, @@ -45,6 +46,7 @@ _RULES = { document_end.ID: document_end, document_start.ID: document_start, empty_lines.ID: empty_lines, + empty_values.ID: empty_values, hyphens.ID: hyphens, indentation.ID: indentation, key_duplicates.ID: key_duplicates, diff --git a/yamllint/rules/empty_values.py b/yamllint/rules/empty_values.py new file mode 100644 index 0000000..2c1991e --- /dev/null +++ b/yamllint/rules/empty_values.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2017 Greg Dubicki +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +Use this rule to prevent nodes with empty content, that implicitly result in +``null`` values. + +.. rubric:: Options + +* Use ``forbid-in-block-mappings`` to prevent implicit empty values in block + mappings. + +.. rubric:: Examples + +#. With ``empty-values: {forbid-in-block-mappings: true}`` + the following code snippets would **PASS**: + :: + + some-mapping: + sub-element: correctly indented + + :: + + explicitly-null: null + + the following code snippets would **FAIL**: + :: + + some-mapping: + sub-element: incorrectly indented + + :: + + implicitly-null: + +""" + +import yaml + +from yamllint.linter import LintProblem + + +ID = 'empty-values' +TYPE = 'token' +CONF = {'forbid-in-block-mappings': bool} + + +def check(conf, token, prev, next, nextnext, context): + + if conf['forbid-in-block-mappings']: + if isinstance(token, yaml.ValueToken) and isinstance(next, ( + yaml.KeyToken, yaml.BlockEndToken, + yaml.StreamEndToken, yaml.DocumentEndToken)): + yield LintProblem(token.start_mark.line + 1, + token.end_mark.column + 1, + 'empty value in block mapping')