Compare commits

..

29 Commits

Author SHA1 Message Date
Adrien Vergé
9d0f59876d yamllint version 1.30.0 2023-03-22 10:38:34 +01:00
Adrien Vergé
ebd6b90d3e anchors: Add new rule to detect undeclared or duplicated anchors
According to the YAML specification [^1]:

- > It is an error for an alias node to use an anchor that does not
  > previously occur in the document.

  The `forbid-undeclared-aliases` option checks that aliases do have a
  matching anchor declared previously in the document. Since this is
  required by the YAML spec, this option is enabled by default.

- > The alias refers to the most recent preceding node having the same
  > anchor.

  This means that having a same anchor repeated in a document is
  allowed. However users could want to avoid this, so the new option
  `forbid-duplicated-anchors` allows that. It's disabled by default.

- > It is not an error to specify an anchor that is not used by any
  > alias node.

  This means that it's OK to declare anchors but don't have any alias
  referencing them. However users could want to avoid this, so a new
  option (e.g. `forbid-unused-anchors`) could be implemented in the
  future. See https://github.com/adrienverge/yamllint/pull/537.

Fixes #395
Closes #420

[^1]: https://yaml.org/spec/1.2.2/#71-alias-nodes
2023-03-22 10:34:35 +01:00
Andrew Imeson
8aaa226830 docs: Update pre-commit hook example
Update syntax of pre-commit hook docs to work with newer pre-commit versions.

Closes #551, #553
2023-03-22 10:31:20 +01:00
Adrien Vergé
15f8204427 linter: Prevent testing is_file_ignored() with filepath == None
As reported in https://github.com/adrienverge/yamllint/pull/548, there
might be a problem with	pathspec 0.11.1 which does't allow calling
`match_file()` with argument `None` anymore.

The `linter.run()` function shouldn't call
`YamlLintConfig.is_file_ignored(None)` anyway.
2023-03-19 11:06:52 +01:00
Andrew Imeson
404656394c docs: Explicitly specify language even when it's plain text
rstcheck succeeds with a failure (heh) when there's a code block without
a language specified. This can lead to false negatives as the file is no
longer being checked by rstcheck.

Error:

    An `AttributeError` error occured. This is most propably due to a
    code block directive (code/code-block/sourcecode) without a
    specified language. This may result in a false negative for source:
    'docs/disable_with_comments.rst'. See
    https://rstcheck-core.readthedocs.io/en/latest/faq/#code-blocks-without-language-sphinx
    for more information.  Success! No issues detected.
2023-03-02 18:03:00 +01:00
Okue
06db2af9b0 docs: Fix misleading Python API example
`yamllint.linter.run("example.yaml", yaml_config)` example seems
`yamllint.linter.run` opens a given file.
It's misleading.
2023-01-30 18:04:51 +01:00
Adrien Vergé
b9e1fd18c1 yamllint version 1.29.0 2023-01-10 18:57:21 +01:00
Peter Leitzen
fa0bb03f9a cli: Add --list-files command line option
This option lists the files to lint by yamllint, taking into account `ignore`
and `yaml-files` configuration options.
2023-01-10 18:48:38 +01:00
Matthew Gamble
2a904f8fc1 configuration: Allow using a list of strings in ignore configuration
This may feel more natural for some users, rather than embedding
multiple entries in a multi-line string.
2023-01-10 18:45:09 +01:00
Ville Skyttä
6194a282fc docs: Spelling and grammar fixes 2022-12-12 19:08:31 +01:00
Dimitri Papadopoulos
5ac3ed4490 Apply some pyupgrade suggestions
io.open() is an alias for for the builtin open() function:
	https://docs.python.org/3/library/io.html#io.open

If open() cannot open a file, an OSError is raised:
	https://docs.python.org/3/library/functions.html#open

EnvironmentError is kept for compatibility with previous versions;
starting from Python 3.3, it is an alias of OSError:
	https://docs.python.org/3/library/exceptions.html#EnvironmentError

Directly yield from an iterable instead of iterating to yield items.
2022-10-28 07:48:54 +02:00
Dimitri Papadopoulos
5b21a3d9ea Remove Unicode marker before strings
All strings are Unicode in Python 3. No need for u'€', just use '€'.
2022-10-28 07:46:33 +02:00
Dimitri Papadopoulos
5fbf44c203 docs: Fix typos 2022-10-28 07:42:28 +02:00
Michael Käufl
c9c5e0b1c7 CI: Add support for Python 3.11 2022-10-26 19:27:06 +02:00
Adrien Vergé
a6e0e1213a CI: Fix pip install problem with Pygments version
Recently `python setup.py build_sphinx` started failing with:

    pkg_resources.VersionConflict: (Pygments 2.3.1
    (/usr/lib/python3/dist-packages), Requirement.parse('Pygments>=2.12'))

The reason is that `doc8` 1.0.0 installs `Pygments` 2.3.1, then `Sphinx`
5.3.0 needs `Pygments` ≥ 2.12.

The easiest fix is to change the install order.
2022-10-26 16:37:45 +02:00
Adrien Vergé
eb7b7ca627 docs: Fix Sphinx error on non-YAML code snippet
This problem was just introduced by commit cec4f33 "Clarify disable-line
and parser errors, workaround" and produced this error when building
documentation:

    docs/disable_with_comments.rst:120:Could not lex literal_block as
    "yaml". Highlighting skipped.
2022-10-26 16:37:45 +02:00
Andrew Imeson
cec4f3383a cocs: Clarify disable-line and parser errors, workaround
Lots of user confusion expecting `disable-line` to work around parser
errors caused by templating syntax.

Relates to #61, #65, #128, #311, #460, #462
2022-10-24 14:44:05 +02:00
Andrew Imeson
52234b7a46 docs: remove erroneous example text in disable-file 2022-10-24 14:44:05 +02:00
Adrien Vergé
151b1c4086 style: Fix indentation test file missing whitespace
Commit 764586d "indentation: Fix indent-sequences in nested collections"
introduced 2 style-related problems:

    tests/rules/test_indentation.py:1394:45: E231 missing whitespace after ','
    tests/rules/test_indentation.py:1402:45: E231 missing whitespace after ','

Let's fix them.
2022-10-24 14:37:48 +02:00
Brian Brookman
764586d836 indentation: Fix indent-sequences in nested collections
When {spaces: consistent, indent-sequences: true} (the defaults),
and the expected indent for a block sequence that should be indented
is unknown, set the expected indent to an unknown value (-1) rather
than an arbitrary one (2). This ensures wrong-indentation is properly
caught when block sequences are nested.

Fixes issue #485
2022-10-18 18:04:49 +02:00
Dimitri Papadopoulos
47cd8f2e9e No need to inherit from object in Python 3 2022-10-18 17:52:06 +02:00
Dimitri Papadopoulos
4d271f3daf ci: Security hardening for GitHub Actions
https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs

The idea is that the software supply chain relies on 3rd party actions
that could be compromised. Mitigate this risk by giving these actions
minimal rights to the repository. Here read-only access is good enough.
2022-10-14 17:30:28 +02:00
Dimitri Papadopoulos
22ddf4c8e5 linter: Use proper Python 3 I/O type for reading
Co-authored-by: Adrien Vergé <adrienverge@gmail.com>
2022-10-14 17:29:48 +02:00
Adrien Vergé
b8c85f0dfd build: Stop releasing universal wheels
This reverts commit 3c525ab "Release as a universal wheel".

Python 2 support was definitely dropped in early 2021 in commit a3fc64d,
since then it's no longer useful to build universal wheels.

According to the `wheel` documentation:
> If your project contains no C extensions and is expected to work on
> both Python 2 and 3, you will want to tell wheel to produce universal
> wheels

Partly fixes https://github.com/adrienverge/yamllint/issues/501
2022-10-11 11:38:01 +02:00
Dimitri Papadopoulos
e0f749bf5d comments-indentation: Refactor to use 'max' builtin
It is unnecessary to use an `if` statement to check the maximum of two
values and then assign the value to a name. You can use the max
built-in do do this. It is straightforward and more readable.
2022-10-07 17:01:51 +02:00
Dimitri Papadopoulos
19d00809d1 quoted-strings: Merge comparisons with in
To check if a variable is equal to one of many values, combine the
values into a tuple and check if the variable is contained in it
instead of checking for equality against each of the values. This
is faster, less verbose, and more readable.
2022-10-07 17:01:51 +02:00
Adrien Vergé
008db4aa09 float-values: Fix bug on strings containing fordidden values
The rule correctly reports number values like `.1`, `1e2`, `.NaN` and
`.Inf`, but it also reported false positives on strings like `.1two3`,
`1e2a`, `.NaNa` and `.Infinit∞`.

The regexps need to end with an end delimiter (`$`) otherwise longer
strings can be matched too.

Fixes https://github.com/adrienverge/yamllint/issues/495
2022-10-04 08:59:51 +02:00
Dimitri Papadopoulos Orfanos
e8391de711 Drop support for Python 3.6
Python 3.6 reached end-of-life on 2021-12-23.
https://peps.python.org/pep-0494/
2022-09-13 07:59:17 +02:00
Dimitri Papadopoulos
a5adec1570 ci: Update GitHub Actions
https://github.com/actions/checkout
https://github.com/actions/setup-python
2022-09-13 07:58:03 +02:00
30 changed files with 667 additions and 83 deletions

View File

@@ -8,18 +8,21 @@ on: # yamllint disable-line rule:truthy
branches:
- master
permissions:
contents: read
jobs:
lint:
name: Linters
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
- run:
python -m pip install flake8 flake8-import-order doc8 sphinx
rstcheck[sphinx]
python -m pip install flake8 flake8-import-order sphinx
rstcheck[sphinx] doc8
- run: python -m pip install .
- run: flake8 .
- run: doc8 $(git ls-files '*.rst')
@@ -34,16 +37,16 @@ jobs:
fail-fast: false
matrix:
python-version:
- '3.6'
- '3.7'
- '3.8'
- '3.9'
- '3.10'
- '3.11'
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Append GitHub Actions system path

View File

@@ -1,6 +1,28 @@
Changelog
=========
1.30.0 (2023-03-22)
-------------------
- Rule ``anchors``: add new rule to detect undeclared or duplicated anchors
- Python API: prevent using ``is_file_ignored()`` with null ``filepath``
- Docs: fix misleading Python API example
- Docs: fix plain text code snippet example
- Docs: update pre-commit hook example
1.29.0 (2023-01-10)
-------------------
- Add support for Python 3.11, drop support for Python 3.6
- Rule ``float-values``: fix bug on strings containing fordidden values
- Stop releasing universal wheels
- Use proper Python 3 I/O type for file reading
- Rule ``indentation``: fix ``indent-sequences`` in nested collections
- Docs: clarify ``disable-line`` and parser errors, give a workaround
- Refactors to apply some pyupgrade suggestions
- Allow using a list of strings in ``ignore`` configuration
- Add ``--list-files`` command line option
1.28.0 (2022-09-12)
-------------------

View File

@@ -38,7 +38,7 @@ htmlhelp_basename = 'yamllintdoc'
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'yamllint', 'Linter for YAML files', [u'Adrien Vergé'], 1)
('index', 'yamllint', 'Linter for YAML files', ['Adrien Vergé'], 1)
]
# -- Build with sphinx automodule without needing to install third-party libs

View File

@@ -136,11 +136,19 @@ directories, set ``yaml-files`` configuration option. The default is:
The same rules as for ignoring paths apply (``.gitignore``-style path pattern,
see below).
If you need to know the exact list of files that yamllint would process,
without really linting them, you can use ``--list-files``:
.. code:: bash
yamllint --list-files .
Ignoring paths
--------------
It is possible to exclude specific files or directories, so that the linter
doesn't process them.
doesn't process them. They can be provided either as a list of paths, or as a
bulk string.
You can either totally ignore files (they won't be looked at):
@@ -153,6 +161,13 @@ You can either totally ignore files (they won't be looked at):
all/this/directory/
*.template.yaml
# or:
ignore:
- /this/specific/file.yaml
- all/this/directory/
- '*.template.yaml'
or ignore paths only for specific rules:
.. code-block:: yaml
@@ -165,6 +180,14 @@ or ignore paths only for specific rules:
/this-file-has-trailing-spaces-but-it-is-OK.yaml
/generated/*.yaml
# or:
rules:
trailing-spaces:
ignore:
- /this-file-has-trailing-spaces-but-it-is-OK.yaml
- /generated/*.yaml
Note that this ``.gitignore``-style path pattern allows complex path
exclusion/inclusion, see the `pathspec README file
<https://pypi.python.org/pypi/pathspec>`_ for more details.
@@ -204,6 +227,13 @@ or:
.. note:: However, this is mutually exclusive with the ``ignore`` key.
If you need to know the exact list of files that yamllint would process,
without really linting them, you can use ``--list-files``:
.. code:: bash
yamllint --list-files .
Setting the locale
------------------

View File

@@ -11,7 +11,7 @@ Basic example of running the linter from Python:
import yamllint
yaml_config = yamllint.config.YamlLintConfig("extends: default")
for p in yamllint.linter.run("example.yaml", yaml_config):
for p in yamllint.linter.run(open("example.yaml", "r"), yaml_config):
print(p.desc, p.line, p.rule)
.. automodule:: yamllint.linter

View File

@@ -40,6 +40,11 @@ specific line:
# yamllint disable-line
- { all : rules ,are disabled for this line}
You can't make yamllint ignore invalid YAML syntax on a line (which generates a
`syntax error`), such as when templating a YAML file with Jinja. In some cases,
you can workaround this by putting the templating syntax in a YAML comment. See
`Putting template flow control in comments`_.
If you need to disable multiple rules, it is allowed to chain rules like this:
``# yamllint disable-line rule:hyphens rule:commas rule:indentation``.
@@ -89,7 +94,6 @@ For instance:
key: value 2
- This line is waaaaaaaaaay too long but yamllint will not report anything about it.
This line will be checked by yamllint.
or:
@@ -101,3 +105,32 @@ or:
key1: value1
{% endif %}
key2: value2
Putting template flow control in comments
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Alternatively for templating you can wrap the template statements in comments
to make it a valid YAML file. As long as the templating language doesn't use
the same comment symbol, it should be a valid template and valid YAML (pre and
post-template processing).
Example of a Jinja2 code that cannot be parsed as YAML because it contains
invalid tokens ``{%`` and ``%}``:
.. code-block:: text
# This file IS NOT valid YAML and will produce syntax errors
{% if extra_info %}
key1: value1
{% endif %}
key2: value2
But it can be fixed using YAML comments:
.. code-block:: yaml
# This file IS valid YAML because the Jinja is in a YAML comment
# {% if extra_info %}
key1: value1
# {% endif %}
key2: value2

View File

@@ -4,7 +4,7 @@ Integration with other software
Integration with pre-commit
---------------------------
You can integrate yamllint in `pre-commit <http://pre-commit.com/>`_ tool.
You can integrate yamllint in the `pre-commit <https://pre-commit.com/>`_ tool.
Here is an example, to add in your .pre-commit-config.yaml
.. code:: yaml
@@ -12,11 +12,13 @@ Here is an example, to add in your .pre-commit-config.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.
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.17.0
hooks:
- id: yamllint
args: [-c=/path/to/.yamllint]
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
-------------------------------
@@ -26,7 +28,7 @@ Actions <https://github.com/features/actions>`_ and automatically uses the
suited output format to decorate code with linting errors. You can also force
the GitHub Actions output with ``yamllint --format github``.
An minimal example workflow using GitHub Actions:
A minimal example workflow using GitHub Actions:
.. code:: yaml

View File

@@ -14,6 +14,11 @@ This page describes the rules and their options.
:local:
:depth: 1
anchors
-------
.. automodule:: yamllint.rules.anchors
braces
------

View File

@@ -1,6 +1,3 @@
[bdist_wheel]
universal = 1
[flake8]
import-order-style = pep8
application-import-names = yamllint
@@ -27,11 +24,11 @@ classifiers =
Intended Audience :: Developers
License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Programming Language :: Python :: 3
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Topic :: Software Development
Topic :: Software Development :: Debuggers
Topic :: Software Development :: Quality Assurance
@@ -46,7 +43,7 @@ project_urls =
[options]
packages = find:
python_requires = >=3.6
python_requires = >=3.7
include_package_data = True
install_requires =

209
tests/rules/test_anchors.py Normal file
View File

@@ -0,0 +1,209 @@
# 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')
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,
problem1=(9, 3),
problem2=(10, 3),
problem3=(11, 3),
problem4=(12, 3),
problem5=(13, 3),
problem6=(24, 7),
problem7=(27, 36))
def test_forbid_duplicated_anchors(self):
conf = ('anchors:\n'
' forbid-undeclared-aliases: false\n'
' forbid-duplicated-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'
'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=(21, 18),
problem4=(27, 20))

View File

@@ -40,6 +40,8 @@ class FloatValuesTestCase(RuleTestCase):
'- 0.0\n'
'- .1\n'
'- \'.1\'\n'
'- string.1\n'
'- .1string\n'
'- !custom_tag .2\n'
'- &angle1 0.0\n'
'- *angle1\n'
@@ -47,7 +49,7 @@ class FloatValuesTestCase(RuleTestCase):
'- *angle2\n',
conf,
problem1=(3, 3),
problem2=(8, 11))
problem2=(10, 11))
def test_scientific_notation(self):
conf = (
@@ -61,6 +63,8 @@ class FloatValuesTestCase(RuleTestCase):
'- 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'
@@ -71,8 +75,8 @@ class FloatValuesTestCase(RuleTestCase):
conf,
problem1=(2, 3),
problem2=(3, 3),
problem3=(9, 11),
problem4=(11, 11))
problem3=(11, 11),
problem4=(13, 11))
def test_nan(self):
conf = (
@@ -85,13 +89,15 @@ class FloatValuesTestCase(RuleTestCase):
'- .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=(6, 10))
problem3=(8, 10))
def test_inf(self):
conf = (
@@ -106,6 +112,8 @@ class FloatValuesTestCase(RuleTestCase):
'- -.inf\n'
'- -.INF\n'
'- \'.inf\'\n'
'- ∞.infinity\n'
'- .infinity∞\n'
'- !custom_tag .inf\n'
'- &angle .inf\n'
'- *angle\n'
@@ -116,5 +124,5 @@ class FloatValuesTestCase(RuleTestCase):
problem2=(3, 3),
problem3=(4, 3),
problem4=(5, 3),
problem5=(8, 10),
problem6=(10, 10))
problem5=(10, 10),
problem6=(12, 10))

View File

@@ -1370,6 +1370,45 @@ class IndentationTestCase(RuleTestCase):
' 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'

View File

@@ -92,12 +92,12 @@ class CommandLineTestCase(unittest.TestCase):
'no-yaml.json': '---\n'
'key: value\n',
# non-ASCII chars
u'non-ascii/éçäγλνπ¥/utf-8': (
u'---\n'
u'- hétérogénéité\n'
u'# 19.99 €\n'
u'- お早う御座います。\n'
u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'),
'non-ascii/éçäγλνπ¥/utf-8': (
'---\n'
'- hétérogénéité\n'
'# 19.99 €\n'
'- お早う御座います。\n'
'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'),
# dos line endings yaml
'dos.yml': '---\r\n'
'dos: true',
@@ -678,6 +678,39 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_list_files(self):
with RunContext(self) as ctx:
cli.run(('--list-files', self.wd))
self.assertEqual(ctx.returncode, 0)
self.assertEqual(
sorted(ctx.stdout.splitlines()),
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml'),
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/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
config = '{ignore: "*.yml", yaml-files: ["*.*"]}'
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'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'en.yaml'),
os.path.join(self.wd, 'no-yaml.json'),
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, 'warn.yaml')]
)
class CommandLineConfigTestCase(unittest.TestCase):
def test_config_file(self):

View File

@@ -369,7 +369,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(c.rules['colons']['max-spaces-before'], 0)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
def test_extended_ignore(self):
def test_extended_ignore_str(self):
with tempfile.NamedTemporaryFile('w') as f:
f.write('ignore: |\n'
' *.template.yaml\n')
@@ -379,6 +379,16 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(c.ignore.match_file('test.template.yaml'), True)
self.assertEqual(c.ignore.match_file('test.yaml'), False)
def test_extended_ignore_list(self):
with tempfile.NamedTemporaryFile('w') as f:
f.write('ignore:\n'
' - "*.template.yaml"\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n')
self.assertEqual(c.ignore.match_file('test.template.yaml'), True)
self.assertEqual(c.ignore.match_file('test.yaml'), False)
class ExtendedLibraryConfigTestCase(unittest.TestCase):
def test_extend_config_disable_rule(self):
@@ -539,7 +549,7 @@ class IgnoreConfigTestCase(unittest.TestCase):
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
)))
def test_run_with_ignore(self):
def test_run_with_ignore_str(self):
with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
f.write('extends: default\n'
'ignore: |\n'
@@ -593,6 +603,60 @@ class IgnoreConfigTestCase(unittest.TestCase):
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
)))
def test_run_with_ignore_list(self):
with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
f.write('extends: default\n'
'ignore:\n'
' - "*.dont-lint-me.yaml"\n'
' - "/bin/"\n'
' - "!/bin/*.lint-me-anyway.yaml"\n'
'rules:\n'
' key-duplicates:\n'
' ignore:\n'
' - "/ign-dup"\n'
' trailing-spaces:\n'
' ignore:\n'
' - "ign-trail"\n'
' - "!*.lint-me-anyway.yaml"\n')
sys.stdout = StringIO()
with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.'))
out = sys.stdout.getvalue()
out = '\n'.join(sorted(out.splitlines()))
docstart = '[warning] missing document start "---" (document-start)'
keydup = '[error] duplication of key "key" in mapping (key-duplicates)'
trailing = '[error] trailing spaces (trailing-spaces)'
hyphen = '[error] too many spaces after hyphen (hyphens)'
self.assertEqual(out, '\n'.join((
'./.yamllint:1:1: ' + docstart,
'./bin/file.lint-me-anyway.yaml:3:3: ' + keydup,
'./bin/file.lint-me-anyway.yaml:4:17: ' + trailing,
'./bin/file.lint-me-anyway.yaml:5:5: ' + hyphen,
'./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:4:17: ' + trailing,
'./ign-dup/file.yaml:5:5: ' + hyphen,
'./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,
'./ign-trail/file.yaml:5:5: ' + hyphen,
'./include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup,
'./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_from_file(self):
with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
f.write('extends: default\n'

View File

@@ -31,10 +31,10 @@ class LinterTestCase(unittest.TestCase):
linter.run(b'test: document', self.fake_config())
def test_run_on_unicode(self):
linter.run(u'test: document', self.fake_config())
linter.run('test: document', self.fake_config())
def test_run_on_stream(self):
linter.run(io.StringIO(u'hello'), self.fake_config())
linter.run(io.StringIO('hello'), self.fake_config())
def test_run_on_int(self):
self.assertRaises(TypeError, linter.run, 42, self.fake_config())
@@ -44,14 +44,14 @@ class LinterTestCase(unittest.TestCase):
['h', 'e', 'l', 'l', 'o'], self.fake_config())
def test_run_on_non_ascii_chars(self):
s = (u'- hétérogénéité\n'
u'# 19.99 €\n')
s = ('- hétérogénéité\n'
'# 19.99 €\n')
linter.run(s, self.fake_config())
linter.run(s.encode('utf-8'), self.fake_config())
linter.run(s.encode('iso-8859-15'), self.fake_config())
s = (u'- お早う御座います。\n'
u'# الأَبْجَدِيَّة العَرَبِيَّة\n')
s = ('- お早う御座います。\n'
'# الأَبْجَدِيَّة العَرَبِيَّة\n')
linter.run(s, self.fake_config())
linter.run(s.encode('utf-8'), self.fake_config())

View File

@@ -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 open
import os
from tests.common import RuleTestCase

View File

@@ -21,10 +21,10 @@ indentation, etc."""
APP_NAME = 'yamllint'
APP_VERSION = '1.28.0'
APP_VERSION = '1.30.0'
APP_DESCRIPTION = __doc__
__author__ = u'Adrien Vergé'
__copyright__ = u'Copyright 2022, Adrien Vergé'
__author__ = 'Adrien Vergé'
__copyright__ = 'Copyright 2022, Adrien Vergé'
__license__ = 'GPLv3'
__version__ = APP_VERSION

View File

@@ -14,7 +14,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import argparse
import io
import locale
import os
import platform
@@ -47,7 +46,7 @@ def supports_color():
hasattr(sys.stdout, 'isatty') and sys.stdout.isatty())
class Format(object):
class Format:
@staticmethod
def parsable(problem, filename):
return ('%(file)s:%(line)s:%(column)s: [%(level)s] %(message)s' %
@@ -158,6 +157,8 @@ def run(argv=None):
config_group.add_argument('-d', '--config-data', dest='config_data',
action='store',
help='custom configuration (as YAML source)')
parser.add_argument('--list-files', action='store_true', dest='list_files',
help='list files to lint and exit')
parser.add_argument('-f', '--format',
choices=('parsable', 'standard', 'colored', 'github',
'auto'),
@@ -208,14 +209,20 @@ def run(argv=None):
if conf.locale is not None:
locale.setlocale(locale.LC_ALL, conf.locale)
if args.list_files:
for file in find_files_recursively(args.files, conf):
if not conf.is_file_ignored(file):
print(file)
sys.exit(0)
max_level = 0
for file in find_files_recursively(args.files, conf):
filepath = file[2:] if file.startswith('./') else file
try:
with io.open(file, newline='') as f:
with open(file, newline='') as f:
problems = linter.run(f, conf, filepath)
except EnvironmentError as e:
except OSError as e:
print(e, file=sys.stderr)
sys.exit(-1)
prob_level = show_problems(problems, file, args_format=args.format,
@@ -226,7 +233,7 @@ def run(argv=None):
if args.stdin:
try:
problems = linter.run(sys.stdin, conf, '')
except EnvironmentError as e:
except OSError as e:
print(e, file=sys.stderr)
sys.exit(-1)
prob_level = show_problems(problems, 'stdin', args_format=args.format,

View File

@@ -6,6 +6,7 @@ yaml-files:
- '.yamllint'
rules:
anchors: enable
braces: enable
brackets: enable
colons: enable

View File

@@ -112,11 +112,16 @@ class YamlLintConfig:
with fileinput.input(conf['ignore-from-file']) as f:
self.ignore = pathspec.PathSpec.from_lines('gitwildmatch', f)
elif 'ignore' in conf:
if not isinstance(conf['ignore'], str):
if isinstance(conf['ignore'], str):
self.ignore = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
elif (isinstance(conf['ignore'], list) and
all(isinstance(line, str) for line in conf['ignore'])):
self.ignore = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'])
else:
raise YamlLintConfigError(
'invalid config: ignore should contain file patterns')
self.ignore = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
if 'yaml-files' in conf:
if not (isinstance(conf['yaml-files'], list)
@@ -150,11 +155,16 @@ def validate_rule_conf(rule, conf):
if isinstance(conf, dict):
if ('ignore' in conf and
not isinstance(conf['ignore'], pathspec.pathspec.PathSpec)):
if not isinstance(conf['ignore'], str):
if isinstance(conf['ignore'], str):
conf['ignore'] = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
elif (isinstance(conf['ignore'], list) and
all(isinstance(line, str) for line in conf['ignore'])):
conf['ignore'] = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'])
else:
raise YamlLintConfigError(
'invalid config: ignore should contain file patterns')
conf['ignore'] = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
if 'level' not in conf:
conf['level'] = 'error'

View File

@@ -14,6 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
import io
import yaml
@@ -33,7 +34,7 @@ DISABLE_RULE_PATTERN = re.compile(r'^# yamllint disable( rule:\S+)*\s*$')
ENABLE_RULE_PATTERN = re.compile(r'^# yamllint enable( rule:\S+)*\s*$')
class LintProblem(object):
class LintProblem:
"""Represents a linting problem found by yamllint."""
def __init__(self, line, column, desc='<no description>', rule=None):
#: Line on which the problem was found (starting at 1)
@@ -121,7 +122,7 @@ def get_cosmetic_problems(buffer, conf, filepath):
if id in self.all_rules:
self.rules.add(id)
# Use a cache to store problems and flush it only when a end of line is
# Use a cache to store problems and flush it only when an end of line is
# found. This allows the use of yamllint directive to disable some rules on
# some lines.
cache = []
@@ -222,12 +223,12 @@ def run(input, conf, filepath=None):
:param input: buffer, string or stream to read from
:param conf: yamllint configuration object
"""
if conf.is_file_ignored(filepath):
if filepath is not None and conf.is_file_ignored(filepath):
return ()
if isinstance(input, (bytes, str)):
return _run(input, conf, filepath)
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase
elif isinstance(input, io.IOBase):
# We need to have everything in memory to parse correctly
content = input.read()
return _run(content, conf, filepath)

View File

@@ -132,8 +132,7 @@ def token_or_comment_generator(buffer):
yield Token(curr.start_mark.line + 1, curr, prev, next, nextnext)
for comment in comments_between_tokens(curr, next):
yield comment
yield from comments_between_tokens(curr, next)
prev = curr
curr = next

View File

@@ -14,6 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from yamllint.rules import (
anchors,
braces,
brackets,
colons,
@@ -39,6 +40,7 @@ from yamllint.rules import (
)
_RULES = {
anchors.ID: anchors,
braces.ID: braces,
brackets.ID: brackets,
colons.ID: colons,

118
yamllint/rules/anchors.py Normal file
View File

@@ -0,0 +1,118 @@
# 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/>.
"""
Use this rule to report duplicated anchors and aliases referencing undeclared
anchors.
.. rubric:: Options
* Set ``forbid-undeclared-aliases`` to ``true`` to avoid aliases that reference
an anchor that hasn't been declared (either not declared at all, or declared
later in the document).
* Set ``forbid-duplicated-anchors`` to ``true`` to avoid duplications of a same
anchor.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
anchors:
forbid-undeclared-aliases: true
forbid-duplicated-anchors: false
.. rubric:: Examples
#. With ``anchors: {forbid-undeclared-aliases: true}``
the following code snippet would **PASS**:
::
---
- &anchor
foo: bar
- *anchor
the following code snippet would **FAIL**:
::
---
- &anchor
foo: bar
- *unknown
the following code snippet would **FAIL**:
::
---
- &anchor
foo: bar
- <<: *unknown
extra: value
#. With ``anchors: {forbid-duplicated-anchors: true}``
the following code snippet would **PASS**:
::
---
- &anchor1 Foo Bar
- &anchor2 [item 1, item 2]
the following code snippet would **FAIL**:
::
---
- &anchor Foo Bar
- &anchor [item 1, item 2]
"""
import yaml
from yamllint.linter import LintProblem
ID = 'anchors'
TYPE = 'token'
CONF = {'forbid-undeclared-aliases': bool,
'forbid-duplicated-anchors': bool}
DEFAULT = {'forbid-undeclared-aliases': True,
'forbid-duplicated-anchors': False}
def check(conf, token, prev, next, nextnext, context):
if conf['forbid-undeclared-aliases'] or conf['forbid-duplicated-anchors']:
if isinstance(token, (yaml.StreamStartToken, yaml.DocumentStartToken)):
context['anchors'] = set()
if (conf['forbid-undeclared-aliases'] and
isinstance(token, yaml.AliasToken) and
token.value not in context['anchors']):
yield LintProblem(
token.start_mark.line + 1, token.start_mark.column + 1,
f'found undeclared alias "{token.value}"')
if (conf['forbid-duplicated-anchors'] and
isinstance(token, yaml.AnchorToken) and
token.value in context['anchors']):
yield LintProblem(
token.start_mark.line + 1, token.start_mark.column + 1,
f'found duplicated anchor "{token.value}"')
if conf['forbid-undeclared-aliases'] or conf['forbid-duplicated-anchors']:
if isinstance(token, yaml.AnchorToken):
context['anchors'].add(token.value)

View File

@@ -117,8 +117,7 @@ def check(conf, comment):
# # comment
# - 1
# - 2
if prev_line_indent <= next_line_indent:
prev_line_indent = next_line_indent
prev_line_indent = max(prev_line_indent, next_line_indent)
# If two indents are valid but a previous comment went back to normal
# indent, for the next ones to do the same. In other words, avoid this:

View File

@@ -107,13 +107,13 @@ DEFAULT = {
}
IS_NUMERAL_BEFORE_DECIMAL_PATTERN = (
re.compile('[-+]?(\\.[0-9]+)([eE][-+]?[0-9]+)?')
re.compile('[-+]?(\\.[0-9]+)([eE][-+]?[0-9]+)?$')
)
IS_SCIENTIFIC_NOTATION_PATTERN = re.compile(
'[-+]?(\\.[0-9]+|[0-9]+(\\.[0-9]*)?)([eE][-+]?[0-9]+)'
'[-+]?(\\.[0-9]+|[0-9]+(\\.[0-9]*)?)([eE][-+]?[0-9]+)$'
)
IS_INF_PATTERN = re.compile('[-+]?(\\.inf|\\.Inf|\\.INF)')
IS_NAN_PATTERN = re.compile('\\.nan|\\.NaN|\\.NAN')
IS_INF_PATTERN = re.compile('[-+]?(\\.inf|\\.Inf|\\.INF)$')
IS_NAN_PATTERN = re.compile('(\\.nan|\\.NaN|\\.NAN)$')
def check(conf, token, prev, next, nextnext, context):

View File

@@ -341,14 +341,18 @@ def _check(conf, token, prev, next, nextnext, context):
expected = detect_indent(expected, token)
if found_indentation != expected:
yield LintProblem(token.start_mark.line + 1, found_indentation + 1,
'wrong indentation: expected %d but found %d' %
(expected, found_indentation))
if expected < 0:
message = 'wrong indentation: expected at least %d' % \
(found_indentation + 1)
else:
message = 'wrong indentation: expected %d but found %d' % \
(expected, found_indentation)
yield LintProblem(token.start_mark.line + 1,
found_indentation + 1, message)
if (isinstance(token, yaml.ScalarToken) and
conf['check-multi-line-strings']):
for problem in check_scalar_indentation(conf, token, context):
yield problem
yield from check_scalar_indentation(conf, token, context)
# Step 2.a:
@@ -493,8 +497,8 @@ def _check(conf, token, prev, next, nextnext, context):
# indentation it should have (because `spaces` is
# `consistent` and its value has not been computed yet
# -- this is probably the beginning of the document).
# So we choose an arbitrary value (2).
indent = 2
# So we choose an unknown value (-1).
indent = -1
else:
indent = detect_indent(context['stack'][-1].indent,
next)
@@ -576,8 +580,7 @@ def _check(conf, token, prev, next, nextnext, context):
def check(conf, token, prev, next, nextnext, context):
try:
for problem in _check(conf, token, prev, next, nextnext, context):
yield problem
yield from _check(conf, token, prev, next, nextnext, context)
except AssertionError:
yield LintProblem(token.start_mark.line + 1,
token.start_mark.column + 1,

View File

@@ -93,7 +93,7 @@ TYPE = 'token'
MAP, SEQ = range(2)
class Parent(object):
class Parent:
def __init__(self, type):
self.type = type
self.keys = []

View File

@@ -166,7 +166,7 @@ def VALIDATE(conf):
return 'cannot use both "required: false" and "extra-allowed"'
DEFAULT_SCALAR_TAG = u'tag:yaml.org,2002:str'
DEFAULT_SCALAR_TAG = 'tag:yaml.org,2002:str'
# https://stackoverflow.com/a/36514274
yaml.resolver.Resolver.add_implicit_resolver(
@@ -228,7 +228,7 @@ def check(conf, token, prev, next, nextnext, context):
return
# Ignore multi-line strings
if (not token.plain) and (token.style == "|" or token.style == ">"):
if not token.plain and token.style in ("|", ">"):
return
quote_type = conf['quote-type']

View File

@@ -14,7 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to forbid non-explictly typed truthy values other than allowed
Use this rule to forbid non-explicitly typed truthy values other than allowed
ones (by default: ``true`` and ``false``), for example ``YES`` or ``off``.
This can be useful to prevent surprises from YAML parsers transforming