summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yaml62
-rw-r--r--.gitignore7
-rw-r--r--.readthedocs.yaml11
-rw-r--r--CHANGELOG.rst23
-rw-r--r--docs/conf.py8
-rw-r--r--docs/disable_with_comments.rst18
-rw-r--r--docs/integration.rst102
-rw-r--r--docs/requirements.txt1
-rw-r--r--tests/__init__.py1
-rw-r--r--tests/common.py35
-rw-r--r--tests/rules/test_indentation.py8
-rw-r--r--tests/rules/test_key_duplicates.py54
-rw-r--r--tests/rules/test_quoted_strings.py786
-rw-r--r--tests/rules/test_truthy.py88
-rw-r--r--tests/test_cli.py88
-rw-r--r--tests/test_config.py73
-rw-r--r--tests/test_linter.py2
-rw-r--r--tests/test_module.py3
-rw-r--r--tests/test_parser.py11
-rw-r--r--tests/test_spec_examples.py2
-rw-r--r--yamllint/__init__.py2
-rw-r--r--yamllint/__main__.py15
-rw-r--r--yamllint/cli.py14
-rw-r--r--yamllint/config.py25
-rw-r--r--yamllint/linter.py3
-rw-r--r--yamllint/rules/__init__.py2
-rw-r--r--yamllint/rules/anchors.py1
-rw-r--r--yamllint/rules/braces.py1
-rw-r--r--yamllint/rules/brackets.py1
-rw-r--r--yamllint/rules/colons.py1
-rw-r--r--yamllint/rules/commas.py1
-rw-r--r--yamllint/rules/comments.py1
-rw-r--r--yamllint/rules/comments_indentation.py1
-rw-r--r--yamllint/rules/document_end.py1
-rw-r--r--yamllint/rules/document_start.py1
-rw-r--r--yamllint/rules/empty_lines.py1
-rw-r--r--yamllint/rules/empty_values.py1
-rw-r--r--yamllint/rules/float_values.py1
-rw-r--r--yamllint/rules/hyphens.py1
-rw-r--r--yamllint/rules/indentation.py1
-rw-r--r--yamllint/rules/key_duplicates.py42
-rw-r--r--yamllint/rules/key_ordering.py1
-rw-r--r--yamllint/rules/line_length.py1
-rw-r--r--yamllint/rules/new_line_at_end_of_file.py1
-rw-r--r--yamllint/rules/new_lines.py5
-rw-r--r--yamllint/rules/octal_values.py1
-rw-r--r--yamllint/rules/quoted_strings.py64
-rw-r--r--yamllint/rules/trailing_spaces.py1
-rw-r--r--yamllint/rules/truthy.py58
49 files changed, 1449 insertions, 183 deletions
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..7e55e6a
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,62 @@
+---
+
+name: CI
+
+on: # yamllint disable-line rule:truthy
+ push:
+ pull_request:
+ branches:
+ - master
+
+permissions:
+ contents: read
+
+jobs:
+ lint:
+ name: Linters
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ - run:
+ pip install flake8 flake8-import-order sphinx sphinx_rtd_theme
+ rstcheck[sphinx] doc8
+ - run: pip install .
+ - run: flake8 .
+ - run: doc8 $(git ls-files '*.rst')
+ - run: rstcheck --ignore-directives automodule $(git ls-files '*.rst')
+ - run: yamllint --strict $(git ls-files '*.yaml' '*.yml')
+ - run: make -C docs html
+ - name: Check for broken links in documentation
+ run: make -C docs linkcheck
+
+ test:
+ name: Tests
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version:
+ - '3.8'
+ - '3.9'
+ - '3.10'
+ - '3.11'
+ - '3.12'
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Append GitHub Actions system path
+ run: echo "$HOME/.local/bin" >> $GITHUB_PATH
+ - run: pip install coverage
+ - run: pip install .
+ # https://github.com/AndreMiras/coveralls-python-action/issues/18
+ - run: echo -e "[run]\nrelative_files = True" > .coveragerc
+ - run: coverage run -m unittest discover
+ - name: Coveralls
+ uses: AndreMiras/coveralls-python-action@develop
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..58d609b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+__pycache__
+*.py[cod]
+/docs/_build
+/dist
+/yamllint.egg-info
+/build
+/.eggs
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..4d6a254
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,11 @@
+---
+version: 2
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.12"
+sphinx:
+ configuration: docs/conf.py
+python:
+ install:
+ - requirements: docs/requirements.txt
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 0b847d2..c73e882 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,29 @@
Changelog
=========
+1.35.1 (2024-02-16)
+-------------------
+
+- Restore ignoration of files passed as command-line arguments
+- Revert API change from version 1.35.0
+
+1.35.0 (2024-02-15)
+-------------------
+
+- Fix failure on broken symlinks that should be ignored
+- API change: ``linter.run(stream, config)`` doesn't filter files anymore
+- Docs: Restore official Read the Docs theme
+
+1.34.0 (2024-02-06)
+-------------------
+
+- Config: validate ``ignore-from-file`` inside rules
+- Rule ``quoted-strings``: fix ``only-when-needed`` in flow maps and sequences
+- Rule ``key-duplicates``: add ``forbid-duplicated-merge-keys`` option
+- Rule ``quoted-strings``: add ``check-keys`` option
+- Docs: add GitLab CI example
+- Rule ``truthy``: adapt forbidden values based on YAML version
+
1.33.0 (2023-11-09)
-------------------
diff --git a/docs/conf.py b/docs/conf.py
index 2c40177..8a03b2f 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,17 +1,17 @@
# yamllint documentation build configuration file, created by
# sphinx-quickstart on Thu Jan 21 21:18:52 2016.
-import sys
import os
+import sys
from unittest.mock import MagicMock
sys.path.insert(0, os.path.abspath('..'))
-
-from yamllint import __copyright__, APP_NAME, APP_VERSION # noqa
+from yamllint import __copyright__, APP_NAME, APP_VERSION # noqa: I001, E402
# -- General configuration ------------------------------------------------
extensions = [
+ 'sphinx_rtd_theme',
'sphinx.ext.autodoc',
]
@@ -29,7 +29,7 @@ pygments_style = 'sphinx'
# -- Options for HTML output ----------------------------------------------
-html_theme = 'default'
+html_theme = 'sphinx_rtd_theme'
htmlhelp_basename = 'yamllintdoc'
diff --git a/docs/disable_with_comments.rst b/docs/disable_with_comments.rst
index a973da6..6fcd221 100644
--- a/docs/disable_with_comments.rst
+++ b/docs/disable_with_comments.rst
@@ -12,11 +12,11 @@ line above. For instance:
# The following mapping contains the same key twice,
# but I know what I'm doing:
- key: value 1
- key: value 2 # yamllint disable-line rule:key-duplicates
+ - key: value 1
+ key: value 2 # yamllint disable-line rule:key-duplicates
- This line is waaaaaaaaaay too long but yamllint will not report anything about it. # yamllint disable-line rule:line-length
- This line will be checked by yamllint.
+ - This line will be checked by yamllint.
or:
@@ -24,13 +24,13 @@ or:
# The following mapping contains the same key twice,
# but I know what I'm doing:
- key: value 1
- # yamllint disable-line rule:key-duplicates
- key: value 2
+ - key: value 1
+ # yamllint disable-line rule:key-duplicates
+ key: value 2
# yamllint disable-line rule:line-length
- This line is waaaaaaaaaay too long but yamllint will not report anything about it.
- This line will be checked by yamllint.
+ - This line will be checked by yamllint.
It is possible, although not recommend, to disabled **all** rules for a
specific line:
@@ -90,8 +90,8 @@ For instance:
# yamllint disable-file
# The following mapping contains the same key twice, but I know what I'm doing:
- key: value 1
- key: value 2
+ - key: value 1
+ key: value 2
- This line is waaaaaaaaaay too long but yamllint will not report anything about it.
diff --git a/docs/integration.rst b/docs/integration.rst
index 9a6a935..e805667 100644
--- a/docs/integration.rst
+++ b/docs/integration.rst
@@ -9,15 +9,15 @@ Here is an example, to add in your .pre-commit-config.yaml
.. code:: yaml
- ---
- # Update the rev variable with the release version that you want, from the yamllint repo
- # You can pass your custom .yamllint with args attribute.
- repos:
- - repo: https://github.com/adrienverge/yamllint.git
- rev: v1.29.0
- hooks:
- - id: yamllint
- args: [--strict, -c=/path/to/.yamllint]
+ ---
+ # Update the rev variable with the release version that you want, from the yamllint repo
+ # You can pass your custom .yamllint with args attribute.
+ repos:
+ - repo: https://github.com/adrienverge/yamllint.git
+ rev: v1.29.0
+ hooks:
+ - id: yamllint
+ args: [--strict, -c=/path/to/.yamllint]
Integration with GitHub Actions
@@ -32,20 +32,62 @@ A minimal example workflow using GitHub Actions:
.. code:: yaml
- ---
- on: push # yamllint disable-line rule:truthy
+ ---
+ on: push # yamllint disable-line rule:truthy
- jobs:
- lint:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
+ jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
- - name: Install yamllint
- run: pip install yamllint
+ - name: Install yamllint
+ run: pip install yamllint
- - name: Lint YAML files
- run: yamllint .
+ - name: Lint YAML files
+ run: yamllint .
+
+Integration with GitLab
+-----------------------
+
+You can use the following GitLab CI/CD stage to run yamllint and get the
+results as a `Code quality (Code Climate)
+<https://docs.gitlab.com/ee/ci/testing/code_quality.html>` report.
+
+.. code:: yaml
+
+ ---
+ lint:
+ stage: lint
+ script:
+ - pip install yamllint
+ - mkdir reports
+ - >
+ yamllint -f parsable . | tee >(awk '
+ BEGIN {FS = ":"; ORS="\n"; first=1}
+ {
+ gsub(/^[ \t]+|[ \t]+$|"/, "", $4);
+ match($4, /^\[(warning|error)\](.*)\((.*)\)$/, a);
+ sev = (a[1] == "error" ? "major" : "minor");
+ if (first) {
+ first=0;
+ printf("[");
+ } else {
+ printf(",");
+ }
+ printf("{\"location\":{\"path\":\"%s\",\"lines\":{\"begin\":%s",\
+ "\"end\":%s}},\"severity\":\"%s\",\"check_name\":\"%s\","\
+ "\"categories\":[\"Style\"],\"type\":\"issue\","\
+ "\"description\":\"%s\"}", $1, $2, $3, sev, a[3], a[2]);
+ }
+ END { if (!first) printf("]\n"); }' > reports/codequality.json)
+ artifacts:
+ when: always
+ paths:
+ - reports
+ expire_in: 1 week
+ reports:
+ codequality: reports/codequality.json
Integration with Arcanist
-------------------------
@@ -55,13 +97,13 @@ You can configure yamllint to run on ``arc lint``. Here is an example
.. code:: json
- {
- "linters": {
- "yamllint": {
- "type": "script-and-regex",
- "script-and-regex.script": "yamllint",
- "script-and-regex.regex": "/^(?P<line>\\d+):(?P<offset>\\d+) +(?P<severity>warning|error) +(?P<message>.*) +\\((?P<name>.*)\\)$/m",
- "include": "(\\.(yml|yaml)$)"
- }
- }
- }
+ {
+ "linters": {
+ "yamllint": {
+ "type": "script-and-regex",
+ "script-and-regex.script": "yamllint",
+ "script-and-regex.regex": "/^(?P<line>\\d+):(?P<offset>\\d+) +(?P<severity>warning|error) +(?P<message>.*) +\\((?P<name>.*)\\)$/m",
+ "include": "(\\.(yml|yaml)$)"
+ }
+ }
+ }
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..f5a3564
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1 @@
+sphinx-rtd-theme >=2.0.0
diff --git a/tests/__init__.py b/tests/__init__.py
index da1cd75..d8c46fc 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -15,5 +15,4 @@
import locale
-
locale.setlocale(locale.LC_ALL, 'C')
diff --git a/tests/common.py b/tests/common.py
index 65af63b..29dcfb9 100644
--- a/tests/common.py
+++ b/tests/common.py
@@ -14,15 +14,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import contextlib
+from io import StringIO
import os
import shutil
+import sys
import tempfile
import unittest
import yaml
-from yamllint.config import YamlLintConfig
from yamllint import linter
+from yamllint.config import YamlLintConfig
class RuleTestCase(unittest.TestCase):
@@ -54,6 +56,33 @@ class RuleTestCase(unittest.TestCase):
self.assertEqual(real_problems, expected_problems)
+class RunContext:
+ """Context manager for ``cli.run()`` to capture exit code and streams."""
+
+ def __init__(self, case):
+ self.stdout = self.stderr = None
+ self._raises_ctx = case.assertRaises(SystemExit)
+
+ def __enter__(self):
+ self._raises_ctx.__enter__()
+ self.old_sys_stdout = sys.stdout
+ self.old_sys_stderr = sys.stderr
+ sys.stdout = self.outstream = StringIO()
+ sys.stderr = self.errstream = StringIO()
+ return self
+
+ def __exit__(self, *exc_info):
+ self.stdout = self.outstream.getvalue()
+ self.stderr = self.errstream.getvalue()
+ sys.stdout = self.old_sys_stdout
+ sys.stderr = self.old_sys_stderr
+ return self._raises_ctx.__exit__(*exc_info)
+
+ @property
+ def returncode(self):
+ return self._raises_ctx.exception.code
+
+
def build_temp_workspace(files):
tempdir = tempfile.mkdtemp(prefix='yamllint-tests-')
@@ -62,8 +91,10 @@ def build_temp_workspace(files):
if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))
- if type(content) is list:
+ if isinstance(content, list):
os.mkdir(path)
+ elif isinstance(content, str) and content.startswith('symlink://'):
+ os.symlink(content[10:], path)
else:
mode = 'wb' if isinstance(content, bytes) else 'w'
with open(path, mode) as f:
diff --git a/tests/rules/test_indentation.py b/tests/rules/test_indentation.py
index 1c6eddb..b08ada7 100644
--- a/tests/rules/test_indentation.py
+++ b/tests/rules/test_indentation.py
@@ -15,7 +15,7 @@
from tests.common import RuleTestCase
-from yamllint.parser import token_or_comment_generator, Comment
+from yamllint.parser import Comment, token_or_comment_generator
from yamllint.rules.indentation import check
@@ -50,8 +50,8 @@ class IndentationStackTestCase(RuleTestCase):
.replace('Mapping', 'Map'))
if token_type in ('StreamStart', 'StreamEnd'):
continue
- output += '{:>9} {}\n'.format(token_type,
- self.format_stack(context['stack']))
+ stack = self.format_stack(context['stack'])
+ output += f'{token_type:>9} {stack}\n'
return output
def test_simple_mapping(self):
@@ -192,7 +192,7 @@ class IndentationStackTestCase(RuleTestCase):
'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
+ ' Value B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2 VAL:4\n' # noqa: E501
' 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
diff --git a/tests/rules/test_key_duplicates.py b/tests/rules/test_key_duplicates.py
index 3f8a9e6..c1704e6 100644
--- a/tests/rules/test_key_duplicates.py
+++ b/tests/rules/test_key_duplicates.py
@@ -179,3 +179,57 @@ class KeyDuplicatesTestCase(RuleTestCase):
'[\n'
' flow: sequence, with, key: value, mappings\n'
']\n', conf)
+
+ def test_forbid_duplicated_merge_keys(self):
+ conf = 'key-duplicates: {forbid-duplicated-merge-keys: true}'
+ self.check('---\n'
+ 'Multiple Merge Keys are NOT 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, problem=(9, 3))
+ self.check('---\n'
+ 'Multiple Merge Keys are NOT OK:\n'
+ 'anchor_one: &anchor_one\n'
+ ' one: one\n'
+ 'anchor_two: &anchor_two\n'
+ ' two: two\n'
+ 'anchor_three: &anchor_three\n'
+ ' two: three\n'
+ 'anchor_reference:\n'
+ ' <<: *anchor_one\n'
+ ' <<: *anchor_two\n'
+ ' <<: *anchor_three\n', conf,
+ problem1=(11, 3), problem2=(12, 3))
+ self.check('---\n'
+ 'Multiple Merge Keys are NOT OK:\n'
+ 'anchor_one: &anchor_one\n'
+ ' one: one\n'
+ 'anchor_two: &anchor_two\n'
+ ' two: two\n'
+ 'anchor_reference:\n'
+ ' a: 1\n'
+ ' <<: *anchor_one\n'
+ ' b: 2\n'
+ ' <<: *anchor_two\n', conf, problem=(11, 3))
+ self.check('---\n'
+ 'Single Merge Key is OK:\n'
+ 'anchor_one: &anchor_one\n'
+ ' one: one\n'
+ 'anchor_two: &anchor_two\n'
+ ' two: two\n'
+ 'anchor_reference:\n'
+ ' <<: [*anchor_one, *anchor_two]\n', conf)
+ self.check('---\n'
+ 'Duplicate keys without Merge Keys:\n'
+ ' key: a\n'
+ ' otherkey: b\n'
+ ' key: c\n', conf,
+ problem=(5, 3))
+ self.check('---\n'
+ 'No Merge Keys:\n'
+ ' key: a\n'
+ ' otherkey: b\n', conf)
diff --git a/tests/rules/test_quoted_strings.py b/tests/rules/test_quoted_strings.py
index 543cc0d..1bcb6f8 100644
--- a/tests/rules/test_quoted_strings.py
+++ b/tests/rules/test_quoted_strings.py
@@ -18,7 +18,7 @@ from tests.common import RuleTestCase
from yamllint import config
-class QuotedTestCase(RuleTestCase):
+class QuotedValuesTestCase(RuleTestCase):
rule_id = 'quoted-strings'
def test_disabled(self):
@@ -57,9 +57,14 @@ class QuotedTestCase(RuleTestCase):
' - 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))
+ 'flow-map: {a: foo, b: "foo"}\n' # fails
+ 'flow-seq2: [foo, "foo,bar", "foo[bar]", "foo{bar}"]\n'
+ 'flow-map2: {a: foo, b: "foo,bar"}\n'
+ 'nested-flow1: {a: foo, b: [foo, "foo,bar"]}\n'
+ 'nested-flow2: [{a: foo}, {b: "foo,bar", c: ["d[e]"]}]\n',
+ conf, problem1=(4, 10), problem2=(17, 5), problem3=(19, 12),
+ problem4=(20, 15), problem5=(21, 13), problem6=(22, 16),
+ problem7=(23, 19), problem8=(23, 28), problem9=(24, 20))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
@@ -97,11 +102,19 @@ class QuotedTestCase(RuleTestCase):
' - foo\n' # fails
' - "foo"\n' # fails
'flow-seq: [foo, "foo"]\n' # fails
- 'flow-map: {a: foo, b: "foo"}\n', # fails
+ 'flow-map: {a: foo, b: "foo"}\n' # fails
+ 'flow-seq2: [foo, "foo,bar", "foo[bar]", "foo{bar}"]\n'
+ 'flow-map2: {a: foo, b: "foo,bar"}\n'
+ 'nested-flow1: {a: foo, b: [foo, "foo,bar"]}\n'
+ 'nested-flow2: [{a: foo}, {b: "foo,bar", c: ["d[e]"]}]\n',
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))
+ problem10=(20, 23), problem11=(21, 13), problem12=(21, 18),
+ problem13=(21, 29), problem14=(21, 41), problem15=(22, 16),
+ problem16=(22, 24), problem17=(23, 19), problem18=(23, 28),
+ problem19=(23, 33), problem20=(24, 20), problem21=(24, 30),
+ problem22=(24, 45))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
@@ -139,9 +152,15 @@ class QuotedTestCase(RuleTestCase):
' - foo\n' # fails
' - "foo"\n'
'flow-seq: [foo, "foo"]\n' # fails
- 'flow-map: {a: foo, b: "foo"}\n', # fails
+ 'flow-map: {a: foo, b: "foo"}\n' # fails
+ 'flow-seq2: [foo, "foo,bar", "foo[bar]", "foo{bar}"]\n'
+ 'flow-map2: {a: foo, b: "foo,bar"}\n'
+ 'nested-flow1: {a: foo, b: [foo, "foo,bar"]}\n'
+ 'nested-flow2: [{a: foo}, {b: "foo,bar", c: ["d[e]"]}]\n',
conf, problem1=(4, 10), problem2=(8, 10), problem3=(17, 5),
- problem4=(19, 12), problem5=(20, 15))
+ problem4=(19, 12), problem5=(20, 15), problem6=(21, 13),
+ problem7=(22, 16), problem8=(23, 19), problem9=(23, 28),
+ problem10=(24, 20))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
@@ -179,7 +198,11 @@ class QuotedTestCase(RuleTestCase):
' - foo\n' # fails
' - "foo"\n'
'flow-seq: [foo, "foo"]\n' # fails
- 'flow-map: {a: foo, b: "foo"}\n', # fails
+ 'flow-map: {a: foo, b: "foo"}\n' # fails
+ 'flow-seq2: [foo, "foo,bar", "foo[bar]", "foo{bar}"]\n'
+ 'flow-map2: {a: foo, b: "foo,bar"}\n'
+ 'nested-flow1: {a: foo, b: [foo, "foo,bar"]}\n'
+ 'nested-flow2: [{a: foo}, {b: "foo,bar", c: ["d[e]"]}]\n',
conf)
self.check('---\n'
'multiline string 1: |\n'
@@ -218,9 +241,16 @@ class QuotedTestCase(RuleTestCase):
' - foo\n' # fails
' - "foo"\n'
'flow-seq: [foo, "foo"]\n' # fails
- 'flow-map: {a: foo, b: "foo"}\n', # fails
+ 'flow-map: {a: foo, b: "foo"}\n' # fails
+ 'flow-seq2: [foo, "foo,bar", "foo[bar]", "foo{bar}"]\n'
+ 'flow-map2: {a: foo, b: "foo,bar"}\n'
+ 'nested-flow1: {a: foo, b: [foo, "foo,bar"]}\n'
+ 'nested-flow2: [{a: foo}, {b: "foo,bar", c: ["d[e]"]}]\n',
conf, problem1=(5, 10), problem2=(6, 10), problem3=(7, 10),
- problem4=(18, 5), problem5=(19, 17), problem6=(20, 23))
+ problem4=(18, 5), problem5=(19, 17), problem6=(20, 23),
+ problem7=(21, 18), problem8=(21, 29), problem9=(21, 41),
+ problem10=(22, 24), problem11=(23, 33), problem12=(24, 30),
+ problem13=(24, 45))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
@@ -258,7 +288,11 @@ class QuotedTestCase(RuleTestCase):
' - foo\n'
' - "foo"\n' # fails
'flow-seq: [foo, "foo"]\n' # fails
- 'flow-map: {a: foo, b: "foo"}\n', # fails
+ 'flow-map: {a: foo, b: "foo"}\n' # fails
+ 'flow-seq2: [foo, "foo,bar", "foo[bar]", "foo{bar}"]\n'
+ 'flow-map2: {a: foo, b: "foo,bar"}\n'
+ 'nested-flow1: {a: foo, b: [foo, "foo,bar"]}\n'
+ 'nested-flow2: [{a: foo}, {b: "foo,bar", c: ["d[e]"]}]\n',
conf, problem1=(5, 10), problem2=(8, 10), problem3=(18, 5),
problem4=(19, 17), problem5=(20, 23))
self.check('---\n'
@@ -299,10 +333,15 @@ class QuotedTestCase(RuleTestCase):
' - foo\n'
' - "foo"\n' # fails
'flow-seq: [foo, "foo"]\n' # fails
- 'flow-map: {a: foo, b: "foo"}\n', # fails
+ 'flow-map: {a: foo, b: "foo"}\n' # fails
+ 'flow-seq2: [foo, "foo,bar"]\n' # fails
+ 'flow-map2: {a: foo, b: "foo,bar"}\n' # fails
+ 'nested-flow1: {a: foo, b: [foo, "foo,bar"]}\n'
+ 'nested-flow2: [{a: foo}, {b: "foo,bar", c: ["d[e]"]}]\n',
conf, problem1=(5, 10), problem2=(6, 10), problem3=(7, 10),
problem4=(8, 10), problem5=(18, 5), problem6=(19, 17),
- problem7=(20, 23))
+ problem7=(20, 23), problem8=(21, 18), problem9=(22, 24),
+ problem10=(23, 33), problem11=(24, 30), problem12=(24, 45))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
@@ -556,3 +595,722 @@ class QuotedTestCase(RuleTestCase):
"foo1: '[barbaz]'\n"
"foo2: '[bar\"baz]'\n",
conf)
+
+
+class QuotedKeysTestCase(RuleTestCase):
+ rule_id = 'quoted-strings'
+
+ def test_disabled(self):
+ conf_disabled = "quoted-strings: {}"
+ key_strings = ('---\n'
+ 'true: 2\n'
+ '123: 3\n'
+ 'foo1: 4\n'
+ '"foo2": 5\n'
+ '"false": 6\n'
+ '"234": 7\n'
+ '\'bar\': 8\n'
+ '!!str generic_string: 9\n'
+ '!!str 456: 10\n'
+ '!!str "quoted_generic_string": 11\n'
+ '!!binary binstring: 12\n'
+ '!!int int_string: 13\n'
+ '!!bool bool_string: 14\n'
+ '!!bool "quoted_bool_string": 15\n'
+ # Sequences and mappings
+ '? - 16\n'
+ ' - 17\n'
+ ': 18\n'
+ '[119, 219]: 19\n'
+ '? a: 20\n'
+ ' "b": 21\n'
+ ': 22\n'
+ '{a: 123, "b": 223}: 23\n'
+ # Multiline strings
+ '? |\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 27\n'
+ '? >\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 31\n'
+ '?\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 35\n'
+ '?\n'
+ ' "line 1\\\n'
+ ' line 2"\n'
+ ': 39\n')
+ self.check(key_strings, conf_disabled)
+
+ def test_default(self):
+ # Default configuration, but with check-keys
+ conf_default = ("quoted-strings:\n"
+ " check-keys: true\n")
+ key_strings = ('---\n'
+ 'true: 2\n'
+ '123: 3\n'
+ 'foo1: 4\n'
+ '"foo2": 5\n'
+ '"false": 6\n'
+ '"234": 7\n'
+ '\'bar\': 8\n'
+ '!!str generic_string: 9\n'
+ '!!str 456: 10\n'
+ '!!str "quoted_generic_string": 11\n'
+ '!!binary binstring: 12\n'
+ '!!int int_string: 13\n'
+ '!!bool bool_string: 14\n'
+ '!!bool "quoted_bool_string": 15\n'
+ # Sequences and mappings
+ '? - 16\n'
+ ' - 17\n'
+ ': 18\n'
+ '[119, 219]: 19\n'
+ '? a: 20\n'
+ ' "b": 21\n'
+ ': 22\n'
+ '{a: 123, "b": 223}: 23\n'
+ # Multiline strings
+ '? |\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 27\n'
+ '? >\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 31\n'
+ '?\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 35\n'
+ '?\n'
+ ' "line 1\\\n'
+ ' line 2"\n'
+ ': 39\n')
+ self.check(key_strings, conf_default, problem1=(4, 1),
+ problem3=(20, 3), problem4=(23, 2), problem5=(33, 3))
+
+ def test_quote_type_any(self):
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: any\n')
+
+ key_strings = ('---\n'
+ 'true: 2\n'
+ '123: 3\n'
+ 'foo1: 4\n'
+ '"foo2": 5\n'
+ '"false": 6\n'
+ '"234": 7\n'
+ '\'bar\': 8\n'
+ '!!str generic_string: 9\n'
+ '!!str 456: 10\n'
+ '!!str "quoted_generic_string": 11\n'
+ '!!binary binstring: 12\n'
+ '!!int int_string: 13\n'
+ '!!bool bool_string: 14\n'
+ '!!bool "quoted_bool_string": 15\n'
+ # Sequences and mappings
+ '? - 16\n'
+ ' - 17\n'
+ ': 18\n'
+ '[119, 219]: 19\n'
+ '? a: 20\n'
+ ' "b": 21\n'
+ ': 22\n'
+ '{a: 123, "b": 223}: 23\n'
+ # Multiline strings
+ '? |\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 27\n'
+ '? >\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 31\n'
+ '?\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 35\n'
+ '?\n'
+ ' "line 1\\\n'
+ ' line 2"\n'
+ ': 39\n')
+ self.check(key_strings, conf,
+ problem1=(4, 1), problem2=(20, 3), problem3=(23, 2),
+ problem4=(33, 3))
+
+ def test_quote_type_single(self):
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: single\n')
+
+ key_strings = ('---\n'
+ 'true: 2\n'
+ '123: 3\n'
+ 'foo1: 4\n'
+ '"foo2": 5\n'
+ '"false": 6\n'
+ '"234": 7\n'
+ '\'bar\': 8\n'
+ '!!str generic_string: 9\n'
+ '!!str 456: 10\n'
+ '!!str "quoted_generic_string": 11\n'
+ '!!binary binstring: 12\n'
+ '!!int int_string: 13\n'
+ '!!bool bool_string: 14\n'
+ '!!bool "quoted_bool_string": 15\n'
+ # Sequences and mappings
+ '? - 16\n'
+ ' - 17\n'
+ ': 18\n'
+ '[119, 219]: 19\n'
+ '? a: 20\n'
+ ' "b": 21\n'
+ ': 22\n'
+ '{a: 123, "b": 223}: 23\n'
+ # Multiline strings
+ '? |\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 27\n'
+ '? >\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 31\n'
+ '?\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 35\n'
+ '?\n'
+ ' "line 1\\\n'
+ ' line 2"\n'
+ ': 39\n')
+ self.check(key_strings, conf,
+ problem1=(4, 1), problem2=(5, 1), problem3=(6, 1),
+ problem4=(7, 1), problem5=(20, 3), problem6=(21, 3),
+ problem7=(23, 2), problem8=(23, 10), problem9=(33, 3),
+ problem10=(37, 3))
+
+ def test_quote_type_double(self):
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: double\n')
+
+ key_strings = ('---\n'
+ 'true: 2\n'
+ '123: 3\n'
+ 'foo1: 4\n'
+ '"foo2": 5\n'
+ '"false": 6\n'
+ '"234": 7\n'
+ '\'bar\': 8\n'
+ '!!str generic_string: 9\n'
+ '!!str 456: 10\n'
+ '!!str "quoted_generic_string": 11\n'
+ '!!binary binstring: 12\n'
+ '!!int int_string: 13\n'
+ '!!bool bool_string: 14\n'
+ '!!bool "quoted_bool_string": 15\n'
+ # Sequences and mappings
+ '? - 16\n'
+ ' - 17\n'
+ ': 18\n'
+ '[119, 219]: 19\n'
+ '? a: 20\n'
+ ' "b": 21\n'
+ ': 22\n'
+ '{a: 123, "b": 223}: 23\n'
+ # Multiline strings
+ '? |\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 27\n'
+ '? >\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 31\n'
+ '?\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 35\n'
+ '?\n'
+ ' "line 1\\\n'
+ ' line 2"\n'
+ ': 39\n')
+ self.check(key_strings, conf,
+ problem1=(4, 1), problem2=(8, 1), problem3=(20, 3),
+ problem4=(23, 2), problem5=(33, 3))
+
+ def test_any_quotes_not_required(self):
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: any\n'
+ ' required: false\n')
+
+ key_strings = ('---\n'
+ 'true: 2\n'
+ '123: 3\n'
+ 'foo1: 4\n'
+ '"foo2": 5\n'
+ '"false": 6\n'
+ '"234": 7\n'
+ '\'bar\': 8\n'
+ '!!str generic_string: 9\n'
+ '!!str 456: 10\n'
+ '!!str "quoted_generic_string": 11\n'
+ '!!binary binstring: 12\n'
+ '!!int int_string: 13\n'
+ '!!bool bool_string: 14\n'
+ '!!bool "quoted_bool_string": 15\n'
+ # Sequences and mappings
+ '? - 16\n'
+ ' - 17\n'
+ ': 18\n'
+ '[119, 219]: 19\n'
+ '? a: 20\n'
+ ' "b": 21\n'
+ ': 22\n'
+ '{a: 123, "b": 223}: 23\n'
+ # Multiline strings
+ '? |\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 27\n'
+ '? >\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 31\n'
+ '?\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 35\n'
+ '?\n'
+ ' "line 1\\\n'
+ ' line 2"\n'
+ ': 39\n')
+ self.check(key_strings, conf)
+
+ def test_single_quotes_not_required(self):
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: single\n'
+ ' required: false\n')
+
+ key_strings = ('---\n'
+ 'true: 2\n'
+ '123: 3\n'
+ 'foo1: 4\n'
+ '"foo2": 5\n'
+ '"false": 6\n'
+ '"234": 7\n'
+ '\'bar\': 8\n'
+ '!!str generic_string: 9\n'
+ '!!str 456: 10\n'
+ '!!str "quoted_generic_string": 11\n'
+ '!!binary binstring: 12\n'
+ '!!int int_string: 13\n'
+ '!!bool bool_string: 14\n'
+ '!!bool "quoted_bool_string": 15\n'
+ # Sequences and mappings
+ '? - 16\n'
+ ' - 17\n'
+ ': 18\n'
+ '[119, 219]: 19\n'
+ '? a: 20\n'
+ ' "b": 21\n'
+ ': 22\n'
+ '{a: 123, "b": 223}: 23\n'
+ # Multiline strings
+ '? |\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 27\n'
+ '? >\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 31\n'
+ '?\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 35\n'
+ '?\n'
+ ' "line 1\\\n'
+ ' line 2"\n'
+ ': 39\n')
+ self.check(key_strings, conf,
+ problem1=(5, 1), problem2=(6, 1), problem3=(7, 1),
+ problem4=(21, 3), problem5=(23, 10), problem6=(37, 3))
+
+ def test_only_when_needed(self):
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' required: only-when-needed\n')
+
+ key_strings = ('---\n'
+ 'true: 2\n'
+ '123: 3\n'
+ 'foo1: 4\n'
+ '"foo2": 5\n'
+ '"false": 6\n'
+ '"234": 7\n'
+ '\'bar\': 8\n'
+ '!!str generic_string: 9\n'
+ '!!str 456: 10\n'
+ '!!str "quoted_generic_string": 11\n'
+ '!!binary binstring: 12\n'
+ '!!int int_string: 13\n'
+ '!!bool bool_string: 14\n'
+ '!!bool "quoted_bool_string": 15\n'
+ # Sequences and mappings
+ '? - 16\n'
+ ' - 17\n'
+ ': 18\n'
+ '[119, 219]: 19\n'
+ '? a: 20\n'
+ ' "b": 21\n'
+ ': 22\n'
+ '{a: 123, "b": 223}: 23\n'
+ # Multiline strings
+ '? |\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 27\n'
+ '? >\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 31\n'
+ '?\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 35\n'
+ '?\n'
+ ' "line 1\\\n'
+ ' line 2"\n'
+ ': 39\n')
+ self.check(key_strings, conf,
+ problem1=(5, 1), problem2=(8, 1), problem3=(21, 3),
+ problem4=(23, 10), problem5=(37, 3))
+
+ def test_only_when_needed_single_quotes(self):
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: single\n'
+ ' required: only-when-needed\n')
+
+ key_strings = ('---\n'
+ 'true: 2\n'
+ '123: 3\n'
+ 'foo1: 4\n'
+ '"foo2": 5\n'
+ '"false": 6\n'
+ '"234": 7\n'
+ '\'bar\': 8\n'
+ '!!str generic_string: 9\n'
+ '!!str 456: 10\n'
+ '!!str "quoted_generic_string": 11\n'
+ '!!binary binstring: 12\n'
+ '!!int int_string: 13\n'
+ '!!bool bool_string: 14\n'
+ '!!bool "quoted_bool_string": 15\n'
+ # Sequences and mappings
+ '? - 16\n'
+ ' - 17\n'
+ ': 18\n'
+ '[119, 219]: 19\n'
+ '? a: 20\n'
+ ' "b": 21\n'
+ ': 22\n'
+ '{a: 123, "b": 223}: 23\n'
+ # Multiline strings
+ '? |\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 27\n'
+ '? >\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 31\n'
+ '?\n'
+ ' line 1\n'
+ ' line 2\n'
+ ': 35\n'
+ '?\n'
+ ' "line 1\\\n'
+ ' line 2"\n'
+ ': 39\n')
+ self.check(key_strings, conf,
+ problem1=(5, 1), problem2=(6, 1), problem3=(7, 1),
+ problem4=(8, 1), problem5=(21, 3), problem6=(23, 10),
+ problem7=(37, 3))
+
+ def test_only_when_needed_corner_cases(self):
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' required: only-when-needed\n')
+
+ self.check('---\n'
+ '"": 2\n'
+ '"- item": 3\n'
+ '"key: value": 4\n'
+ '"%H:%M:%S": 5\n'
+ '"%wheel ALL=(ALL) NOPASSWD: ALL": 6\n'
+ '\'"quoted"\': 7\n'
+ '"\'foo\' == \'bar\'": 8\n'
+ '"\'Mac\' in ansible_facts.product_name": 9\n'
+ '\'foo # bar\': 10\n',
+ conf)
+ self.check('---\n'
+ '"": 2\n'
+ '"- item": 3\n'
+ '"key: value": 4\n'
+ '"%H:%M:%S": 5\n'
+ '"%wheel ALL=(ALL) NOPASSWD: ALL": 6\n'
+ '\'"quoted"\': 7\n'
+ '"\'foo\' == \'bar\'": 8\n'
+ '"\'Mac\' in ansible_facts.product_name": 9\n',
+ conf)
+
+ self.check('---\n'
+ '---: 2\n'
+ '"----": 3\n' # fails
+ '---------: 4\n'
+ '"----------": 5\n' # fails
+ ':wq: 6\n'
+ '":cw": 7\n', # fails
+ conf, problem1=(3, 1), problem2=(5, 1), problem3=(7, 1))
+
+ def test_only_when_needed_extras(self):
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' required: true\n'
+ ' extra-allowed: [^http://]\n')
+ self.assertRaises(config.YamlLintConfigError, self.check, '', conf)
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' required: true\n'
+ ' extra-required: [^http://]\n')
+ self.assertRaises(config.YamlLintConfigError, self.check, '', conf)
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' required: false\n'
+ ' extra-allowed: [^http://]\n')
+ self.assertRaises(config.YamlLintConfigError, self.check, '', conf)
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' required: true\n')
+ self.check('---\n'
+ '123: 2\n'
+ '"234": 3\n'
+ 'localhost: 4\n' # fails
+ '"host.local": 5\n'
+ 'http://localhost: 6\n' # fails
+ '"http://host.local": 7\n'
+ 'ftp://localhost: 8\n' # fails
+ '"ftp://host.local": 9\n',
+ conf, problem1=(4, 1), problem2=(6, 1), problem3=(8, 1))
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' required: only-when-needed\n'
+ ' extra-allowed: [^ftp://]\n'
+ ' extra-required: [^http://]\n')
+ self.check('---\n'
+ '123: 2\n'
+ '"234": 3\n'
+ 'localhost: 4\n'
+ '"host.local": 5\n' # fails
+ 'http://localhost: 6\n' # fails
+ '"http://host.local": 7\n'
+ 'ftp://localhost: 8\n'
+ '"ftp://host.local": 9\n',
+ conf, problem1=(5, 1), problem2=(6, 1))
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' required: false\n'
+ ' extra-required: [^http://, ^ftp://]\n')
+ self.check('---\n'
+ '123: 2\n'
+ '"234": 3\n'
+ 'localhost: 4\n'
+ '"host.local": 5\n'
+ 'http://localhost: 6\n' # fails
+ '"http://host.local": 7\n'
+ 'ftp://localhost: 8\n' # fails
+ '"ftp://host.local": 9\n',
+ conf, problem1=(6, 1), problem2=(8, 1))
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' required: only-when-needed\n'
+ ' extra-allowed: [^ftp://, ";$", " "]\n')
+ self.check('---\n'
+ 'localhost: 2\n'
+ '"host.local": 3\n' # fails
+ 'ftp://localhost: 4\n'
+ '"ftp://host.local": 5\n'
+ 'i=i+1: 6\n'
+ '"i=i+2": 7\n' # fails
+ 'i=i+3;: 8\n'
+ '"i=i+4;": 9\n'
+ 'foo1: 10\n'
+ '"foo2": 11\n' # fails
+ 'foo bar1: 12\n'
+ '"foo bar2": 13\n',
+ conf, problem1=(3, 1), problem2=(7, 1), problem3=(11, 1))
+
+ def test_octal_values(self):
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' required: true\n')
+
+ self.check('---\n'
+ '100: 2\n'
+ '0100: 3\n'
+ '0o100: 4\n'
+ '777: 5\n'
+ '0777: 6\n'
+ '0o777: 7\n'
+ '800: 8\n'
+ '0800: 9\n' # fails
+ '0o800: 10\n' # fails
+ '"0900": 11\n'
+ '"0o900": 12\n',
+ conf,
+ problem1=(9, 1), problem2=(10, 1))
+
+ def test_allow_quoted_quotes(self):
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: single\n'
+ ' required: false\n'
+ ' allow-quoted-quotes: false\n')
+ self.check('---\n'
+ '"[barbaz]": 2\n' # fails
+ '"[bar\'baz]": 3\n', # fails
+ conf, problem1=(2, 1), problem2=(3, 1))
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: single\n'
+ ' required: false\n'
+ ' allow-quoted-quotes: true\n')
+ self.check('---\n'
+ '"[barbaz]": 2\n' # fails
+ '"[bar\'baz]": 3\n',
+ conf, problem1=(2, 1))
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: single\n'
+ ' required: true\n'
+ ' allow-quoted-quotes: false\n')
+ self.check('---\n'
+ '"[barbaz]": 2\n' # fails
+ '"[bar\'baz]": 3\n', # fails
+ conf, problem1=(2, 1), problem2=(3, 1))
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: single\n'
+ ' required: true\n'
+ ' allow-quoted-quotes: true\n')
+ self.check('---\n'
+ '"[barbaz]": 2\n' # fails
+ '"[bar\'baz]": 3\n',
+ conf, problem1=(2, 1))
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: single\n'
+ ' required: only-when-needed\n'
+ ' allow-quoted-quotes: false\n')
+ self.check('---\n'
+ '"[barbaz]": 2\n' # fails
+ '"[bar\'baz]": 3\n', # fails
+ conf, problem1=(2, 1), problem2=(3, 1))
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: single\n'
+ ' required: only-when-needed\n'
+ ' allow-quoted-quotes: true\n')
+ self.check('---\n'
+ '"[barbaz]": 2\n' # fails
+ '"[bar\'baz]": 3\n',
+ conf, problem1=(2, 1))
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: double\n'
+ ' required: false\n'
+ ' allow-quoted-quotes: false\n')
+ self.check("---\n"
+ "'[barbaz]': 2\n" # fails
+ "'[bar\"baz]': 3\n", # fails
+ conf, problem1=(2, 1), problem2=(3, 1))
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: double\n'
+ ' required: false\n'
+ ' allow-quoted-quotes: true\n')
+ self.check("---\n"
+ "'[barbaz]': 2\n" # fails
+ "'[bar\"baz]': 3\n",
+ conf, problem1=(2, 1))
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: double\n'
+ ' required: true\n'
+ ' allow-quoted-quotes: false\n')
+ self.check("---\n"
+ "'[barbaz]': 2\n" # fails
+ "'[bar\"baz]': 3\n", # fails
+ conf, problem1=(2, 1), problem2=(3, 1))
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: double\n'
+ ' required: true\n'
+ ' allow-quoted-quotes: true\n')
+ self.check("---\n"
+ "'[barbaz]': 2\n" # fails
+ "'[bar\"baz]': 3\n",
+ conf, problem1=(2, 1))
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: double\n'
+ ' required: only-when-needed\n'
+ ' allow-quoted-quotes: false\n')
+ self.check("---\n"
+ "'[barbaz]': 2\n" # fails
+ "'[bar\"baz]': 3\n", # fails
+ conf, problem1=(2, 1), problem2=(3, 1))
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: double\n'
+ ' required: only-when-needed\n'
+ ' allow-quoted-quotes: true\n')
+ self.check("---\n"
+ "'[barbaz]': 2\n" # fails
+ "'[bar\"baz]': 3\n",
+ conf, problem1=(2, 1))
+
+ conf = ('quoted-strings:\n'
+ ' check-keys: true\n'
+ ' quote-type: any\n')
+ self.check("---\n"
+ "'[barbaz]': 2\n"
+ "'[bar\"baz]': 3\n",
+ conf)
diff --git a/tests/rules/test_truthy.py b/tests/rules/test_truthy.py
index c8c8b7a..e485d07 100644
--- a/tests/rules/test_truthy.py
+++ b/tests/rules/test_truthy.py
@@ -27,7 +27,8 @@ class TruthyTestCase(RuleTestCase):
'True: 1\n', conf)
def test_enabled(self):
- conf = 'truthy: enable\n'
+ conf = ('truthy: enable\n'
+ 'document-start: disable\n')
self.check('---\n'
'1: True\n'
'True: 1\n',
@@ -35,7 +36,8 @@ class TruthyTestCase(RuleTestCase):
self.check('---\n'
'1: "True"\n'
'"True": 1\n', conf)
- self.check('---\n'
+ self.check('%YAML 1.1\n'
+ '---\n'
'[\n'
' true, false,\n'
' "false", "FALSE",\n'
@@ -44,9 +46,47 @@ class TruthyTestCase(RuleTestCase):
' 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))
+ problem1=(7, 3), problem2=(7, 9),
+ problem3=(8, 3), problem4=(8, 7),
+ problem5=(9, 3), problem6=(9, 7))
+ self.check('y: 1\n'
+ 'yes: 2\n'
+ 'on: 3\n'
+ 'true: 4\n'
+ 'True: 5\n'
+ '...\n'
+ '%YAML 1.2\n'
+ '---\n'
+ 'y: 1\n'
+ 'yes: 2\n'
+ 'on: 3\n'
+ 'true: 4\n'
+ 'True: 5\n'
+ '...\n'
+ '%YAML 1.1\n'
+ '---\n'
+ 'y: 1\n'
+ 'yes: 2\n'
+ 'on: 3\n'
+ 'true: 4\n'
+ 'True: 5\n'
+ '---\n'
+ 'y: 1\n'
+ 'yes: 2\n'
+ 'on: 3\n'
+ 'true: 4\n'
+ 'True: 5\n',
+ conf,
+ problem1=(2, 1),
+ problem2=(3, 1),
+ problem3=(5, 1),
+ problem4=(13, 1),
+ problem5=(18, 1),
+ problem6=(19, 1),
+ problem7=(21, 1),
+ problem8=(24, 1),
+ problem9=(25, 1),
+ problem10=(27, 1))
def test_different_allowed_values(self):
conf = ('truthy:\n'
@@ -56,15 +96,16 @@ class TruthyTestCase(RuleTestCase):
'key2: yes\n'
'key3: bar\n'
'key4: no\n', conf)
- self.check('---\n'
+ self.check('%YAML 1.1\n'
+ '---\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))
+ problem1=(3, 7), problem2=(4, 7),
+ problem3=(5, 7))
def test_combined_allowed_values(self):
conf = ('truthy:\n'
@@ -81,6 +122,22 @@ class TruthyTestCase(RuleTestCase):
'key4: no\n'
'key5: yes\n',
conf, problem1=(3, 7))
+ self.check('%YAML 1.1\n'
+ '---\n'
+ 'key1: true\n'
+ 'key2: Yes\n'
+ 'key3: false\n'
+ 'key4: no\n'
+ 'key5: yes\n',
+ conf, problem1=(4, 7))
+ self.check('%YAML 1.2\n'
+ '---\n'
+ 'key1: true\n'
+ 'key2: Yes\n'
+ 'key3: false\n'
+ 'key4: no\n'
+ 'key5: yes\n',
+ conf)
def test_no_allowed_values(self):
conf = ('truthy:\n'
@@ -95,6 +152,21 @@ class TruthyTestCase(RuleTestCase):
'key4: no\n', conf,
problem1=(2, 7), problem2=(3, 7),
problem3=(4, 7), problem4=(5, 7))
+ self.check('%YAML 1.1\n'
+ '---\n'
+ 'key1: true\n'
+ 'key2: yes\n'
+ 'key3: false\n'
+ 'key4: no\n', conf,
+ problem1=(3, 7), problem2=(4, 7),
+ problem3=(5, 7), problem4=(6, 7))
+ self.check('%YAML 1.2\n'
+ '---\n'
+ 'key1: true\n'
+ 'key2: yes\n'
+ 'key3: false\n'
+ 'key4: no\n', conf,
+ problem1=(3, 7), problem2=(5, 7))
def test_explicit_types(self):
conf = 'truthy: enable\n'
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 444f2f9..e0ae0fe 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -13,7 +13,6 @@
# 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 io import StringIO
import fcntl
import locale
import os
@@ -22,34 +21,11 @@ import shutil
import sys
import tempfile
import unittest
+from io import StringIO
-from tests.common import build_temp_workspace, temp_workspace
-
-from yamllint import cli
-from yamllint import config
-
-
-class RunContext:
- """Context manager for ``cli.run()`` to capture exit code and streams."""
-
- def __init__(self, case):
- self.stdout = self.stderr = None
- self._raises_ctx = case.assertRaises(SystemExit)
-
- def __enter__(self):
- self._raises_ctx.__enter__()
- sys.stdout = self.outstream = StringIO()
- sys.stderr = self.errstream = StringIO()
- return self
-
- def __exit__(self, *exc_info):
- self.stdout, sys.stdout = self.outstream.getvalue(), sys.__stdout__
- self.stderr, sys.stderr = self.errstream.getvalue(), sys.__stderr__
- return self._raises_ctx.__exit__(*exc_info)
+from tests.common import build_temp_workspace, RunContext, temp_workspace
- @property
- def returncode(self):
- return self._raises_ctx.exception.code
+from yamllint import cli, config
# Check system's UTF-8 availability
@@ -57,9 +33,30 @@ def utf8_available():
try:
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
locale.setlocale(locale.LC_ALL, (None, None))
- return True
except locale.Error: # pragma: no cover
return False
+ else:
+ return True
+
+
+def setUpModule():
+ # yamllint uses these environment variables to find a config file.
+ env_vars_that_could_interfere = (
+ 'YAMLLINT_CONFIG_FILE',
+ 'XDG_CONFIG_HOME',
+ # These variables are used to determine where the user’s home
+ # directory is. See
+ # https://docs.python.org/3/library/os.path.html#os.path.expanduser
+ 'HOME',
+ 'USERPROFILE',
+ 'HOMEPATH',
+ 'HOMEDRIVE'
+ )
+ for name in env_vars_that_could_interfere:
+ try:
+ del os.environ[name]
+ except KeyError:
+ pass
class CommandLineTestCase(unittest.TestCase):
@@ -88,6 +85,9 @@ class CommandLineTestCase(unittest.TestCase):
'key: other value\n',
# empty dir
'empty-dir': [],
+ # symbolic link
+ 'symlinks/file-without-yaml-extension': '42\n',
+ 'symlinks/link.yaml': 'symlink://file-without-yaml-extension',
# non-YAML file
'no-yaml.json': '---\n'
'key: value\n',
@@ -116,8 +116,6 @@ class CommandLineTestCase(unittest.TestCase):
shutil.rmtree(cls.wd)
- @unittest.skipIf(not utf8_available() and sys.version_info < (3, 7),
- 'UTF-8 paths not supported')
def test_find_files_recursively(self):
conf = config.YamlLintConfig('extends: default')
self.assertEqual(
@@ -130,6 +128,7 @@ class CommandLineTestCase(unittest.TestCase):
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/ok.yaml'),
+ os.path.join(self.wd, 'symlinks/link.yaml'),
os.path.join(self.wd, 'warn.yaml')],
)
@@ -167,6 +166,7 @@ class CommandLineTestCase(unittest.TestCase):
os.path.join(self.wd, 'en.yaml'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/ok.yaml'),
+ os.path.join(self.wd, 'symlinks/link.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
@@ -204,6 +204,8 @@ class CommandLineTestCase(unittest.TestCase):
os.path.join(self.wd, 'sub/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/directory.yaml/not-yaml.txt'),
os.path.join(self.wd, 'sub/ok.yaml'),
+ os.path.join(self.wd, 'symlinks/file-without-yaml-extension'),
+ os.path.join(self.wd, 'symlinks/link.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
@@ -225,6 +227,8 @@ class CommandLineTestCase(unittest.TestCase):
os.path.join(self.wd, 'sub/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/directory.yaml/not-yaml.txt'),
os.path.join(self.wd, 'sub/ok.yaml'),
+ os.path.join(self.wd, 'symlinks/file-without-yaml-extension'),
+ os.path.join(self.wd, 'symlinks/link.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
@@ -306,14 +310,13 @@ class CommandLineTestCase(unittest.TestCase):
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
self.assertEqual(ctx.returncode, 1)
- @unittest.skipIf(os.environ.get('GITHUB_RUN_ID'), '$HOME not overridable')
def test_run_with_user_global_config_file(self):
home = os.path.join(self.wd, 'fake-home')
dir = os.path.join(home, '.config', 'yamllint')
os.makedirs(dir)
config = os.path.join(dir, 'config')
- self.addCleanup(os.environ.update, HOME=os.environ['HOME'])
+ self.addCleanup(os.environ.__delitem__, 'HOME')
os.environ['HOME'] = home
with open(config, 'w') as f:
@@ -690,6 +693,7 @@ class CommandLineTestCase(unittest.TestCase):
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/ok.yaml'),
+ os.path.join(self.wd, 'symlinks/link.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
@@ -706,9 +710,29 @@ class CommandLineTestCase(unittest.TestCase):
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/directory.yaml/not-yaml.txt'),
os.path.join(self.wd, 'sub/ok.yaml'),
+ os.path.join(self.wd, 'symlinks/link.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
+ config = 'ignore: ["*.yaml", "*.yml", "!a.yaml"]'
+ with RunContext(self) as ctx:
+ cli.run(('--list-files', '-d', config, self.wd))
+ self.assertEqual(ctx.returncode, 0)
+ self.assertEqual(
+ sorted(ctx.stdout.splitlines()),
+ [os.path.join(self.wd, 'a.yaml')]
+ )
+ with RunContext(self) as ctx:
+ cli.run(('--list-files', '-d', config,
+ os.path.join(self.wd, 'a.yaml'),
+ os.path.join(self.wd, 'en.yaml'),
+ os.path.join(self.wd, 'c.yaml')))
+ self.assertEqual(ctx.returncode, 0)
+ self.assertEqual(
+ sorted(ctx.stdout.splitlines()),
+ [os.path.join(self.wd, 'a.yaml')]
+ )
+
class CommandLineConfigTestCase(unittest.TestCase):
def test_config_file(self):
diff --git a/tests/test_config.py b/tests/test_config.py
index 8e90246..fb570c6 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -13,18 +13,17 @@
# 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 io import StringIO
import os
import shutil
import sys
import tempfile
import unittest
+from io import StringIO
-from tests.common import build_temp_workspace
+from tests.common import build_temp_workspace, RunContext
+from yamllint import cli, config
from yamllint.config import YamlLintConfigError
-from yamllint import cli
-from yamllint import config
class SimpleConfigTestCase(unittest.TestCase):
@@ -212,6 +211,14 @@ class SimpleConfigTestCase(unittest.TestCase):
' colons:\n'
' ignore: yes\n')
+ def test_invalid_rule_ignore_from_file(self):
+ self.assertRaises(
+ config.YamlLintConfigError,
+ config.YamlLintConfig,
+ 'rules:\n'
+ ' colons:\n'
+ ' ignore-from-file: 1337\n')
+
def test_invalid_locale(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
@@ -660,12 +667,19 @@ class IgnoreConfigTestCase(unittest.TestCase):
def test_run_with_ignore_from_file(self):
with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
f.write('extends: default\n'
- 'ignore-from-file: .gitignore\n')
+ 'ignore-from-file: .gitignore\n'
+ 'rules:\n'
+ ' key-duplicates:\n'
+ ' ignore-from-file: .ignore-key-duplicates\n')
+
with open(os.path.join(self.wd, '.gitignore'), 'w') as f:
f.write('*.dont-lint-me.yaml\n'
'/bin/\n'
'!/bin/*.lint-me-anyway.yaml\n')
+ with open(os.path.join(self.wd, '.ignore-key-duplicates'), 'w') as f:
+ f.write('/ign-dup\n')
+
sys.stdout = StringIO()
with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.'))
@@ -686,10 +700,8 @@ class IgnoreConfigTestCase(unittest.TestCase):
'./file-at-root.yaml:3:3: ' + keydup,
'./file-at-root.yaml:4:17: ' + trailing,
'./file-at-root.yaml:5:5: ' + hyphen,
- './ign-dup/file.yaml:3:3: ' + keydup,
'./ign-dup/file.yaml:4:17: ' + trailing,
'./ign-dup/file.yaml:5:5: ' + hyphen,
- './ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./ign-trail/file.yaml:3:3: ' + keydup,
@@ -761,3 +773,50 @@ class IgnoreConfigTestCase(unittest.TestCase):
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
)))
+
+ def test_run_with_ignore_with_broken_symlink(self):
+ wd = build_temp_workspace({
+ 'file-without-yaml-extension': '42\n',
+ 'link.yaml': 'symlink://file-without-yaml-extension',
+ 'link-404.yaml': 'symlink://file-that-does-not-exist',
+ })
+ backup_wd = os.getcwd()
+ os.chdir(wd)
+
+ with RunContext(self) as ctx:
+ cli.run(('-f', 'parsable', '.'))
+ self.assertNotEqual(ctx.returncode, 0)
+
+ with open(os.path.join(wd, '.yamllint'), 'w') as f:
+ f.write('extends: default\n'
+ 'ignore: |\n'
+ ' *404.yaml\n')
+ with RunContext(self) as ctx:
+ cli.run(('-f', 'parsable', '.'))
+ self.assertEqual(ctx.returncode, 0)
+ docstart = '[warning] missing document start "---" (document-start)'
+ out = '\n'.join(sorted(ctx.stdout.splitlines()))
+ self.assertEqual(out, '\n'.join((
+ './.yamllint:1:1: ' + docstart,
+ './link.yaml:1:1: ' + docstart,
+ )))
+
+ os.chdir(backup_wd)
+ shutil.rmtree(wd)
+
+ def test_run_with_ignore_on_ignored_file(self):
+ with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
+ f.write('ignore: file.dont-lint-me.yaml\n'
+ 'rules:\n'
+ ' trailing-spaces: enable\n'
+ ' key-duplicates:\n'
+ ' ignore: file-at-root.yaml\n')
+
+ sys.stdout = StringIO()
+ with self.assertRaises(SystemExit):
+ cli.run(('-f', 'parsable', 'file.dont-lint-me.yaml',
+ 'file-at-root.yaml'))
+ self.assertEqual(
+ sys.stdout.getvalue().strip(),
+ 'file-at-root.yaml:4:17: [error] trailing spaces (trailing-spaces)'
+ )
diff --git a/tests/test_linter.py b/tests/test_linter.py
index 9855120..ea509d8 100644
--- a/tests/test_linter.py
+++ b/tests/test_linter.py
@@ -16,8 +16,8 @@
import io
import unittest
-from yamllint.config import YamlLintConfig
from yamllint import linter
+from yamllint.config import YamlLintConfig
class LinterTestCase(unittest.TestCase):
diff --git a/tests/test_module.py b/tests/test_module.py
index 299e153..7f4f62b 100644
--- a/tests/test_module.py
+++ b/tests/test_module.py
@@ -16,11 +16,10 @@
import os
import shutil
import subprocess
-import tempfile
import sys
+import tempfile
import unittest
-
PYTHON = sys.executable or 'python'
diff --git a/tests/test_parser.py b/tests/test_parser.py
index dbeb36b..c2b598a 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -17,9 +17,14 @@ import unittest
import yaml
-from yamllint.parser import (line_generator, token_or_comment_generator,
- token_or_comment_or_line_generator,
- Line, Token, Comment)
+from yamllint.parser import (
+ Comment,
+ Line,
+ Token,
+ line_generator,
+ token_or_comment_generator,
+ token_or_comment_or_line_generator,
+)
class ParserTestCase(unittest.TestCase):
diff --git a/tests/test_spec_examples.py b/tests/test_spec_examples.py
index ac68e12..87a57f6 100644
--- a/tests/test_spec_examples.py
+++ b/tests/test_spec_examples.py
@@ -17,7 +17,6 @@ import os
from tests.common import RuleTestCase
-
# This file checks examples from YAML 1.2 specification [1] against yamllint.
#
# [1]: http://www.yaml.org/spec/1.2/spec.html
@@ -43,6 +42,7 @@ from tests.common import RuleTestCase
# encoding='utf-8') as g:
# g.write(text)
+
class SpecificationTestCase(RuleTestCase):
rule_id = None
diff --git a/yamllint/__init__.py b/yamllint/__init__.py
index 907328e..ff98de5 100644
--- a/yamllint/__init__.py
+++ b/yamllint/__init__.py
@@ -21,7 +21,7 @@ indentation, etc."""
APP_NAME = 'yamllint'
-APP_VERSION = '1.33.0'
+APP_VERSION = '1.35.1'
APP_DESCRIPTION = __doc__
__author__ = 'Adrien Vergé'
diff --git a/yamllint/__main__.py b/yamllint/__main__.py
index bc16534..529ac74 100644
--- a/yamllint/__main__.py
+++ b/yamllint/__main__.py
@@ -1,3 +1,18 @@
+# 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 yamllint.cli import run
if __name__ == '__main__':
diff --git a/yamllint/cli.py b/yamllint/cli.py
index 604e594..9a39bd8 100644
--- a/yamllint/cli.py
+++ b/yamllint/cli.py
@@ -19,8 +19,7 @@ import os
import platform
import sys
-from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION
-from yamllint import linter
+from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION, linter
from yamllint.config import YamlLintConfig, YamlLintConfigError
from yamllint.linter import PROBLEM_LEVELS
@@ -28,10 +27,11 @@ from yamllint.linter import PROBLEM_LEVELS
def find_files_recursively(items, conf):
for item in items:
if os.path.isdir(item):
- for root, dirnames, filenames in os.walk(item):
+ for root, _dirnames, filenames in os.walk(item):
for f in filenames:
filepath = os.path.join(root, f)
- if conf.is_yaml_file(filepath):
+ if (conf.is_yaml_file(filepath) and
+ not conf.is_file_ignored(filepath)):
yield filepath
else:
yield item
@@ -79,9 +79,9 @@ class Format:
@staticmethod
def github(problem, filename):
- line = f'::{problem.level} file={format(filename)},' \
- f'line={format(problem.line)},col={format(problem.column)}' \
- f'::{format(problem.line)}:{format(problem.column)} '
+ line = f'::{problem.level} file={filename},' \
+ f'line={problem.line},col={problem.column}' \
+ f'::{problem.line}:{problem.column} '
if problem.rule:
line += f'[{problem.rule}] '
line += problem.desc
diff --git a/yamllint/config.py b/yamllint/config.py
index 47a61a8..9ce6254 100644
--- a/yamllint/config.py
+++ b/yamllint/config.py
@@ -76,7 +76,7 @@ class YamlLintConfig:
try:
conf = yaml.safe_load(raw_content)
except Exception as e:
- raise YamlLintConfigError(f'invalid config: {e}')
+ raise YamlLintConfigError(f'invalid config: {e}') from e
if not isinstance(conf, dict):
raise YamlLintConfigError('invalid config: not a dict')
@@ -95,7 +95,7 @@ class YamlLintConfig:
try:
self.extend(base)
except Exception as e:
- raise YamlLintConfigError(f'invalid config: {e}')
+ raise YamlLintConfigError(f'invalid config: {e}') from e
if 'ignore' in conf and 'ignore-from-file' in conf:
raise YamlLintConfigError(
@@ -143,7 +143,7 @@ class YamlLintConfig:
try:
rule = yamllint.rules.get(id)
except Exception as e:
- raise YamlLintConfigError(f'invalid config: {e}')
+ raise YamlLintConfigError(f'invalid config: {e}') from e
self.rules[id] = validate_rule_conf(rule, self.rules[id])
@@ -153,8 +153,21 @@ def validate_rule_conf(rule, conf):
return False
if isinstance(conf, dict):
- if ('ignore' in conf and
- not isinstance(conf['ignore'], pathspec.pathspec.PathSpec)):
+ if ('ignore-from-file' in conf and not isinstance(
+ conf['ignore-from-file'], pathspec.pathspec.PathSpec)):
+ if isinstance(conf['ignore-from-file'], str):
+ conf['ignore-from-file'] = [conf['ignore-from-file']]
+ if not (isinstance(conf['ignore-from-file'], list)
+ and all(isinstance(line, str)
+ for line in conf['ignore-from-file'])):
+ raise YamlLintConfigError(
+ 'invalid config: ignore-from-file should contain '
+ 'valid filename(s), either as a list or string')
+ with fileinput.input(conf['ignore-from-file']) as f:
+ conf['ignore'] = pathspec.PathSpec.from_lines(
+ 'gitwildmatch', f)
+ elif ('ignore' in conf and not isinstance(
+ conf['ignore'], pathspec.pathspec.PathSpec)):
if isinstance(conf['ignore'], str):
conf['ignore'] = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
@@ -192,7 +205,7 @@ def validate_rule_conf(rule, conf):
# Example: CONF = {option: ['flag1', 'flag2', int]}
# → {option: [flag1]} → {option: [42, flag1, flag2]}
elif isinstance(options[optkey], list):
- if (type(conf[optkey]) is not list or
+ if (not isinstance(conf[optkey], list) or
any(flag not in options[optkey] and
type(flag) not in options[optkey]
for flag in conf[optkey])):
diff --git a/yamllint/linter.py b/yamllint/linter.py
index 0de1f71..a2faa06 100644
--- a/yamllint/linter.py
+++ b/yamllint/linter.py
@@ -13,14 +13,13 @@
# 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 re
import io
+import re
import yaml
from yamllint import parser
-
PROBLEM_LEVELS = {
0: None,
1: 'warning',
diff --git a/yamllint/rules/__init__.py b/yamllint/rules/__init__.py
index 606b37a..815d4bc 100644
--- a/yamllint/rules/__init__.py
+++ b/yamllint/rules/__init__.py
@@ -25,6 +25,7 @@ from yamllint.rules import (
document_start,
empty_lines,
empty_values,
+ float_values,
hyphens,
indentation,
key_duplicates,
@@ -33,7 +34,6 @@ from yamllint.rules import (
new_line_at_end_of_file,
new_lines,
octal_values,
- float_values,
quoted_strings,
trailing_spaces,
truthy,
diff --git a/yamllint/rules/anchors.py b/yamllint/rules/anchors.py
index 343f38b..d968461 100644
--- a/yamllint/rules/anchors.py
+++ b/yamllint/rules/anchors.py
@@ -108,7 +108,6 @@ import yaml
from yamllint.linter import LintProblem
-
ID = 'anchors'
TYPE = 'token'
CONF = {'forbid-undeclared-aliases': bool,
diff --git a/yamllint/rules/braces.py b/yamllint/rules/braces.py
index e77cda4..19f870a 100644
--- a/yamllint/rules/braces.py
+++ b/yamllint/rules/braces.py
@@ -137,7 +137,6 @@ import yaml
from yamllint.linter import LintProblem
from yamllint.rules.common import spaces_after, spaces_before
-
ID = 'braces'
TYPE = 'token'
CONF = {'forbid': (bool, 'non-empty'),
diff --git a/yamllint/rules/brackets.py b/yamllint/rules/brackets.py
index 47d2ad4..f1c08d5 100644
--- a/yamllint/rules/brackets.py
+++ b/yamllint/rules/brackets.py
@@ -138,7 +138,6 @@ import yaml
from yamllint.linter import LintProblem
from yamllint.rules.common import spaces_after, spaces_before
-
ID = 'brackets'
TYPE = 'token'
CONF = {'forbid': (bool, 'non-empty'),
diff --git a/yamllint/rules/colons.py b/yamllint/rules/colons.py
index 7390e51..2a181c6 100644
--- a/yamllint/rules/colons.py
+++ b/yamllint/rules/colons.py
@@ -82,7 +82,6 @@ import yaml
from yamllint.rules.common import is_explicit_key, spaces_after, spaces_before
-
ID = 'colons'
TYPE = 'token'
CONF = {'max-spaces-before': int,
diff --git a/yamllint/rules/commas.py b/yamllint/rules/commas.py
index e87c8f9..baa8b7d 100644
--- a/yamllint/rules/commas.py
+++ b/yamllint/rules/commas.py
@@ -106,7 +106,6 @@ import yaml
from yamllint.linter import LintProblem
from yamllint.rules.common import spaces_after, spaces_before
-
ID = 'commas'
TYPE = 'token'
CONF = {'max-spaces-before': int,
diff --git a/yamllint/rules/comments.py b/yamllint/rules/comments.py
index 1259dea..7e4f04c 100644
--- a/yamllint/rules/comments.py
+++ b/yamllint/rules/comments.py
@@ -75,7 +75,6 @@ Use this rule to control the position and formatting of comments.
from yamllint.linter import LintProblem
-
ID = 'comments'
TYPE = 'comment'
CONF = {'require-starting-space': bool,
diff --git a/yamllint/rules/comments_indentation.py b/yamllint/rules/comments_indentation.py
index 569abee..8bcda4d 100644
--- a/yamllint/rules/comments_indentation.py
+++ b/yamllint/rules/comments_indentation.py
@@ -79,7 +79,6 @@ import yaml
from yamllint.linter import LintProblem
from yamllint.rules.common import get_line_indent
-
ID = 'comments-indentation'
TYPE = 'comment'
diff --git a/yamllint/rules/document_end.py b/yamllint/rules/document_end.py
index 2337484..e1ce2a1 100644
--- a/yamllint/rules/document_end.py
+++ b/yamllint/rules/document_end.py
@@ -85,7 +85,6 @@ import yaml
from yamllint.linter import LintProblem
-
ID = 'document-end'
TYPE = 'token'
CONF = {'present': bool}
diff --git a/yamllint/rules/document_start.py b/yamllint/rules/document_start.py
index f1d6667..225d7c3 100644
--- a/yamllint/rules/document_start.py
+++ b/yamllint/rules/document_start.py
@@ -75,7 +75,6 @@ import yaml
from yamllint.linter import LintProblem
-
ID = 'document-start'
TYPE = 'token'
CONF = {'present': bool}
diff --git a/yamllint/rules/empty_lines.py b/yamllint/rules/empty_lines.py
index eca7812..7ccbedf 100644
--- a/yamllint/rules/empty_lines.py
+++ b/yamllint/rules/empty_lines.py
@@ -61,7 +61,6 @@ Use this rule to set a maximal number of allowed consecutive blank lines.
from yamllint.linter import LintProblem
-
ID = 'empty-lines'
TYPE = 'line'
CONF = {'max': int,
diff --git a/yamllint/rules/empty_values.py b/yamllint/rules/empty_values.py
index 6c8328b..c1ff4f2 100644
--- a/yamllint/rules/empty_values.py
+++ b/yamllint/rules/empty_values.py
@@ -105,7 +105,6 @@ import yaml
from yamllint.linter import LintProblem
-
ID = 'empty-values'
TYPE = 'token'
CONF = {'forbid-in-block-mappings': bool,
diff --git a/yamllint/rules/float_values.py b/yamllint/rules/float_values.py
index a5852c5..77a243b 100644
--- a/yamllint/rules/float_values.py
+++ b/yamllint/rules/float_values.py
@@ -90,7 +90,6 @@ import yaml
from yamllint.linter import LintProblem
-
ID = 'float-values'
TYPE = 'token'
CONF = {
diff --git a/yamllint/rules/hyphens.py b/yamllint/rules/hyphens.py
index 50e4d6d..54a96bf 100644
--- a/yamllint/rules/hyphens.py
+++ b/yamllint/rules/hyphens.py
@@ -79,7 +79,6 @@ import yaml
from yamllint.rules.common import spaces_after
-
ID = 'hyphens'
TYPE = 'token'
CONF = {'max-spaces-after': int}
diff --git a/yamllint/rules/indentation.py b/yamllint/rules/indentation.py
index d839d5a..bde6fa3 100644
--- a/yamllint/rules/indentation.py
+++ b/yamllint/rules/indentation.py
@@ -204,7 +204,6 @@ import yaml
from yamllint.linter import LintProblem
from yamllint.rules.common import get_real_end_line, is_explicit_key
-
ID = 'indentation'
TYPE = 'token'
CONF = {'spaces': (int, 'consistent'),
diff --git a/yamllint/rules/key_duplicates.py b/yamllint/rules/key_duplicates.py
index 771a8e2..638ac1e 100644
--- a/yamllint/rules/key_duplicates.py
+++ b/yamllint/rules/key_duplicates.py
@@ -16,6 +16,19 @@
"""
Use this rule to prevent multiple entries with the same key in mappings.
+.. rubric:: Options
+
+* Use ``forbid-duplicated-merge-keys`` to forbid the usage of
+ multiple merge keys ``<<``.
+
+.. rubric:: Default values (when enabled)
+
+.. code-block:: yaml
+
+ rules:
+ key-duplicates:
+ forbid-duplicated-merge-keys: false
+
.. rubric:: Examples
#. With ``key-duplicates: {}``
@@ -51,15 +64,39 @@ Use this rule to prevent multiple entries with the same key in mappings.
other
duplication
: 2
+
+#. With `key-duplicates`: {forbid-duplicated-merge-keys: true}``
+
+ the following code snippet would **PASS**:
+ ::
+
+ anchor_one: &anchor_one
+ one: one
+ anchor_two: &anchor_two
+ two: two
+ anchor_reference:
+ <<: [*anchor_one, *anchor_two]
+
+ the following code snippet would **FAIL**:
+ ::
+
+ anchor_one: &anchor_one
+ one: one
+ anchor_two: &anchor_two
+ two: two
+ anchor_reference:
+ <<: *anchor_one
+ <<: *anchor_two
"""
import yaml
from yamllint.linter import LintProblem
-
ID = 'key-duplicates'
TYPE = 'token'
+CONF = {'forbid-duplicated-merge-keys': bool}
+DEFAULT = {'forbid-duplicated-merge-keys': False}
MAP, SEQ = range(2)
@@ -92,7 +129,8 @@ def check(conf, token, prev, next, nextnext, context):
if len(context['stack']) > 0 and context['stack'][-1].type == MAP:
if (next.value in context['stack'][-1].keys and
# `<<` is "merge key", see http://yaml.org/type/merge.html
- next.value != '<<'):
+ (next.value != '<<' or
+ conf['forbid-duplicated-merge-keys'])):
yield LintProblem(
next.start_mark.line + 1, next.start_mark.column + 1,
f'duplication of key "{next.value}" in mapping')
diff --git a/yamllint/rules/key_ordering.py b/yamllint/rules/key_ordering.py
index 7fa9597..ec0716d 100644
--- a/yamllint/rules/key_ordering.py
+++ b/yamllint/rules/key_ordering.py
@@ -86,7 +86,6 @@ import yaml
from yamllint.linter import LintProblem
-
ID = 'key-ordering'
TYPE = 'token'
diff --git a/yamllint/rules/line_length.py b/yamllint/rules/line_length.py
index e7cc8bc..8214d74 100644
--- a/yamllint/rules/line_length.py
+++ b/yamllint/rules/line_length.py
@@ -101,7 +101,6 @@ import yaml
from yamllint.linter import LintProblem
-
ID = 'line-length'
TYPE = 'line'
CONF = {'max': int,
diff --git a/yamllint/rules/new_line_at_end_of_file.py b/yamllint/rules/new_line_at_end_of_file.py
index 302cfe6..f49edb9 100644
--- a/yamllint/rules/new_line_at_end_of_file.py
+++ b/yamllint/rules/new_line_at_end_of_file.py
@@ -25,7 +25,6 @@ this convention too.
from yamllint.linter import LintProblem
-
ID = 'new-line-at-end-of-file'
TYPE = 'line'
diff --git a/yamllint/rules/new_lines.py b/yamllint/rules/new_lines.py
index b3f018a..2ee5512 100644
--- a/yamllint/rules/new_lines.py
+++ b/yamllint/rules/new_lines.py
@@ -37,7 +37,6 @@ from os import linesep
from yamllint.linter import LintProblem
-
ID = 'new-lines'
TYPE = 'line'
CONF = {'type': ('unix', 'dos', 'platform')}
@@ -54,6 +53,6 @@ def check(conf, line):
if line.start == 0 and len(line.buffer) > line.end:
if line.buffer[line.end:line.end + len(newline_char)] != newline_char:
+ c = repr(newline_char).strip('\'')
yield LintProblem(1, line.end - line.start + 1,
- 'wrong new line character: expected {}'
- .format(repr(newline_char).strip('\'')))
+ f'wrong new line character: expected {c}')
diff --git a/yamllint/rules/octal_values.py b/yamllint/rules/octal_values.py
index eb24c81..e94e4bf 100644
--- a/yamllint/rules/octal_values.py
+++ b/yamllint/rules/octal_values.py
@@ -76,7 +76,6 @@ import yaml
from yamllint.linter import LintProblem
-
ID = 'octal-values'
TYPE = 'token'
CONF = {'forbid-implicit-octal': bool,
diff --git a/yamllint/rules/quoted_strings.py b/yamllint/rules/quoted_strings.py
index 9380ae5..b38f6ed 100644
--- a/yamllint/rules/quoted_strings.py
+++ b/yamllint/rules/quoted_strings.py
@@ -32,6 +32,9 @@ used.
even if ``required: only-when-needed`` is set.
* ``allow-quoted-quotes`` allows (``true``) using disallowed quotes for strings
with allowed quotes inside. Default ``false``.
+* ``check-keys`` defines whether to apply the rules to keys in mappings. By
+ default, ``quoted-strings`` rules apply only to values. Set this option to
+ ``true`` to apply the rules to keys as well.
**Note**: Multi-line strings (with ``|`` or ``>``) will not be checked.
@@ -46,6 +49,7 @@ used.
extra-required: []
extra-allowed: []
allow-quoted-quotes: false
+ check-keys: false
.. rubric:: Examples
@@ -135,6 +139,18 @@ used.
foo: 'bar"baz'
+#. With ``quoted-strings: {required: only-when-needed, check-keys: true,
+ extra-required: ["[:]"]}``
+
+ the following code snippet would **FAIL**:
+ ::
+
+ foo:bar: baz
+
+ the following code snippet would **PASS**:
+ ::
+
+ "foo:bar": baz
"""
import re
@@ -149,12 +165,14 @@ CONF = {'quote-type': ('any', 'single', 'double'),
'required': (True, False, 'only-when-needed'),
'extra-required': [str],
'extra-allowed': [str],
- 'allow-quoted-quotes': bool}
+ 'allow-quoted-quotes': bool,
+ 'check-keys': bool}
DEFAULT = {'quote-type': 'any',
'required': True,
'extra-required': [],
'extra-allowed': [],
- 'allow-quoted-quotes': False}
+ 'allow-quoted-quotes': False,
+ 'check-keys': False}
def VALIDATE(conf):
@@ -186,7 +204,11 @@ def _quote_match(quote_type, token_style):
(quote_type == 'double' and token_style == '"'))
-def _quotes_are_needed(string):
+def _quotes_are_needed(string, is_inside_a_flow):
+ # Quotes needed on strings containing flow tokens
+ if is_inside_a_flow and set(string) & {',', '[', ']', '{', '}'}:
+ return True
+
loader = yaml.BaseLoader('key: ' + string)
# Remove the 5 first tokens corresponding to 'key: ' (StreamStartToken,
# BlockMappingStartToken, KeyToken, ScalarToken(value=key), ValueToken)
@@ -194,12 +216,13 @@ def _quotes_are_needed(string):
loader.get_token()
try:
a, b = loader.get_token(), loader.get_token()
+ except yaml.scanner.ScannerError:
+ return True
+ else:
if (isinstance(a, yaml.ScalarToken) and a.style is None and
isinstance(b, yaml.BlockEndToken) and a.value == string):
return False
return True
- except yaml.scanner.ScannerError:
- return True
def _has_quoted_quotes(token):
@@ -209,11 +232,25 @@ def _has_quoted_quotes(token):
def check(conf, token, prev, next, nextnext, context):
+ if 'flow_nest_count' not in context:
+ context['flow_nest_count'] = 0
+
+ if isinstance(token, (yaml.FlowMappingStartToken,
+ yaml.FlowSequenceStartToken)):
+ context['flow_nest_count'] += 1
+ elif isinstance(token, (yaml.FlowMappingEndToken,
+ yaml.FlowSequenceEndToken)):
+ context['flow_nest_count'] -= 1
+
if not (isinstance(token, yaml.tokens.ScalarToken) and
isinstance(prev, (yaml.BlockEntryToken, yaml.FlowEntryToken,
yaml.FlowSequenceStartToken, yaml.TagToken,
- yaml.ValueToken))):
+ yaml.ValueToken, yaml.KeyToken))):
+
+ return
+ node = 'key' if isinstance(prev, yaml.KeyToken) else 'value'
+ if node == 'key' and not conf['check-keys']:
return
# Ignore explicit types, e.g. !!str testtest or !!int 42
@@ -240,7 +277,7 @@ def check(conf, token, prev, next, nextnext, context):
if (token.style is None or
not (_quote_match(quote_type, token.style) or
(conf['allow-quoted-quotes'] and _has_quoted_quotes(token)))):
- msg = f"string value is not quoted with {quote_type} quotes"
+ msg = f"string {node} is not quoted with {quote_type} quotes"
elif conf['required'] is False:
@@ -249,38 +286,39 @@ def check(conf, token, prev, next, nextnext, context):
not _quote_match(quote_type, token.style) and
not (conf['allow-quoted-quotes'] and
_has_quoted_quotes(token))):
- msg = f"string value is not quoted with {quote_type} quotes"
+ msg = f"string {node} is not quoted with {quote_type} quotes"
elif not token.style:
is_extra_required = any(re.search(r, token.value)
for r in conf['extra-required'])
if is_extra_required:
- msg = "string value is not quoted"
+ msg = f"string {node} is not quoted"
elif conf['required'] == 'only-when-needed':
# Quotes are not strictly needed here
if (token.style and tag == DEFAULT_SCALAR_TAG and token.value and
- not _quotes_are_needed(token.value)):
+ not _quotes_are_needed(token.value,
+ context['flow_nest_count'] > 0)):
is_extra_required = any(re.search(r, token.value)
for r in conf['extra-required'])
is_extra_allowed = any(re.search(r, token.value)
for r in conf['extra-allowed'])
if not (is_extra_required or is_extra_allowed):
- msg = f"string value is redundantly quoted with " \
+ msg = f"string {node} is redundantly quoted with " \
f"{quote_type} quotes"
# But when used need to match config
elif (token.style and
not _quote_match(quote_type, token.style) and
not (conf['allow-quoted-quotes'] and _has_quoted_quotes(token))):
- msg = f"string value is not quoted with {quote_type} quotes"
+ msg = f"string {node} is not quoted with {quote_type} quotes"
elif not token.style:
is_extra_required = len(conf['extra-required']) and any(
re.search(r, token.value) for r in conf['extra-required'])
if is_extra_required:
- msg = "string value is not quoted"
+ msg = f"string {node} is not quoted"
if msg is not None:
yield LintProblem(
diff --git a/yamllint/rules/trailing_spaces.py b/yamllint/rules/trailing_spaces.py
index 2295714..b5455f3 100644
--- a/yamllint/rules/trailing_spaces.py
+++ b/yamllint/rules/trailing_spaces.py
@@ -40,7 +40,6 @@ import string
from yamllint.linter import LintProblem
-
ID = 'trailing-spaces'
TYPE = 'line'
diff --git a/yamllint/rules/truthy.py b/yamllint/rules/truthy.py
index d19f6ea..ff47a83 100644
--- a/yamllint/rules/truthy.py
+++ b/yamllint/rules/truthy.py
@@ -21,6 +21,13 @@ This can be useful to prevent surprises from YAML parsers transforming
``[yes, FALSE, Off]`` into ``[true, false, false]`` or
``{y: 1, yes: 2, on: 3, true: 4, True: 5}`` into ``{y: 1, true: 5}``.
+Depending on the YAML specification version used by the YAML document, the list
+of truthy values can differ. In YAML 1.2, only capitalized / uppercased
+combinations of ``true`` and ``false`` are considered truthy, whereas in YAML
+1.1 combinations of ``yes``, ``no``, ``on`` and ``off`` are too. To make the
+YAML specification version explicit in a YAML document, a ``%YAML 1.2``
+directive can be used (see example below).
+
.. rubric:: Options
* ``allowed-values`` defines the list of truthy values which will be ignored
@@ -80,10 +87,21 @@ This can be useful to prevent surprises from YAML parsers transforming
the following code snippet would **FAIL**:
::
+ %YAML 1.1
+ ---
yes: 1
on: 2
True: 3
+ the following code snippet would **PASS**:
+ ::
+
+ %YAML 1.2
+ ---
+ yes: 1
+ on: 2
+ true: 3
+
#. With ``truthy: {allowed-values: ["yes", "no"]}``
the following code snippet would **PASS**:
@@ -125,22 +143,35 @@ import yaml
from yamllint.linter import LintProblem
-
-TRUTHY = ['YES', 'Yes', 'yes',
- 'NO', 'No', 'no',
- 'TRUE', 'True', 'true',
- 'FALSE', 'False', 'false',
- 'ON', 'On', 'on',
- 'OFF', 'Off', 'off']
+TRUTHY_1_1 = ['YES', 'Yes', 'yes',
+ 'NO', 'No', 'no',
+ 'TRUE', 'True', 'true',
+ 'FALSE', 'False', 'false',
+ 'ON', 'On', 'on',
+ 'OFF', 'Off', 'off']
+TRUTHY_1_2 = ['TRUE', 'True', 'true',
+ 'FALSE', 'False', 'false']
ID = 'truthy'
TYPE = 'token'
-CONF = {'allowed-values': TRUTHY.copy(), 'check-keys': bool}
+CONF = {'allowed-values': TRUTHY_1_1.copy(), 'check-keys': bool}
DEFAULT = {'allowed-values': ['true', 'false'], 'check-keys': True}
+def yaml_spec_version_for_document(context):
+ if 'yaml_spec_version' in context:
+ return context['yaml_spec_version']
+ return (1, 1)
+
+
def check(conf, token, prev, next, nextnext, context):
+ if isinstance(token, yaml.tokens.DirectiveToken) and token.name == 'YAML':
+ context['yaml_spec_version'] = token.value
+ elif isinstance(token, yaml.tokens.DocumentEndToken):
+ context.pop('yaml_spec_version', None)
+ context.pop('bad_truthy_values', None)
+
if prev and isinstance(prev, yaml.tokens.TagToken):
return
@@ -148,9 +179,14 @@ def check(conf, token, prev, next, nextnext, context):
isinstance(token, yaml.tokens.ScalarToken)):
return
- if isinstance(token, yaml.tokens.ScalarToken):
- if (token.value in (set(TRUTHY) - set(conf['allowed-values'])) and
- token.style is None):
+ if isinstance(token, yaml.tokens.ScalarToken) and token.style is None:
+ if 'bad_truthy_values' not in context:
+ context['bad_truthy_values'] = set(
+ TRUTHY_1_2 if yaml_spec_version_for_document(context) == (1, 2)
+ else TRUTHY_1_1)
+ context['bad_truthy_values'] -= set(conf['allowed-values'])
+
+ if token.value in context['bad_truthy_values']:
yield LintProblem(token.start_mark.line + 1,
token.start_mark.column + 1,
"truthy value should be one of [" +