summaryrefslogtreecommitdiffstats
path: root/tests/rules
diff options
context:
space:
mode:
Diffstat (limited to 'tests/rules')
-rw-r--r--tests/rules/__init__.py0
-rw-r--r--tests/rules/test_anchors.py281
-rw-r--r--tests/rules/test_braces.py340
-rw-r--r--tests/rules/test_brackets.py337
-rw-r--r--tests/rules/test_colons.py274
-rw-r--r--tests/rules/test_commas.py264
-rw-r--r--tests/rules/test_comments.py236
-rw-r--r--tests/rules/test_comments_indentation.py156
-rw-r--r--tests/rules/test_common.py43
-rw-r--r--tests/rules/test_document_end.py92
-rw-r--r--tests/rules/test_document_start.py103
-rw-r--r--tests/rules/test_empty_lines.py98
-rw-r--r--tests/rules/test_empty_values.py368
-rw-r--r--tests/rules/test_float_values.py128
-rw-r--r--tests/rules/test_hyphens.py105
-rw-r--r--tests/rules/test_indentation.py2160
-rw-r--r--tests/rules/test_key_duplicates.py181
-rw-r--r--tests/rules/test_key_ordering.py149
-rw-r--r--tests/rules/test_line_length.py198
-rw-r--r--tests/rules/test_new_line_at_end_of_file.py41
-rw-r--r--tests/rules/test_new_lines.py96
-rw-r--r--tests/rules/test_octal_values.py80
-rw-r--r--tests/rules/test_quoted_strings.py558
-rw-r--r--tests/rules/test_trailing_spaces.py47
-rw-r--r--tests/rules/test_truthy.py145
25 files changed, 6480 insertions, 0 deletions
diff --git a/tests/rules/__init__.py b/tests/rules/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/rules/__init__.py
diff --git a/tests/rules/test_anchors.py b/tests/rules/test_anchors.py
new file mode 100644
index 0000000..7d7cbb7
--- /dev/null
+++ b/tests/rules/test_anchors.py
@@ -0,0 +1,281 @@
+# Copyright (C) 2023 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class AnchorsTestCase(RuleTestCase):
+ rule_id = 'anchors'
+
+ def test_disabled(self):
+ conf = 'anchors: disable'
+ self.check('---\n'
+ '- &b true\n'
+ '- &i 42\n'
+ '- &s hello\n'
+ '- &f_m {k: v}\n'
+ '- &f_s [1, 2]\n'
+ '- *b\n'
+ '- *i\n'
+ '- *s\n'
+ '- *f_m\n'
+ '- *f_s\n'
+ '---\n' # redeclare anchors in a new document
+ '- &b true\n'
+ '- &i 42\n'
+ '- &s hello\n'
+ '- *b\n'
+ '- *i\n'
+ '- *s\n'
+ '---\n'
+ 'block mapping: &b_m\n'
+ ' key: value\n'
+ 'extended:\n'
+ ' <<: *b_m\n'
+ ' foo: bar\n'
+ '---\n'
+ '{a: 1, &x b: 2, c: &y 3, *x : 4, e: *y}\n'
+ '...\n', conf)
+ self.check('---\n'
+ '- &i 42\n'
+ '---\n'
+ '- &b true\n'
+ '- &b true\n'
+ '- &b true\n'
+ '- &s hello\n'
+ '- *b\n'
+ '- *i\n' # declared in a previous document
+ '- *f_m\n' # never declared
+ '- *f_m\n'
+ '- *f_m\n'
+ '- *f_s\n' # declared after
+ '- &f_s [1, 2]\n'
+ '---\n'
+ 'block mapping: &b_m\n'
+ ' key: value\n'
+ '---\n'
+ 'block mapping 1: &b_m_bis\n'
+ ' key: value\n'
+ 'block mapping 2: &b_m_bis\n'
+ ' key: value\n'
+ 'extended:\n'
+ ' <<: *b_m\n'
+ ' foo: bar\n'
+ '---\n'
+ '{a: 1, &x b: 2, c: &x 3, *x : 4, e: *y}\n'
+ '...\n', conf)
+
+ def test_forbid_undeclared_aliases(self):
+ conf = ('anchors:\n'
+ ' forbid-undeclared-aliases: true\n'
+ ' forbid-duplicated-anchors: false\n'
+ ' forbid-unused-anchors: false\n')
+ self.check('---\n'
+ '- &b true\n'
+ '- &i 42\n'
+ '- &s hello\n'
+ '- &f_m {k: v}\n'
+ '- &f_s [1, 2]\n'
+ '- *b\n'
+ '- *i\n'
+ '- *s\n'
+ '- *f_m\n'
+ '- *f_s\n'
+ '---\n' # redeclare anchors in a new document
+ '- &b true\n'
+ '- &i 42\n'
+ '- &s hello\n'
+ '- *b\n'
+ '- *i\n'
+ '- *s\n'
+ '---\n'
+ 'block mapping: &b_m\n'
+ ' key: value\n'
+ 'extended:\n'
+ ' <<: *b_m\n'
+ ' foo: bar\n'
+ '---\n'
+ '{a: 1, &x b: 2, c: &y 3, *x : 4, e: *y}\n'
+ '...\n', conf)
+ self.check('---\n'
+ '- &i 42\n'
+ '---\n'
+ '- &b true\n'
+ '- &b true\n'
+ '- &b true\n'
+ '- &s hello\n'
+ '- *b\n'
+ '- *i\n' # declared in a previous document
+ '- *f_m\n' # never declared
+ '- *f_m\n'
+ '- *f_m\n'
+ '- *f_s\n' # declared after
+ '- &f_s [1, 2]\n'
+ '...\n'
+ '---\n'
+ 'block mapping: &b_m\n'
+ ' key: value\n'
+ '---\n'
+ 'block mapping 1: &b_m_bis\n'
+ ' key: value\n'
+ 'block mapping 2: &b_m_bis\n'
+ ' key: value\n'
+ 'extended:\n'
+ ' <<: *b_m\n'
+ ' foo: bar\n'
+ '---\n'
+ '{a: 1, &x b: 2, c: &x 3, *x : 4, e: *y}\n'
+ '...\n', conf,
+ problem1=(9, 3),
+ problem2=(10, 3),
+ problem3=(11, 3),
+ problem4=(12, 3),
+ problem5=(13, 3),
+ problem6=(25, 7),
+ problem7=(28, 37))
+
+ def test_forbid_duplicated_anchors(self):
+ conf = ('anchors:\n'
+ ' forbid-undeclared-aliases: false\n'
+ ' forbid-duplicated-anchors: true\n'
+ ' forbid-unused-anchors: false\n')
+ self.check('---\n'
+ '- &b true\n'
+ '- &i 42\n'
+ '- &s hello\n'
+ '- &f_m {k: v}\n'
+ '- &f_s [1, 2]\n'
+ '- *b\n'
+ '- *i\n'
+ '- *s\n'
+ '- *f_m\n'
+ '- *f_s\n'
+ '---\n' # redeclare anchors in a new document
+ '- &b true\n'
+ '- &i 42\n'
+ '- &s hello\n'
+ '- *b\n'
+ '- *i\n'
+ '- *s\n'
+ '---\n'
+ 'block mapping: &b_m\n'
+ ' key: value\n'
+ 'extended:\n'
+ ' <<: *b_m\n'
+ ' foo: bar\n'
+ '---\n'
+ '{a: 1, &x b: 2, c: &y 3, *x : 4, e: *y}\n'
+ '...\n', conf)
+ self.check('---\n'
+ '- &i 42\n'
+ '---\n'
+ '- &b true\n'
+ '- &b true\n'
+ '- &b true\n'
+ '- &s hello\n'
+ '- *b\n'
+ '- *i\n' # declared in a previous document
+ '- *f_m\n' # never declared
+ '- *f_m\n'
+ '- *f_m\n'
+ '- *f_s\n' # declared after
+ '- &f_s [1, 2]\n'
+ '...\n'
+ '---\n'
+ 'block mapping: &b_m\n'
+ ' key: value\n'
+ '---\n'
+ 'block mapping 1: &b_m_bis\n'
+ ' key: value\n'
+ 'block mapping 2: &b_m_bis\n'
+ ' key: value\n'
+ 'extended:\n'
+ ' <<: *b_m\n'
+ ' foo: bar\n'
+ '---\n'
+ '{a: 1, &x b: 2, c: &x 3, *x : 4, e: *y}\n'
+ '...\n', conf,
+ problem1=(5, 3),
+ problem2=(6, 3),
+ problem3=(22, 18),
+ problem4=(28, 20))
+
+ def test_forbid_unused_anchors(self):
+ conf = ('anchors:\n'
+ ' forbid-undeclared-aliases: false\n'
+ ' forbid-duplicated-anchors: false\n'
+ ' forbid-unused-anchors: true\n')
+
+ self.check('---\n'
+ '- &b true\n'
+ '- &i 42\n'
+ '- &s hello\n'
+ '- &f_m {k: v}\n'
+ '- &f_s [1, 2]\n'
+ '- *b\n'
+ '- *i\n'
+ '- *s\n'
+ '- *f_m\n'
+ '- *f_s\n'
+ '---\n' # redeclare anchors in a new document
+ '- &b true\n'
+ '- &i 42\n'
+ '- &s hello\n'
+ '- *b\n'
+ '- *i\n'
+ '- *s\n'
+ '---\n'
+ 'block mapping: &b_m\n'
+ ' key: value\n'
+ 'extended:\n'
+ ' <<: *b_m\n'
+ ' foo: bar\n'
+ '---\n'
+ '{a: 1, &x b: 2, c: &y 3, *x : 4, e: *y}\n'
+ '...\n', conf)
+ self.check('---\n'
+ '- &i 42\n'
+ '---\n'
+ '- &b true\n'
+ '- &b true\n'
+ '- &b true\n'
+ '- &s hello\n'
+ '- *b\n'
+ '- *i\n' # declared in a previous document
+ '- *f_m\n' # never declared
+ '- *f_m\n'
+ '- *f_m\n'
+ '- *f_s\n' # declared after
+ '- &f_s [1, 2]\n'
+ '...\n'
+ '---\n'
+ 'block mapping: &b_m\n'
+ ' key: value\n'
+ '---\n'
+ 'block mapping 1: &b_m_bis\n'
+ ' key: value\n'
+ 'block mapping 2: &b_m_bis\n'
+ ' key: value\n'
+ 'extended:\n'
+ ' <<: *b_m\n'
+ ' foo: bar\n'
+ '---\n'
+ '{a: 1, &x b: 2, c: &x 3, *x : 4, e: *y}\n'
+ '...\n', conf,
+ problem1=(2, 3),
+ problem2=(7, 3),
+ problem3=(14, 3),
+ problem4=(17, 16),
+ problem5=(22, 18))
diff --git a/tests/rules/test_braces.py b/tests/rules/test_braces.py
new file mode 100644
index 0000000..03636a9
--- /dev/null
+++ b/tests/rules/test_braces.py
@@ -0,0 +1,340 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class ColonTestCase(RuleTestCase):
+ rule_id = 'braces'
+
+ def test_disabled(self):
+ conf = 'braces: disable'
+ self.check('---\n'
+ 'dict1: {}\n'
+ 'dict2: { }\n'
+ 'dict3: { a: 1, b}\n'
+ 'dict4: {a: 1, b, c: 3 }\n'
+ 'dict5: {a: 1, b, c: 3 }\n'
+ 'dict6: { a: 1, b, c: 3 }\n'
+ 'dict7: { a: 1, b, c: 3 }\n', conf)
+
+ def test_forbid(self):
+ conf = ('braces:\n'
+ ' forbid: false\n')
+ self.check('---\n'
+ 'dict: {}\n', conf)
+ self.check('---\n'
+ 'dict: {a}\n', conf)
+ self.check('---\n'
+ 'dict: {a: 1}\n', conf)
+ self.check('---\n'
+ 'dict: {\n'
+ ' a: 1\n'
+ '}\n', conf)
+
+ conf = ('braces:\n'
+ ' forbid: true\n')
+ self.check('---\n'
+ 'dict:\n'
+ ' a: 1\n', conf)
+ self.check('---\n'
+ 'dict: {}\n', conf, problem=(2, 8))
+ 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))
+
+ 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'
+ ' 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:\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'
+ 'dict: { }\n', conf)
+ self.check('---\n'
+ 'dict: {a: 1, b}\n', conf,
+ problem1=(2, 8), problem2=(2, 15))
+ self.check('---\n'
+ 'dict: { a: 1, b }\n', conf)
+ self.check('---\n'
+ 'dict: {\n'
+ ' a: 1,\n'
+ ' b\n'
+ '}\n', conf)
+
+ 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))
+ self.check('---\n'
+ 'dict: { a: 1, b }\n', conf)
+
+ def test_max_spaces(self):
+ 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'
+ 'dict: { }\n', conf, problem=(2, 8))
+ self.check('---\n'
+ 'dict: {a: 1, b}\n', conf)
+ self.check('---\n'
+ 'dict: { a: 1, b }\n', conf,
+ problem1=(2, 8), problem2=(2, 16))
+ self.check('---\n'
+ 'dict: { a: 1, b }\n', conf,
+ problem1=(2, 10), problem2=(2, 20))
+ self.check('---\n'
+ 'dict: {\n'
+ ' a: 1,\n'
+ ' b\n'
+ '}\n', conf)
+
+ 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'
+ 'dict: { a: 1, b }\n', conf,
+ problem1=(2, 11), problem2=(2, 23))
+
+ def test_min_and_max_spaces(self):
+ 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'
+ 'dict: { }\n', conf, problem=(2, 8))
+ self.check('---\n'
+ 'dict: { a: 1, b}\n', conf, problem=(2, 10))
+
+ 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:\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
new file mode 100644
index 0000000..e17566b
--- /dev/null
+++ b/tests/rules/test_brackets.py
@@ -0,0 +1,337 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class ColonTestCase(RuleTestCase):
+ rule_id = 'brackets'
+
+ def test_disabled(self):
+ conf = 'brackets: disable'
+ self.check('---\n'
+ 'array1: []\n'
+ 'array2: [ ]\n'
+ 'array3: [ a, b]\n'
+ 'array4: [a, b, c ]\n'
+ 'array5: [a, b, c ]\n'
+ 'array6: [ a, b, c ]\n'
+ 'array7: [ a, b, c ]\n', conf)
+
+ def test_forbid(self):
+ conf = ('brackets:\n'
+ ' forbid: false\n')
+ self.check('---\n'
+ 'array: []\n', conf)
+ self.check('---\n'
+ 'array: [a, b]\n', conf)
+ self.check('---\n'
+ 'array: [\n'
+ ' a,\n'
+ ' b\n'
+ ']\n', conf)
+
+ conf = ('brackets:\n'
+ ' forbid: true\n')
+ self.check('---\n'
+ 'array:\n'
+ ' - a\n'
+ ' - b\n', conf)
+ self.check('---\n'
+ 'array: []\n', conf, problem=(2, 9))
+ 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))
+
+ 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'
+ ' 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:\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)
+ self.check('---\n'
+ 'array: [a, b]\n', conf, problem1=(2, 9), problem2=(2, 13))
+ self.check('---\n'
+ 'array: [ a, b ]\n', conf)
+ self.check('---\n'
+ 'array: [\n'
+ ' a,\n'
+ ' b\n'
+ ']\n', conf)
+
+ 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))
+ self.check('---\n'
+ 'array: [ a, b ]\n', conf)
+
+ def test_max_spaces(self):
+ 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'
+ 'array: [ ]\n', conf, problem=(2, 9))
+ self.check('---\n'
+ 'array: [a, b]\n', conf)
+ self.check('---\n'
+ 'array: [ a, b ]\n', conf,
+ problem1=(2, 9), problem2=(2, 14))
+ self.check('---\n'
+ 'array: [ a, b ]\n', conf,
+ problem1=(2, 11), problem2=(2, 18))
+ self.check('---\n'
+ 'array: [\n'
+ ' a,\n'
+ ' b\n'
+ ']\n', conf)
+
+ 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'
+ 'array: [ a, b ]\n', conf,
+ problem1=(2, 12), problem2=(2, 21))
+
+ def test_min_and_max_spaces(self):
+ 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'
+ 'array: [ ]\n', conf, problem=(2, 9))
+ self.check('---\n'
+ 'array: [ a, b]\n', conf, problem=(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, c ]\n', conf, problem=(2, 9))
+
+ 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/tests/rules/test_colons.py b/tests/rules/test_colons.py
new file mode 100644
index 0000000..5467c8b
--- /dev/null
+++ b/tests/rules/test_colons.py
@@ -0,0 +1,274 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class ColonTestCase(RuleTestCase):
+ rule_id = 'colons'
+
+ def test_disabled(self):
+ conf = 'colons: disable'
+ self.check('---\n'
+ 'object:\n'
+ ' k1 : v1\n'
+ 'obj2:\n'
+ ' k2 :\n'
+ ' - 8\n'
+ ' k3:\n'
+ ' val\n'
+ ' property : value\n'
+ ' prop2 : val2\n'
+ ' propriété : [valeur]\n'
+ ' o:\n'
+ ' k1: [v1, v2]\n'
+ ' p:\n'
+ ' - k3: >\n'
+ ' val\n'
+ ' - o: {k1: v1}\n'
+ ' - p: kdjf\n'
+ ' - q: val0\n'
+ ' - q2:\n'
+ ' - val1\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' k1: v1\n'
+ 'obj2:\n'
+ ' k2:\n'
+ ' - 8\n'
+ ' k3:\n'
+ ' val\n'
+ ' property: value\n'
+ ' prop2: val2\n'
+ ' propriété: [valeur]\n'
+ ' o:\n'
+ ' k1: [v1, v2]\n', conf)
+ self.check('---\n'
+ 'obj:\n'
+ ' p:\n'
+ ' - k1: >\n'
+ ' val\n'
+ ' - k3: >\n'
+ ' val\n'
+ ' - o: {k1: v1}\n'
+ ' - o: {k1: v1}\n'
+ ' - q2:\n'
+ ' - val1\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'a: {b: {c: d, e : f}}\n', conf)
+
+ def test_before_enabled(self):
+ conf = 'colons: {max-spaces-before: 0, max-spaces-after: -1}'
+ self.check('---\n'
+ 'object:\n'
+ ' k1:\n'
+ ' - a\n'
+ ' - b\n'
+ ' k2: v2\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' k1 :\n'
+ ' - a\n'
+ ' - b\n'
+ ' k2: v2\n'
+ '...\n', conf, problem=(3, 5))
+ self.check('---\n'
+ 'lib :\n'
+ ' - var\n'
+ '...\n', conf, problem=(2, 4))
+ self.check('---\n'
+ '- lib :\n'
+ ' - var\n'
+ '...\n', conf, problem=(2, 6))
+ self.check('---\n'
+ 'a: {b: {c : d, e : f}}\n', conf,
+ problem1=(2, 10), problem2=(2, 17))
+
+ def test_before_max(self):
+ conf = 'colons: {max-spaces-before: 3, max-spaces-after: -1}'
+ self.check('---\n'
+ 'object :\n'
+ ' k1 :\n'
+ ' - a\n'
+ ' - b\n'
+ ' k2 : v2\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'object :\n'
+ ' k1 :\n'
+ ' - a\n'
+ ' - b\n'
+ ' k2 : v2\n'
+ '...\n', conf, problem=(3, 8))
+
+ def test_before_with_explicit_block_mappings(self):
+ conf = 'colons: {max-spaces-before: 0, max-spaces-after: 1}'
+ self.check('---\n'
+ 'object:\n'
+ ' ? key\n'
+ ' : value\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'object :\n'
+ ' ? key\n'
+ ' : value\n'
+ '...\n', conf, problem=(2, 7))
+ self.check('---\n'
+ '? >\n'
+ ' multi-line\n'
+ ' key\n'
+ ': >\n'
+ ' multi-line\n'
+ ' value\n'
+ '...\n', conf)
+ self.check('---\n'
+ '- ? >\n'
+ ' multi-line\n'
+ ' key\n'
+ ' : >\n'
+ ' multi-line\n'
+ ' value\n'
+ '...\n', conf)
+ self.check('---\n'
+ '- ? >\n'
+ ' multi-line\n'
+ ' key\n'
+ ' : >\n'
+ ' multi-line\n'
+ ' value\n'
+ '...\n', conf, problem=(5, 5))
+
+ def test_after_enabled(self):
+ conf = 'colons: {max-spaces-before: -1, max-spaces-after: 1}'
+ self.check('---\n'
+ 'key: value\n', conf)
+ self.check('---\n'
+ 'key: value\n', conf, problem=(2, 6))
+ self.check('---\n'
+ 'object:\n'
+ ' k1: [a, b]\n'
+ ' k2: string\n', conf, problem=(3, 7))
+ self.check('---\n'
+ 'object:\n'
+ ' k1: [a, b]\n'
+ ' k2: string\n', conf, problem=(4, 7))
+ self.check('---\n'
+ 'object:\n'
+ ' other: {key: value}\n'
+ '...\n', conf, problem=(3, 16))
+ self.check('---\n'
+ 'a: {b: {c: d, e : f}}\n', conf,
+ problem1=(2, 12), problem2=(2, 20))
+
+ def test_after_enabled_question_mark(self):
+ conf = 'colons: {max-spaces-before: -1, max-spaces-after: 1}'
+ self.check('---\n'
+ '? key\n'
+ ': value\n', conf)
+ self.check('---\n'
+ '? key\n'
+ ': value\n', conf, problem=(2, 3))
+ self.check('---\n'
+ '? key\n'
+ ': value\n', conf, problem1=(2, 3), problem2=(3, 3))
+ self.check('---\n'
+ '- ? key\n'
+ ' : value\n', conf, problem1=(2, 5), problem2=(3, 5))
+
+ def test_after_max(self):
+ conf = 'colons: {max-spaces-before: -1, max-spaces-after: 3}'
+ self.check('---\n'
+ 'object:\n'
+ ' k1: [a, b]\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' k1: [a, b]\n', conf, problem=(3, 9))
+ self.check('---\n'
+ 'object:\n'
+ ' k2: string\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' k2: string\n', conf, problem=(3, 9))
+ self.check('---\n'
+ 'object:\n'
+ ' other: {key: value}\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' other: {key: value}\n'
+ '...\n', conf, problem=(3, 18))
+
+ def test_after_with_explicit_block_mappings(self):
+ conf = 'colons: {max-spaces-before: -1, max-spaces-after: 1}'
+ self.check('---\n'
+ 'object:\n'
+ ' ? key\n'
+ ' : value\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' ? key\n'
+ ' : value\n'
+ '...\n', conf, problem=(4, 5))
+
+ def test_after_do_not_confound_with_trailing_space(self):
+ conf = ('colons: {max-spaces-before: 1, max-spaces-after: 1}\n'
+ 'trailing-spaces: disable\n')
+ self.check('---\n'
+ 'trailing: \n'
+ ' - spaces\n', conf)
+
+ def test_both_before_and_after(self):
+ conf = 'colons: {max-spaces-before: 0, max-spaces-after: 1}'
+ self.check('---\n'
+ 'obj:\n'
+ ' string: text\n'
+ ' k:\n'
+ ' - 8\n'
+ ' k3:\n'
+ ' val\n'
+ ' property: [value]\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' k1 : v1\n', conf, problem1=(3, 5), problem2=(3, 8))
+ self.check('---\n'
+ 'obj:\n'
+ ' string: text\n'
+ ' k :\n'
+ ' - 8\n'
+ ' k3:\n'
+ ' val\n'
+ ' property: {a: 1, b: 2, c : 3}\n', conf,
+ problem1=(3, 11), problem2=(4, 4),
+ problem3=(8, 23), problem4=(8, 28))
+
+ # Although accepted by PyYAML, `{*x: 4}` is not valid YAML: it should be
+ # noted `{*x : 4}`. The reason is that a colon can be part of an anchor
+ # name. See commit message for more details.
+ def test_with_alias_as_key(self):
+ conf = 'colons: {max-spaces-before: 0, max-spaces-after: 1}'
+ self.check('---\n'
+ '- anchor: &a key\n'
+ '- *a: 42\n'
+ '- {*a: 42}\n'
+ '- *a : 42\n'
+ '- {*a : 42}\n'
+ '- *a : 42\n'
+ '- {*a : 42}\n',
+ conf,
+ problem1=(7, 6), problem2=(8, 7))
diff --git a/tests/rules/test_commas.py b/tests/rules/test_commas.py
new file mode 100644
index 0000000..0d0abd8
--- /dev/null
+++ b/tests/rules/test_commas.py
@@ -0,0 +1,264 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class CommaTestCase(RuleTestCase):
+ rule_id = 'commas'
+
+ def test_disabled(self):
+ conf = 'commas: disable'
+ self.check('---\n'
+ 'dict: {a: b , c: "1 2 3", d: e , f: [g, h]}\n'
+ 'array: [\n'
+ ' elem ,\n'
+ ' key: val ,\n'
+ ']\n'
+ 'map: {\n'
+ ' key1: val1 ,\n'
+ ' key2: val2,\n'
+ '}\n'
+ '...\n', conf)
+ self.check('---\n'
+ '- [one, two , three,four]\n'
+ '- {five,six , seven, eight}\n'
+ '- [\n'
+ ' nine, ten\n'
+ ' , eleven\n'
+ ' ,twelve\n'
+ ']\n'
+ '- {\n'
+ ' thirteen: 13, fourteen\n'
+ ' , fifteen: 15\n'
+ ' ,sixteen: 16\n'
+ '}\n', conf)
+
+ def test_before_max(self):
+ conf = ('commas:\n'
+ ' max-spaces-before: 0\n'
+ ' min-spaces-after: 0\n'
+ ' max-spaces-after: -1\n')
+ self.check('---\n'
+ 'array: [1, 2, 3, 4]\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'array: [1, 2 , 3, 4]\n'
+ '...\n', conf, problem=(2, 13))
+ self.check('---\n'
+ 'array: [1 , 2, 3 , 4]\n'
+ '...\n', conf, problem1=(2, 10), problem2=(2, 23))
+ self.check('---\n'
+ 'dict: {a: b, c: "1 2 3", d: e, f: [g, h]}\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'dict: {a: b, c: "1 2 3" , d: e, f: [g, h]}\n'
+ '...\n', conf, problem=(2, 24))
+ self.check('---\n'
+ 'dict: {a: b , c: "1 2 3", d: e, f: [g , h]}\n'
+ '...\n', conf, problem1=(2, 12), problem2=(2, 42))
+ self.check('---\n'
+ 'array: [\n'
+ ' elem,\n'
+ ' key: val,\n'
+ ']\n', conf)
+ self.check('---\n'
+ 'array: [\n'
+ ' elem ,\n'
+ ' key: val,\n'
+ ']\n', conf, problem=(3, 7))
+ self.check('---\n'
+ 'map: {\n'
+ ' key1: val1,\n'
+ ' key2: val2,\n'
+ '}\n', conf)
+ self.check('---\n'
+ 'map: {\n'
+ ' key1: val1,\n'
+ ' key2: val2 ,\n'
+ '}\n', conf, problem=(4, 13))
+
+ def test_before_max_with_comma_on_new_line(self):
+ conf = ('commas:\n'
+ ' max-spaces-before: 0\n'
+ ' min-spaces-after: 0\n'
+ ' max-spaces-after: -1\n')
+ self.check('---\n'
+ 'flow-seq: [1, 2, 3\n'
+ ' , 4, 5, 6]\n'
+ '...\n', conf, problem=(3, 11))
+ self.check('---\n'
+ 'flow-map: {a: 1, b: 2\n'
+ ' , c: 3}\n'
+ '...\n', conf, problem=(3, 11))
+
+ conf = ('commas:\n'
+ ' max-spaces-before: 0\n'
+ ' min-spaces-after: 0\n'
+ ' max-spaces-after: -1\n'
+ 'indentation: disable\n')
+ self.check('---\n'
+ 'flow-seq: [1, 2, 3\n'
+ ' , 4, 5, 6]\n'
+ '...\n', conf, problem=(3, 9))
+ self.check('---\n'
+ 'flow-map: {a: 1, b: 2\n'
+ ' , c: 3}\n'
+ '...\n', conf, problem=(3, 9))
+ self.check('---\n'
+ '[\n'
+ '1,\n'
+ '2\n'
+ ', 3\n'
+ ']\n', conf, problem=(5, 1))
+ self.check('---\n'
+ '{\n'
+ 'a: 1,\n'
+ 'b: 2\n'
+ ', c: 3\n'
+ '}\n', conf, problem=(5, 1))
+
+ def test_before_max_3(self):
+ conf = ('commas:\n'
+ ' max-spaces-before: 3\n'
+ ' min-spaces-after: 0\n'
+ ' max-spaces-after: -1\n')
+ self.check('---\n'
+ 'array: [1 , 2, 3 , 4]\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'array: [1 , 2, 3 , 4]\n'
+ '...\n', conf, problem=(2, 20))
+ self.check('---\n'
+ 'array: [\n'
+ ' elem1 ,\n'
+ ' elem2 ,\n'
+ ' key: val,\n'
+ ']\n', conf, problem=(4, 11))
+
+ def test_after_min(self):
+ conf = ('commas:\n'
+ ' max-spaces-before: -1\n'
+ ' min-spaces-after: 1\n'
+ ' max-spaces-after: -1\n')
+ self.check('---\n'
+ '- [one, two , three,four]\n'
+ '- {five,six , seven, eight}\n'
+ '- [\n'
+ ' nine, ten\n'
+ ' , eleven\n'
+ ' ,twelve\n'
+ ']\n'
+ '- {\n'
+ ' thirteen: 13, fourteen\n'
+ ' , fifteen: 15\n'
+ ' ,sixteen: 16\n'
+ '}\n', conf,
+ problem1=(2, 21), problem2=(3, 9),
+ problem3=(7, 4), problem4=(12, 4))
+
+ def test_after_max(self):
+ conf = ('commas:\n'
+ ' max-spaces-before: -1\n'
+ ' min-spaces-after: 0\n'
+ ' max-spaces-after: 1\n')
+ self.check('---\n'
+ 'array: [1, 2, 3, 4]\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'array: [1, 2, 3, 4]\n'
+ '...\n', conf, problem=(2, 15))
+ self.check('---\n'
+ 'array: [1, 2, 3, 4]\n'
+ '...\n', conf, problem1=(2, 12), problem2=(2, 22))
+ self.check('---\n'
+ 'dict: {a: b , c: "1 2 3", d: e, f: [g, h]}\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'dict: {a: b , c: "1 2 3", d: e, f: [g, h]}\n'
+ '...\n', conf, problem=(2, 27))
+ self.check('---\n'
+ 'dict: {a: b , c: "1 2 3", d: e, f: [g, h]}\n'
+ '...\n', conf, problem1=(2, 15), problem2=(2, 44))
+ self.check('---\n'
+ 'array: [\n'
+ ' elem,\n'
+ ' key: val,\n'
+ ']\n', conf)
+ self.check('---\n'
+ 'array: [\n'
+ ' elem, key: val,\n'
+ ']\n', conf, problem=(3, 9))
+ self.check('---\n'
+ 'map: {\n'
+ ' key1: val1, key2: [val2, val3]\n'
+ '}\n', conf, problem1=(3, 16), problem2=(3, 30))
+
+ def test_after_max_3(self):
+ conf = ('commas:\n'
+ ' max-spaces-before: -1\n'
+ ' min-spaces-after: 1\n'
+ ' max-spaces-after: 3\n')
+ self.check('---\n'
+ 'array: [1, 2, 3, 4]\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'array: [1, 2, 3, 4]\n'
+ '...\n', conf, problem=(2, 21))
+ self.check('---\n'
+ 'dict: {a: b , c: "1 2 3", d: e, f: [g, h]}\n'
+ '...\n', conf, problem1=(2, 31), problem2=(2, 49))
+
+ def test_both_before_and_after(self):
+ conf = ('commas:\n'
+ ' max-spaces-before: 0\n'
+ ' min-spaces-after: 1\n'
+ ' max-spaces-after: 1\n')
+ self.check('---\n'
+ 'dict: {a: b , c: "1 2 3", d: e , f: [g, h]}\n'
+ 'array: [\n'
+ ' elem ,\n'
+ ' key: val ,\n'
+ ']\n'
+ 'map: {\n'
+ ' key1: val1 ,\n'
+ ' key2: val2,\n'
+ '}\n'
+ '...\n', conf,
+ problem1=(2, 12), problem2=(2, 16), problem3=(2, 31),
+ problem4=(2, 36), problem5=(2, 50), problem6=(4, 8),
+ problem7=(5, 11), problem8=(8, 13))
+ conf = ('commas:\n'
+ ' max-spaces-before: 0\n'
+ ' min-spaces-after: 1\n'
+ ' max-spaces-after: 1\n'
+ 'indentation: disable\n')
+ self.check('---\n'
+ '- [one, two , three,four]\n'
+ '- {five,six , seven, eight}\n'
+ '- [\n'
+ ' nine, ten\n'
+ ' , eleven\n'
+ ' ,twelve\n'
+ ']\n'
+ '- {\n'
+ ' thirteen: 13, fourteen\n'
+ ' , fifteen: 15\n'
+ ' ,sixteen: 16\n'
+ '}\n', conf,
+ problem1=(2, 12), problem2=(2, 21), problem3=(3, 9),
+ problem4=(3, 12), problem5=(5, 9), problem6=(6, 2),
+ problem7=(7, 2), problem8=(7, 4), problem9=(10, 17),
+ problem10=(11, 2), problem11=(12, 2), problem12=(12, 4))
diff --git a/tests/rules/test_comments.py b/tests/rules/test_comments.py
new file mode 100644
index 0000000..1f5a20c
--- /dev/null
+++ b/tests/rules/test_comments.py
@@ -0,0 +1,236 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class CommentsTestCase(RuleTestCase):
+ rule_id = 'comments'
+
+ def test_disabled(self):
+ conf = ('comments: disable\n'
+ 'comments-indentation: disable\n')
+ self.check('---\n'
+ '#comment\n'
+ '\n'
+ 'test: # description\n'
+ ' - foo # bar\n'
+ ' - hello #world\n'
+ '\n'
+ '# comment 2\n'
+ '#comment 3\n'
+ ' #comment 3 bis\n'
+ ' # comment 3 ter\n'
+ '\n'
+ '################################\n'
+ '## comment 4\n'
+ '##comment 5\n'
+ '\n'
+ 'string: "Une longue phrase." # this is French\n', conf)
+
+ def test_starting_space(self):
+ conf = ('comments:\n'
+ ' require-starting-space: true\n'
+ ' min-spaces-from-content: -1\n'
+ 'comments-indentation: disable\n')
+ self.check('---\n'
+ '# comment\n'
+ '\n'
+ 'test: # description\n'
+ ' - foo # bar\n'
+ ' - hello # world\n'
+ '\n'
+ '# comment 2\n'
+ '# comment 3\n'
+ ' # comment 3 bis\n'
+ ' # comment 3 ter\n'
+ '\n'
+ '################################\n'
+ '## comment 4\n'
+ '## comment 5\n', conf)
+ self.check('---\n'
+ '#comment\n'
+ '\n'
+ 'test: # description\n'
+ ' - foo # bar\n'
+ ' - hello #world\n'
+ '\n'
+ '# comment 2\n'
+ '#comment 3\n'
+ ' #comment 3 bis\n'
+ ' # comment 3 ter\n'
+ '\n'
+ '################################\n'
+ '## comment 4\n'
+ '##comment 5\n', conf,
+ problem1=(2, 2), problem2=(6, 13),
+ problem3=(9, 2), problem4=(10, 4),
+ problem5=(15, 3))
+
+ def test_shebang(self):
+ conf = ('comments:\n'
+ ' require-starting-space: true\n'
+ ' ignore-shebangs: false\n'
+ 'comments-indentation: disable\n'
+ 'document-start: disable\n')
+ self.check('#!/bin/env my-interpreter\n',
+ conf, problem1=(1, 2))
+ self.check('# comment\n'
+ '#!/bin/env my-interpreter\n', conf,
+ problem1=(2, 2))
+ self.check('#!/bin/env my-interpreter\n'
+ '---\n'
+ '#comment\n'
+ '#!/bin/env my-interpreter\n'
+ '', conf,
+ problem1=(1, 2), problem2=(3, 2), problem3=(4, 2))
+ self.check('#! is a valid shebang too\n',
+ conf, problem1=(1, 2))
+ self.check('key: #!/not/a/shebang\n',
+ conf, problem1=(1, 8))
+
+ def test_ignore_shebang(self):
+ conf = ('comments:\n'
+ ' require-starting-space: true\n'
+ ' ignore-shebangs: true\n'
+ 'comments-indentation: disable\n'
+ 'document-start: disable\n')
+ self.check('#!/bin/env my-interpreter\n', conf)
+ self.check('# comment\n'
+ '#!/bin/env my-interpreter\n', conf,
+ problem1=(2, 2))
+ self.check('#!/bin/env my-interpreter\n'
+ '---\n'
+ '#comment\n'
+ '#!/bin/env my-interpreter\n', conf,
+ problem2=(3, 2), problem3=(4, 2))
+ self.check('#! is a valid shebang too\n', conf)
+ self.check('key: #!/not/a/shebang\n',
+ conf, problem1=(1, 8))
+
+ def test_spaces_from_content(self):
+ conf = ('comments:\n'
+ ' require-starting-space: false\n'
+ ' min-spaces-from-content: 2\n')
+ self.check('---\n'
+ '# comment\n'
+ '\n'
+ 'test: # description\n'
+ ' - foo # bar\n'
+ ' - hello #world\n'
+ '\n'
+ 'string: "Une longue phrase." # this is French\n', conf)
+ self.check('---\n'
+ '# comment\n'
+ '\n'
+ 'test: # description\n'
+ ' - foo # bar\n'
+ ' - hello #world\n'
+ '\n'
+ 'string: "Une longue phrase." # this is French\n', conf,
+ problem1=(4, 7), problem2=(6, 11), problem3=(8, 30))
+
+ def test_both(self):
+ conf = ('comments:\n'
+ ' require-starting-space: true\n'
+ ' min-spaces-from-content: 2\n'
+ 'comments-indentation: disable\n')
+ self.check('---\n'
+ '#comment\n'
+ '\n'
+ 'test: # description\n'
+ ' - foo # bar\n'
+ ' - hello #world\n'
+ '\n'
+ '# comment 2\n'
+ '#comment 3\n'
+ ' #comment 3 bis\n'
+ ' # comment 3 ter\n'
+ '\n'
+ '################################\n'
+ '## comment 4\n'
+ '##comment 5\n'
+ '\n'
+ 'string: "Une longue phrase." # this is French\n', conf,
+ problem1=(2, 2),
+ problem2=(4, 7),
+ problem3=(6, 11), problem4=(6, 12),
+ problem5=(9, 2),
+ problem6=(10, 4),
+ problem7=(15, 3),
+ problem8=(17, 30))
+
+ def test_empty_comment(self):
+ conf = ('comments:\n'
+ ' require-starting-space: true\n'
+ ' min-spaces-from-content: 2\n')
+ self.check('---\n'
+ '# This is paragraph 1.\n'
+ '#\n'
+ '# This is paragraph 2.\n', conf)
+ self.check('---\n'
+ 'inline: comment #\n'
+ 'foo: bar\n', conf)
+
+ def test_empty_comment_crlf_dos_newlines(self):
+ conf = ('comments:\n'
+ ' require-starting-space: true\n'
+ ' min-spaces-from-content: 2\n'
+ 'new-lines:\n'
+ ' type: dos\n')
+ self.check('---\r\n'
+ '# This is paragraph 1.\r\n'
+ '#\r\n'
+ '# This is paragraph 2.\r\n', conf)
+
+ def test_empty_comment_crlf_disabled_newlines(self):
+ conf = ('comments:\n'
+ ' require-starting-space: true\n'
+ ' min-spaces-from-content: 2\n'
+ 'new-lines: disable\n')
+ self.check('---\r\n'
+ '# This is paragraph 1.\r\n'
+ '#\r\n'
+ '# This is paragraph 2.\r\n', conf)
+
+ def test_first_line(self):
+ conf = ('comments:\n'
+ ' require-starting-space: true\n'
+ ' min-spaces-from-content: 2\n')
+ self.check('# comment\n', conf)
+
+ def test_last_line(self):
+ conf = ('comments:\n'
+ ' require-starting-space: true\n'
+ ' min-spaces-from-content: 2\n'
+ 'new-line-at-end-of-file: disable\n')
+ self.check('# comment with no newline char:\n'
+ '#', conf)
+
+ def test_multi_line_scalar(self):
+ conf = ('comments:\n'
+ ' require-starting-space: true\n'
+ ' min-spaces-from-content: 2\n'
+ 'trailing-spaces: disable\n')
+ self.check('---\n'
+ 'string: >\n'
+ ' this is plain text\n'
+ '\n'
+ '# comment\n', conf)
+ self.check('---\n'
+ '- string: >\n'
+ ' this is plain text\n'
+ ' \n'
+ ' # comment\n', conf)
diff --git a/tests/rules/test_comments_indentation.py b/tests/rules/test_comments_indentation.py
new file mode 100644
index 0000000..0aa5aac
--- /dev/null
+++ b/tests/rules/test_comments_indentation.py
@@ -0,0 +1,156 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class CommentsIndentationTestCase(RuleTestCase):
+ rule_id = 'comments-indentation'
+
+ def test_disable(self):
+ conf = 'comments-indentation: disable'
+ self.check('---\n'
+ ' # line 1\n'
+ '# line 2\n'
+ ' # line 3\n'
+ ' # line 4\n'
+ '\n'
+ 'obj:\n'
+ ' # these\n'
+ ' # are\n'
+ ' # [good]\n'
+ '# bad\n'
+ ' # comments\n'
+ ' a: b\n'
+ '\n'
+ 'obj1:\n'
+ ' a: 1\n'
+ ' # comments\n'
+ '\n'
+ 'obj2:\n'
+ ' b: 2\n'
+ '\n'
+ '# empty\n'
+ '#\n'
+ '# comment\n'
+ '...\n', conf)
+
+ def test_enabled(self):
+ conf = 'comments-indentation: enable'
+ self.check('---\n'
+ '# line 1\n'
+ '# line 2\n', conf)
+ self.check('---\n'
+ ' # line 1\n'
+ '# line 2\n', conf, problem=(2, 2))
+ self.check('---\n'
+ ' # line 1\n'
+ ' # line 2\n', conf, problem1=(2, 3))
+ self.check('---\n'
+ 'obj:\n'
+ ' # normal\n'
+ ' a: b\n', conf)
+ self.check('---\n'
+ 'obj:\n'
+ ' # bad\n'
+ ' a: b\n', conf, problem=(3, 2))
+ self.check('---\n'
+ 'obj:\n'
+ '# bad\n'
+ ' a: b\n', conf, problem=(3, 1))
+ self.check('---\n'
+ 'obj:\n'
+ ' # bad\n'
+ ' a: b\n', conf, problem=(3, 4))
+ self.check('---\n'
+ 'obj:\n'
+ ' # these\n'
+ ' # are\n'
+ ' # [good]\n'
+ '# bad\n'
+ ' # comments\n'
+ ' a: b\n', conf,
+ problem1=(3, 2), problem2=(4, 4),
+ problem3=(6, 1), problem4=(7, 7))
+ self.check('---\n'
+ 'obj1:\n'
+ ' a: 1\n'
+ ' # the following line is disabled\n'
+ ' # b: 2\n', conf)
+ self.check('---\n'
+ 'obj1:\n'
+ ' a: 1\n'
+ ' # b: 2\n'
+ '\n'
+ 'obj2:\n'
+ ' b: 2\n', conf)
+ self.check('---\n'
+ 'obj1:\n'
+ ' a: 1\n'
+ ' # b: 2\n'
+ '# this object is useless\n'
+ 'obj2: "no"\n', conf)
+ self.check('---\n'
+ 'obj1:\n'
+ ' a: 1\n'
+ '# this object is useless\n'
+ ' # b: 2\n'
+ 'obj2: "no"\n', conf, problem=(5, 3))
+ self.check('---\n'
+ 'obj1:\n'
+ ' a: 1\n'
+ ' # comments\n'
+ ' b: 2\n', conf)
+ self.check('---\n'
+ 'my list for today:\n'
+ ' - todo 1\n'
+ ' - todo 2\n'
+ ' # commented for now\n'
+ ' # - todo 3\n'
+ '...\n', conf)
+
+ def test_first_line(self):
+ conf = 'comments-indentation: enable'
+ self.check('# comment\n', conf)
+ self.check(' # comment\n', conf, problem=(1, 3))
+
+ def test_no_newline_at_end(self):
+ conf = ('comments-indentation: enable\n'
+ 'new-line-at-end-of-file: disable\n')
+ self.check('# comment', conf)
+ self.check(' # comment', conf, problem=(1, 3))
+
+ def test_empty_comment(self):
+ conf = 'comments-indentation: enable'
+ self.check('---\n'
+ '# hey\n'
+ '# normal\n'
+ '#\n', conf)
+ self.check('---\n'
+ '# hey\n'
+ '# normal\n'
+ ' #\n', conf, problem=(4, 2))
+
+ def test_inline_comment(self):
+ conf = 'comments-indentation: enable'
+ self.check('---\n'
+ '- a # inline\n'
+ '# ok\n', conf)
+ self.check('---\n'
+ '- a # inline\n'
+ ' # not ok\n', conf, problem=(3, 2))
+ self.check('---\n'
+ ' # not ok\n'
+ '- a # inline\n', conf, problem=(2, 2))
diff --git a/tests/rules/test_common.py b/tests/rules/test_common.py
new file mode 100644
index 0000000..196b419
--- /dev/null
+++ b/tests/rules/test_common.py
@@ -0,0 +1,43 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+import unittest
+
+import yaml
+
+from yamllint.rules.common import get_line_indent
+
+
+class CommonTestCase(unittest.TestCase):
+ def test_get_line_indent(self):
+ tokens = list(yaml.scan('a: 1\n'
+ 'b:\n'
+ ' - c: [2, 3, {d: 4}]\n'))
+
+ self.assertEqual(tokens[3].value, 'a')
+ self.assertEqual(tokens[5].value, '1')
+ self.assertEqual(tokens[7].value, 'b')
+ self.assertEqual(tokens[13].value, 'c')
+ self.assertEqual(tokens[16].value, '2')
+ self.assertEqual(tokens[18].value, '3')
+ self.assertEqual(tokens[22].value, 'd')
+ self.assertEqual(tokens[24].value, '4')
+
+ for i in (3, 5):
+ self.assertEqual(get_line_indent(tokens[i]), 0)
+ for i in (7,):
+ self.assertEqual(get_line_indent(tokens[i]), 0)
+ for i in (13, 16, 18, 22, 24):
+ self.assertEqual(get_line_indent(tokens[i]), 2)
diff --git a/tests/rules/test_document_end.py b/tests/rules/test_document_end.py
new file mode 100644
index 0000000..8340c6f
--- /dev/null
+++ b/tests/rules/test_document_end.py
@@ -0,0 +1,92 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class DocumentEndTestCase(RuleTestCase):
+ rule_id = 'document-end'
+
+ def test_disabled(self):
+ conf = 'document-end: disable'
+ self.check('---\n'
+ 'with:\n'
+ ' document: end\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'without:\n'
+ ' document: end\n', conf)
+
+ def test_required(self):
+ conf = 'document-end: {present: true}'
+ self.check('', conf)
+ self.check('\n', conf)
+ self.check('---\n'
+ 'with:\n'
+ ' document: end\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'without:\n'
+ ' document: end\n', conf, problem=(3, 1))
+
+ def test_forbidden(self):
+ conf = 'document-end: {present: false}'
+ self.check('---\n'
+ 'with:\n'
+ ' document: end\n'
+ '...\n', conf, problem=(4, 1))
+ self.check('---\n'
+ 'without:\n'
+ ' document: end\n', conf)
+
+ def test_multiple_documents(self):
+ conf = ('document-end: {present: true}\n'
+ 'document-start: disable\n')
+ self.check('---\n'
+ 'first: document\n'
+ '...\n'
+ '---\n'
+ 'second: document\n'
+ '...\n'
+ '---\n'
+ 'third: document\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'first: document\n'
+ '...\n'
+ '---\n'
+ 'second: document\n'
+ '---\n'
+ 'third: document\n'
+ '...\n', conf, problem=(6, 1))
+
+ def test_directives(self):
+ conf = 'document-end: {present: true}'
+ self.check('%YAML 1.2\n'
+ '---\n'
+ 'document: end\n'
+ '...\n', conf)
+ self.check('%YAML 1.2\n'
+ '%TAG ! tag:clarkevans.com,2002:\n'
+ '---\n'
+ 'document: end\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'first: document\n'
+ '...\n'
+ '%YAML 1.2\n'
+ '---\n'
+ 'second: document\n'
+ '...\n', conf)
diff --git a/tests/rules/test_document_start.py b/tests/rules/test_document_start.py
new file mode 100644
index 0000000..ee2e9d3
--- /dev/null
+++ b/tests/rules/test_document_start.py
@@ -0,0 +1,103 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class DocumentStartTestCase(RuleTestCase):
+ rule_id = 'document-start'
+
+ def test_disabled(self):
+ conf = 'document-start: disable'
+ self.check('', conf)
+ self.check('key: val\n', conf)
+ self.check('---\n'
+ 'key: val\n', conf)
+
+ def test_required(self):
+ conf = ('document-start: {present: true}\n'
+ 'empty-lines: disable\n')
+ self.check('', conf)
+ self.check('\n', conf)
+ self.check('key: val\n', conf, problem=(1, 1))
+ self.check('\n'
+ '\n'
+ 'key: val\n', conf, problem=(3, 1))
+ self.check('---\n'
+ 'key: val\n', conf)
+ self.check('\n'
+ '\n'
+ '---\n'
+ 'key: val\n', conf)
+
+ def test_forbidden(self):
+ conf = ('document-start: {present: false}\n'
+ 'empty-lines: disable\n')
+ self.check('', conf)
+ self.check('key: val\n', conf)
+ self.check('\n'
+ '\n'
+ 'key: val\n', conf)
+ self.check('---\n'
+ 'key: val\n', conf, problem=(1, 1))
+ self.check('\n'
+ '\n'
+ '---\n'
+ 'key: val\n', conf, problem=(3, 1))
+ self.check('first: document\n'
+ '---\n'
+ 'key: val\n', conf, problem=(2, 1))
+
+ def test_multiple_documents(self):
+ conf = 'document-start: {present: true}'
+ self.check('---\n'
+ 'first: document\n'
+ '...\n'
+ '---\n'
+ 'second: document\n'
+ '...\n'
+ '---\n'
+ 'third: document\n', conf)
+ self.check('---\n'
+ 'first: document\n'
+ '---\n'
+ 'second: document\n'
+ '---\n'
+ 'third: document\n', conf)
+ self.check('---\n'
+ 'first: document\n'
+ '...\n'
+ 'second: document\n'
+ '---\n'
+ 'third: document\n', conf, problem=(4, 1, 'syntax'))
+
+ def test_directives(self):
+ conf = 'document-start: {present: true}'
+ self.check('%YAML 1.2\n'
+ '---\n'
+ 'doc: ument\n'
+ '...\n', conf)
+ self.check('%YAML 1.2\n'
+ '%TAG ! tag:clarkevans.com,2002:\n'
+ '---\n'
+ 'doc: ument\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'doc: 1\n'
+ '...\n'
+ '%YAML 1.2\n'
+ '---\n'
+ 'doc: 2\n'
+ '...\n', conf)
diff --git a/tests/rules/test_empty_lines.py b/tests/rules/test_empty_lines.py
new file mode 100644
index 0000000..fbecbcd
--- /dev/null
+++ b/tests/rules/test_empty_lines.py
@@ -0,0 +1,98 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class EmptyLinesTestCase(RuleTestCase):
+ rule_id = 'empty-lines'
+
+ def test_disabled(self):
+ conf = ('empty-lines: disable\n'
+ 'new-line-at-end-of-file: disable\n'
+ 'document-start: disable\n')
+ self.check('', conf)
+ self.check('\n', conf)
+ self.check('\n\n', conf)
+ self.check('\n\n\n\n\n\n\n\n\n', conf)
+ self.check('some text\n\n\n\n\n\n\n\n\n', conf)
+ self.check('\n\n\n\n\n\n\n\n\nsome text', conf)
+ self.check('\n\n\nsome text\n\n\n', conf)
+
+ def test_empty_document(self):
+ conf = ('empty-lines: {max: 0, max-start: 0, max-end: 0}\n'
+ 'new-line-at-end-of-file: disable\n'
+ 'document-start: disable\n')
+ self.check('', conf)
+ self.check('\n', conf)
+
+ def test_0_empty_lines(self):
+ conf = ('empty-lines: {max: 0, max-start: 0, max-end: 0}\n'
+ 'new-line-at-end-of-file: disable\n')
+ self.check('---\n', conf)
+ self.check('---\ntext\n\ntext', conf, problem=(3, 1))
+ self.check('---\ntext\n\ntext\n', conf, problem=(3, 1))
+
+ def test_10_empty_lines(self):
+ conf = 'empty-lines: {max: 10, max-start: 0, max-end: 0}'
+ self.check('---\nintro\n\n\n\n\n\n\n\n\n\n\nconclusion\n', conf)
+ self.check('---\nintro\n\n\n\n\n\n\n\n\n\n\n\nconclusion\n', conf,
+ problem=(13, 1))
+
+ def test_spaces(self):
+ conf = ('empty-lines: {max: 1, max-start: 0, max-end: 0}\n'
+ 'trailing-spaces: disable\n')
+ self.check('---\nintro\n\n \n\nconclusion\n', conf)
+ self.check('---\nintro\n\n \n\n\nconclusion\n', conf, problem=(6, 1))
+
+ def test_empty_lines_at_start(self):
+ conf = ('empty-lines: {max: 2, max-start: 4, max-end: 0}\n'
+ 'document-start: disable\n')
+ self.check('\n\n\n\nnon empty\n', conf)
+ self.check('\n\n\n\n\nnon empty\n', conf, problem=(5, 1))
+
+ conf = ('empty-lines: {max: 2, max-start: 0, max-end: 0}\n'
+ 'document-start: disable\n')
+ self.check('non empty\n', conf)
+ self.check('\nnon empty\n', conf, problem=(1, 1))
+
+ def test_empty_lines_at_end(self):
+ conf = ('empty-lines: {max: 2, max-start: 0, max-end: 4}\n'
+ 'document-start: disable\n')
+ self.check('non empty\n\n\n\n\n', conf)
+ self.check('non empty\n\n\n\n\n\n', conf, problem=(6, 1))
+ conf = ('empty-lines: {max: 2, max-start: 0, max-end: 0}\n'
+ 'document-start: disable\n')
+ self.check('non empty\n', conf)
+ self.check('non empty\n\n', conf, problem=(2, 1))
+
+ def test_with_dos_newlines(self):
+ conf = ('empty-lines: {max: 2, max-start: 0, max-end: 0}\n'
+ 'new-lines: {type: dos}\n'
+ 'document-start: disable\n')
+ self.check('---\r\n', conf)
+ self.check('---\r\ntext\r\n\r\ntext\r\n', conf)
+ self.check('\r\n---\r\ntext\r\n\r\ntext\r\n', conf,
+ problem=(1, 1))
+ self.check('\r\n\r\n\r\n---\r\ntext\r\n\r\ntext\r\n', conf,
+ problem=(3, 1))
+ self.check('---\r\ntext\r\n\r\n\r\n\r\ntext\r\n', conf,
+ problem=(5, 1))
+ self.check('---\r\ntext\r\n\r\n\r\n\r\n\r\n\r\n\r\ntext\r\n', conf,
+ problem=(8, 1))
+ self.check('---\r\ntext\r\n\r\ntext\r\n\r\n', conf,
+ problem=(5, 1))
+ self.check('---\r\ntext\r\n\r\ntext\r\n\r\n\r\n\r\n', conf,
+ problem=(7, 1))
diff --git a/tests/rules/test_empty_values.py b/tests/rules/test_empty_values.py
new file mode 100644
index 0000000..653f218
--- /dev/null
+++ b/tests/rules/test_empty_values.py
@@ -0,0 +1,368 @@
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class EmptyValuesTestCase(RuleTestCase):
+ rule_id = 'empty-values'
+
+ def test_disabled(self):
+ conf = ('empty-values: disable\n'
+ 'braces: disable\n'
+ 'commas: disable\n')
+ self.check('---\n'
+ 'foo:\n', conf)
+ self.check('---\n'
+ 'foo:\n'
+ ' bar:\n', conf)
+ self.check('---\n'
+ '{a:}\n', conf)
+ self.check('---\n'
+ 'foo: {a:}\n', conf)
+ self.check('---\n'
+ '- {a:}\n'
+ '- {a:, b: 2}\n'
+ '- {a: 1, b:}\n'
+ '- {a: 1, b: , }\n', conf)
+ self.check('---\n'
+ '{a: {b: , c: {d: 4, e:}}, f:}\n', conf)
+
+ def test_in_block_mappings_disabled(self):
+ conf = ('empty-values: {forbid-in-block-mappings: false,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: false}\n')
+ self.check('---\n'
+ 'foo:\n', conf)
+ self.check('---\n'
+ 'foo:\n'
+ 'bar: aaa\n', conf)
+
+ def test_in_block_mappings_single_line(self):
+ conf = ('empty-values: {forbid-in-block-mappings: true,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: false}\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_in_block_mappings_all_lines(self):
+ conf = ('empty-values: {forbid-in-block-mappings: true,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: false}\n')
+ self.check('---\n'
+ 'foo:\n'
+ 'bar:\n'
+ 'foobar:\n', conf, problem1=(2, 5),
+ problem2=(3, 5), problem3=(4, 8))
+
+ def test_in_block_mappings_explicit_end_of_document(self):
+ conf = ('empty-values: {forbid-in-block-mappings: true,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: false}\n')
+ self.check('---\n'
+ 'foo:\n'
+ '...\n', conf, problem1=(2, 5))
+
+ def test_in_block_mappings_not_end_of_document(self):
+ conf = ('empty-values: {forbid-in-block-mappings: true,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: false}\n')
+ self.check('---\n'
+ 'foo:\n'
+ 'bar:\n'
+ ' aaa\n', conf, problem1=(2, 5))
+
+ def test_in_block_mappings_different_level(self):
+ conf = ('empty-values: {forbid-in-block-mappings: true,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: false}\n')
+ self.check('---\n'
+ 'foo:\n'
+ ' bar:\n'
+ 'aaa: bbb\n', conf, problem1=(3, 6))
+
+ def test_in_block_mappings_empty_flow_mapping(self):
+ conf = ('empty-values: {forbid-in-block-mappings: true,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: false}\n'
+ 'braces: disable\n'
+ 'commas: disable\n')
+ self.check('---\n'
+ 'foo: {a:}\n', conf)
+ self.check('---\n'
+ '- {a:, b: 2}\n'
+ '- {a: 1, b:}\n'
+ '- {a: 1, b: , }\n', conf)
+
+ def test_in_block_mappings_empty_block_sequence(self):
+ conf = ('empty-values: {forbid-in-block-mappings: true,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: false}\n')
+ self.check('---\n'
+ 'foo:\n'
+ ' -\n', conf)
+
+ def test_in_block_mappings_not_empty_or_explicit_null(self):
+ conf = ('empty-values: {forbid-in-block-mappings: true,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: false}\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_in_block_mappings_various_explicit_null(self):
+ conf = ('empty-values: {forbid-in-block-mappings: true,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: false}\n')
+ self.check('---\n'
+ 'null-alias: ~\n', conf)
+ self.check('---\n'
+ 'null-key1: {?: val}\n', conf)
+ self.check('---\n'
+ 'null-key2: {? !!null "": val}\n', conf)
+
+ def test_in_block_mappings_comments(self):
+ conf = ('empty-values: {forbid-in-block-mappings: true,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: false}\n'
+ 'comments: disable\n')
+ self.check('---\n'
+ 'empty: # comment\n'
+ 'foo:\n'
+ ' bar: # comment\n', conf,
+ problem1=(2, 7),
+ problem2=(4, 7))
+
+ def test_in_flow_mappings_disabled(self):
+ conf = ('empty-values: {forbid-in-block-mappings: false,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: false}\n'
+ 'braces: disable\n'
+ 'commas: disable\n')
+ self.check('---\n'
+ '{a:}\n', conf)
+ self.check('---\n'
+ 'foo: {a:}\n', conf)
+ self.check('---\n'
+ '- {a:}\n'
+ '- {a:, b: 2}\n'
+ '- {a: 1, b:}\n'
+ '- {a: 1, b: , }\n', conf)
+ self.check('---\n'
+ '{a: {b: , c: {d: 4, e:}}, f:}\n', conf)
+
+ def test_in_flow_mappings_single_line(self):
+ conf = ('empty-values: {forbid-in-block-mappings: false,\n'
+ ' forbid-in-flow-mappings: true,\n'
+ ' forbid-in-block-sequences: false}\n'
+ 'braces: disable\n'
+ 'commas: disable\n')
+ self.check('---\n'
+ '{a:}\n', conf,
+ problem=(2, 4))
+ self.check('---\n'
+ 'foo: {a:}\n', conf,
+ problem=(2, 9))
+ self.check('---\n'
+ '- {a:}\n'
+ '- {a:, b: 2}\n'
+ '- {a: 1, b:}\n'
+ '- {a: 1, b: , }\n', conf,
+ problem1=(2, 6),
+ problem2=(3, 6),
+ problem3=(4, 12),
+ problem4=(5, 12))
+ self.check('---\n'
+ '{a: {b: , c: {d: 4, e:}}, f:}\n', conf,
+ problem1=(2, 8),
+ problem2=(2, 23),
+ problem3=(2, 29))
+
+ def test_in_flow_mappings_multi_line(self):
+ conf = ('empty-values: {forbid-in-block-mappings: false,\n'
+ ' forbid-in-flow-mappings: true,\n'
+ ' forbid-in-block-sequences: false}\n'
+ 'braces: disable\n'
+ 'commas: disable\n')
+ self.check('---\n'
+ 'foo: {\n'
+ ' a:\n'
+ '}\n', conf,
+ problem=(3, 5))
+ self.check('---\n'
+ '{\n'
+ ' a: {\n'
+ ' b: ,\n'
+ ' c: {\n'
+ ' d: 4,\n'
+ ' e:\n'
+ ' }\n'
+ ' },\n'
+ ' f:\n'
+ '}\n', conf,
+ problem1=(4, 7),
+ problem2=(7, 9),
+ problem3=(10, 5))
+
+ def test_in_flow_mappings_various_explicit_null(self):
+ conf = ('empty-values: {forbid-in-block-mappings: false,\n'
+ ' forbid-in-flow-mappings: true,\n'
+ ' forbid-in-block-sequences: false}\n'
+ 'braces: disable\n'
+ 'commas: disable\n')
+ self.check('---\n'
+ '{explicit-null: null}\n', conf)
+ self.check('---\n'
+ '{null-alias: ~}\n', conf)
+ self.check('---\n'
+ 'null-key1: {?: val}\n', conf)
+ self.check('---\n'
+ 'null-key2: {? !!null "": val}\n', conf)
+
+ def test_in_flow_mappings_comments(self):
+ conf = ('empty-values: {forbid-in-block-mappings: false,\n'
+ ' forbid-in-flow-mappings: true,\n'
+ ' forbid-in-block-sequences: false}\n'
+ 'braces: disable\n'
+ 'commas: disable\n'
+ 'comments: disable\n')
+ self.check('---\n'
+ '{\n'
+ ' a: {\n'
+ ' b: , # comment\n'
+ ' c: {\n'
+ ' d: 4, # comment\n'
+ ' e: # comment\n'
+ ' }\n'
+ ' },\n'
+ ' f: # comment\n'
+ '}\n', conf,
+ problem1=(4, 7),
+ problem2=(7, 9),
+ problem3=(10, 5))
+
+ def test_in_block_sequences_disabled(self):
+ conf = ('empty-values: {forbid-in-block-mappings: false,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: false}\n')
+ self.check('---\n'
+ 'foo:\n'
+ ' - bar\n'
+ ' -\n', conf)
+ self.check('---\n'
+ 'foo:\n'
+ ' -\n', conf)
+
+ def test_in_block_sequences_primative_item(self):
+ conf = ('empty-values: {forbid-in-block-mappings: false,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: true}\n')
+ self.check('---\n'
+ 'foo:\n'
+ ' -\n', conf,
+ problem=(3, 4))
+ self.check('---\n'
+ 'foo:\n'
+ ' - bar\n'
+ ' -\n', conf,
+ problem=(4, 4))
+ self.check('---\n'
+ 'foo:\n'
+ ' - 1\n'
+ ' - 2\n'
+ ' -\n', conf,
+ problem=(5, 4))
+ self.check('---\n'
+ 'foo:\n'
+ ' - true\n', conf)
+
+ def test_in_block_sequences_complex_objects(self):
+ conf = ('empty-values: {forbid-in-block-mappings: false,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: true}\n')
+ self.check('---\n'
+ 'foo:\n'
+ ' - a: 1\n', conf)
+ self.check('---\n'
+ 'foo:\n'
+ ' - a: 1\n'
+ ' -\n', conf,
+ problem=(4, 4))
+ self.check('---\n'
+ 'foo:\n'
+ ' - a: 1\n'
+ ' b: 2\n'
+ ' -\n', conf,
+ problem=(5, 4))
+ self.check('---\n'
+ 'foo:\n'
+ ' - a: 1\n'
+ ' - b: 2\n'
+ ' -\n', conf,
+ problem=(5, 4))
+ self.check('---\n'
+ 'foo:\n'
+ ' - - a\n'
+ ' - b: 2\n'
+ ' -\n', conf,
+ problem=(5, 6))
+ self.check('---\n'
+ 'foo:\n'
+ ' - - a\n'
+ ' - b: 2\n'
+ ' -\n', conf,
+ problem=(5, 4))
+
+ def test_in_block_sequences_various_explicit_null(self):
+ conf = ('empty-values: {forbid-in-block-mappings: false,\n'
+ ' forbid-in-flow-mappings: false,\n'
+ ' forbid-in-block-sequences: true}\n')
+ self.check('---\n'
+ 'foo:\n'
+ ' - null\n', conf)
+ self.check('---\n'
+ '- null\n', conf)
+ self.check('---\n'
+ 'foo:\n'
+ ' - bar: null\n'
+ ' - null\n', conf)
+ self.check('---\n'
+ '- null\n'
+ '- null\n', conf)
+ self.check('---\n'
+ '- - null\n'
+ ' - null\n', conf)
diff --git a/tests/rules/test_float_values.py b/tests/rules/test_float_values.py
new file mode 100644
index 0000000..8aa980c
--- /dev/null
+++ b/tests/rules/test_float_values.py
@@ -0,0 +1,128 @@
+# Copyright (C) 2022 the yamllint contributors
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class FloatValuesTestCase(RuleTestCase):
+ rule_id = 'float-values'
+
+ def test_disabled(self):
+ conf = 'float-values: disable\n'
+ self.check('---\n'
+ '- 0.0\n'
+ '- .NaN\n'
+ '- .INF\n'
+ '- .1\n'
+ '- 10e-6\n',
+ conf)
+
+ def test_numeral_before_decimal(self):
+ conf = (
+ 'float-values:\n'
+ ' require-numeral-before-decimal: true\n'
+ ' forbid-scientific-notation: false\n'
+ ' forbid-nan: false\n'
+ ' forbid-inf: false\n')
+ self.check('---\n'
+ '- 0.0\n'
+ '- .1\n'
+ '- \'.1\'\n'
+ '- string.1\n'
+ '- .1string\n'
+ '- !custom_tag .2\n'
+ '- &angle1 0.0\n'
+ '- *angle1\n'
+ '- &angle2 .3\n'
+ '- *angle2\n',
+ conf,
+ problem1=(3, 3),
+ problem2=(10, 11))
+
+ def test_scientific_notation(self):
+ conf = (
+ 'float-values:\n'
+ ' require-numeral-before-decimal: false\n'
+ ' forbid-scientific-notation: true\n'
+ ' forbid-nan: false\n'
+ ' forbid-inf: false\n')
+ self.check('---\n'
+ '- 10e6\n'
+ '- 10e-6\n'
+ '- 0.00001\n'
+ '- \'10e-6\'\n'
+ '- string10e-6\n'
+ '- 10e-6string\n'
+ '- !custom_tag 10e-6\n'
+ '- &angle1 0.000001\n'
+ '- *angle1\n'
+ '- &angle2 10e-6\n'
+ '- *angle2\n'
+ '- &angle3 10e6\n'
+ '- *angle3\n',
+ conf,
+ problem1=(2, 3),
+ problem2=(3, 3),
+ problem3=(11, 11),
+ problem4=(13, 11))
+
+ def test_nan(self):
+ conf = (
+ 'float-values:\n'
+ ' require-numeral-before-decimal: false\n'
+ ' forbid-scientific-notation: false\n'
+ ' forbid-nan: true\n'
+ ' forbid-inf: false\n')
+ self.check('---\n'
+ '- .NaN\n'
+ '- .NAN\n'
+ '- \'.NaN\'\n'
+ '- a.NaN\n'
+ '- .NaNa\n'
+ '- !custom_tag .NaN\n'
+ '- &angle .nan\n'
+ '- *angle\n',
+ conf,
+ problem1=(2, 3),
+ problem2=(3, 3),
+ problem3=(8, 10))
+
+ def test_inf(self):
+ conf = (
+ 'float-values:\n'
+ ' require-numeral-before-decimal: false\n'
+ ' forbid-scientific-notation: false\n'
+ ' forbid-nan: false\n'
+ ' forbid-inf: true\n')
+ self.check('---\n'
+ '- .inf\n'
+ '- .INF\n'
+ '- -.inf\n'
+ '- -.INF\n'
+ '- \'.inf\'\n'
+ '- ∞.infinity\n'
+ '- .infinity∞\n'
+ '- !custom_tag .inf\n'
+ '- &angle .inf\n'
+ '- *angle\n'
+ '- &angle -.inf\n'
+ '- *angle\n',
+ conf,
+ problem1=(2, 3),
+ problem2=(3, 3),
+ problem3=(4, 3),
+ problem4=(5, 3),
+ problem5=(10, 10),
+ problem6=(12, 10))
diff --git a/tests/rules/test_hyphens.py b/tests/rules/test_hyphens.py
new file mode 100644
index 0000000..a0ec577
--- /dev/null
+++ b/tests/rules/test_hyphens.py
@@ -0,0 +1,105 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class HyphenTestCase(RuleTestCase):
+ rule_id = 'hyphens'
+
+ def test_disabled(self):
+ conf = 'hyphens: disable'
+ self.check('---\n'
+ '- elem1\n'
+ '- elem2\n', conf)
+ self.check('---\n'
+ '- elem1\n'
+ '- elem2\n', conf)
+ self.check('---\n'
+ '- elem1\n'
+ '- elem2\n', conf)
+ self.check('---\n'
+ '- elem1\n'
+ '- elem2\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' - elem1\n'
+ ' - elem2\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' - elem1\n'
+ ' - elem2\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' subobject:\n'
+ ' - elem1\n'
+ ' - elem2\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' subobject:\n'
+ ' - elem1\n'
+ ' - elem2\n', conf)
+
+ def test_enabled(self):
+ conf = 'hyphens: {max-spaces-after: 1}'
+ self.check('---\n'
+ '- elem1\n'
+ '- elem2\n', conf)
+ self.check('---\n'
+ '- elem1\n'
+ '- elem2\n', conf, problem=(3, 3))
+ self.check('---\n'
+ '- elem1\n'
+ '- elem2\n', conf, problem1=(2, 3), problem2=(3, 3))
+ self.check('---\n'
+ '- elem1\n'
+ '- elem2\n', conf, problem=(2, 3))
+ self.check('---\n'
+ 'object:\n'
+ ' - elem1\n'
+ ' - elem2\n', conf, problem=(4, 5))
+ self.check('---\n'
+ 'object:\n'
+ ' - elem1\n'
+ ' - elem2\n', conf, problem1=(3, 5), problem2=(4, 5))
+ self.check('---\n'
+ 'object:\n'
+ ' subobject:\n'
+ ' - elem1\n'
+ ' - elem2\n', conf, problem=(5, 7))
+ self.check('---\n'
+ 'object:\n'
+ ' subobject:\n'
+ ' - elem1\n'
+ ' - elem2\n', conf, problem1=(4, 7), problem2=(5, 7))
+
+ def test_max_3(self):
+ conf = 'hyphens: {max-spaces-after: 3}'
+ self.check('---\n'
+ '- elem1\n'
+ '- elem2\n', conf)
+ self.check('---\n'
+ '- elem1\n'
+ '- elem2\n', conf, problem=(2, 5))
+ self.check('---\n'
+ 'a:\n'
+ ' b:\n'
+ ' - elem1\n'
+ ' - elem2\n', conf)
+ self.check('---\n'
+ 'a:\n'
+ ' b:\n'
+ ' - elem1\n'
+ ' - elem2\n', conf, problem1=(4, 9), problem2=(5, 9))
diff --git a/tests/rules/test_indentation.py b/tests/rules/test_indentation.py
new file mode 100644
index 0000000..1c6eddb
--- /dev/null
+++ b/tests/rules/test_indentation.py
@@ -0,0 +1,2160 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+from yamllint.parser import token_or_comment_generator, Comment
+from yamllint.rules.indentation import check
+
+
+class IndentationStackTestCase(RuleTestCase):
+ # This test suite checks that the "indentation stack" built by the
+ # indentation rule is valid. It is important, since everything else in the
+ # rule relies on this stack.
+
+ maxDiff = None
+
+ def format_stack(self, stack):
+ """Transform the stack at a given moment into a printable string like:
+
+ B_MAP:0 KEY:0 VAL:5
+ """
+ return ' '.join(map(str, stack[1:]))
+
+ def full_stack(self, source):
+ conf = {'spaces': 2, 'indent-sequences': True,
+ 'check-multi-line-strings': False}
+ context = {}
+ output = ''
+ for elem in [t for t in token_or_comment_generator(source)
+ if not isinstance(t, Comment)]:
+ list(check(conf, elem.curr, elem.prev, elem.next, elem.nextnext,
+ context))
+
+ token_type = (elem.curr.__class__.__name__
+ .replace('Token', '')
+ .replace('Block', 'B').replace('Flow', 'F')
+ .replace('Sequence', 'Seq')
+ .replace('Mapping', 'Map'))
+ if token_type in ('StreamStart', 'StreamEnd'):
+ continue
+ output += '{:>9} {}\n'.format(token_type,
+ self.format_stack(context['stack']))
+ return output
+
+ def test_simple_mapping(self):
+ self.assertMultiLineEqual(
+ self.full_stack('key: val\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:5\n'
+ ' Scalar B_MAP:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack(' key: val\n'),
+ 'BMapStart B_MAP:5\n'
+ ' Key B_MAP:5 KEY:5\n'
+ ' Scalar B_MAP:5 KEY:5\n'
+ ' Value B_MAP:5 KEY:5 VAL:10\n'
+ ' Scalar B_MAP:5\n'
+ ' BEnd \n')
+
+ def test_simple_sequence(self):
+ self.assertMultiLineEqual(
+ self.full_stack('- 1\n'
+ '- 2\n'
+ '- 3\n'),
+ 'BSeqStart B_SEQ:0\n'
+ ' BEntry B_SEQ:0 B_ENT:2\n'
+ ' Scalar B_SEQ:0\n'
+ ' BEntry B_SEQ:0 B_ENT:2\n'
+ ' Scalar B_SEQ:0\n'
+ ' BEntry B_SEQ:0 B_ENT:2\n'
+ ' Scalar B_SEQ:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('key:\n'
+ ' - 1\n'
+ ' - 2\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:2\n'
+ 'BSeqStart B_MAP:0 KEY:0 VAL:2 B_SEQ:2\n'
+ ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:2 B_ENT:4\n'
+ ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:2\n'
+ ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:2 B_ENT:4\n'
+ ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:2\n'
+ ' BEnd B_MAP:0\n'
+ ' BEnd \n')
+
+ def test_non_indented_sequences(self):
+ # There seems to be a bug in pyyaml: depending on the indentation, a
+ # sequence does not produce the same tokens. More precisely, the
+ # following YAML:
+ # usr:
+ # - lib
+ # produces a BlockSequenceStartToken and a BlockEndToken around the
+ # "lib" sequence, whereas the following:
+ # usr:
+ # - lib
+ # does not (both two tokens are omitted).
+ # So, yamllint must create fake 'B_SEQ'. This test makes sure it does.
+
+ self.assertMultiLineEqual(
+ self.full_stack('usr:\n'
+ ' - lib\n'
+ 'var: cache\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:2\n'
+ 'BSeqStart B_MAP:0 KEY:0 VAL:2 B_SEQ:2\n'
+ ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:2 B_ENT:4\n'
+ ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:2\n'
+ ' BEnd B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:5\n'
+ ' Scalar B_MAP:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('usr:\n'
+ '- lib\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:2\n'
+ # missing BSeqStart here
+ ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n'
+ ' Scalar B_MAP:0\n'
+ # missing BEnd here
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('usr:\n'
+ '- lib\n'
+ 'var: cache\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:2\n'
+ # missing BSeqStart here
+ ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n'
+ ' Scalar B_MAP:0\n'
+ # missing BEnd here
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:5\n'
+ ' Scalar B_MAP:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('usr:\n'
+ '- []\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:2\n'
+ # missing BSeqStart here
+ ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n'
+ 'FSeqStart B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 F_SEQ:3\n'
+ ' FSeqEnd B_MAP:0\n'
+ # missing BEnd here
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('usr:\n'
+ '- k:\n'
+ ' v\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:2\n'
+ # missing BSeqStart here
+ ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n'
+ 'BMapStart B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2\n'
+ ' Key B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2\n'
+ ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2\n'
+ ' Value B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2 VAL:4\n' # noqa
+ ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2\n'
+ ' BEnd B_MAP:0\n'
+ # missing BEnd here
+ ' BEnd \n')
+
+ def test_flows(self):
+ self.assertMultiLineEqual(
+ self.full_stack('usr: [\n'
+ ' {k:\n'
+ ' v}\n'
+ ' ]\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:5\n'
+ 'FSeqStart B_MAP:0 KEY:0 VAL:5 F_SEQ:2\n'
+ 'FMapStart B_MAP:0 KEY:0 VAL:5 F_SEQ:2 F_MAP:3\n'
+ ' Key B_MAP:0 KEY:0 VAL:5 F_SEQ:2 F_MAP:3 KEY:3\n'
+ ' Scalar B_MAP:0 KEY:0 VAL:5 F_SEQ:2 F_MAP:3 KEY:3\n'
+ ' Value B_MAP:0 KEY:0 VAL:5 F_SEQ:2 F_MAP:3 KEY:3 VAL:5\n'
+ ' Scalar B_MAP:0 KEY:0 VAL:5 F_SEQ:2 F_MAP:3\n'
+ ' FMapEnd B_MAP:0 KEY:0 VAL:5 F_SEQ:2\n'
+ ' FSeqEnd B_MAP:0\n'
+ ' BEnd \n')
+
+ def test_anchors(self):
+ self.assertMultiLineEqual(
+ self.full_stack('key: &anchor value\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:5\n'
+ ' Anchor B_MAP:0 KEY:0 VAL:5\n'
+ ' Scalar B_MAP:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('key: &anchor\n'
+ ' value\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:2\n'
+ ' Anchor B_MAP:0 KEY:0 VAL:2\n'
+ ' Scalar B_MAP:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('- &anchor value\n'),
+ 'BSeqStart B_SEQ:0\n'
+ ' BEntry B_SEQ:0 B_ENT:2\n'
+ ' Anchor B_SEQ:0 B_ENT:2\n'
+ ' Scalar B_SEQ:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('- &anchor\n'
+ ' value\n'),
+ 'BSeqStart B_SEQ:0\n'
+ ' BEntry B_SEQ:0 B_ENT:2\n'
+ ' Anchor B_SEQ:0 B_ENT:2\n'
+ ' Scalar B_SEQ:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('- &anchor\n'
+ ' - 1\n'
+ ' - 2\n'),
+ 'BSeqStart B_SEQ:0\n'
+ ' BEntry B_SEQ:0 B_ENT:2\n'
+ ' Anchor B_SEQ:0 B_ENT:2\n'
+ 'BSeqStart B_SEQ:0 B_ENT:2 B_SEQ:2\n'
+ ' BEntry B_SEQ:0 B_ENT:2 B_SEQ:2 B_ENT:4\n'
+ ' Scalar B_SEQ:0 B_ENT:2 B_SEQ:2\n'
+ ' BEntry B_SEQ:0 B_ENT:2 B_SEQ:2 B_ENT:4\n'
+ ' Scalar B_SEQ:0 B_ENT:2 B_SEQ:2\n'
+ ' BEnd B_SEQ:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('&anchor key:\n'
+ ' value\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Anchor B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:2\n'
+ ' Scalar B_MAP:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('pre:\n'
+ ' &anchor1 0\n'
+ '&anchor2 key:\n'
+ ' value\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:2\n'
+ ' Anchor B_MAP:0 KEY:0 VAL:2\n'
+ ' Scalar B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Anchor B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:2\n'
+ ' Scalar B_MAP:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('sequence: &anchor\n'
+ '- entry\n'
+ '- &anchor\n'
+ ' - nested\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:2\n'
+ ' Anchor B_MAP:0 KEY:0 VAL:2\n'
+ # missing BSeqStart here
+ ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n'
+ ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:0\n'
+ ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n'
+ ' Anchor B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n'
+ 'BSeqStart B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_SEQ:2\n'
+ ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_SEQ:2 B_ENT:4\n'
+ ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_SEQ:2\n'
+ ' BEnd B_MAP:0\n'
+ # missing BEnd here
+ ' BEnd \n')
+
+ def test_tags(self):
+ self.assertMultiLineEqual(
+ self.full_stack('key: !!tag value\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:5\n'
+ ' Tag B_MAP:0 KEY:0 VAL:5\n'
+ ' Scalar B_MAP:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('- !!map # Block collection\n'
+ ' foo : bar\n'),
+ 'BSeqStart B_SEQ:0\n'
+ ' BEntry B_SEQ:0 B_ENT:2\n'
+ ' Tag B_SEQ:0 B_ENT:2\n'
+ 'BMapStart B_SEQ:0 B_ENT:2 B_MAP:2\n'
+ ' Key B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2\n'
+ ' Scalar B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2\n'
+ ' Value B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2 VAL:8\n'
+ ' Scalar B_SEQ:0 B_ENT:2 B_MAP:2\n'
+ ' BEnd B_SEQ:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('- !!seq\n'
+ ' - nested item\n'),
+ 'BSeqStart B_SEQ:0\n'
+ ' BEntry B_SEQ:0 B_ENT:2\n'
+ ' Tag B_SEQ:0 B_ENT:2\n'
+ 'BSeqStart B_SEQ:0 B_ENT:2 B_SEQ:2\n'
+ ' BEntry B_SEQ:0 B_ENT:2 B_SEQ:2 B_ENT:4\n'
+ ' Scalar B_SEQ:0 B_ENT:2 B_SEQ:2\n'
+ ' BEnd B_SEQ:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('sequence: !!seq\n'
+ '- entry\n'
+ '- !!seq\n'
+ ' - nested\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ ' Scalar B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:2\n'
+ ' Tag B_MAP:0 KEY:0 VAL:2\n'
+ # missing BSeqStart here
+ ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n'
+ ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:0\n'
+ ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n'
+ ' Tag B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n'
+ 'BSeqStart B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_SEQ:2\n'
+ ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_SEQ:2 B_ENT:4\n'
+ ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_SEQ:2\n'
+ ' BEnd B_MAP:0\n'
+ # missing BEnd here
+ ' BEnd \n')
+
+ def test_flows_imbrication(self):
+ self.assertMultiLineEqual(
+ self.full_stack('[[val]]\n'),
+ 'FSeqStart F_SEQ:1\n'
+ 'FSeqStart F_SEQ:1 F_SEQ:2\n'
+ ' Scalar F_SEQ:1 F_SEQ:2\n'
+ ' FSeqEnd F_SEQ:1\n'
+ ' FSeqEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('[[val], [val2]]\n'),
+ 'FSeqStart F_SEQ:1\n'
+ 'FSeqStart F_SEQ:1 F_SEQ:2\n'
+ ' Scalar F_SEQ:1 F_SEQ:2\n'
+ ' FSeqEnd F_SEQ:1\n'
+ ' FEntry F_SEQ:1\n'
+ 'FSeqStart F_SEQ:1 F_SEQ:9\n'
+ ' Scalar F_SEQ:1 F_SEQ:9\n'
+ ' FSeqEnd F_SEQ:1\n'
+ ' FSeqEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('{{key}}\n'),
+ 'FMapStart F_MAP:1\n'
+ 'FMapStart F_MAP:1 F_MAP:2\n'
+ ' Scalar F_MAP:1 F_MAP:2\n'
+ ' FMapEnd F_MAP:1\n'
+ ' FMapEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('[key]: value\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ 'FSeqStart B_MAP:0 KEY:0 F_SEQ:1\n'
+ ' Scalar B_MAP:0 KEY:0 F_SEQ:1\n'
+ ' FSeqEnd B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:7\n'
+ ' Scalar B_MAP:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('[[key]]: value\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ 'FSeqStart B_MAP:0 KEY:0 F_SEQ:1\n'
+ 'FSeqStart B_MAP:0 KEY:0 F_SEQ:1 F_SEQ:2\n'
+ ' Scalar B_MAP:0 KEY:0 F_SEQ:1 F_SEQ:2\n'
+ ' FSeqEnd B_MAP:0 KEY:0 F_SEQ:1\n'
+ ' FSeqEnd B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:9\n'
+ ' Scalar B_MAP:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('{key}: value\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ 'FMapStart B_MAP:0 KEY:0 F_MAP:1\n'
+ ' Scalar B_MAP:0 KEY:0 F_MAP:1\n'
+ ' FMapEnd B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:7\n'
+ ' Scalar B_MAP:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('{key: value}: value\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ 'FMapStart B_MAP:0 KEY:0 F_MAP:1\n'
+ ' Key B_MAP:0 KEY:0 F_MAP:1 KEY:1\n'
+ ' Scalar B_MAP:0 KEY:0 F_MAP:1 KEY:1\n'
+ ' Value B_MAP:0 KEY:0 F_MAP:1 KEY:1 VAL:6\n'
+ ' Scalar B_MAP:0 KEY:0 F_MAP:1\n'
+ ' FMapEnd B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:14\n'
+ ' Scalar B_MAP:0\n'
+ ' BEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('{{key}}: value\n'),
+ 'BMapStart B_MAP:0\n'
+ ' Key B_MAP:0 KEY:0\n'
+ 'FMapStart B_MAP:0 KEY:0 F_MAP:1\n'
+ 'FMapStart B_MAP:0 KEY:0 F_MAP:1 F_MAP:2\n'
+ ' Scalar B_MAP:0 KEY:0 F_MAP:1 F_MAP:2\n'
+ ' FMapEnd B_MAP:0 KEY:0 F_MAP:1\n'
+ ' FMapEnd B_MAP:0 KEY:0\n'
+ ' Value B_MAP:0 KEY:0 VAL:9\n'
+ ' Scalar B_MAP:0\n'
+ ' BEnd \n')
+ self.assertMultiLineEqual(
+ self.full_stack('{{key}: val, {key2}: {val2}}\n'),
+ 'FMapStart F_MAP:1\n'
+ ' Key F_MAP:1 KEY:1\n'
+ 'FMapStart F_MAP:1 KEY:1 F_MAP:2\n'
+ ' Scalar F_MAP:1 KEY:1 F_MAP:2\n'
+ ' FMapEnd F_MAP:1 KEY:1\n'
+ ' Value F_MAP:1 KEY:1 VAL:8\n'
+ ' Scalar F_MAP:1\n'
+ ' FEntry F_MAP:1\n'
+ ' Key F_MAP:1 KEY:1\n'
+ 'FMapStart F_MAP:1 KEY:1 F_MAP:14\n'
+ ' Scalar F_MAP:1 KEY:1 F_MAP:14\n'
+ ' FMapEnd F_MAP:1 KEY:1\n'
+ ' Value F_MAP:1 KEY:1 VAL:21\n'
+ 'FMapStart F_MAP:1 KEY:1 VAL:21 F_MAP:22\n'
+ ' Scalar F_MAP:1 KEY:1 VAL:21 F_MAP:22\n'
+ ' FMapEnd F_MAP:1\n'
+ ' FMapEnd \n')
+
+ self.assertMultiLineEqual(
+ self.full_stack('{[{{[val]}}, [{[key]: val2}]]}\n'),
+ 'FMapStart F_MAP:1\n'
+ 'FSeqStart F_MAP:1 F_SEQ:2\n'
+ 'FMapStart F_MAP:1 F_SEQ:2 F_MAP:3\n'
+ 'FMapStart F_MAP:1 F_SEQ:2 F_MAP:3 F_MAP:4\n'
+ 'FSeqStart F_MAP:1 F_SEQ:2 F_MAP:3 F_MAP:4 F_SEQ:5\n'
+ ' Scalar F_MAP:1 F_SEQ:2 F_MAP:3 F_MAP:4 F_SEQ:5\n'
+ ' FSeqEnd F_MAP:1 F_SEQ:2 F_MAP:3 F_MAP:4\n'
+ ' FMapEnd F_MAP:1 F_SEQ:2 F_MAP:3\n'
+ ' FMapEnd F_MAP:1 F_SEQ:2\n'
+ ' FEntry F_MAP:1 F_SEQ:2\n'
+ 'FSeqStart F_MAP:1 F_SEQ:2 F_SEQ:14\n'
+ 'FMapStart F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15\n'
+ ' Key F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15 KEY:15\n'
+ 'FSeqStart F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15 KEY:15 F_SEQ:16\n'
+ ' Scalar F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15 KEY:15 F_SEQ:16\n'
+ ' FSeqEnd F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15 KEY:15\n'
+ ' Value F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15 KEY:15 VAL:22\n'
+ ' Scalar F_MAP:1 F_SEQ:2 F_SEQ:14 F_MAP:15\n'
+ ' FMapEnd F_MAP:1 F_SEQ:2 F_SEQ:14\n'
+ ' FSeqEnd F_MAP:1 F_SEQ:2\n'
+ ' FSeqEnd F_MAP:1\n'
+ ' FMapEnd \n')
+
+
+class IndentationTestCase(RuleTestCase):
+ rule_id = 'indentation'
+
+ def test_disabled(self):
+ conf = 'indentation: disable'
+ self.check('---\n'
+ 'object:\n'
+ ' k1: v1\n'
+ 'obj2:\n'
+ ' k2:\n'
+ ' - 8\n'
+ ' k3:\n'
+ ' val\n'
+ '...\n', conf)
+ self.check('---\n'
+ ' o:\n'
+ ' k1: v1\n'
+ ' p:\n'
+ ' k3:\n'
+ ' val\n'
+ '...\n', conf)
+ self.check('---\n'
+ ' - o:\n'
+ ' k1: v1\n'
+ ' - p: kdjf\n'
+ ' - q:\n'
+ ' k3:\n'
+ ' - val\n'
+ '...\n', conf)
+
+ def test_one_space(self):
+ conf = 'indentation: {spaces: 1, indent-sequences: false}'
+ self.check('---\n'
+ 'object:\n'
+ ' k1:\n'
+ ' - a\n'
+ ' - b\n'
+ ' k2: v2\n'
+ ' k3:\n'
+ ' - name: Unix\n'
+ ' date: 1969\n'
+ ' - name: Linux\n'
+ ' date: 1991\n'
+ '...\n', conf)
+ conf = 'indentation: {spaces: 1, indent-sequences: true}'
+ self.check('---\n'
+ 'object:\n'
+ ' k1:\n'
+ ' - a\n'
+ ' - b\n'
+ ' k2: v2\n'
+ ' k3:\n'
+ ' - name: Unix\n'
+ ' date: 1969\n'
+ ' - name: Linux\n'
+ ' date: 1991\n'
+ '...\n', conf)
+
+ def test_two_spaces(self):
+ conf = 'indentation: {spaces: 2, indent-sequences: false}'
+ self.check('---\n'
+ 'object:\n'
+ ' k1:\n'
+ ' - a\n'
+ ' - b\n'
+ ' k2: v2\n'
+ ' k3:\n'
+ ' - name: Unix\n'
+ ' date: 1969\n'
+ ' - name: Linux\n'
+ ' date: 1991\n'
+ ' k4:\n'
+ ' -\n'
+ ' k5: v3\n'
+ '...\n', conf)
+ conf = 'indentation: {spaces: 2, indent-sequences: true}'
+ self.check('---\n'
+ 'object:\n'
+ ' k1:\n'
+ ' - a\n'
+ ' - b\n'
+ ' k2: v2\n'
+ ' k3:\n'
+ ' - name: Unix\n'
+ ' date: 1969\n'
+ ' - name: Linux\n'
+ ' date: 1991\n'
+ '...\n', conf)
+
+ def test_three_spaces(self):
+ conf = 'indentation: {spaces: 3, indent-sequences: false}'
+ self.check('---\n'
+ 'object:\n'
+ ' k1:\n'
+ ' - a\n'
+ ' - b\n'
+ ' k2: v2\n'
+ ' k3:\n'
+ ' - name: Unix\n'
+ ' date: 1969\n'
+ ' - name: Linux\n'
+ ' date: 1991\n'
+ '...\n', conf)
+ conf = 'indentation: {spaces: 3, indent-sequences: true}'
+ self.check('---\n'
+ 'object:\n'
+ ' k1:\n'
+ ' - a\n'
+ ' - b\n'
+ ' k2: v2\n'
+ ' k3:\n'
+ ' - name: Unix\n'
+ ' date: 1969\n'
+ ' - name: Linux\n'
+ ' date: 1991\n'
+ '...\n', conf)
+
+ def test_consistent_spaces(self):
+ conf = ('indentation: {spaces: consistent,\n'
+ ' indent-sequences: whatever}\n'
+ 'document-start: disable\n')
+ self.check('---\n'
+ 'object:\n'
+ ' k1:\n'
+ ' - a\n'
+ ' - b\n'
+ ' k2: v2\n'
+ ' k3:\n'
+ ' - name: Unix\n'
+ ' date: 1969\n'
+ ' - name: Linux\n'
+ ' date: 1991\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' k1:\n'
+ ' - a\n'
+ ' - b\n'
+ ' k2: v2\n'
+ ' k3:\n'
+ ' - name: Unix\n'
+ ' date: 1969\n'
+ ' - name: Linux\n'
+ ' date: 1991\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' k1:\n'
+ ' - a\n'
+ ' - b\n'
+ ' k2: v2\n'
+ ' k3:\n'
+ ' - name: Unix\n'
+ ' date: 1969\n'
+ ' - name: Linux\n'
+ ' date: 1991\n'
+ '...\n', conf)
+ self.check('first is not indented:\n'
+ ' value is indented\n', conf)
+ self.check('first is not indented:\n'
+ ' value:\n'
+ ' is indented\n', conf)
+ self.check('- first is already indented:\n'
+ ' value is indented too\n', conf)
+ self.check('- first is already indented:\n'
+ ' value:\n'
+ ' is indented too\n', conf)
+ self.check('- first is already indented:\n'
+ ' value:\n'
+ ' is indented too\n', conf, problem=(3, 14))
+ self.check('---\n'
+ 'list one:\n'
+ ' - 1\n'
+ ' - 2\n'
+ ' - 3\n'
+ 'list two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf, problem=(7, 5))
+ self.check('---\n'
+ 'list one:\n'
+ '- 1\n'
+ '- 2\n'
+ '- 3\n'
+ 'list two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf)
+ self.check('---\n'
+ 'list one:\n'
+ ' - 1\n'
+ ' - 2\n'
+ ' - 3\n'
+ 'list two:\n'
+ '- a\n'
+ '- b\n'
+ '- c\n', conf)
+
+ def test_consistent_spaces_and_indent_sequences(self):
+ conf = 'indentation: {spaces: consistent, indent-sequences: true}'
+ self.check('---\n'
+ 'list one:\n'
+ '- 1\n'
+ '- 2\n'
+ '- 3\n'
+ 'list two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf, problem1=(3, 1))
+ self.check('---\n'
+ 'list one:\n'
+ ' - 1\n'
+ ' - 2\n'
+ ' - 3\n'
+ 'list two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf, problem1=(7, 5))
+ self.check('---\n'
+ 'list one:\n'
+ ' - 1\n'
+ ' - 2\n'
+ ' - 3\n'
+ 'list two:\n'
+ '- a\n'
+ '- b\n'
+ '- c\n', conf, problem1=(7, 1))
+
+ conf = 'indentation: {spaces: consistent, indent-sequences: false}'
+ self.check('---\n'
+ 'list one:\n'
+ '- 1\n'
+ '- 2\n'
+ '- 3\n'
+ 'list two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf, problem1=(7, 5))
+ self.check('---\n'
+ 'list one:\n'
+ '- 1\n'
+ '- 2\n'
+ '- 3\n'
+ 'list two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf, problem1=(7, 3))
+ self.check('---\n'
+ 'list one:\n'
+ ' - 1\n'
+ ' - 2\n'
+ ' - 3\n'
+ 'list two:\n'
+ '- a\n'
+ '- b\n'
+ '- c\n', conf, problem1=(3, 3))
+
+ conf = ('indentation: {spaces: consistent,\n'
+ ' indent-sequences: consistent}')
+ self.check('---\n'
+ 'list one:\n'
+ '- 1\n'
+ '- 2\n'
+ '- 3\n'
+ 'list two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf, problem1=(7, 5))
+ self.check('---\n'
+ 'list one:\n'
+ ' - 1\n'
+ ' - 2\n'
+ ' - 3\n'
+ 'list two:\n'
+ '- a\n'
+ '- b\n'
+ '- c\n', conf, problem1=(7, 1))
+ self.check('---\n'
+ 'list one:\n'
+ '- 1\n'
+ '- 2\n'
+ '- 3\n'
+ 'list two:\n'
+ '- a\n'
+ '- b\n'
+ '- c\n', conf)
+ self.check('---\n'
+ 'list one:\n'
+ ' - 1\n'
+ ' - 2\n'
+ ' - 3\n'
+ 'list two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf, problem1=(7, 5))
+
+ conf = 'indentation: {spaces: consistent, indent-sequences: whatever}'
+ self.check('---\n'
+ 'list one:\n'
+ '- 1\n'
+ '- 2\n'
+ '- 3\n'
+ 'list two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf)
+ self.check('---\n'
+ 'list one:\n'
+ ' - 1\n'
+ ' - 2\n'
+ ' - 3\n'
+ 'list two:\n'
+ '- a\n'
+ '- b\n'
+ '- c\n', conf)
+ self.check('---\n'
+ 'list one:\n'
+ '- 1\n'
+ '- 2\n'
+ '- 3\n'
+ 'list two:\n'
+ '- a\n'
+ '- b\n'
+ '- c\n', conf)
+ self.check('---\n'
+ 'list one:\n'
+ ' - 1\n'
+ ' - 2\n'
+ ' - 3\n'
+ 'list two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf, problem1=(7, 5))
+
+ def test_indent_sequences_whatever(self):
+ conf = 'indentation: {spaces: 4, indent-sequences: whatever}'
+ self.check('---\n'
+ 'list one:\n'
+ '- 1\n'
+ '- 2\n'
+ '- 3\n'
+ 'list two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf)
+ self.check('---\n'
+ 'list one:\n'
+ ' - 1\n'
+ ' - 2\n'
+ ' - 3\n'
+ 'list two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf, problem=(3, 3))
+ self.check('---\n'
+ 'list one:\n'
+ '- 1\n'
+ '- 2\n'
+ '- 3\n'
+ 'list two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf, problem=(7, 3))
+ self.check('---\n'
+ 'list:\n'
+ ' - 1\n'
+ ' - 2\n'
+ ' - 3\n'
+ '- a\n'
+ '- b\n'
+ '- c\n', conf, problem=(6, 1, 'syntax'))
+
+ def test_indent_sequences_consistent(self):
+ conf = 'indentation: {spaces: 4, indent-sequences: consistent}'
+ self.check('---\n'
+ 'list one:\n'
+ '- 1\n'
+ '- 2\n'
+ '- 3\n'
+ 'list:\n'
+ ' two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf)
+ self.check('---\n'
+ 'list one:\n'
+ ' - 1\n'
+ ' - 2\n'
+ ' - 3\n'
+ 'list:\n'
+ ' two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf)
+ self.check('---\n'
+ 'list one:\n'
+ '- 1\n'
+ '- 2\n'
+ '- 3\n'
+ 'list two:\n'
+ ' - a\n'
+ ' - b\n'
+ ' - c\n', conf, problem=(7, 5))
+ self.check('---\n'
+ 'list one:\n'
+ ' - 1\n'
+ ' - 2\n'
+ ' - 3\n'
+ 'list two:\n'
+ '- a\n'
+ '- b\n'
+ '- c\n', conf, problem=(7, 1))
+ self.check('---\n'
+ 'list one:\n'
+ ' - 1\n'
+ ' - 2\n'
+ ' - 3\n'
+ 'list two:\n'
+ '- a\n'
+ '- b\n'
+ '- c\n', conf, problem1=(3, 2), problem2=(7, 1))
+
+ def test_direct_flows(self):
+ # flow: [ ...
+ # ]
+ conf = 'indentation: {spaces: consistent}'
+ self.check('---\n'
+ 'a: {x: 1,\n'
+ ' y,\n'
+ ' z: 1}\n', conf)
+ self.check('---\n'
+ 'a: {x: 1,\n'
+ ' y,\n'
+ ' z: 1}\n', conf, problem=(3, 4))
+ self.check('---\n'
+ 'a: {x: 1,\n'
+ ' y,\n'
+ ' z: 1}\n', conf, problem=(3, 6))
+ self.check('---\n'
+ 'a: {x: 1,\n'
+ ' y, z: 1}\n', conf, problem=(3, 3))
+ self.check('---\n'
+ 'a: {x: 1,\n'
+ ' y, z: 1\n'
+ '}\n', conf)
+ self.check('---\n'
+ 'a: {x: 1,\n'
+ ' y, z: 1\n'
+ '}\n', conf, problem=(3, 3))
+ self.check('---\n'
+ 'a: [x,\n'
+ ' y,\n'
+ ' z]\n', conf)
+ self.check('---\n'
+ 'a: [x,\n'
+ ' y,\n'
+ ' z]\n', conf, problem=(3, 4))
+ self.check('---\n'
+ 'a: [x,\n'
+ ' y,\n'
+ ' z]\n', conf, problem=(3, 6))
+ self.check('---\n'
+ 'a: [x,\n'
+ ' y, z]\n', conf, problem=(3, 3))
+ self.check('---\n'
+ 'a: [x,\n'
+ ' y, z\n'
+ ']\n', conf)
+ self.check('---\n'
+ 'a: [x,\n'
+ ' y, z\n'
+ ']\n', conf, problem=(3, 3))
+
+ def test_broken_flows(self):
+ # flow: [
+ # ...
+ # ]
+ conf = 'indentation: {spaces: consistent}'
+ self.check('---\n'
+ 'a: {\n'
+ ' x: 1,\n'
+ ' y, z: 1\n'
+ '}\n', conf)
+ self.check('---\n'
+ 'a: {\n'
+ ' x: 1,\n'
+ ' y, z: 1}\n', conf)
+ self.check('---\n'
+ 'a: {\n'
+ ' x: 1,\n'
+ ' y, z: 1\n'
+ '}\n', conf, problem=(4, 3))
+ self.check('---\n'
+ 'a: {\n'
+ ' x: 1,\n'
+ ' y, z: 1\n'
+ ' }\n', conf, problem=(5, 3))
+ self.check('---\n'
+ 'a: [\n'
+ ' x,\n'
+ ' y, z\n'
+ ']\n', conf)
+ self.check('---\n'
+ 'a: [\n'
+ ' x,\n'
+ ' y, z]\n', conf)
+ self.check('---\n'
+ 'a: [\n'
+ ' x,\n'
+ ' y, z\n'
+ ']\n', conf, problem=(4, 3))
+ self.check('---\n'
+ 'a: [\n'
+ ' x,\n'
+ ' y, z\n'
+ ' ]\n', conf, problem=(5, 3))
+ self.check('---\n'
+ 'obj: {\n'
+ ' a: 1,\n'
+ ' b: 2,\n'
+ ' c: 3\n'
+ '}\n', conf, problem1=(4, 4), problem2=(5, 2))
+ self.check('---\n'
+ 'list: [\n'
+ ' 1,\n'
+ ' 2,\n'
+ ' 3\n'
+ ']\n', conf, problem1=(4, 4), problem2=(5, 2))
+ self.check('---\n'
+ 'top:\n'
+ ' rules: [\n'
+ ' 1, 2,\n'
+ ' ]\n', conf)
+ self.check('---\n'
+ 'top:\n'
+ ' rules: [\n'
+ ' 1, 2,\n'
+ ']\n'
+ ' rulez: [\n'
+ ' 1, 2,\n'
+ ' ]\n', conf, problem1=(5, 1), problem2=(8, 5))
+ self.check('---\n'
+ 'top:\n'
+ ' rules:\n'
+ ' here: {\n'
+ ' foo: 1,\n'
+ ' bar: 2\n'
+ ' }\n', conf)
+ self.check('---\n'
+ 'top:\n'
+ ' rules:\n'
+ ' here: {\n'
+ ' foo: 1,\n'
+ ' bar: 2\n'
+ ' }\n'
+ ' there: {\n'
+ ' foo: 1,\n'
+ ' bar: 2\n'
+ ' }\n', conf, problem1=(7, 7), problem2=(11, 3))
+ conf = 'indentation: {spaces: 2}'
+ self.check('---\n'
+ 'a: {\n'
+ ' x: 1,\n'
+ ' y, z: 1\n'
+ '}\n', conf, problem=(3, 4))
+ self.check('---\n'
+ 'a: [\n'
+ ' x,\n'
+ ' y, z\n'
+ ']\n', conf, problem=(3, 4))
+
+ def test_cleared_flows(self):
+ # flow:
+ # [
+ # ...
+ # ]
+ conf = 'indentation: {spaces: consistent}'
+ self.check('---\n'
+ 'top:\n'
+ ' rules:\n'
+ ' {\n'
+ ' foo: 1,\n'
+ ' bar: 2\n'
+ ' }\n', conf)
+ self.check('---\n'
+ 'top:\n'
+ ' rules:\n'
+ ' {\n'
+ ' foo: 1,\n'
+ ' bar: 2\n'
+ ' }\n', conf, problem=(5, 8))
+ self.check('---\n'
+ 'top:\n'
+ ' rules:\n'
+ ' {\n'
+ ' foo: 1,\n'
+ ' bar: 2\n'
+ ' }\n', conf, problem=(4, 4))
+ self.check('---\n'
+ 'top:\n'
+ ' rules:\n'
+ ' {\n'
+ ' foo: 1,\n'
+ ' bar: 2\n'
+ ' }\n', conf, problem=(7, 4))
+ self.check('---\n'
+ 'top:\n'
+ ' rules:\n'
+ ' {\n'
+ ' foo: 1,\n'
+ ' bar: 2\n'
+ ' }\n', conf, problem=(7, 6))
+ self.check('---\n'
+ 'top:\n'
+ ' [\n'
+ ' a, b, c\n'
+ ' ]\n', conf)
+ self.check('---\n'
+ 'top:\n'
+ ' [\n'
+ ' a, b, c\n'
+ ' ]\n', conf, problem=(4, 6))
+ self.check('---\n'
+ 'top:\n'
+ ' [\n'
+ ' a, b, c\n'
+ ' ]\n', conf, problem=(4, 6))
+ self.check('---\n'
+ 'top:\n'
+ ' [\n'
+ ' a, b, c\n'
+ ' ]\n', conf, problem=(5, 4))
+ self.check('---\n'
+ 'top:\n'
+ ' rules: [\n'
+ ' {\n'
+ ' foo: 1\n'
+ ' },\n'
+ ' {\n'
+ ' foo: 2,\n'
+ ' bar: [\n'
+ ' a, b, c\n'
+ ' ],\n'
+ ' },\n'
+ ' ]\n', conf)
+ self.check('---\n'
+ 'top:\n'
+ ' rules: [\n'
+ ' {\n'
+ ' foo: 1\n'
+ ' },\n'
+ ' {\n'
+ ' foo: 2,\n'
+ ' bar: [\n'
+ ' a, b, c\n'
+ ' ],\n'
+ ' },\n'
+ ']\n', conf, problem1=(5, 6), problem2=(6, 6),
+ problem3=(9, 9), problem4=(11, 7), problem5=(13, 1))
+
+ def test_under_indented(self):
+ conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
+ self.check('---\n'
+ 'object:\n'
+ ' val: 1\n'
+ '...\n', conf, problem=(3, 2))
+ self.check('---\n'
+ 'object:\n'
+ ' k1:\n'
+ ' - a\n'
+ '...\n', conf, problem=(4, 4))
+ self.check('---\n'
+ 'object:\n'
+ ' k3:\n'
+ ' - name: Unix\n'
+ ' date: 1969\n'
+ '...\n', conf, problem=(5, 6, 'syntax'))
+ conf = 'indentation: {spaces: 4, indent-sequences: consistent}'
+ self.check('---\n'
+ 'object:\n'
+ ' val: 1\n'
+ '...\n', conf, problem=(3, 4))
+ self.check('---\n'
+ '- el1\n'
+ '- el2:\n'
+ ' - subel\n'
+ '...\n', conf, problem=(4, 4))
+ self.check('---\n'
+ 'object:\n'
+ ' k3:\n'
+ ' - name: Linux\n'
+ ' date: 1991\n'
+ '...\n', conf, problem=(5, 10, 'syntax'))
+ conf = 'indentation: {spaces: 2, indent-sequences: true}'
+ self.check('---\n'
+ 'a:\n'
+ '-\n' # empty list
+ 'b: c\n'
+ '...\n', conf, problem=(3, 1))
+ conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
+ self.check('---\n'
+ 'a:\n'
+ ' -\n' # empty list
+ 'b:\n'
+ '-\n'
+ 'c: d\n'
+ '...\n', conf, problem=(5, 1))
+
+ def test_over_indented(self):
+ conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
+ self.check('---\n'
+ 'object:\n'
+ ' val: 1\n'
+ '...\n', conf, problem=(3, 4))
+ self.check('---\n'
+ 'object:\n'
+ ' k1:\n'
+ ' - a\n'
+ '...\n', conf, problem=(4, 6))
+ self.check('---\n'
+ 'object:\n'
+ ' k3:\n'
+ ' - name: Unix\n'
+ ' date: 1969\n'
+ '...\n', conf, problem=(5, 12, 'syntax'))
+ conf = 'indentation: {spaces: 4, indent-sequences: consistent}'
+ self.check('---\n'
+ 'object:\n'
+ ' val: 1\n'
+ '...\n', conf, problem=(3, 6))
+ self.check('---\n'
+ ' object:\n'
+ ' val: 1\n'
+ '...\n', conf, problem=(2, 2))
+ self.check('---\n'
+ '- el1\n'
+ '- el2:\n'
+ ' - subel\n'
+ '...\n', conf, problem=(4, 6))
+ self.check('---\n'
+ '- el1\n'
+ '- el2:\n'
+ ' - subel\n'
+ '...\n', conf, problem=(4, 15))
+ self.check('---\n'
+ ' - el1\n'
+ ' - el2:\n'
+ ' - subel\n'
+ '...\n', conf,
+ problem=(2, 3))
+ self.check('---\n'
+ 'object:\n'
+ ' k3:\n'
+ ' - name: Linux\n'
+ ' date: 1991\n'
+ '...\n', conf, problem=(5, 16, 'syntax'))
+ conf = 'indentation: {spaces: 4, indent-sequences: whatever}'
+ self.check('---\n'
+ ' - el1\n'
+ ' - el2:\n'
+ ' - subel\n'
+ '...\n', conf,
+ problem=(2, 3))
+ conf = 'indentation: {spaces: 2, indent-sequences: false}'
+ self.check('---\n'
+ 'a:\n'
+ ' -\n' # empty list
+ 'b: c\n'
+ '...\n', conf, problem=(3, 3))
+ conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
+ self.check('---\n'
+ 'a:\n'
+ '-\n' # empty list
+ 'b:\n'
+ ' -\n'
+ 'c: d\n'
+ '...\n', conf, problem=(5, 3))
+
+ def test_multi_lines(self):
+ conf = 'indentation: {spaces: consistent, indent-sequences: true}'
+ self.check('---\n'
+ 'long_string: >\n'
+ ' bla bla blah\n'
+ ' blah bla bla\n'
+ '...\n', conf)
+ self.check('---\n'
+ '- long_string: >\n'
+ ' bla bla blah\n'
+ ' blah bla bla\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'obj:\n'
+ ' - long_string: >\n'
+ ' bla bla blah\n'
+ ' blah bla bla\n'
+ '...\n', conf)
+
+ def test_empty_value(self):
+ conf = 'indentation: {spaces: consistent}'
+ self.check('---\n'
+ 'key1:\n'
+ 'key2: not empty\n'
+ 'key3:\n'
+ '...\n', conf)
+ self.check('---\n'
+ '-\n'
+ '- item 2\n'
+ '-\n'
+ '...\n', conf)
+
+ def test_nested_collections(self):
+ conf = 'indentation: {spaces: 2}'
+ self.check('---\n'
+ '- o:\n'
+ ' k1: v1\n'
+ '...\n', conf)
+ self.check('---\n'
+ '- o:\n'
+ ' k1: v1\n'
+ '...\n', conf, problem=(3, 2, 'syntax'))
+ self.check('---\n'
+ '- o:\n'
+ ' k1: v1\n'
+ '...\n', conf, problem=(3, 4))
+ conf = 'indentation: {spaces: 4}'
+ self.check('---\n'
+ '- o:\n'
+ ' k1: v1\n'
+ '...\n', conf)
+ self.check('---\n'
+ '- o:\n'
+ ' k1: v1\n'
+ '...\n', conf, problem=(3, 6))
+ self.check('---\n'
+ '- o:\n'
+ ' k1: v1\n'
+ '...\n', conf, problem=(3, 8))
+ self.check('---\n'
+ '- - - - item\n'
+ ' - elem 1\n'
+ ' - elem 2\n'
+ ' - - - - - very nested: a\n'
+ ' key: value\n'
+ '...\n', conf)
+ self.check('---\n'
+ ' - - - - item\n'
+ ' - elem 1\n'
+ ' - elem 2\n'
+ ' - - - - - very nested: a\n'
+ ' key: value\n'
+ '...\n', conf, problem=(2, 2))
+
+ def test_nested_collections_with_spaces_consistent(self):
+ """Tests behavior of {spaces: consistent} in nested collections to
+ ensure wrong-indentation is properly caught--especially when the
+ expected indent value is initially unknown. For details, see
+ https://github.com/adrienverge/yamllint/issues/485.
+ """
+ conf = ('indentation: {spaces: consistent,\n'
+ ' indent-sequences: true}')
+ self.check('---\n'
+ '- item:\n'
+ ' - elem\n'
+ '- item:\n'
+ ' - elem\n'
+ '...\n', conf, problem=(3, 3))
+ conf = ('indentation: {spaces: consistent,\n'
+ ' indent-sequences: false}')
+ self.check('---\n'
+ '- item:\n'
+ ' - elem\n'
+ '- item:\n'
+ ' - elem\n'
+ '...\n', conf, problem=(5, 5))
+ conf = ('indentation: {spaces: consistent,\n'
+ ' indent-sequences: consistent}')
+ self.check('---\n'
+ '- item:\n'
+ ' - elem\n'
+ '- item:\n'
+ ' - elem\n'
+ '...\n', conf, problem=(5, 5))
+ conf = ('indentation: {spaces: consistent,\n'
+ ' indent-sequences: whatever}')
+ self.check('---\n'
+ '- item:\n'
+ ' - elem\n'
+ '- item:\n'
+ ' - elem\n'
+ '...\n', conf)
+
+ def test_return(self):
+ conf = 'indentation: {spaces: consistent}'
+ self.check('---\n'
+ 'a:\n'
+ ' b:\n'
+ ' c:\n'
+ ' d:\n'
+ ' e:\n'
+ ' f:\n'
+ 'g:\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'a:\n'
+ ' b:\n'
+ ' c:\n'
+ ' d:\n'
+ '...\n', conf, problem=(5, 4, 'syntax'))
+ self.check('---\n'
+ 'a:\n'
+ ' b:\n'
+ ' c:\n'
+ ' d:\n'
+ '...\n', conf, problem=(5, 2, 'syntax'))
+
+ def test_first_line(self):
+ conf = ('indentation: {spaces: consistent}\n'
+ 'document-start: disable\n')
+ self.check(' a: 1\n', conf, problem=(1, 3))
+
+ def test_explicit_block_mappings(self):
+ conf = 'indentation: {spaces: consistent}'
+ self.check('---\n'
+ 'object:\n'
+ ' ? key\n'
+ ' : value\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' ? key\n'
+ ' :\n'
+ ' value\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' ?\n'
+ ' key\n'
+ ' : value\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' ?\n'
+ ' key\n'
+ ' :\n'
+ ' value\n'
+ '...\n', conf)
+ self.check('---\n'
+ '- ? key\n'
+ ' : value\n', conf)
+ self.check('---\n'
+ '- ? key\n'
+ ' :\n'
+ ' value\n'
+ '...\n', conf)
+ self.check('---\n'
+ '- ?\n'
+ ' key\n'
+ ' : value\n', conf)
+ self.check('---\n'
+ '- ?\n'
+ ' key\n'
+ ' :\n'
+ ' value\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'object:\n'
+ ' ? key\n'
+ ' :\n'
+ ' value\n'
+ '...\n', conf, problem=(5, 8))
+ self.check('---\n'
+ '- - ?\n'
+ ' key\n'
+ ' :\n'
+ ' value\n'
+ '...\n', conf, problem=(5, 7))
+ self.check('---\n'
+ 'object:\n'
+ ' ?\n'
+ ' key\n'
+ ' :\n'
+ ' value\n'
+ '...\n', conf, problem1=(4, 8), problem2=(6, 10))
+ self.check('---\n'
+ 'object:\n'
+ ' ?\n'
+ ' key\n'
+ ' :\n'
+ ' value\n'
+ '...\n', conf, problem1=(4, 10), problem2=(6, 8))
+
+ def test_clear_sequence_item(self):
+ conf = 'indentation: {spaces: consistent}'
+ self.check('---\n'
+ '-\n'
+ ' string\n'
+ '-\n'
+ ' map: ping\n'
+ '-\n'
+ ' - sequence\n'
+ ' -\n'
+ ' nested\n'
+ ' -\n'
+ ' >\n'
+ ' multi\n'
+ ' line\n'
+ '...\n', conf)
+ self.check('---\n'
+ '-\n'
+ ' string\n'
+ '-\n'
+ ' string\n', conf, problem=(5, 4))
+ self.check('---\n'
+ '-\n'
+ ' map: ping\n'
+ '-\n'
+ ' map: ping\n', conf, problem=(5, 4))
+ self.check('---\n'
+ '-\n'
+ ' - sequence\n'
+ '-\n'
+ ' - sequence\n', conf, problem=(5, 4))
+ self.check('---\n'
+ '-\n'
+ ' -\n'
+ ' nested\n'
+ ' -\n'
+ ' nested\n', conf, problem1=(4, 4), problem2=(6, 6))
+ self.check('---\n'
+ '-\n'
+ ' -\n'
+ ' >\n'
+ ' multi\n'
+ ' line\n'
+ '...\n', conf, problem=(4, 6))
+ conf = 'indentation: {spaces: 2}'
+ self.check('---\n'
+ '-\n'
+ ' string\n'
+ '-\n'
+ ' string\n', conf, problem1=(3, 2), problem2=(5, 4))
+ self.check('---\n'
+ '-\n'
+ ' map: ping\n'
+ '-\n'
+ ' map: ping\n', conf, problem1=(3, 2), problem2=(5, 4))
+ self.check('---\n'
+ '-\n'
+ ' - sequence\n'
+ '-\n'
+ ' - sequence\n', conf, problem1=(3, 2), problem2=(5, 4))
+ self.check('---\n'
+ '-\n'
+ ' -\n'
+ ' nested\n'
+ ' -\n'
+ ' nested\n', conf, problem1=(4, 4), problem2=(6, 6))
+
+ def test_anchors(self):
+ conf = 'indentation: {spaces: consistent}'
+ self.check('---\n'
+ 'key: &anchor value\n', conf)
+ self.check('---\n'
+ 'key: &anchor\n'
+ ' value\n', conf)
+ self.check('---\n'
+ '- &anchor value\n', conf)
+ self.check('---\n'
+ '- &anchor\n'
+ ' value\n', conf)
+ self.check('---\n'
+ 'key: &anchor [1,\n'
+ ' 2]\n', conf)
+ self.check('---\n'
+ 'key: &anchor\n'
+ ' [1,\n'
+ ' 2]\n', conf)
+ self.check('---\n'
+ 'key: &anchor\n'
+ ' - 1\n'
+ ' - 2\n', conf)
+ self.check('---\n'
+ '- &anchor [1,\n'
+ ' 2]\n', conf)
+ self.check('---\n'
+ '- &anchor\n'
+ ' [1,\n'
+ ' 2]\n', conf)
+ self.check('---\n'
+ '- &anchor\n'
+ ' - 1\n'
+ ' - 2\n', conf)
+ self.check('---\n'
+ 'key:\n'
+ ' &anchor1\n'
+ ' value\n', conf)
+ self.check('---\n'
+ 'pre:\n'
+ ' &anchor1 0\n'
+ '&anchor2 key:\n'
+ ' value\n', conf)
+ self.check('---\n'
+ 'machine0:\n'
+ ' /etc/hosts: &ref-etc-hosts\n'
+ ' content:\n'
+ ' - 127.0.0.1: localhost\n'
+ ' - ::1: localhost\n'
+ ' mode: 0644\n'
+ 'machine1:\n'
+ ' /etc/hosts: *ref-etc-hosts\n', conf)
+ self.check('---\n'
+ 'list:\n'
+ ' - k: v\n'
+ ' - &a truc\n'
+ ' - &b\n'
+ ' truc\n'
+ ' - k: *a\n', conf)
+
+ def test_tags(self):
+ conf = 'indentation: {spaces: consistent}'
+ self.check('---\n'
+ '-\n'
+ ' "flow in block"\n'
+ '- >\n'
+ ' Block scalar\n'
+ '- !!map # Block collection\n'
+ ' foo: bar\n', conf)
+
+ conf = 'indentation: {spaces: consistent, indent-sequences: false}'
+ self.check('---\n'
+ 'sequence: !!seq\n'
+ '- entry\n'
+ '- !!seq\n'
+ ' - nested\n', conf)
+ self.check('---\n'
+ 'mapping: !!map\n'
+ ' foo: bar\n'
+ 'Block style: !!map\n'
+ ' Clark: Evans\n'
+ ' Ingy: döt Net\n'
+ ' Oren: Ben-Kiki\n', conf)
+ self.check('---\n'
+ 'Flow style: !!map {Clark: Evans, Ingy: döt Net}\n'
+ 'Block style: !!seq\n'
+ '- Clark Evans\n'
+ '- Ingy döt Net\n', conf)
+
+ def test_flows_imbrication(self):
+ conf = 'indentation: {spaces: consistent}'
+ self.check('---\n'
+ '[val]: value\n', conf)
+ self.check('---\n'
+ '{key}: value\n', conf)
+ self.check('---\n'
+ '{key: val}: value\n', conf)
+ self.check('---\n'
+ '[[val]]: value\n', conf)
+ self.check('---\n'
+ '{{key}}: value\n', conf)
+ self.check('---\n'
+ '{{key: val1}: val2}: value\n', conf)
+ self.check('---\n'
+ '- [val, {{key: val}: val}]: value\n'
+ '- {[val,\n'
+ ' {{key: val}: val}]}\n'
+ '- {[val,\n'
+ ' {{key: val,\n'
+ ' key2}}]}\n'
+ '- {{{{{moustaches}}}}}\n'
+ '- {{{{{moustache,\n'
+ ' moustache},\n'
+ ' moustache}},\n'
+ ' moustache}}\n', conf)
+ self.check('---\n'
+ '- {[val,\n'
+ ' {{key: val}: val}]}\n',
+ conf, problem=(3, 6))
+ self.check('---\n'
+ '- {[val,\n'
+ ' {{key: val,\n'
+ ' key2}}]}\n',
+ conf, problem=(4, 6))
+ self.check('---\n'
+ '- {{{{{moustache,\n'
+ ' moustache},\n'
+ ' moustache}},\n'
+ ' moustache}}\n',
+ conf, problem1=(4, 8), problem2=(5, 4))
+
+
+class ScalarIndentationTestCase(RuleTestCase):
+ rule_id = 'indentation'
+
+ def test_basics_plain(self):
+ conf = ('indentation: {spaces: consistent,\n'
+ ' check-multi-line-strings: false}\n'
+ 'document-start: disable\n')
+ self.check('multi\n'
+ 'line\n', conf)
+ self.check('multi\n'
+ ' line\n', conf)
+ self.check('- multi\n'
+ ' line\n', conf)
+ self.check('- multi\n'
+ ' line\n', conf)
+ self.check('a key: multi\n'
+ ' line\n', conf)
+ self.check('a key: multi\n'
+ ' line\n', conf)
+ self.check('a key: multi\n'
+ ' line\n', conf)
+ self.check('a key:\n'
+ ' multi\n'
+ ' line\n', conf)
+ self.check('- C code: void main() {\n'
+ ' printf("foo");\n'
+ ' }\n', conf)
+ self.check('- C code:\n'
+ ' void main() {\n'
+ ' printf("foo");\n'
+ ' }\n', conf)
+
+ def test_check_multi_line_plain(self):
+ conf = ('indentation: {spaces: consistent,\n'
+ ' check-multi-line-strings: true}\n'
+ 'document-start: disable\n')
+ self.check('multi\n'
+ ' line\n', conf, problem=(2, 2))
+ self.check('- multi\n'
+ ' line\n', conf, problem=(2, 4))
+ self.check('a key: multi\n'
+ ' line\n', conf, problem=(2, 3))
+ self.check('a key: multi\n'
+ ' line\n', conf, problem=(2, 9))
+ self.check('a key:\n'
+ ' multi\n'
+ ' line\n', conf, problem=(3, 4))
+ self.check('- C code: void main() {\n'
+ ' printf("foo");\n'
+ ' }\n', conf, problem=(2, 15))
+ self.check('- C code:\n'
+ ' void main() {\n'
+ ' printf("foo");\n'
+ ' }\n', conf, problem=(3, 9))
+
+ def test_basics_quoted(self):
+ conf = ('indentation: {spaces: consistent,\n'
+ ' check-multi-line-strings: false}\n'
+ 'document-start: disable\n')
+ self.check('"multi\n'
+ ' line"\n', conf)
+ self.check('- "multi\n'
+ ' line"\n', conf)
+ self.check('a key: "multi\n'
+ ' line"\n', conf)
+ self.check('a key:\n'
+ ' "multi\n'
+ ' line"\n', conf)
+ self.check('- jinja2: "{% if ansible is defined %}\n'
+ ' {{ ansible }}\n'
+ ' {% else %}\n'
+ ' {{ chef }}\n'
+ ' {% endif %}"\n', conf)
+ self.check('- jinja2:\n'
+ ' "{% if ansible is defined %}\n'
+ ' {{ ansible }}\n'
+ ' {% else %}\n'
+ ' {{ chef }}\n'
+ ' {% endif %}"\n', conf)
+ self.check('["this is a very long line\n'
+ ' that needs to be split",\n'
+ ' "other line"]\n', conf)
+ self.check('["multi\n'
+ ' line 1", "multi\n'
+ ' line 2"]\n', conf)
+
+ def test_check_multi_line_quoted(self):
+ conf = ('indentation: {spaces: consistent,\n'
+ ' check-multi-line-strings: true}\n'
+ 'document-start: disable\n')
+ self.check('"multi\n'
+ 'line"\n', conf, problem=(2, 1))
+ self.check('"multi\n'
+ ' line"\n', conf, problem=(2, 3))
+ self.check('- "multi\n'
+ ' line"\n', conf, problem=(2, 3))
+ self.check('- "multi\n'
+ ' line"\n', conf, problem=(2, 5))
+ self.check('a key: "multi\n'
+ ' line"\n', conf, problem=(2, 3))
+ self.check('a key: "multi\n'
+ ' line"\n', conf, problem=(2, 8))
+ self.check('a key: "multi\n'
+ ' line"\n', conf, problem=(2, 10))
+ self.check('a key:\n'
+ ' "multi\n'
+ ' line"\n', conf, problem=(3, 3))
+ self.check('a key:\n'
+ ' "multi\n'
+ ' line"\n', conf, problem=(3, 5))
+ self.check('- jinja2: "{% if ansible is defined %}\n'
+ ' {{ ansible }}\n'
+ ' {% else %}\n'
+ ' {{ chef }}\n'
+ ' {% endif %}"\n', conf,
+ problem1=(2, 14), problem2=(4, 14))
+ self.check('- jinja2:\n'
+ ' "{% if ansible is defined %}\n'
+ ' {{ ansible }}\n'
+ ' {% else %}\n'
+ ' {{ chef }}\n'
+ ' {% endif %}"\n', conf,
+ problem1=(3, 8), problem2=(5, 8))
+ self.check('["this is a very long line\n'
+ ' that needs to be split",\n'
+ ' "other line"]\n', conf)
+ self.check('["this is a very long line\n'
+ ' that needs to be split",\n'
+ ' "other line"]\n', conf, problem=(2, 2))
+ self.check('["this is a very long line\n'
+ ' that needs to be split",\n'
+ ' "other line"]\n', conf, problem=(2, 4))
+ self.check('["multi\n'
+ ' line 1", "multi\n'
+ ' line 2"]\n', conf)
+ self.check('["multi\n'
+ ' line 1", "multi\n'
+ ' line 2"]\n', conf, problem=(3, 12))
+ self.check('["multi\n'
+ ' line 1", "multi\n'
+ ' line 2"]\n', conf, problem=(3, 14))
+
+ def test_basics_folded_style(self):
+ conf = ('indentation: {spaces: consistent,\n'
+ ' check-multi-line-strings: false}\n'
+ 'document-start: disable\n')
+ self.check('>\n'
+ ' multi\n'
+ ' line\n', conf)
+ self.check('- >\n'
+ ' multi\n'
+ ' line\n', conf)
+ self.check('- key: >\n'
+ ' multi\n'
+ ' line\n', conf)
+ self.check('- key:\n'
+ ' >\n'
+ ' multi\n'
+ ' line\n', conf)
+ self.check('- ? >\n'
+ ' multi-line\n'
+ ' key\n'
+ ' : >\n'
+ ' multi-line\n'
+ ' value\n', conf)
+ self.check('- ?\n'
+ ' >\n'
+ ' multi-line\n'
+ ' key\n'
+ ' :\n'
+ ' >\n'
+ ' multi-line\n'
+ ' value\n', conf)
+ self.check('- jinja2: >\n'
+ ' {% if ansible is defined %}\n'
+ ' {{ ansible }}\n'
+ ' {% else %}\n'
+ ' {{ chef }}\n'
+ ' {% endif %}\n', conf)
+
+ def test_check_multi_line_folded_style(self):
+ conf = ('indentation: {spaces: consistent,\n'
+ ' check-multi-line-strings: true}\n'
+ 'document-start: disable\n')
+ self.check('>\n'
+ ' multi\n'
+ ' line\n', conf, problem=(3, 4))
+ self.check('- >\n'
+ ' multi\n'
+ ' line\n', conf, problem=(3, 6))
+ self.check('- key: >\n'
+ ' multi\n'
+ ' line\n', conf, problem=(3, 6))
+ self.check('- key:\n'
+ ' >\n'
+ ' multi\n'
+ ' line\n', conf, problem=(4, 8))
+ self.check('- ? >\n'
+ ' multi-line\n'
+ ' key\n'
+ ' : >\n'
+ ' multi-line\n'
+ ' value\n', conf,
+ problem1=(3, 8), problem2=(6, 8))
+ self.check('- ?\n'
+ ' >\n'
+ ' multi-line\n'
+ ' key\n'
+ ' :\n'
+ ' >\n'
+ ' multi-line\n'
+ ' value\n', conf,
+ problem1=(4, 8), problem2=(8, 8))
+ self.check('- jinja2: >\n'
+ ' {% if ansible is defined %}\n'
+ ' {{ ansible }}\n'
+ ' {% else %}\n'
+ ' {{ chef }}\n'
+ ' {% endif %}\n', conf,
+ problem1=(3, 7), problem2=(5, 7))
+
+ def test_basics_literal_style(self):
+ conf = ('indentation: {spaces: consistent,\n'
+ ' check-multi-line-strings: false}\n'
+ 'document-start: disable\n')
+ self.check('|\n'
+ ' multi\n'
+ ' line\n', conf)
+ self.check('- |\n'
+ ' multi\n'
+ ' line\n', conf)
+ self.check('- key: |\n'
+ ' multi\n'
+ ' line\n', conf)
+ self.check('- key:\n'
+ ' |\n'
+ ' multi\n'
+ ' line\n', conf)
+ self.check('- ? |\n'
+ ' multi-line\n'
+ ' key\n'
+ ' : |\n'
+ ' multi-line\n'
+ ' value\n', conf)
+ self.check('- ?\n'
+ ' |\n'
+ ' multi-line\n'
+ ' key\n'
+ ' :\n'
+ ' |\n'
+ ' multi-line\n'
+ ' value\n', conf)
+ self.check('- jinja2: |\n'
+ ' {% if ansible is defined %}\n'
+ ' {{ ansible }}\n'
+ ' {% else %}\n'
+ ' {{ chef }}\n'
+ ' {% endif %}\n', conf)
+
+ def test_check_multi_line_literal_style(self):
+ conf = ('indentation: {spaces: consistent,\n'
+ ' check-multi-line-strings: true}\n'
+ 'document-start: disable\n')
+ self.check('|\n'
+ ' multi\n'
+ ' line\n', conf, problem=(3, 4))
+ self.check('- |\n'
+ ' multi\n'
+ ' line\n', conf, problem=(3, 6))
+ self.check('- key: |\n'
+ ' multi\n'
+ ' line\n', conf, problem=(3, 6))
+ self.check('- key:\n'
+ ' |\n'
+ ' multi\n'
+ ' line\n', conf, problem=(4, 8))
+ self.check('- ? |\n'
+ ' multi-line\n'
+ ' key\n'
+ ' : |\n'
+ ' multi-line\n'
+ ' value\n', conf,
+ problem1=(3, 8), problem2=(6, 8))
+ self.check('- ?\n'
+ ' |\n'
+ ' multi-line\n'
+ ' key\n'
+ ' :\n'
+ ' |\n'
+ ' multi-line\n'
+ ' value\n', conf,
+ problem1=(4, 8), problem2=(8, 8))
+ self.check('- jinja2: |\n'
+ ' {% if ansible is defined %}\n'
+ ' {{ ansible }}\n'
+ ' {% else %}\n'
+ ' {{ chef }}\n'
+ ' {% endif %}\n', conf,
+ problem1=(3, 7), problem2=(5, 7))
+
+ # The following "paragraph" examples are inspired from
+ # http://stackoverflow.com/questions/3790454/in-yaml-how-do-i-break-a-string-over-multiple-lines
+
+ def test_paragraph_plain(self):
+ conf = ('indentation: {spaces: consistent,\n'
+ ' check-multi-line-strings: true}\n'
+ 'document-start: disable\n')
+ self.check('- long text: very "long"\n'
+ ' \'string\' with\n'
+ '\n'
+ ' paragraph gap, \\n and\n'
+ ' spaces.\n', conf)
+ self.check('- long text: very "long"\n'
+ ' \'string\' with\n'
+ '\n'
+ ' paragraph gap, \\n and\n'
+ ' spaces.\n', conf,
+ problem1=(2, 5), problem2=(4, 5), problem3=(5, 5))
+ self.check('- long text:\n'
+ ' very "long"\n'
+ ' \'string\' with\n'
+ '\n'
+ ' paragraph gap, \\n and\n'
+ ' spaces.\n', conf)
+
+ def test_paragraph_double_quoted(self):
+ conf = ('indentation: {spaces: consistent,\n'
+ ' check-multi-line-strings: true}\n'
+ 'document-start: disable\n')
+ self.check('- long text: "very \\"long\\"\n'
+ ' \'string\' with\n'
+ '\n'
+ ' paragraph gap, \\n and\n'
+ ' spaces."\n', conf)
+ self.check('- long text: "very \\"long\\"\n'
+ ' \'string\' with\n'
+ '\n'
+ ' paragraph gap, \\n and\n'
+ ' spaces."\n', conf,
+ problem1=(2, 5), problem2=(4, 5), problem3=(5, 5))
+ self.check('- long text: "very \\"long\\"\n'
+ '\'string\' with\n'
+ '\n'
+ 'paragraph gap, \\n and\n'
+ 'spaces."\n', conf,
+ problem1=(2, 1), problem2=(4, 1), problem3=(5, 1))
+ self.check('- long text:\n'
+ ' "very \\"long\\"\n'
+ ' \'string\' with\n'
+ '\n'
+ ' paragraph gap, \\n and\n'
+ ' spaces."\n', conf)
+
+ def test_paragraph_single_quoted(self):
+ conf = ('indentation: {spaces: consistent,\n'
+ ' check-multi-line-strings: true}\n'
+ 'document-start: disable\n')
+ self.check('- long text: \'very "long"\n'
+ ' \'\'string\'\' with\n'
+ '\n'
+ ' paragraph gap, \\n and\n'
+ ' spaces.\'\n', conf)
+ self.check('- long text: \'very "long"\n'
+ ' \'\'string\'\' with\n'
+ '\n'
+ ' paragraph gap, \\n and\n'
+ ' spaces.\'\n', conf,
+ problem1=(2, 5), problem2=(4, 5), problem3=(5, 5))
+ self.check('- long text: \'very "long"\n'
+ '\'\'string\'\' with\n'
+ '\n'
+ 'paragraph gap, \\n and\n'
+ 'spaces.\'\n', conf,
+ problem1=(2, 1), problem2=(4, 1), problem3=(5, 1))
+ self.check('- long text:\n'
+ ' \'very "long"\n'
+ ' \'\'string\'\' with\n'
+ '\n'
+ ' paragraph gap, \\n and\n'
+ ' spaces.\'\n', conf)
+
+ def test_paragraph_folded(self):
+ conf = ('indentation: {spaces: consistent,\n'
+ ' check-multi-line-strings: true}\n'
+ 'document-start: disable\n')
+ self.check('- long text: >\n'
+ ' very "long"\n'
+ ' \'string\' with\n'
+ '\n'
+ ' paragraph gap, \\n and\n'
+ ' spaces.\n', conf)
+ self.check('- long text: >\n'
+ ' very "long"\n'
+ ' \'string\' with\n'
+ '\n'
+ ' paragraph gap, \\n and\n'
+ ' spaces.\n', conf,
+ problem1=(3, 6), problem2=(5, 7), problem3=(6, 8))
+
+ def test_paragraph_literal(self):
+ conf = ('indentation: {spaces: consistent,\n'
+ ' check-multi-line-strings: true}\n'
+ 'document-start: disable\n')
+ self.check('- long text: |\n'
+ ' very "long"\n'
+ ' \'string\' with\n'
+ '\n'
+ ' paragraph gap, \\n and\n'
+ ' spaces.\n', conf)
+ self.check('- long text: |\n'
+ ' very "long"\n'
+ ' \'string\' with\n'
+ '\n'
+ ' paragraph gap, \\n and\n'
+ ' spaces.\n', conf,
+ problem1=(3, 6), problem2=(5, 7), problem3=(6, 8))
+
+ def test_consistent(self):
+ conf = ('indentation: {spaces: consistent,\n'
+ ' check-multi-line-strings: true}\n'
+ 'document-start: disable\n')
+ self.check('multi\n'
+ 'line\n', conf)
+ self.check('multi\n'
+ ' line\n', conf, problem=(2, 2))
+ self.check('- multi\n'
+ ' line\n', conf)
+ self.check('- multi\n'
+ ' line\n', conf, problem=(2, 4))
+ self.check('a key: multi\n'
+ ' line\n', conf, problem=(2, 3))
+ self.check('a key: multi\n'
+ ' line\n', conf, problem=(2, 9))
+ self.check('a key:\n'
+ ' multi\n'
+ ' line\n', conf, problem=(3, 4))
+ self.check('- C code: void main() {\n'
+ ' printf("foo");\n'
+ ' }\n', conf, problem=(2, 15))
+ self.check('- C code:\n'
+ ' void main() {\n'
+ ' printf("foo");\n'
+ ' }\n', conf, problem=(3, 9))
+ self.check('>\n'
+ ' multi\n'
+ ' line\n', conf)
+ self.check('>\n'
+ ' multi\n'
+ ' line\n', conf)
+ self.check('>\n'
+ ' multi\n'
+ ' line\n', conf, problem=(3, 7))
diff --git a/tests/rules/test_key_duplicates.py b/tests/rules/test_key_duplicates.py
new file mode 100644
index 0000000..3f8a9e6
--- /dev/null
+++ b/tests/rules/test_key_duplicates.py
@@ -0,0 +1,181 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class KeyDuplicatesTestCase(RuleTestCase):
+ rule_id = 'key-duplicates'
+
+ def test_disabled(self):
+ conf = 'key-duplicates: disable'
+ self.check('---\n'
+ 'block mapping:\n'
+ ' key: a\n'
+ ' otherkey: b\n'
+ ' key: c\n', conf)
+ self.check('---\n'
+ 'flow mapping:\n'
+ ' {key: a, otherkey: b, key: c}\n', conf)
+ self.check('---\n'
+ 'duplicated twice:\n'
+ ' - k: a\n'
+ ' ok: b\n'
+ ' k: c\n'
+ ' k: d\n', conf)
+ self.check('---\n'
+ 'duplicated twice:\n'
+ ' - {k: a, ok: b, k: c, k: d}\n', conf)
+ self.check('---\n'
+ 'multiple duplicates:\n'
+ ' a: 1\n'
+ ' b: 2\n'
+ ' c: 3\n'
+ ' d: 4\n'
+ ' d: 5\n'
+ ' b: 6\n', conf)
+ self.check('---\n'
+ 'multiple duplicates:\n'
+ ' {a: 1, b: 2, c: 3, d: 4, d: 5, b: 6}\n', conf)
+ self.check('---\n'
+ 'at: root\n'
+ 'multiple: times\n'
+ 'at: root\n', conf)
+ self.check('---\n'
+ 'nested but OK:\n'
+ ' a: {a: {a: 1}}\n'
+ ' b:\n'
+ ' b: 2\n'
+ ' c: 3\n', conf)
+ self.check('---\n'
+ 'nested duplicates:\n'
+ ' a: {a: 1, a: 1}\n'
+ ' b:\n'
+ ' c: 3\n'
+ ' d: 4\n'
+ ' d: 4\n'
+ ' b: 2\n', conf)
+ self.check('---\n'
+ 'duplicates with many styles: 1\n'
+ '"duplicates with many styles": 1\n'
+ '\'duplicates with many styles\': 1\n'
+ '? duplicates with many styles\n'
+ ': 1\n'
+ '? >-\n'
+ ' duplicates with\n'
+ ' many styles\n'
+ ': 1\n', conf)
+ self.check('---\n'
+ 'Merge Keys are OK:\n'
+ 'anchor_one: &anchor_one\n'
+ ' one: one\n'
+ 'anchor_two: &anchor_two\n'
+ ' two: two\n'
+ 'anchor_reference:\n'
+ ' <<: *anchor_one\n'
+ ' <<: *anchor_two\n', conf)
+ self.check('---\n'
+ '{a: 1, b: 2}}\n', conf, problem=(2, 13, 'syntax'))
+ self.check('---\n'
+ '[a, b, c]]\n', conf, problem=(2, 10, 'syntax'))
+
+ def test_enabled(self):
+ conf = 'key-duplicates: enable'
+ self.check('---\n'
+ 'block mapping:\n'
+ ' key: a\n'
+ ' otherkey: b\n'
+ ' key: c\n', conf,
+ problem=(5, 3))
+ self.check('---\n'
+ 'flow mapping:\n'
+ ' {key: a, otherkey: b, key: c}\n', conf,
+ problem=(3, 25))
+ self.check('---\n'
+ 'duplicated twice:\n'
+ ' - k: a\n'
+ ' ok: b\n'
+ ' k: c\n'
+ ' k: d\n', conf,
+ problem1=(5, 5), problem2=(6, 5))
+ self.check('---\n'
+ 'duplicated twice:\n'
+ ' - {k: a, ok: b, k: c, k: d}\n', conf,
+ problem1=(3, 19), problem2=(3, 25))
+ self.check('---\n'
+ 'multiple duplicates:\n'
+ ' a: 1\n'
+ ' b: 2\n'
+ ' c: 3\n'
+ ' d: 4\n'
+ ' d: 5\n'
+ ' b: 6\n', conf,
+ problem1=(7, 3), problem2=(8, 3))
+ self.check('---\n'
+ 'multiple duplicates:\n'
+ ' {a: 1, b: 2, c: 3, d: 4, d: 5, b: 6}\n', conf,
+ problem1=(3, 28), problem2=(3, 34))
+ self.check('---\n'
+ 'at: root\n'
+ 'multiple: times\n'
+ 'at: root\n', conf,
+ problem=(4, 1))
+ self.check('---\n'
+ 'nested but OK:\n'
+ ' a: {a: {a: 1}}\n'
+ ' b:\n'
+ ' b: 2\n'
+ ' c: 3\n', conf)
+ self.check('---\n'
+ 'nested duplicates:\n'
+ ' a: {a: 1, a: 1}\n'
+ ' b:\n'
+ ' c: 3\n'
+ ' d: 4\n'
+ ' d: 4\n'
+ ' b: 2\n', conf,
+ problem1=(3, 13), problem2=(7, 5), problem3=(8, 3))
+ self.check('---\n'
+ 'duplicates with many styles: 1\n'
+ '"duplicates with many styles": 1\n'
+ '\'duplicates with many styles\': 1\n'
+ '? duplicates with many styles\n'
+ ': 1\n'
+ '? >-\n'
+ ' duplicates with\n'
+ ' many styles\n'
+ ': 1\n', conf,
+ problem1=(3, 1), problem2=(4, 1), problem3=(5, 3),
+ problem4=(7, 3))
+ self.check('---\n'
+ 'Merge Keys are OK:\n'
+ 'anchor_one: &anchor_one\n'
+ ' one: one\n'
+ 'anchor_two: &anchor_two\n'
+ ' two: two\n'
+ 'anchor_reference:\n'
+ ' <<: *anchor_one\n'
+ ' <<: *anchor_two\n', conf)
+ self.check('---\n'
+ '{a: 1, b: 2}}\n', conf, problem=(2, 13, 'syntax'))
+ self.check('---\n'
+ '[a, b, c]]\n', conf, problem=(2, 10, 'syntax'))
+
+ def test_key_tokens_in_flow_sequences(self):
+ conf = 'key-duplicates: enable'
+ self.check('---\n'
+ '[\n'
+ ' flow: sequence, with, key: value, mappings\n'
+ ']\n', conf)
diff --git a/tests/rules/test_key_ordering.py b/tests/rules/test_key_ordering.py
new file mode 100644
index 0000000..7d17603
--- /dev/null
+++ b/tests/rules/test_key_ordering.py
@@ -0,0 +1,149 @@
+# Copyright (C) 2017 Johannes F. Knauf
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+import locale
+
+from tests.common import RuleTestCase
+
+
+class KeyOrderingTestCase(RuleTestCase):
+ rule_id = 'key-ordering'
+
+ def test_disabled(self):
+ conf = 'key-ordering: disable'
+ self.check('---\n'
+ 'block mapping:\n'
+ ' secondkey: a\n'
+ ' firstkey: b\n', conf)
+ self.check('---\n'
+ 'flow mapping:\n'
+ ' {secondkey: a, firstkey: b}\n', conf)
+ self.check('---\n'
+ 'second: before_first\n'
+ 'at: root\n', conf)
+ self.check('---\n'
+ 'nested but OK:\n'
+ ' second: {first: 1}\n'
+ ' third:\n'
+ ' second: 2\n', conf)
+
+ def test_enabled(self):
+ conf = 'key-ordering: enable'
+ self.check('---\n'
+ 'block mapping:\n'
+ ' secondkey: a\n'
+ ' firstkey: b\n', conf,
+ problem=(4, 3))
+ self.check('---\n'
+ 'flow mapping:\n'
+ ' {secondkey: a, firstkey: b}\n', conf,
+ problem=(3, 18))
+ self.check('---\n'
+ 'second: before_first\n'
+ 'at: root\n', conf,
+ problem=(3, 1))
+ self.check('---\n'
+ 'nested but OK:\n'
+ ' second: {first: 1}\n'
+ ' third:\n'
+ ' second: 2\n', conf)
+
+ def test_word_length(self):
+ conf = 'key-ordering: enable'
+ self.check('---\n'
+ 'a: 1\n'
+ 'ab: 1\n'
+ 'abc: 1\n', conf)
+ self.check('---\n'
+ 'a: 1\n'
+ 'abc: 1\n'
+ 'ab: 1\n', conf,
+ problem=(4, 1))
+
+ def test_key_duplicates(self):
+ conf = ('key-duplicates: disable\n'
+ 'key-ordering: enable')
+ self.check('---\n'
+ 'key: 1\n'
+ 'key: 2\n', conf)
+
+ def test_case(self):
+ conf = 'key-ordering: enable'
+ self.check('---\n'
+ 'T-shirt: 1\n'
+ 'T-shirts: 2\n'
+ 't-shirt: 3\n'
+ 't-shirts: 4\n', conf)
+ self.check('---\n'
+ 'T-shirt: 1\n'
+ 't-shirt: 2\n'
+ 'T-shirts: 3\n'
+ 't-shirts: 4\n', conf,
+ problem=(4, 1))
+
+ def test_accents(self):
+ conf = 'key-ordering: enable'
+ self.check('---\n'
+ 'hair: true\n'
+ 'hais: true\n'
+ 'haïr: true\n'
+ 'haïssable: true\n', conf)
+ self.check('---\n'
+ 'haïr: true\n'
+ 'hais: true\n', conf,
+ problem=(3, 1))
+
+ def test_key_tokens_in_flow_sequences(self):
+ conf = 'key-ordering: enable'
+ self.check('---\n'
+ '[\n'
+ ' key: value, mappings, in, flow: sequence\n'
+ ']\n', conf)
+
+ def test_locale_case(self):
+ self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None))
+ try:
+ locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
+ except locale.Error: # pragma: no cover
+ self.skipTest('locale en_US.UTF-8 not available')
+ conf = ('key-ordering: enable')
+ self.check('---\n'
+ 't-shirt: 1\n'
+ 'T-shirt: 2\n'
+ 't-shirts: 3\n'
+ 'T-shirts: 4\n', conf)
+ self.check('---\n'
+ 't-shirt: 1\n'
+ 't-shirts: 2\n'
+ 'T-shirt: 3\n'
+ 'T-shirts: 4\n', conf,
+ problem=(4, 1))
+
+ def test_locale_accents(self):
+ self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None))
+ try:
+ locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
+ except locale.Error: # pragma: no cover
+ self.skipTest('locale en_US.UTF-8 not available')
+ conf = ('key-ordering: enable')
+ self.check('---\n'
+ 'hair: true\n'
+ 'haïr: true\n'
+ 'hais: true\n'
+ 'haïssable: true\n', conf)
+ self.check('---\n'
+ 'hais: true\n'
+ 'haïr: true\n', conf,
+ problem=(3, 1))
diff --git a/tests/rules/test_line_length.py b/tests/rules/test_line_length.py
new file mode 100644
index 0000000..ef68178
--- /dev/null
+++ b/tests/rules/test_line_length.py
@@ -0,0 +1,198 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class LineLengthTestCase(RuleTestCase):
+ rule_id = 'line-length'
+
+ def test_disabled(self):
+ conf = ('line-length: disable\n'
+ 'empty-lines: disable\n'
+ 'new-line-at-end-of-file: disable\n'
+ 'document-start: disable\n')
+ self.check('', conf)
+ self.check('\n', conf)
+ self.check('---\n', conf)
+ self.check(81 * 'a', conf)
+ self.check('---\n' + 81 * 'a' + '\n', conf)
+ self.check(1000 * 'b', conf)
+ self.check('---\n' + 1000 * 'b' + '\n', conf)
+ self.check('content: |\n'
+ ' {% this line is' + 99 * ' really' + ' long %}\n',
+ conf)
+
+ def test_default(self):
+ conf = ('line-length: {max: 80}\n'
+ 'empty-lines: disable\n'
+ 'new-line-at-end-of-file: disable\n'
+ 'document-start: disable\n')
+ self.check('', conf)
+ self.check('\n', conf)
+ self.check('---\n', conf)
+ self.check(80 * 'a', conf)
+ self.check('---\n' + 80 * 'a' + '\n', conf)
+ self.check(16 * 'aaaa ' + 'z', conf, problem=(1, 81))
+ self.check('---\n' + 16 * 'aaaa ' + 'z' + '\n', conf, problem=(2, 81))
+ self.check(1000 * 'word ' + 'end', conf, problem=(1, 81))
+ self.check('---\n' + 1000 * 'word ' + 'end\n', conf, problem=(2, 81))
+
+ def test_max_length_10(self):
+ conf = ('line-length: {max: 10}\n'
+ 'new-line-at-end-of-file: disable\n')
+ self.check('---\nABCD EFGHI', conf)
+ self.check('---\nABCD EFGHIJ', conf, problem=(2, 11))
+ self.check('---\nABCD EFGHIJ\n', conf, problem=(2, 11))
+
+ def test_spaces(self):
+ conf = ('line-length: {max: 80}\n'
+ 'new-line-at-end-of-file: disable\n'
+ 'trailing-spaces: disable\n')
+ self.check('---\n' + 81 * ' ', conf, problem=(2, 81))
+ self.check('---\n' + 81 * ' ' + '\n', conf, problem=(2, 81))
+
+ def test_non_breakable_word(self):
+ conf = 'line-length: {max: 20, allow-non-breakable-words: true}'
+ self.check('---\n' + 30 * 'A' + '\n', conf)
+ self.check('---\n'
+ 'this:\n'
+ ' is:\n'
+ ' - a:\n'
+ ' http://localhost/very/long/url\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'this:\n'
+ ' is:\n'
+ ' - a:\n'
+ ' # http://localhost/very/long/url\n'
+ ' comment\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'this:\n'
+ 'is:\n'
+ 'another:\n'
+ ' - https://localhost/very/very/long/url\n'
+ '...\n', conf)
+ self.check('---\n'
+ 'long_line: http://localhost/very/very/long/url\n', conf,
+ problem=(2, 21))
+
+ conf = 'line-length: {max: 20, allow-non-breakable-words: false}'
+ self.check('---\n' + 30 * 'A' + '\n', conf, problem=(2, 21))
+ self.check('---\n'
+ 'this:\n'
+ ' is:\n'
+ ' - a:\n'
+ ' http://localhost/very/long/url\n'
+ '...\n', conf, problem=(5, 21))
+ self.check('---\n'
+ 'this:\n'
+ ' is:\n'
+ ' - a:\n'
+ ' # http://localhost/very/long/url\n'
+ ' comment\n'
+ '...\n', conf, problem=(5, 21))
+ self.check('---\n'
+ 'this:\n'
+ 'is:\n'
+ 'another:\n'
+ ' - https://localhost/very/very/long/url\n'
+ '...\n', conf, problem=(5, 21))
+ self.check('---\n'
+ 'long_line: http://localhost/very/very/long/url\n'
+ '...\n', conf, problem=(2, 21))
+
+ conf = 'line-length: {max: 20, allow-non-breakable-words: true}'
+ self.check('---\n'
+ '# http://www.verylongurlurlurlurlurlurlurlurl.com\n'
+ 'key:\n'
+ ' subkey: value\n', conf)
+ self.check('---\n'
+ '## http://www.verylongurlurlurlurlurlurlurlurl.com\n'
+ 'key:\n'
+ ' subkey: value\n', conf)
+ self.check('---\n'
+ '# # http://www.verylongurlurlurlurlurlurlurlurl.com\n'
+ 'key:\n'
+ ' subkey: value\n', conf,
+ problem=(2, 21))
+ self.check('---\n'
+ '#A http://www.verylongurlurlurlurlurlurlurlurl.com\n'
+ 'key:\n'
+ ' subkey: value\n', conf,
+ problem1=(2, 2, 'comments'),
+ problem2=(2, 21, 'line-length'))
+
+ conf = ('line-length: {max: 20, allow-non-breakable-words: true}\n'
+ 'trailing-spaces: disable')
+ self.check('---\n'
+ 'loooooooooong+word+and+some+space+at+the+end \n',
+ conf, problem=(2, 21))
+
+ def test_non_breakable_inline_mappings(self):
+ conf = 'line-length: {max: 20, ' \
+ 'allow-non-breakable-inline-mappings: true}'
+ self.check('---\n'
+ 'long_line: http://localhost/very/very/long/url\n'
+ 'long line: http://localhost/very/very/long/url\n', conf)
+ self.check('---\n'
+ '- long line: http://localhost/very/very/long/url\n', conf)
+
+ self.check('---\n'
+ 'long_line: http://localhost/short/url + word\n'
+ 'long line: http://localhost/short/url + word\n',
+ conf, problem1=(2, 21), problem2=(3, 21))
+
+ conf = ('line-length: {max: 20,'
+ ' allow-non-breakable-inline-mappings: true}\n'
+ 'trailing-spaces: disable')
+ self.check('---\n'
+ 'long_line: and+some+space+at+the+end \n',
+ conf, problem=(2, 21))
+ self.check('---\n'
+ 'long line: and+some+space+at+the+end \n',
+ conf, problem=(2, 21))
+ self.check('---\n'
+ '- long line: and+some+space+at+the+end \n',
+ conf, problem=(2, 21))
+
+ # See https://github.com/adrienverge/yamllint/issues/21
+ conf = 'line-length: {allow-non-breakable-inline-mappings: true}'
+ self.check('---\n'
+ 'content: |\n'
+ ' {% this line is' + 99 * ' really' + ' long %}\n',
+ conf, problem=(3, 81))
+
+ def test_unicode(self):
+ conf = 'line-length: {max: 53}'
+ self.check('---\n'
+ '# This is a test to check if “line-length” works nice\n'
+ 'with: “unicode characters” that span across bytes! ↺\n',
+ conf)
+ conf = 'line-length: {max: 51}'
+ self.check('---\n'
+ '# This is a test to check if “line-length” works nice\n'
+ 'with: “unicode characters” that span across bytes! ↺\n',
+ conf, problem1=(2, 52), problem2=(3, 52))
+
+ def test_with_dos_newlines(self):
+ conf = ('line-length: {max: 10}\n'
+ 'new-lines: {type: dos}\n'
+ 'new-line-at-end-of-file: disable\n')
+ self.check('---\r\nABCD EFGHI', conf)
+ self.check('---\r\nABCD EFGHI\r\n', conf)
+ self.check('---\r\nABCD EFGHIJ', conf, problem=(2, 11))
+ self.check('---\r\nABCD EFGHIJ\r\n', conf, problem=(2, 11))
diff --git a/tests/rules/test_new_line_at_end_of_file.py b/tests/rules/test_new_line_at_end_of_file.py
new file mode 100644
index 0000000..10a0bf0
--- /dev/null
+++ b/tests/rules/test_new_line_at_end_of_file.py
@@ -0,0 +1,41 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class NewLineAtEndOfFileTestCase(RuleTestCase):
+ rule_id = 'new-line-at-end-of-file'
+
+ def test_disabled(self):
+ conf = ('new-line-at-end-of-file: disable\n'
+ 'empty-lines: disable\n'
+ 'document-start: disable\n')
+ self.check('', conf)
+ self.check('\n', conf)
+ self.check('word', conf)
+ self.check('Sentence.\n', conf)
+
+ def test_enabled(self):
+ conf = ('new-line-at-end-of-file: enable\n'
+ 'empty-lines: disable\n'
+ 'document-start: disable\n')
+ self.check('', conf)
+ self.check('\n', conf)
+ self.check('word', conf, problem=(1, 5))
+ self.check('Sentence.\n', conf)
+ self.check('---\n'
+ 'yaml: document\n'
+ '...', conf, problem=(3, 4))
diff --git a/tests/rules/test_new_lines.py b/tests/rules/test_new_lines.py
new file mode 100644
index 0000000..80334ea
--- /dev/null
+++ b/tests/rules/test_new_lines.py
@@ -0,0 +1,96 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from unittest import mock
+
+from tests.common import RuleTestCase
+
+
+class NewLinesTestCase(RuleTestCase):
+ rule_id = 'new-lines'
+
+ def test_disabled(self):
+ conf = ('new-line-at-end-of-file: disable\n'
+ 'new-lines: disable\n')
+ self.check('', conf)
+ self.check('\n', conf)
+ self.check('\r', conf)
+ self.check('\r\n', conf)
+ self.check('---\ntext\n', conf)
+ self.check('---\r\ntext\r\n', conf)
+
+ def test_unix_type(self):
+ conf = ('new-line-at-end-of-file: disable\n'
+ 'new-lines: {type: unix}\n')
+ self.check('', conf)
+ self.check('\r', conf)
+ self.check('\n', conf)
+ self.check('\r\n', conf, problem=(1, 1))
+ self.check('---\ntext\n', conf)
+ self.check('---\r\ntext\r\n', conf, problem=(1, 4))
+
+ def test_unix_type_required_st_sp(self):
+ # If we find a CRLF when looking for Unix newlines, yamllint
+ # should always raise, regardless of logic with
+ # require-starting-space.
+ conf = ('new-line-at-end-of-file: disable\n'
+ 'new-lines: {type: unix}\n'
+ 'comments:\n'
+ ' require-starting-space: true\n')
+ self.check('---\r\n#\r\n', conf, problem=(1, 4))
+
+ def test_dos_type(self):
+ conf = ('new-line-at-end-of-file: disable\n'
+ 'new-lines: {type: dos}\n')
+ self.check('', conf)
+ self.check('\r', conf)
+ self.check('\n', conf, problem=(1, 1))
+ self.check('\r\n', conf)
+ self.check('---\ntext\n', conf, problem=(1, 4))
+ self.check('---\r\ntext\r\n', conf)
+
+ def test_platform_type(self):
+ conf = ('new-line-at-end-of-file: disable\n'
+ 'new-lines: {type: platform}\n')
+
+ self.check('', conf)
+
+ # mock the Linux new-line-character
+ with mock.patch('yamllint.rules.new_lines.linesep', '\n'):
+ self.check('\n', conf)
+ self.check('\r\n', conf, problem=(1, 1))
+ self.check('---\ntext\n', conf)
+ self.check('---\r\ntext\r\n', conf, problem=(1, 4))
+ self.check('---\r\ntext\n', conf, problem=(1, 4))
+ # FIXME: the following tests currently don't work
+ # because only the first line is checked for line-endings
+ # see: issue #475
+ # ---
+ # self.check('---\ntext\r\nfoo\n', conf, problem=(2, 4))
+ # self.check('---\ntext\r\n', conf, problem=(2, 4))
+
+ # mock the Windows new-line-character
+ with mock.patch('yamllint.rules.new_lines.linesep', '\r\n'):
+ self.check('\r\n', conf)
+ self.check('\n', conf, problem=(1, 1))
+ self.check('---\r\ntext\r\n', conf)
+ self.check('---\ntext\n', conf, problem=(1, 4))
+ self.check('---\ntext\r\n', conf, problem=(1, 4))
+ # FIXME: the following tests currently don't work
+ # because only the first line is checked for line-endings
+ # see: issue #475
+ # ---
+ # self.check('---\r\ntext\nfoo\r\n', conf, problem=(2, 4))
+ # self.check('---\r\ntext\n', conf, problem=(2, 4))
diff --git a/tests/rules/test_octal_values.py b/tests/rules/test_octal_values.py
new file mode 100644
index 0000000..be5b039
--- /dev/null
+++ b/tests/rules/test_octal_values.py
@@ -0,0 +1,80 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class OctalValuesTestCase(RuleTestCase):
+ rule_id = 'octal-values'
+
+ def test_disabled(self):
+ conf = ('octal-values: disable\n'
+ 'new-line-at-end-of-file: disable\n'
+ 'document-start: disable\n')
+ self.check('user-city: 010', conf)
+ self.check('user-city: 0o10', conf)
+
+ def test_implicit_octal_values(self):
+ conf = ('octal-values:\n'
+ ' forbid-implicit-octal: true\n'
+ ' forbid-explicit-octal: false\n'
+ 'new-line-at-end-of-file: disable\n'
+ 'document-start: disable\n')
+ self.check('after-tag: !custom_tag 010', conf)
+ self.check('user-city: 010', conf, problem=(1, 15))
+ self.check('user-city: abc', conf)
+ self.check('user-city: 010,0571', conf)
+ self.check("user-city: '010'", conf)
+ self.check('user-city: "010"', conf)
+ self.check('user-city:\n'
+ ' - 010', conf, problem=(2, 8))
+ self.check('user-city: [010]', conf, problem=(1, 16))
+ self.check('user-city: {beijing: 010}', conf, problem=(1, 25))
+ self.check('explicit-octal: 0o10', conf)
+ self.check('not-number: 0abc', conf)
+ self.check('zero: 0', conf)
+ self.check('hex-value: 0x10', conf)
+ self.check('number-values:\n'
+ ' - 0.10\n'
+ ' - .01\n'
+ ' - 0e3\n', conf)
+ self.check('with-decimal-digits: 012345678', conf)
+ self.check('with-decimal-digits: 012345679', conf)
+
+ def test_explicit_octal_values(self):
+ conf = ('octal-values:\n'
+ ' forbid-implicit-octal: false\n'
+ ' forbid-explicit-octal: true\n'
+ 'new-line-at-end-of-file: disable\n'
+ 'document-start: disable\n')
+ self.check('user-city: 0o10', conf, problem=(1, 16))
+ self.check('user-city: abc', conf)
+ self.check('user-city: 0o10,0571', conf)
+ self.check("user-city: '0o10'", conf)
+ self.check('user-city:\n'
+ ' - 0o10', conf, problem=(2, 9))
+ self.check('user-city: [0o10]', conf, problem=(1, 17))
+ self.check('user-city: {beijing: 0o10}', conf, problem=(1, 26))
+ self.check('implicit-octal: 010', conf)
+ self.check('not-number: 0oabc', conf)
+ self.check('zero: 0', conf)
+ self.check('hex-value: 0x10', conf)
+ self.check('number-values:\n'
+ ' - 0.10\n'
+ ' - .01\n'
+ ' - 0e3\n', conf)
+ self.check('user-city: "010"', conf)
+ self.check('with-decimal-digits: 0o012345678', conf)
+ self.check('with-decimal-digits: 0o012345679', conf)
diff --git a/tests/rules/test_quoted_strings.py b/tests/rules/test_quoted_strings.py
new file mode 100644
index 0000000..543cc0d
--- /dev/null
+++ b/tests/rules/test_quoted_strings.py
@@ -0,0 +1,558 @@
+# Copyright (C) 2018 ClearScore
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+from yamllint import config
+
+
+class QuotedTestCase(RuleTestCase):
+ rule_id = 'quoted-strings'
+
+ def test_disabled(self):
+ conf = 'quoted-strings: disable'
+
+ self.check('---\n'
+ 'foo: bar\n', conf)
+ self.check('---\n'
+ 'foo: "bar"\n', conf)
+ self.check('---\n'
+ 'foo: \'bar\'\n', conf)
+ self.check('---\n'
+ 'bar: 123\n', conf)
+ self.check('---\n'
+ 'bar: "123"\n', conf)
+
+ def test_quote_type_any(self):
+ conf = 'quoted-strings: {quote-type: any}\n'
+
+ self.check('---\n'
+ 'boolean1: true\n'
+ 'number1: 123\n'
+ 'string1: foo\n' # fails
+ 'string2: "foo"\n'
+ 'string3: "true"\n'
+ 'string4: "123"\n'
+ 'string5: \'bar\'\n'
+ 'string6: !!str genericstring\n'
+ 'string7: !!str 456\n'
+ 'string8: !!str "quotedgenericstring"\n'
+ 'binary: !!binary binstring\n'
+ 'integer: !!int intstring\n'
+ 'boolean2: !!bool boolstring\n'
+ 'boolean3: !!bool "quotedboolstring"\n'
+ 'block-seq:\n'
+ ' - foo\n' # fails
+ ' - "foo"\n'
+ 'flow-seq: [foo, "foo"]\n' # fails
+ 'flow-map: {a: foo, b: "foo"}\n', # fails
+ conf, problem1=(4, 10), problem2=(17, 5),
+ problem3=(19, 12), problem4=(20, 15))
+ self.check('---\n'
+ 'multiline string 1: |\n'
+ ' line 1\n'
+ ' line 2\n'
+ 'multiline string 2: >\n'
+ ' word 1\n'
+ ' word 2\n'
+ 'multiline string 3:\n'
+ ' word 1\n' # fails
+ ' word 2\n'
+ 'multiline string 4:\n'
+ ' "word 1\\\n'
+ ' word 2"\n',
+ conf, problem1=(9, 3))
+
+ def test_quote_type_single(self):
+ conf = 'quoted-strings: {quote-type: single}\n'
+
+ self.check('---\n'
+ 'boolean1: true\n'
+ 'number1: 123\n'
+ 'string1: foo\n' # fails
+ 'string2: "foo"\n' # fails
+ 'string3: "true"\n' # fails
+ 'string4: "123"\n' # fails
+ 'string5: \'bar\'\n'
+ 'string6: !!str genericstring\n'
+ 'string7: !!str 456\n'
+ 'string8: !!str "quotedgenericstring"\n'
+ 'binary: !!binary binstring\n'
+ 'integer: !!int intstring\n'
+ 'boolean2: !!bool boolstring\n'
+ 'boolean3: !!bool "quotedboolstring"\n'
+ 'block-seq:\n'
+ ' - foo\n' # fails
+ ' - "foo"\n' # fails
+ 'flow-seq: [foo, "foo"]\n' # fails
+ 'flow-map: {a: foo, b: "foo"}\n', # fails
+ conf, problem1=(4, 10), problem2=(5, 10), problem3=(6, 10),
+ problem4=(7, 10), problem5=(17, 5), problem6=(18, 5),
+ problem7=(19, 12), problem8=(19, 17), problem9=(20, 15),
+ problem10=(20, 23))
+ self.check('---\n'
+ 'multiline string 1: |\n'
+ ' line 1\n'
+ ' line 2\n'
+ 'multiline string 2: >\n'
+ ' word 1\n'
+ ' word 2\n'
+ 'multiline string 3:\n'
+ ' word 1\n' # fails
+ ' word 2\n'
+ 'multiline string 4:\n'
+ ' "word 1\\\n'
+ ' word 2"\n',
+ conf, problem1=(9, 3), problem2=(12, 3))
+
+ def test_quote_type_double(self):
+ conf = 'quoted-strings: {quote-type: double}\n'
+
+ self.check('---\n'
+ 'boolean1: true\n'
+ 'number1: 123\n'
+ 'string1: foo\n' # fails
+ 'string2: "foo"\n'
+ 'string3: "true"\n'
+ 'string4: "123"\n'
+ 'string5: \'bar\'\n' # fails
+ 'string6: !!str genericstring\n'
+ 'string7: !!str 456\n'
+ 'string8: !!str "quotedgenericstring"\n'
+ 'binary: !!binary binstring\n'
+ 'integer: !!int intstring\n'
+ 'boolean2: !!bool boolstring\n'
+ 'boolean3: !!bool "quotedboolstring"\n'
+ 'block-seq:\n'
+ ' - foo\n' # fails
+ ' - "foo"\n'
+ 'flow-seq: [foo, "foo"]\n' # fails
+ 'flow-map: {a: foo, b: "foo"}\n', # fails
+ conf, problem1=(4, 10), problem2=(8, 10), problem3=(17, 5),
+ problem4=(19, 12), problem5=(20, 15))
+ self.check('---\n'
+ 'multiline string 1: |\n'
+ ' line 1\n'
+ ' line 2\n'
+ 'multiline string 2: >\n'
+ ' word 1\n'
+ ' word 2\n'
+ 'multiline string 3:\n'
+ ' word 1\n' # fails
+ ' word 2\n'
+ 'multiline string 4:\n'
+ ' "word 1\\\n'
+ ' word 2"\n',
+ conf, problem1=(9, 3))
+
+ def test_any_quotes_not_required(self):
+ conf = 'quoted-strings: {quote-type: any, required: false}\n'
+
+ self.check('---\n'
+ 'boolean1: true\n'
+ 'number1: 123\n'
+ 'string1: foo\n'
+ 'string2: "foo"\n'
+ 'string3: "true"\n'
+ 'string4: "123"\n'
+ 'string5: \'bar\'\n'
+ 'string6: !!str genericstring\n'
+ 'string7: !!str 456\n'
+ 'string8: !!str "quotedgenericstring"\n'
+ 'binary: !!binary binstring\n'
+ 'integer: !!int intstring\n'
+ 'boolean2: !!bool boolstring\n'
+ 'boolean3: !!bool "quotedboolstring"\n'
+ 'block-seq:\n'
+ ' - foo\n' # fails
+ ' - "foo"\n'
+ 'flow-seq: [foo, "foo"]\n' # fails
+ 'flow-map: {a: foo, b: "foo"}\n', # fails
+ conf)
+ self.check('---\n'
+ 'multiline string 1: |\n'
+ ' line 1\n'
+ ' line 2\n'
+ 'multiline string 2: >\n'
+ ' word 1\n'
+ ' word 2\n'
+ 'multiline string 3:\n'
+ ' word 1\n'
+ ' word 2\n'
+ 'multiline string 4:\n'
+ ' "word 1\\\n'
+ ' word 2"\n',
+ conf)
+
+ def test_single_quotes_not_required(self):
+ conf = 'quoted-strings: {quote-type: single, required: false}\n'
+
+ self.check('---\n'
+ 'boolean1: true\n'
+ 'number1: 123\n'
+ 'string1: foo\n'
+ 'string2: "foo"\n' # fails
+ 'string3: "true"\n' # fails
+ 'string4: "123"\n' # fails
+ 'string5: \'bar\'\n'
+ 'string6: !!str genericstring\n'
+ 'string7: !!str 456\n'
+ 'string8: !!str "quotedgenericstring"\n'
+ 'binary: !!binary binstring\n'
+ 'integer: !!int intstring\n'
+ 'boolean2: !!bool boolstring\n'
+ 'boolean3: !!bool "quotedboolstring"\n'
+ 'block-seq:\n'
+ ' - foo\n' # fails
+ ' - "foo"\n'
+ 'flow-seq: [foo, "foo"]\n' # fails
+ 'flow-map: {a: foo, b: "foo"}\n', # fails
+ conf, problem1=(5, 10), problem2=(6, 10), problem3=(7, 10),
+ problem4=(18, 5), problem5=(19, 17), problem6=(20, 23))
+ self.check('---\n'
+ 'multiline string 1: |\n'
+ ' line 1\n'
+ ' line 2\n'
+ 'multiline string 2: >\n'
+ ' word 1\n'
+ ' word 2\n'
+ 'multiline string 3:\n'
+ ' word 1\n'
+ ' word 2\n'
+ 'multiline string 4:\n'
+ ' "word 1\\\n' # fails
+ ' word 2"\n',
+ conf, problem1=(12, 3))
+
+ def test_only_when_needed(self):
+ conf = 'quoted-strings: {required: only-when-needed}\n'
+
+ self.check('---\n'
+ 'boolean1: true\n'
+ 'number1: 123\n'
+ 'string1: foo\n'
+ 'string2: "foo"\n' # fails
+ 'string3: "true"\n'
+ 'string4: "123"\n'
+ 'string5: \'bar\'\n' # fails
+ 'string6: !!str genericstring\n'
+ 'string7: !!str 456\n'
+ 'string8: !!str "quotedgenericstring"\n'
+ 'binary: !!binary binstring\n'
+ 'integer: !!int intstring\n'
+ 'boolean2: !!bool boolstring\n'
+ 'boolean3: !!bool "quotedboolstring"\n'
+ 'block-seq:\n'
+ ' - foo\n'
+ ' - "foo"\n' # fails
+ 'flow-seq: [foo, "foo"]\n' # fails
+ 'flow-map: {a: foo, b: "foo"}\n', # fails
+ conf, problem1=(5, 10), problem2=(8, 10), problem3=(18, 5),
+ problem4=(19, 17), problem5=(20, 23))
+ self.check('---\n'
+ 'multiline string 1: |\n'
+ ' line 1\n'
+ ' line 2\n'
+ 'multiline string 2: >\n'
+ ' word 1\n'
+ ' word 2\n'
+ 'multiline string 3:\n'
+ ' word 1\n'
+ ' word 2\n'
+ 'multiline string 4:\n'
+ ' "word 1\\\n' # fails
+ ' word 2"\n',
+ conf, problem1=(12, 3))
+
+ def test_only_when_needed_single_quotes(self):
+ conf = ('quoted-strings: {quote-type: single,\n'
+ ' required: only-when-needed}\n')
+
+ self.check('---\n'
+ 'boolean1: true\n'
+ 'number1: 123\n'
+ 'string1: foo\n'
+ 'string2: "foo"\n' # fails
+ 'string3: "true"\n' # fails
+ 'string4: "123"\n' # fails
+ 'string5: \'bar\'\n' # fails
+ 'string6: !!str genericstring\n'
+ 'string7: !!str 456\n'
+ 'string8: !!str "quotedgenericstring"\n'
+ 'binary: !!binary binstring\n'
+ 'integer: !!int intstring\n'
+ 'boolean2: !!bool boolstring\n'
+ 'boolean3: !!bool "quotedboolstring"\n'
+ 'block-seq:\n'
+ ' - foo\n'
+ ' - "foo"\n' # fails
+ 'flow-seq: [foo, "foo"]\n' # fails
+ 'flow-map: {a: foo, b: "foo"}\n', # fails
+ conf, problem1=(5, 10), problem2=(6, 10), problem3=(7, 10),
+ problem4=(8, 10), problem5=(18, 5), problem6=(19, 17),
+ problem7=(20, 23))
+ self.check('---\n'
+ 'multiline string 1: |\n'
+ ' line 1\n'
+ ' line 2\n'
+ 'multiline string 2: >\n'
+ ' word 1\n'
+ ' word 2\n'
+ 'multiline string 3:\n'
+ ' word 1\n'
+ ' word 2\n'
+ 'multiline string 4:\n'
+ ' "word 1\\\n' # fails
+ ' word 2"\n',
+ conf, problem1=(12, 3))
+
+ def test_only_when_needed_corner_cases(self):
+ conf = 'quoted-strings: {required: only-when-needed}\n'
+
+ self.check('---\n'
+ '- ""\n'
+ '- "- item"\n'
+ '- "key: value"\n'
+ '- "%H:%M:%S"\n'
+ '- "%wheel ALL=(ALL) NOPASSWD: ALL"\n'
+ '- \'"quoted"\'\n'
+ '- "\'foo\' == \'bar\'"\n'
+ '- "\'Mac\' in ansible_facts.product_name"\n'
+ '- \'foo # bar\'\n',
+ conf)
+ self.check('---\n'
+ 'k1: ""\n'
+ 'k2: "- item"\n'
+ 'k3: "key: value"\n'
+ 'k4: "%H:%M:%S"\n'
+ 'k5: "%wheel ALL=(ALL) NOPASSWD: ALL"\n'
+ 'k6: \'"quoted"\'\n'
+ 'k7: "\'foo\' == \'bar\'"\n'
+ 'k8: "\'Mac\' in ansible_facts.product_name"\n',
+ conf)
+
+ self.check('---\n'
+ '- ---\n'
+ '- "---"\n' # fails
+ '- ----------\n'
+ '- "----------"\n' # fails
+ '- :wq\n'
+ '- ":wq"\n', # fails
+ conf, problem1=(3, 3), problem2=(5, 3), problem3=(7, 3))
+ self.check('---\n'
+ 'k1: ---\n'
+ 'k2: "---"\n' # fails
+ 'k3: ----------\n'
+ 'k4: "----------"\n' # fails
+ 'k5: :wq\n'
+ 'k6: ":wq"\n', # fails
+ conf, problem1=(3, 5), problem2=(5, 5), problem3=(7, 5))
+
+ def test_only_when_needed_extras(self):
+ conf = ('quoted-strings:\n'
+ ' required: true\n'
+ ' extra-allowed: [^http://]\n')
+ self.assertRaises(config.YamlLintConfigError, self.check, '', conf)
+
+ conf = ('quoted-strings:\n'
+ ' required: true\n'
+ ' extra-required: [^http://]\n')
+ self.assertRaises(config.YamlLintConfigError, self.check, '', conf)
+
+ conf = ('quoted-strings:\n'
+ ' required: false\n'
+ ' extra-allowed: [^http://]\n')
+ self.assertRaises(config.YamlLintConfigError, self.check, '', conf)
+
+ conf = ('quoted-strings:\n'
+ ' required: true\n')
+ self.check('---\n'
+ '- 123\n'
+ '- "123"\n'
+ '- localhost\n' # fails
+ '- "localhost"\n'
+ '- http://localhost\n' # fails
+ '- "http://localhost"\n'
+ '- ftp://localhost\n' # fails
+ '- "ftp://localhost"\n',
+ conf, problem1=(4, 3), problem2=(6, 3), problem3=(8, 3))
+
+ conf = ('quoted-strings:\n'
+ ' required: only-when-needed\n'
+ ' extra-allowed: [^ftp://]\n'
+ ' extra-required: [^http://]\n')
+ self.check('---\n'
+ '- 123\n'
+ '- "123"\n'
+ '- localhost\n'
+ '- "localhost"\n' # fails
+ '- http://localhost\n' # fails
+ '- "http://localhost"\n'
+ '- ftp://localhost\n'
+ '- "ftp://localhost"\n',
+ conf, problem1=(5, 3), problem2=(6, 3))
+
+ conf = ('quoted-strings:\n'
+ ' required: false\n'
+ ' extra-required: [^http://, ^ftp://]\n')
+ self.check('---\n'
+ '- 123\n'
+ '- "123"\n'
+ '- localhost\n'
+ '- "localhost"\n'
+ '- http://localhost\n' # fails
+ '- "http://localhost"\n'
+ '- ftp://localhost\n' # fails
+ '- "ftp://localhost"\n',
+ conf, problem1=(6, 3), problem2=(8, 3))
+
+ conf = ('quoted-strings:\n'
+ ' required: only-when-needed\n'
+ ' extra-allowed: [^ftp://, ";$", " "]\n')
+ self.check('---\n'
+ '- localhost\n'
+ '- "localhost"\n' # fails
+ '- ftp://localhost\n'
+ '- "ftp://localhost"\n'
+ '- i=i+1\n'
+ '- "i=i+1"\n' # fails
+ '- i=i+2;\n'
+ '- "i=i+2;"\n'
+ '- foo\n'
+ '- "foo"\n' # fails
+ '- foo bar\n'
+ '- "foo bar"\n',
+ conf, problem1=(3, 3), problem2=(7, 3), problem3=(11, 3))
+
+ def test_octal_values(self):
+ conf = 'quoted-strings: {required: true}\n'
+
+ self.check('---\n'
+ '- 100\n'
+ '- 0100\n'
+ '- 0o100\n'
+ '- 777\n'
+ '- 0777\n'
+ '- 0o777\n'
+ '- 800\n'
+ '- 0800\n'
+ '- 0o800\n'
+ '- "0800"\n'
+ '- "0o800"\n',
+ conf,
+ problem1=(9, 3), problem2=(10, 3))
+
+ def test_allow_quoted_quotes(self):
+ conf = ('quoted-strings: {quote-type: single,\n'
+ ' required: false,\n'
+ ' allow-quoted-quotes: false}\n')
+ self.check('---\n'
+ 'foo1: "[barbaz]"\n' # fails
+ 'foo2: "[bar\'baz]"\n', # fails
+ conf, problem1=(2, 7), problem2=(3, 7))
+
+ conf = ('quoted-strings: {quote-type: single,\n'
+ ' required: false,\n'
+ ' allow-quoted-quotes: true}\n')
+ self.check('---\n'
+ 'foo1: "[barbaz]"\n' # fails
+ 'foo2: "[bar\'baz]"\n',
+ conf, problem1=(2, 7))
+
+ conf = ('quoted-strings: {quote-type: single,\n'
+ ' required: true,\n'
+ ' allow-quoted-quotes: false}\n')
+ self.check('---\n'
+ 'foo1: "[barbaz]"\n' # fails
+ 'foo2: "[bar\'baz]"\n', # fails
+ conf, problem1=(2, 7), problem2=(3, 7))
+
+ conf = ('quoted-strings: {quote-type: single,\n'
+ ' required: true,\n'
+ ' allow-quoted-quotes: true}\n')
+ self.check('---\n'
+ 'foo1: "[barbaz]"\n' # fails
+ 'foo2: "[bar\'baz]"\n',
+ conf, problem1=(2, 7))
+
+ conf = ('quoted-strings: {quote-type: single,\n'
+ ' required: only-when-needed,\n'
+ ' allow-quoted-quotes: false}\n')
+ self.check('---\n'
+ 'foo1: "[barbaz]"\n' # fails
+ 'foo2: "[bar\'baz]"\n', # fails
+ conf, problem1=(2, 7), problem2=(3, 7))
+
+ conf = ('quoted-strings: {quote-type: single,\n'
+ ' required: only-when-needed,\n'
+ ' allow-quoted-quotes: true}\n')
+ self.check('---\n'
+ 'foo1: "[barbaz]"\n' # fails
+ 'foo2: "[bar\'baz]"\n',
+ conf, problem1=(2, 7))
+
+ conf = ('quoted-strings: {quote-type: double,\n'
+ ' required: false,\n'
+ ' allow-quoted-quotes: false}\n')
+ self.check("---\n"
+ "foo1: '[barbaz]'\n" # fails
+ "foo2: '[bar\"baz]'\n", # fails
+ conf, problem1=(2, 7), problem2=(3, 7))
+
+ conf = ('quoted-strings: {quote-type: double,\n'
+ ' required: false,\n'
+ ' allow-quoted-quotes: true}\n')
+ self.check("---\n"
+ "foo1: '[barbaz]'\n" # fails
+ "foo2: '[bar\"baz]'\n",
+ conf, problem1=(2, 7))
+
+ conf = ('quoted-strings: {quote-type: double,\n'
+ ' required: true,\n'
+ ' allow-quoted-quotes: false}\n')
+ self.check("---\n"
+ "foo1: '[barbaz]'\n" # fails
+ "foo2: '[bar\"baz]'\n", # fails
+ conf, problem1=(2, 7), problem2=(3, 7))
+
+ conf = ('quoted-strings: {quote-type: double,\n'
+ ' required: true,\n'
+ ' allow-quoted-quotes: true}\n')
+ self.check("---\n"
+ "foo1: '[barbaz]'\n" # fails
+ "foo2: '[bar\"baz]'\n",
+ conf, problem1=(2, 7))
+
+ conf = ('quoted-strings: {quote-type: double,\n'
+ ' required: only-when-needed,\n'
+ ' allow-quoted-quotes: false}\n')
+ self.check("---\n"
+ "foo1: '[barbaz]'\n" # fails
+ "foo2: '[bar\"baz]'\n", # fails
+ conf, problem1=(2, 7), problem2=(3, 7))
+
+ conf = ('quoted-strings: {quote-type: double,\n'
+ ' required: only-when-needed,\n'
+ ' allow-quoted-quotes: true}\n')
+ self.check("---\n"
+ "foo1: '[barbaz]'\n" # fails
+ "foo2: '[bar\"baz]'\n",
+ conf, problem1=(2, 7))
+
+ conf = ('quoted-strings: {quote-type: any}\n')
+ self.check("---\n"
+ "foo1: '[barbaz]'\n"
+ "foo2: '[bar\"baz]'\n",
+ conf)
diff --git a/tests/rules/test_trailing_spaces.py b/tests/rules/test_trailing_spaces.py
new file mode 100644
index 0000000..016f56e
--- /dev/null
+++ b/tests/rules/test_trailing_spaces.py
@@ -0,0 +1,47 @@
+# Copyright (C) 2016 Adrien Vergé
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class TrailingSpacesTestCase(RuleTestCase):
+ rule_id = 'trailing-spaces'
+
+ def test_disabled(self):
+ conf = 'trailing-spaces: disable'
+ self.check('', conf)
+ self.check('\n', conf)
+ self.check(' \n', conf)
+ self.check('---\n'
+ 'some: text \n', conf)
+
+ def test_enabled(self):
+ conf = 'trailing-spaces: enable'
+ self.check('', conf)
+ self.check('\n', conf)
+ self.check(' \n', conf, problem=(1, 1))
+ self.check('\t\t\t\n', conf, problem=(1, 1, 'syntax'))
+ self.check('---\n'
+ 'some: text \n', conf, problem=(2, 11))
+ self.check('---\n'
+ 'some: text\t\n', conf, problem=(2, 11, 'syntax'))
+
+ def test_with_dos_new_lines(self):
+ conf = ('trailing-spaces: enable\n'
+ 'new-lines: {type: dos}\n')
+ self.check('---\r\n'
+ 'some: text\r\n', conf)
+ self.check('---\r\n'
+ 'some: text \r\n', conf, problem=(2, 11))
diff --git a/tests/rules/test_truthy.py b/tests/rules/test_truthy.py
new file mode 100644
index 0000000..c8c8b7a
--- /dev/null
+++ b/tests/rules/test_truthy.py
@@ -0,0 +1,145 @@
+# Copyright (C) 2016 Peter Ericson
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+from tests.common import RuleTestCase
+
+
+class TruthyTestCase(RuleTestCase):
+ rule_id = 'truthy'
+
+ def test_disabled(self):
+ conf = 'truthy: disable'
+ self.check('---\n'
+ '1: True\n', conf)
+ self.check('---\n'
+ 'True: 1\n', conf)
+
+ def test_enabled(self):
+ conf = 'truthy: enable\n'
+ self.check('---\n'
+ '1: True\n'
+ 'True: 1\n',
+ conf, problem1=(2, 4), problem2=(3, 1))
+ self.check('---\n'
+ '1: "True"\n'
+ '"True": 1\n', conf)
+ self.check('---\n'
+ '[\n'
+ ' true, false,\n'
+ ' "false", "FALSE",\n'
+ ' "true", "True",\n'
+ ' True, FALSE,\n'
+ ' on, OFF,\n'
+ ' NO, Yes\n'
+ ']\n', conf,
+ problem1=(6, 3), problem2=(6, 9),
+ problem3=(7, 3), problem4=(7, 7),
+ problem5=(8, 3), problem6=(8, 7))
+
+ def test_different_allowed_values(self):
+ conf = ('truthy:\n'
+ ' allowed-values: ["yes", "no"]\n')
+ self.check('---\n'
+ 'key1: foo\n'
+ 'key2: yes\n'
+ 'key3: bar\n'
+ 'key4: no\n', conf)
+ self.check('---\n'
+ 'key1: true\n'
+ 'key2: Yes\n'
+ 'key3: false\n'
+ 'key4: no\n'
+ 'key5: yes\n',
+ conf,
+ problem1=(2, 7), problem2=(3, 7),
+ problem3=(4, 7))
+
+ def test_combined_allowed_values(self):
+ conf = ('truthy:\n'
+ ' allowed-values: ["yes", "no", "true", "false"]\n')
+ self.check('---\n'
+ 'key1: foo\n'
+ 'key2: yes\n'
+ 'key3: bar\n'
+ 'key4: no\n', conf)
+ self.check('---\n'
+ 'key1: true\n'
+ 'key2: Yes\n'
+ 'key3: false\n'
+ 'key4: no\n'
+ 'key5: yes\n',
+ conf, problem1=(3, 7))
+
+ def test_no_allowed_values(self):
+ conf = ('truthy:\n'
+ ' allowed-values: []\n')
+ self.check('---\n'
+ 'key1: foo\n'
+ 'key2: bar\n', conf)
+ self.check('---\n'
+ 'key1: true\n'
+ 'key2: yes\n'
+ 'key3: false\n'
+ 'key4: no\n', conf,
+ problem1=(2, 7), problem2=(3, 7),
+ problem3=(4, 7), problem4=(5, 7))
+
+ def test_explicit_types(self):
+ conf = 'truthy: enable\n'
+ self.check('---\n'
+ 'string1: !!str True\n'
+ 'string2: !!str yes\n'
+ 'string3: !!str off\n'
+ 'encoded: !!binary |\n'
+ ' True\n'
+ ' OFF\n'
+ ' pad==\n' # this decodes as 'N\xbb\x9e8Qii'
+ 'boolean1: !!bool true\n'
+ 'boolean2: !!bool "false"\n'
+ 'boolean3: !!bool FALSE\n'
+ 'boolean4: !!bool True\n'
+ 'boolean5: !!bool off\n'
+ 'boolean6: !!bool NO\n',
+ conf)
+
+ def test_check_keys_disabled(self):
+ conf = ('truthy:\n'
+ ' allowed-values: []\n'
+ ' check-keys: false\n'
+ 'key-duplicates: disable\n')
+ self.check('---\n'
+ 'YES: 0\n'
+ 'Yes: 0\n'
+ 'yes: 0\n'
+ 'No: 0\n'
+ 'No: 0\n'
+ 'no: 0\n'
+ 'TRUE: 0\n'
+ 'True: 0\n'
+ 'true: 0\n'
+ 'FALSE: 0\n'
+ 'False: 0\n'
+ 'false: 0\n'
+ 'ON: 0\n'
+ 'On: 0\n'
+ 'on: 0\n'
+ 'OFF: 0\n'
+ 'Off: 0\n'
+ 'off: 0\n'
+ 'YES:\n'
+ ' Yes:\n'
+ ' yes:\n'
+ ' on: 0\n',
+ conf)