Compare commits

..

4 Commits

Author SHA1 Message Date
Adrien Vergé d32d1f65ad WIP - test by Adrien 4 years ago
Satoru SATOH 9f9e282da5 enhancement: add a doc section about how to develop rules' plugins
Add a section into the doc about how to develop rules' plugins.
4 years ago
Satoru SATOH 6abce4e9a9 enhancement: enable rules' plugin support
Enable rules' plugin support and add its test cases.
4 years ago
Satoru SATOH 1c15ad1adc enhancement: add lint rules plugin support
Add plugin support using setuptools (pkg_resources) plugin mechanism to
yamllint to allow users to add their own custom lint rule plugins,
together with an example plugin implementation and test cases.

Signed-off-by: Satoru SATOH <satoru.satoh@gmail.com>
4 years ago

@ -1,4 +0,0 @@
[flake8]
import-order-style = pep8
application-import-names = yamllint
ignore = W503,W504

@ -1,61 +0,0 @@
---
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@v3
- name: Set up Python
uses: actions/setup-python@v4
- run:
pip install flake8 flake8-import-order sphinx 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.7'
- '3.8'
- '3.9'
- '3.10'
- '3.11'
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
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

@ -0,0 +1,28 @@
---
dist: xenial # required for Python >= 3.7 (travis-ci/travis-ci#9069)
language: python
python:
- 2.7
- 3.5
- 3.6
- 3.7
- 3.8
- nightly
env:
- REMOVE_LOCALES=false
- REMOVE_LOCALES=true
install:
- pip install pyyaml coveralls flake8 flake8-import-order doc8
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then pip install sphinx; fi
- pip install .
- if [[ $REMOVE_LOCALES = "true" ]]; then sudo rm -rf /usr/lib/locale/*; fi
script:
- if [[ $TRAVIS_PYTHON_VERSION != nightly ]]; then flake8 .; fi
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then doc8 $(git ls-files '*.rst'); fi
- yamllint --strict $(git ls-files '*.yaml' '*.yml')
- coverage run --source=yamllint -m unittest discover
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then
python setup.py build_sphinx;
fi
after_success:
coveralls

@ -1,110 +1,11 @@
Changelog
=========
1.32.0 (2023-05-22)
-------------------
- Look for configuration file in parent directories
- Rule ``anchors``: add new option ``forbid-unused-anchors``
1.31.0 (2023-04-21)
-------------------
- Build: migrate from ``setup.py`` to ``pyproject.toml``
- Docs: update some outdated URLs
- Rule ``colons``: prevent error when space before is mandatory
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)
-------------------
- Better compress PNG image in documentation
- Remove ``__future__`` imports specific to Python 2
- Remove inheritance from ``object`` specific to Python 2
- Simplify GitHub Actions example in documentation
- Update ALE vim plugin link in documentation
- Update license to latest version of GPLv3
- Pre-compile disable/enable rules regexes
- Rule ``quoted-strings``: add ``allow-quoted-quotes`` option
- Add option ``ignore-from-file`` in config
1.27.1 (2022-07-08)
-------------------
- Fix failing test on ``key-duplicates`` for old PyYAML versions
1.27.0 (2022-07-08)
-------------------
- Add support for Python 3.10, drop Python 3.5
- Fix GitHub Actions workflow
- Refactor ``--format=auto`` logic
- Update GitHub format output to use groups
- Rule ``comments``: allow whitespace after the shebang marker
- Multiple minor fixes in documentation
- Configure Sphinx to make man page show up in apropos
- Attempt to clarify configuration file location in documentation
- Rule ``key-duplicates``: don't crash on redundant closing brackets or braces
- Use ``rstcheck`` to lint documentation on the CI
- Remove UTF-8 headers in Python files, since Python 2 isn't supported
- Add various tests to increase coverage
- Rule ``octal-values``: pre-compile regex for performance
- Add sections for Visual Studio Code and IntelliJ in documentation
- Rule ``new-lines``: add the ``type: platform`` config option
- Add the new rule ``float-values``
1.26.3 (2021-08-21)
-------------------
- Restore runtime dependency ``setuptools`` for Python < 3.8
1.26.2 (2021-08-03)
-------------------
- Fix ``python_requires`` to comply with PEP 345 and PEP 440
1.26.1 (2021-04-06)
-------------------
- Remove runtime dependency ``setuptools`` for Python < 3.8
- Fix ``line_length`` to skip all hash signs starting comment
1.26.0 (2021-01-29)
-------------------
- End support for Python 2 and Python 3.4, add support for Python 3.9
- Add ``forbid: non-empty`` option to ``braces`` and ``brackets`` rules
- Fix ``quoted-strings`` for explicit octal recognition
- Add documentation for integration with Arcanist
- Fix typos in changelog and README
- Stop using deprecated ``python setup.py test`` in tests
1.25.0 (2020-09-29)
-------------------
- Run tests on Travis both with and without UTF-8 locales
- Improve documentation with default values to rules with options
- Improve documentationon with default values to rules with options
- Improve documentation with a Python API usage example
- Fix documentation on ``commas`` examples
- Packaging: move setuptools' configuration from ``setup.py`` to ``setup.cfg``

@ -42,7 +42,5 @@ Pull Request Process
6. Write a `good commit message
<http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html>`_.
If the pull request has multiple commits, each must be atomic (single
irreducible change that makes sense on its own).
7. Then, open a pull request.

@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
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 <https://www.gnu.org/licenses/>.
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

@ -0,0 +1,4 @@
include LICENSE
include README.rst
include docs/*
include tests/*.py tests/rules/*.py tests/yaml-1.2-spec-examples/*

@ -8,8 +8,8 @@ repetition and cosmetic problems such as lines length, trailing spaces,
indentation, etc.
.. image::
https://github.com/adrienverge/yamllint/actions/workflows/ci.yaml/badge.svg?branch=master
:target: https://github.com/adrienverge/yamllint/actions/workflows/ci.yaml?query=branch%3Amaster
https://travis-ci.org/adrienverge/yamllint.svg?branch=master
:target: https://travis-ci.org/adrienverge/yamllint
:alt: CI tests status
.. image::
https://coveralls.io/repos/github/adrienverge/yamllint/badge.svg?branch=master
@ -19,7 +19,11 @@ indentation, etc.
:target: https://yamllint.readthedocs.io/en/latest/?badge=latest
:alt: Documentation status
Written in Python (compatible with Python 3 only).
Written in Python (compatible with Python 2 & 3).
⚠ Python 2 upstream support stopped on January 1, 2020. yamllint will keep
best-effort support for Python 2.7 until January 1, 2021. Passed that date,
yamllint will drop all Python 2-related code.
Documentation
-------------
@ -74,7 +78,7 @@ Usage
# Output a parsable format (for syntax checking in editors like Vim, emacs...)
yamllint -f parsable file.yaml
`Read more in the complete documentation! <https://yamllint.readthedocs.io/>`__
`Read more in the complete documentation! <https://yamllint.readthedocs.io/>`_
Features
^^^^^^^^
@ -132,7 +136,7 @@ Specific files can be ignored (totally or for some rules only) using a
*.ignore-trailing-spaces.yaml
/ascii-art/*
`Read more in the complete documentation! <https://yamllint.readthedocs.io/>`__
`Read more in the complete documentation! <https://yamllint.readthedocs.io/>`_
License
-------

@ -2,7 +2,7 @@
#
# You can set these variables from the command line.
SPHINXOPTS = -W
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# yamllint documentation build configuration file, created by
# sphinx-quickstart on Thu Jan 21 21:18:52 2016.
@ -20,7 +21,7 @@ source_suffix = '.rst'
master_doc = 'index'
project = APP_NAME
copyright = __copyright__.lstrip('Copyright ')
copyright = __copyright__
version = APP_VERSION
release = APP_VERSION
@ -38,7 +39,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', ['Adrien Vergé'], 1)
('index', 'yamllint', '', [u'Adrien Vergé'], 1)
]
# -- Build with sphinx automodule without needing to install third-party libs

@ -14,12 +14,11 @@ To use a custom configuration file, use the ``-c`` option:
If ``-c`` is not provided, yamllint will look for a configuration file in the
following locations (by order of preference):
- a file named ``.yamllint``, ``.yamllint.yaml``, or ``.yamllint.yml`` in the
current working directory, or a parent directory (the search for this file is
terminated at the user's home or filesystem root)
- a filename referenced by ``$YAMLLINT_CONFIG_FILE``, if set
- a file named ``$XDG_CONFIG_HOME/yamllint/config`` or
``~/.config/yamllint/config``, if present
- ``.yamllint``, ``.yamllint.yaml`` or ``.yamllint.yml`` in the current working
directory
- the file referenced by ``$YAMLLINT_CONFIG_FILE``, if set
- ``$XDG_CONFIG_HOME/yamllint/config``
- ``~/.config/yamllint/config``
Finally if no config file is found, the default configuration is applied.
@ -137,19 +136,11 @@ 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. They can be provided either as a list of paths, or as a
bulk string.
doesn't process them.
You can either totally ignore files (they won't be looked at):
@ -162,13 +153,6 @@ 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
@ -181,18 +165,10 @@ 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.org/project/pathspec/>`_ for more details. Here is a more complex
example:
<https://pypi.python.org/pypi/pathspec>`_ for more details.
Here is a more complex example:
.. code-block:: yaml
@ -214,27 +190,6 @@ example:
*.ignore-trailing-spaces.yaml
ascii-art/*
You can also use the ``.gitignore`` file (or any list of files) through:
.. code-block:: yaml
ignore-from-file: .gitignore
or:
.. code-block:: yaml
ignore-from-file: [.gitignore, .yamlignore]
.. 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
------------------

@ -11,8 +11,52 @@ Basic example of running the linter from Python:
import yamllint
yaml_config = yamllint.config.YamlLintConfig("extends: default")
for p in yamllint.linter.run(open("example.yaml", "r"), yaml_config):
for p in yamllint.linter.run("example.yaml", yaml_config):
print(p.desc, p.line, p.rule)
.. automodule:: yamllint.linter
:members:
Develop rule plugins
---------------------
yamllint provides a plugin mechanism using setuptools (pkg_resources) to allow
adding custom rules. So, you can extend yamllint and add rules with your own
custom yamllint rule plugins if you developed them.
Yamllint rule plugins must satisfy the followings.
#. It must be a Python package installable using pip and distributed under
GPLv3+ same as yamllint.
#. It must contains the entry point configuration in ``setup.cfg`` or something
similar packaging configuration files, to make it installed and working as a
yamllint plugin like below. (``<plugin_name>`` is that plugin name and
``<plugin_src_dir>`` is a dir where the rule modules exist.)
::
[options.entry_points]
yamllint.plugins.rules =
<plugin_name> = <plugin_src_dir>
#. It must contain custom yamllint rule modules:
- Each rule module must define a couple of global variables, ID and TYPE. ID
must not conflicts with other rules' ID.
- Each rule module must define a function named 'check' to test input data
complies with the rule.
- Each rule module may have other global variables.
- CONF to define its configuration parameters and those types.
- DEFAULT to provide default values for each configuration parameters.
#. It must define a global variable RULES_MAP to provide mappings of rule ID
and rule modules to yamllint like this.
::
RULES_MAP = {
# rule ID: rule module
a_custom_rule.ID: a_custom_rule
}
To develop yamllint rules, the default rules themselves in yamllint may become
good references.

@ -40,11 +40,6 @@ 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``.
@ -94,6 +89,7 @@ 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:
@ -105,32 +101,3 @@ 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

@ -11,7 +11,7 @@ Screenshot
.. note::
The default output format is inspired by `eslint <https://eslint.org/>`_, a
The default output format is inspired by `eslint <http://eslint.org/>`_, a
great linting tool for Javascript.
Table of contents

@ -4,7 +4,7 @@ Integration with other software
Integration with pre-commit
---------------------------
You can integrate yamllint in the `pre-commit <https://pre-commit.com/>`_ tool.
You can integrate yamllint in `pre-commit <http://pre-commit.com/>`_ tool.
Here is an example, to add in your .pre-commit-config.yaml
.. code:: yaml
@ -12,56 +12,42 @@ 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.
repos:
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.29.0
hooks:
- id: yamllint
args: [--strict, -c=/path/to/.yamllint]
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.17.0
hooks:
- id: yamllint
args: [-c=/path/to/.yamllint]
Integration with GitHub Actions
-------------------------------
yamllint auto-detects when it's running inside of `GitHub
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``.
Actions<https://github.com/features/actions>` and automatically uses the suited
output format to decorate code with linting errors automatically. You can also
force the GitHub Actions output with ``yamllint --format github``.
A minimal example workflow using GitHub Actions:
An example workflow using GitHub Actions:
.. code:: yaml
---
on: push # yamllint disable-line rule:truthy
name: yamllint test
on: push
jobs:
lint:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install yamllint
run: pip install yamllint
- name: Lint YAML files
run: yamllint .
Integration with Arcanist
-------------------------
You can configure yamllint to run on ``arc lint``. Here is an example
``.arclint`` file that makes use of this configuration.
.. 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)$)"
}
}
}

@ -4,7 +4,7 @@ Quickstart
Installing yamllint
-------------------
On Fedora / CentOS (note: `EPEL <https://docs.fedoraproject.org/en-US/epel/>`_ is
On Fedora / CentOS (note: `EPEL <https://fedoraproject.org/wiki/EPEL>`_ is
required on CentOS):
.. code:: bash
@ -45,7 +45,7 @@ If you prefer installing from source, you can run, from the source directory:
.. code:: bash
python -m build
python setup.py sdist
pip install --user dist/yamllint-*.tar.gz
Running yamllint

@ -14,11 +14,6 @@ This page describes the rules and their options.
:local:
:depth: 1
anchors
-------
.. automodule:: yamllint.rules.anchors
braces
------
@ -69,12 +64,6 @@ empty-values
.. automodule:: yamllint.rules.empty_values
float-values
------------
.. automodule:: yamllint.rules.float_values
hyphens
-------

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 41 KiB

@ -9,11 +9,11 @@ text editor.
Vim
---
Assuming that the `ALE <https://github.com/dense-analysis/ale>`_ plugin is
Assuming that the `ALE <https://github.com/w0rp/ale>`_ plugin is
installed, yamllint is supported by default. It is automatically enabled when
editing YAML files.
If you instead use the `syntastic <https://github.com/vim-syntastic/syntastic>`_
If you instead use the `syntastic <https://github.com/scrooloose/syntastic>`_
plugin, add this to your ``.vimrc``:
::
@ -23,7 +23,7 @@ plugin, add this to your ``.vimrc``:
Neovim
------
Assuming that the `neomake <https://github.com/neomake/neomake>`_ plugin is
Assuming that the `neomake <https://github.com/benekastah/neomake>`_ plugin is
installed, yamllint is supported by default. It is automatically enabled when
editing YAML files.
@ -33,16 +33,6 @@ Emacs
If you are `flycheck <https://github.com/flycheck/flycheck>`_ user, you can use
`flycheck-yamllint <https://github.com/krzysztof-magosa/flycheck-yamllint>`_ integration.
Visual Studio Code
------------------
https://marketplace.visualstudio.com/items?itemName=fnando.linter
IntelliJ
--------
https://plugins.jetbrains.com/plugin/15349-yamllint
Other text editors
------------------

@ -1,54 +0,0 @@
[project]
name = "yamllint"
description = "A linter for YAML files."
readme = {file = "README.rst", content-type = "text/x-rst"}
requires-python = ">=3.7"
license = {text = "GPL-3.0-or-later"}
authors = [{name = "Adrien Vergé"}]
keywords = ["yaml", "lint", "linter", "syntax", "checker"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Programming Language :: Python",
"Topic :: Software Development",
"Topic :: Software Development :: Debuggers",
"Topic :: Software Development :: Quality Assurance",
"Topic :: Software Development :: Testing",
]
dependencies = [
"pathspec >= 0.5.3",
"pyyaml",
]
dynamic = ["version"]
[project.optional-dependencies]
dev = [
"doc8",
"flake8",
"flake8-import-order",
"rstcheck[sphinx]",
"sphinx",
]
[project.scripts]
yamllint = "yamllint.cli:run"
[project.urls]
homepage = "https://github.com/adrienverge/yamllint"
repository = "https://github.com/adrienverge/yamllint"
documentation = "https://yamllint.readthedocs.io"
[build-system]
build-backend = "setuptools.build_meta"
requires = ["setuptools >= 61"]
[tool.setuptools]
packages = ["yamllint", "yamllint.conf", "yamllint.rules"]
[tool.setuptools.package-data]
yamllint = ["conf/*.yaml"]
[tool.setuptools.dynamic]
version = {attr = "yamllint.__version__"}

@ -0,0 +1,71 @@
[bdist_wheel]
universal = 1
[flake8]
import-order-style = pep8
application-import-names = yamllint
ignore = W503,W504
[build_sphinx]
all-files = 1
source-dir = docs
build-dir = docs/_build
warning-is-error = 1
[metadata]
keywords =
yaml
lint
linter
syntax
checker
url = https://github.com/adrienverge/yamllint
classifiers =
Development Status :: 5 - Production/Stable
Environment :: Console
Intended Audience :: Developers
License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Topic :: Software Development
Topic :: Software Development :: Debuggers
Topic :: Software Development :: Quality Assurance
Topic :: Software Development :: Testing
project_urls =
Documentation = https://yamllint.readthedocs.io
Download = https://pypi.org/project/yamllint/#files
Bug Tracker = https://github.com/adrienverge/yamllint/issues
Source Code = https://github.com/adrienverge/yamllint
[options]
packages = find:
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
include_package_data = True
install_requires =
pathspec >= 0.5.3
pyyaml
setuptools
test_suite = tests
[options.packages.find]
exclude =
tests
tests.*
[options.package_data]
yamllint = conf/*.yaml
[options.entry_points]
console_scripts =
yamllint = yamllint.cli:run

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -15,6 +16,15 @@
from setuptools import setup
# This is only kept for backward-compatibility with older versions that don't
# support new packaging standards (e.g. PEP 517 or PEP 660):
setup()
from yamllint import (__author__, __license__,
APP_NAME, APP_VERSION, APP_DESCRIPTION)
setup(
name=APP_NAME,
version=APP_VERSION,
author=__author__,
description=APP_DESCRIPTION.split('\n')[0],
long_description=APP_DESCRIPTION,
license=__license__,
)

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -13,9 +14,7 @@
# 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 contextlib
import os
import shutil
import tempfile
import unittest
@ -70,17 +69,3 @@ def build_temp_workspace(files):
f.write(content)
return tempdir
@contextlib.contextmanager
def temp_workspace(files):
"""Provide a temporary workspace that is automatically cleaned up."""
backup_wd = os.getcwd()
wd = build_temp_workspace(files)
try:
os.chdir(wd)
yield
finally:
os.chdir(backup_wd)
shutil.rmtree(wd)

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2020 Satoru SATOH
#
# 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/>.
"""yamllint plugin entry point
"""
from __future__ import absolute_import
from . import override_comments
RULES_MAP = {
override_comments.ID: override_comments
}

@ -0,0 +1,63 @@
#
# Copyright (C) 2020 Satoru SATOH
#
# 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 override some comments' rules.
.. rubric:: Options
* Use ``forbid`` to control comments. Set to ``true`` to forbid comments
completely.
.. rubric:: Examples
#. With ``override-comments: {forbid: true}``
the following code snippet would **PASS**:
::
foo: 1
the following code snippet would **FAIL**:
::
# baz
foo: 1
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
override-comments:
forbid: False
"""
from yamllint.linter import LintProblem
ID = 'override-comments'
TYPE = 'comment'
CONF = {'forbid': bool}
DEFAULT = {'forbid': False}
def check(conf, comment):
"""Check if comments are found.
"""
if conf['forbid']:
yield LintProblem(comment.line_no, comment.column_no,
'forbidden comment')

@ -0,0 +1,11 @@
[metadata]
name = yamllint_plugin_example
version = 1.0.0
[options]
packages = find:
install_requires = yamllint
[options.entry_points]
yamllint.plugins.rules =
example = yamllint_plugin_example

@ -0,0 +1,2 @@
import setuptools
setuptools.setup()

@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2020 Satoru SATOH
#
# 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/>.
"""yamllint plugin entry point
"""
from __future__ import absolute_import
from . import override_comments, random_failure
RULES_MAP = {
override_comments.ID: override_comments,
random_failure.ID: random_failure,
}

@ -0,0 +1,63 @@
#
# Copyright (C) 2020 Satoru SATOH
#
# 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 override some comments' rules.
.. rubric:: Options
* Use ``forbid`` to control comments. Set to ``true`` to forbid comments
completely.
.. rubric:: Examples
#. With ``override-comments: {forbid: true}``
the following code snippet would **PASS**:
::
foo: 1
the following code snippet would **FAIL**:
::
# baz
foo: 1
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
override-comments:
forbid: False
"""
from yamllint.linter import LintProblem
ID = 'override-comments'
TYPE = 'comment'
CONF = {'forbid': bool}
DEFAULT = {'forbid': False}
def check(conf, comment):
"""Check if comments are found.
"""
if conf['forbid']:
yield LintProblem(comment.line_no, comment.column_no,
'forbidden comment')

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2020 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import random
from yamllint.linter import LintProblem
ID = 'random-failure'
TYPE = 'token'
def check(conf, token, prev, next, nextnext, context):
if random.random() > 0.9:
yield LintProblem(token.start_mark.line + 1,
token.start_mark.column + 1,
'random failure')

@ -1,281 +0,0 @@
# Copyright (C) 2023 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.common import RuleTestCase
class AnchorsTestCase(RuleTestCase):
rule_id = 'anchors'
def test_disabled(self):
conf = 'anchors: disable'
self.check('---\n'
'- &b true\n'
'- &i 42\n'
'- &s hello\n'
'- &f_m {k: v}\n'
'- &f_s [1, 2]\n'
'- *b\n'
'- *i\n'
'- *s\n'
'- *f_m\n'
'- *f_s\n'
'---\n' # redeclare anchors in a new document
'- &b true\n'
'- &i 42\n'
'- &s hello\n'
'- *b\n'
'- *i\n'
'- *s\n'
'---\n'
'block mapping: &b_m\n'
' key: value\n'
'extended:\n'
' <<: *b_m\n'
' foo: bar\n'
'---\n'
'{a: 1, &x b: 2, c: &y 3, *x : 4, e: *y}\n'
'...\n', conf)
self.check('---\n'
'- &i 42\n'
'---\n'
'- &b true\n'
'- &b true\n'
'- &b true\n'
'- &s hello\n'
'- *b\n'
'- *i\n' # declared in a previous document
'- *f_m\n' # never declared
'- *f_m\n'
'- *f_m\n'
'- *f_s\n' # declared after
'- &f_s [1, 2]\n'
'---\n'
'block mapping: &b_m\n'
' key: value\n'
'---\n'
'block mapping 1: &b_m_bis\n'
' key: value\n'
'block mapping 2: &b_m_bis\n'
' key: value\n'
'extended:\n'
' <<: *b_m\n'
' foo: bar\n'
'---\n'
'{a: 1, &x b: 2, c: &x 3, *x : 4, e: *y}\n'
'...\n', conf)
def test_forbid_undeclared_aliases(self):
conf = ('anchors:\n'
' forbid-undeclared-aliases: true\n'
' forbid-duplicated-anchors: false\n'
' forbid-unused-anchors: false\n')
self.check('---\n'
'- &b true\n'
'- &i 42\n'
'- &s hello\n'
'- &f_m {k: v}\n'
'- &f_s [1, 2]\n'
'- *b\n'
'- *i\n'
'- *s\n'
'- *f_m\n'
'- *f_s\n'
'---\n' # redeclare anchors in a new document
'- &b true\n'
'- &i 42\n'
'- &s hello\n'
'- *b\n'
'- *i\n'
'- *s\n'
'---\n'
'block mapping: &b_m\n'
' key: value\n'
'extended:\n'
' <<: *b_m\n'
' foo: bar\n'
'---\n'
'{a: 1, &x b: 2, c: &y 3, *x : 4, e: *y}\n'
'...\n', conf)
self.check('---\n'
'- &i 42\n'
'---\n'
'- &b true\n'
'- &b true\n'
'- &b true\n'
'- &s hello\n'
'- *b\n'
'- *i\n' # declared in a previous document
'- *f_m\n' # never declared
'- *f_m\n'
'- *f_m\n'
'- *f_s\n' # declared after
'- &f_s [1, 2]\n'
'...\n'
'---\n'
'block mapping: &b_m\n'
' key: value\n'
'---\n'
'block mapping 1: &b_m_bis\n'
' key: value\n'
'block mapping 2: &b_m_bis\n'
' key: value\n'
'extended:\n'
' <<: *b_m\n'
' foo: bar\n'
'---\n'
'{a: 1, &x b: 2, c: &x 3, *x : 4, e: *y}\n'
'...\n', conf,
problem1=(9, 3),
problem2=(10, 3),
problem3=(11, 3),
problem4=(12, 3),
problem5=(13, 3),
problem6=(25, 7),
problem7=(28, 37))
def test_forbid_duplicated_anchors(self):
conf = ('anchors:\n'
' forbid-undeclared-aliases: false\n'
' forbid-duplicated-anchors: true\n'
' forbid-unused-anchors: false\n')
self.check('---\n'
'- &b true\n'
'- &i 42\n'
'- &s hello\n'
'- &f_m {k: v}\n'
'- &f_s [1, 2]\n'
'- *b\n'
'- *i\n'
'- *s\n'
'- *f_m\n'
'- *f_s\n'
'---\n' # redeclare anchors in a new document
'- &b true\n'
'- &i 42\n'
'- &s hello\n'
'- *b\n'
'- *i\n'
'- *s\n'
'---\n'
'block mapping: &b_m\n'
' key: value\n'
'extended:\n'
' <<: *b_m\n'
' foo: bar\n'
'---\n'
'{a: 1, &x b: 2, c: &y 3, *x : 4, e: *y}\n'
'...\n', conf)
self.check('---\n'
'- &i 42\n'
'---\n'
'- &b true\n'
'- &b true\n'
'- &b true\n'
'- &s hello\n'
'- *b\n'
'- *i\n' # declared in a previous document
'- *f_m\n' # never declared
'- *f_m\n'
'- *f_m\n'
'- *f_s\n' # declared after
'- &f_s [1, 2]\n'
'...\n'
'---\n'
'block mapping: &b_m\n'
' key: value\n'
'---\n'
'block mapping 1: &b_m_bis\n'
' key: value\n'
'block mapping 2: &b_m_bis\n'
' key: value\n'
'extended:\n'
' <<: *b_m\n'
' foo: bar\n'
'---\n'
'{a: 1, &x b: 2, c: &x 3, *x : 4, e: *y}\n'
'...\n', conf,
problem1=(5, 3),
problem2=(6, 3),
problem3=(22, 18),
problem4=(28, 20))
def test_forbid_unused_anchors(self):
conf = ('anchors:\n'
' forbid-undeclared-aliases: false\n'
' forbid-duplicated-anchors: false\n'
' forbid-unused-anchors: true\n')
self.check('---\n'
'- &b true\n'
'- &i 42\n'
'- &s hello\n'
'- &f_m {k: v}\n'
'- &f_s [1, 2]\n'
'- *b\n'
'- *i\n'
'- *s\n'
'- *f_m\n'
'- *f_s\n'
'---\n' # redeclare anchors in a new document
'- &b true\n'
'- &i 42\n'
'- &s hello\n'
'- *b\n'
'- *i\n'
'- *s\n'
'---\n'
'block mapping: &b_m\n'
' key: value\n'
'extended:\n'
' <<: *b_m\n'
' foo: bar\n'
'---\n'
'{a: 1, &x b: 2, c: &y 3, *x : 4, e: *y}\n'
'...\n', conf)
self.check('---\n'
'- &i 42\n'
'---\n'
'- &b true\n'
'- &b true\n'
'- &b true\n'
'- &s hello\n'
'- *b\n'
'- *i\n' # declared in a previous document
'- *f_m\n' # never declared
'- *f_m\n'
'- *f_m\n'
'- *f_s\n' # declared after
'- &f_s [1, 2]\n'
'...\n'
'---\n'
'block mapping: &b_m\n'
' key: value\n'
'---\n'
'block mapping 1: &b_m_bis\n'
' key: value\n'
'block mapping 2: &b_m_bis\n'
' key: value\n'
'extended:\n'
' <<: *b_m\n'
' foo: bar\n'
'---\n'
'{a: 1, &x b: 2, c: &x 3, *x : 4, e: *y}\n'
'...\n', conf,
problem1=(2, 3),
problem2=(7, 3),
problem3=(14, 3),
problem4=(17, 16),
problem5=(22, 18))

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -60,30 +61,6 @@ class ColonTestCase(RuleTestCase):
' a: 1\n'
'}\n', conf, problem=(2, 8))
conf = ('braces:\n'
' forbid: non-empty\n')
self.check('---\n'
'dict:\n'
' a: 1\n', conf)
self.check('---\n'
'dict: {}\n', conf)
self.check('---\n'
'dict: {\n'
'}\n', conf)
self.check('---\n'
'dict: {\n'
'# commented: value\n'
'# another: value2\n'
'}\n', conf)
self.check('---\n'
'dict: {a}\n', conf, problem=(2, 8))
self.check('---\n'
'dict: {a: 1}\n', conf, problem=(2, 8))
self.check('---\n'
'dict: {\n'
' a: 1\n'
'}\n', conf, problem=(2, 8))
def test_min_spaces(self):
conf = ('braces:\n'
' max-spaces-inside: -1\n'

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -59,29 +60,6 @@ class ColonTestCase(RuleTestCase):
' b\n'
']\n', conf, problem=(2, 9))
conf = ('brackets:\n'
' forbid: non-empty\n')
self.check('---\n'
'array:\n'
' - a\n'
' - b\n', conf)
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
'array: [\n\n'
']\n', conf)
self.check('---\n'
'array: [\n'
'# a comment\n'
']\n', conf)
self.check('---\n'
'array: [a, b]\n', conf, problem=(2, 9))
self.check('---\n'
'array: [\n'
' a,\n'
' b\n'
']\n', conf, problem=(2, 9))
def test_min_spaces(self):
conf = ('brackets:\n'
' max-spaces-inside: -1\n'

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -256,19 +257,3 @@ class ColonTestCase(RuleTestCase):
' property: {a: 1, b: 2, c : 3}\n', conf,
problem1=(3, 11), problem2=(4, 4),
problem3=(8, 23), problem4=(8, 28))
# Although accepted by PyYAML, `{*x: 4}` is not valid YAML: it should be
# noted `{*x : 4}`. The reason is that a colon can be part of an anchor
# name. See commit message for more details.
def test_with_alias_as_key(self):
conf = 'colons: {max-spaces-before: 0, max-spaces-after: 1}'
self.check('---\n'
'- anchor: &a key\n'
'- *a: 42\n'
'- {*a: 42}\n'
'- *a : 42\n'
'- {*a : 42}\n'
'- *a : 42\n'
'- {*a : 42}\n',
conf,
problem1=(7, 6), problem2=(8, 7))

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -96,7 +97,7 @@ class CommentsTestCase(RuleTestCase):
'#!/bin/env my-interpreter\n'
'', conf,
problem1=(1, 2), problem2=(3, 2), problem3=(4, 2))
self.check('#! is a valid shebang too\n',
self.check('#! not a shebang\n',
conf, problem1=(1, 2))
self.check('key: #!/not/a/shebang\n',
conf, problem1=(1, 8))
@ -116,7 +117,8 @@ class CommentsTestCase(RuleTestCase):
'#comment\n'
'#!/bin/env my-interpreter\n', conf,
problem2=(3, 2), problem3=(4, 2))
self.check('#! is a valid shebang too\n', conf)
self.check('#! not a shebang\n',
conf, problem1=(1, 2))
self.check('key: #!/not/a/shebang\n',
conf, problem1=(1, 8))

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -71,22 +72,3 @@ class DocumentEndTestCase(RuleTestCase):
'---\n'
'third: document\n'
'...\n', conf, problem=(6, 1))
def test_directives(self):
conf = 'document-end: {present: true}'
self.check('%YAML 1.2\n'
'---\n'
'document: end\n'
'...\n', conf)
self.check('%YAML 1.2\n'
'%TAG ! tag:clarkevans.com,2002:\n'
'---\n'
'document: end\n'
'...\n', conf)
self.check('---\n'
'first: document\n'
'...\n'
'%YAML 1.2\n'
'---\n'
'second: document\n'
'...\n', conf)

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Greg Dubicki
#
# This program is free software: you can redistribute it and/or modify

@ -1,128 +0,0 @@
# Copyright (C) 2022 the yamllint contributors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.common import RuleTestCase
class FloatValuesTestCase(RuleTestCase):
rule_id = 'float-values'
def test_disabled(self):
conf = 'float-values: disable\n'
self.check('---\n'
'- 0.0\n'
'- .NaN\n'
'- .INF\n'
'- .1\n'
'- 10e-6\n',
conf)
def test_numeral_before_decimal(self):
conf = (
'float-values:\n'
' require-numeral-before-decimal: true\n'
' forbid-scientific-notation: false\n'
' forbid-nan: false\n'
' forbid-inf: false\n')
self.check('---\n'
'- 0.0\n'
'- .1\n'
'- \'.1\'\n'
'- string.1\n'
'- .1string\n'
'- !custom_tag .2\n'
'- &angle1 0.0\n'
'- *angle1\n'
'- &angle2 .3\n'
'- *angle2\n',
conf,
problem1=(3, 3),
problem2=(10, 11))
def test_scientific_notation(self):
conf = (
'float-values:\n'
' require-numeral-before-decimal: false\n'
' forbid-scientific-notation: true\n'
' forbid-nan: false\n'
' forbid-inf: false\n')
self.check('---\n'
'- 10e6\n'
'- 10e-6\n'
'- 0.00001\n'
'- \'10e-6\'\n'
'- string10e-6\n'
'- 10e-6string\n'
'- !custom_tag 10e-6\n'
'- &angle1 0.000001\n'
'- *angle1\n'
'- &angle2 10e-6\n'
'- *angle2\n'
'- &angle3 10e6\n'
'- *angle3\n',
conf,
problem1=(2, 3),
problem2=(3, 3),
problem3=(11, 11),
problem4=(13, 11))
def test_nan(self):
conf = (
'float-values:\n'
' require-numeral-before-decimal: false\n'
' forbid-scientific-notation: false\n'
' forbid-nan: true\n'
' forbid-inf: false\n')
self.check('---\n'
'- .NaN\n'
'- .NAN\n'
'- \'.NaN\'\n'
'- a.NaN\n'
'- .NaNa\n'
'- !custom_tag .NaN\n'
'- &angle .nan\n'
'- *angle\n',
conf,
problem1=(2, 3),
problem2=(3, 3),
problem3=(8, 10))
def test_inf(self):
conf = (
'float-values:\n'
' require-numeral-before-decimal: false\n'
' forbid-scientific-notation: false\n'
' forbid-nan: false\n'
' forbid-inf: true\n')
self.check('---\n'
'- .inf\n'
'- .INF\n'
'- -.inf\n'
'- -.INF\n'
'- \'.inf\'\n'
'- ∞.infinity\n'
'- .infinity∞\n'
'- !custom_tag .inf\n'
'- &angle .inf\n'
'- *angle\n'
'- &angle -.inf\n'
'- *angle\n',
conf,
problem1=(2, 3),
problem2=(3, 3),
problem3=(4, 3),
problem4=(5, 3),
problem5=(10, 10),
problem6=(12, 10))

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -1370,45 +1371,6 @@ 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'

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -86,10 +87,6 @@ class KeyDuplicatesTestCase(RuleTestCase):
'anchor_reference:\n'
' <<: *anchor_one\n'
' <<: *anchor_two\n', conf)
self.check('---\n'
'{a: 1, b: 2}}\n', conf, problem=(2, 13, 'syntax'))
self.check('---\n'
'[a, b, c]]\n', conf, problem=(2, 10, 'syntax'))
def test_enabled(self):
conf = 'key-duplicates: enable'
@ -168,10 +165,6 @@ class KeyDuplicatesTestCase(RuleTestCase):
'anchor_reference:\n'
' <<: *anchor_one\n'
' <<: *anchor_two\n', conf)
self.check('---\n'
'{a: 1, b: 2}}\n', conf, problem=(2, 13, 'syntax'))
self.check('---\n'
'[a, b, c]]\n', conf, problem=(2, 10, 'syntax'))
def test_key_tokens_in_flow_sequences(self):
conf = 'key-duplicates: enable'

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Johannes F. Knauf
#
# This program is free software: you can redistribute it and/or modify
@ -116,7 +117,7 @@ class KeyOrderingTestCase(RuleTestCase):
self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None))
try:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
except locale.Error: # pragma: no cover
except locale.Error:
self.skipTest('locale en_US.UTF-8 not available')
conf = ('key-ordering: enable')
self.check('---\n'
@ -135,7 +136,7 @@ class KeyOrderingTestCase(RuleTestCase):
self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None))
try:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
except locale.Error: # pragma: no cover
except locale.Error:
self.skipTest('locale en_US.UTF-8 not available')
conf = ('key-ordering: enable')
self.check('---\n'

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -13,6 +14,9 @@
# 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 sys
import unittest
from tests.common import RuleTestCase
@ -115,27 +119,6 @@ class LineLengthTestCase(RuleTestCase):
'long_line: http://localhost/very/very/long/url\n'
'...\n', conf, problem=(2, 21))
conf = 'line-length: {max: 20, allow-non-breakable-words: true}'
self.check('---\n'
'# http://www.verylongurlurlurlurlurlurlurlurl.com\n'
'key:\n'
' subkey: value\n', conf)
self.check('---\n'
'## http://www.verylongurlurlurlurlurlurlurlurl.com\n'
'key:\n'
' subkey: value\n', conf)
self.check('---\n'
'# # http://www.verylongurlurlurlurlurlurlurlurl.com\n'
'key:\n'
' subkey: value\n', conf,
problem=(2, 21))
self.check('---\n'
'#A http://www.verylongurlurlurlurlurlurlurlurl.com\n'
'key:\n'
' subkey: value\n', conf,
problem1=(2, 2, 'comments'),
problem2=(2, 21, 'line-length'))
conf = ('line-length: {max: 20, allow-non-breakable-words: true}\n'
'trailing-spaces: disable')
self.check('---\n'
@ -176,17 +159,18 @@ class LineLengthTestCase(RuleTestCase):
' {% this line is' + 99 * ' really' + ' long %}\n',
conf, problem=(3, 81))
@unittest.skipIf(sys.version_info < (3, 0), 'Python 2 not supported')
def test_unicode(self):
conf = 'line-length: {max: 53}'
self.check('---\n'
'# This is a test to check if “line-length” works nice\n'
'with: “unicode characters” that span across bytes! ↺\n',
'with: “unicode characters” that span accross bytes! ↺\n',
conf)
conf = 'line-length: {max: 51}'
conf = 'line-length: {max: 52}'
self.check('---\n'
'# This is a test to check if “line-length” works nice\n'
'with: “unicode characters” that span across bytes! ↺\n',
conf, problem1=(2, 52), problem2=(3, 52))
'with: “unicode characters” that span accross bytes! ↺\n',
conf, problem1=(2, 53), problem2=(3, 53))
def test_with_dos_newlines(self):
conf = ('line-length: {max: 10}\n'

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -13,8 +14,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 unittest import mock
from tests.common import RuleTestCase
@ -60,37 +59,3 @@ class NewLinesTestCase(RuleTestCase):
self.check('\r\n', conf)
self.check('---\ntext\n', conf, problem=(1, 4))
self.check('---\r\ntext\r\n', conf)
def test_platform_type(self):
conf = ('new-line-at-end-of-file: disable\n'
'new-lines: {type: platform}\n')
self.check('', conf)
# mock the Linux new-line-character
with mock.patch('yamllint.rules.new_lines.linesep', '\n'):
self.check('\n', conf)
self.check('\r\n', conf, problem=(1, 1))
self.check('---\ntext\n', conf)
self.check('---\r\ntext\r\n', conf, problem=(1, 4))
self.check('---\r\ntext\n', conf, problem=(1, 4))
# FIXME: the following tests currently don't work
# because only the first line is checked for line-endings
# see: issue #475
# ---
# self.check('---\ntext\r\nfoo\n', conf, problem=(2, 4))
# self.check('---\ntext\r\n', conf, problem=(2, 4))
# mock the Windows new-line-character
with mock.patch('yamllint.rules.new_lines.linesep', '\r\n'):
self.check('\r\n', conf)
self.check('\n', conf, problem=(1, 1))
self.check('---\r\ntext\r\n', conf)
self.check('---\ntext\n', conf, problem=(1, 4))
self.check('---\ntext\r\n', conf, problem=(1, 4))
# FIXME: the following tests currently don't work
# because only the first line is checked for line-endings
# see: issue #475
# ---
# self.check('---\r\ntext\nfoo\r\n', conf, problem=(2, 4))
# self.check('---\r\ntext\n', conf, problem=(2, 4))

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -32,7 +33,6 @@ class OctalValuesTestCase(RuleTestCase):
' forbid-explicit-octal: false\n'
'new-line-at-end-of-file: disable\n'
'document-start: disable\n')
self.check('after-tag: !custom_tag 010', conf)
self.check('user-city: 010', conf, problem=(1, 15))
self.check('user-city: abc', conf)
self.check('user-city: 010,0571', conf)

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 ClearScore
#
# This program is free software: you can redistribute it and/or modify
@ -435,124 +436,3 @@ class QuotedTestCase(RuleTestCase):
'- foo bar\n'
'- "foo bar"\n',
conf, problem1=(3, 3), problem2=(7, 3), problem3=(11, 3))
def test_octal_values(self):
conf = 'quoted-strings: {required: true}\n'
self.check('---\n'
'- 100\n'
'- 0100\n'
'- 0o100\n'
'- 777\n'
'- 0777\n'
'- 0o777\n'
'- 800\n'
'- 0800\n'
'- 0o800\n'
'- "0800"\n'
'- "0o800"\n',
conf,
problem1=(9, 3), problem2=(10, 3))
def test_allow_quoted_quotes(self):
conf = ('quoted-strings: {quote-type: single,\n'
' required: false,\n'
' allow-quoted-quotes: false}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n', # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: single,\n'
' required: false,\n'
' allow-quoted-quotes: true}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n',
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: single,\n'
' required: true,\n'
' allow-quoted-quotes: false}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n', # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: single,\n'
' required: true,\n'
' allow-quoted-quotes: true}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n',
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: single,\n'
' required: only-when-needed,\n'
' allow-quoted-quotes: false}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n', # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: single,\n'
' required: only-when-needed,\n'
' allow-quoted-quotes: true}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n',
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: false,\n'
' allow-quoted-quotes: false}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n", # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: false,\n'
' allow-quoted-quotes: true}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n",
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: true,\n'
' allow-quoted-quotes: false}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n", # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: true,\n'
' allow-quoted-quotes: true}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n",
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: only-when-needed,\n'
' allow-quoted-quotes: false}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n", # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: only-when-needed,\n'
' allow-quoted-quotes: true}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n",
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: any}\n')
self.check("---\n"
"foo1: '[barbaz]'\n"
"foo2: '[bar\"baz]'\n",
conf)

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Peter Ericson
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -13,7 +14,10 @@
# 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
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
import fcntl
import locale
import os
@ -23,13 +27,13 @@ import sys
import tempfile
import unittest
from tests.common import build_temp_workspace, temp_workspace
from tests.common import build_temp_workspace
from yamllint import cli
from yamllint import config
class RunContext:
class RunContext(object):
"""Context manager for ``cli.run()`` to capture exit code and streams."""
def __init__(self, case):
@ -58,7 +62,7 @@ def utf8_available():
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
locale.setlocale(locale.LC_ALL, (None, None))
return True
except locale.Error: # pragma: no cover
except locale.Error:
return False
@ -92,12 +96,12 @@ class CommandLineTestCase(unittest.TestCase):
'no-yaml.json': '---\n'
'key: value\n',
# non-ASCII chars
'non-ascii/éçäγλνπ¥/utf-8': (
'---\n'
'- hétérogénéité\n'
'# 19.99 €\n'
'- お早う御座います。\n'
'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'),
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'),
# dos line endings yaml
'dos.yml': '---\r\n'
'dos: true',
@ -242,19 +246,19 @@ class CommandLineTestCase(unittest.TestCase):
cli.run(())
self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '')
self.assertRegex(ctx.stderr, r'^usage')
self.assertRegexpMatches(ctx.stderr, r'^usage')
with RunContext(self) as ctx:
cli.run(('--unknown-arg', ))
self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '')
self.assertRegex(ctx.stderr, r'^usage')
self.assertRegexpMatches(ctx.stderr, r'^usage')
with RunContext(self) as ctx:
cli.run(('-c', './conf.yaml', '-d', 'relaxed', 'file'))
self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '')
self.assertRegex(
self.assertRegexpMatches(
ctx.stderr.splitlines()[-1],
r'^yamllint: error: argument -d\/--config-data: '
r'not allowed with argument -c\/--config-file$'
@ -265,31 +269,21 @@ class CommandLineTestCase(unittest.TestCase):
cli.run(('-', 'file'))
self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '')
self.assertRegex(ctx.stderr, r'^usage')
self.assertRegexpMatches(ctx.stderr, r'^usage')
def test_run_with_bad_config(self):
with RunContext(self) as ctx:
cli.run(('-d', 'rules: {a: b}', 'file'))
self.assertEqual(ctx.returncode, -1)
self.assertEqual(ctx.stdout, '')
self.assertRegex(ctx.stderr, r'^invalid config: no such rule')
self.assertRegexpMatches(ctx.stderr, r'^invalid config: no such rule')
def test_run_with_empty_config(self):
with RunContext(self) as ctx:
cli.run(('-d', '', 'file'))
self.assertEqual(ctx.returncode, -1)
self.assertEqual(ctx.stdout, '')
self.assertRegex(ctx.stderr, r'^invalid config: not a dict')
def test_run_with_implicit_extends_config(self):
path = os.path.join(self.wd, 'warn.yaml')
with RunContext(self) as ctx:
cli.run(('-d', 'default', '-f', 'parsable', path))
expected_out = ('%s:1:1: [warning] missing document start "---" '
'(document-start)\n' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (0, expected_out, ''))
self.assertRegexpMatches(ctx.stderr, r'^invalid config: not a dict')
def test_run_with_config_file(self):
with open(os.path.join(self.wd, 'config'), 'w') as f:
@ -306,7 +300,6 @@ 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')
@ -330,19 +323,6 @@ class CommandLineTestCase(unittest.TestCase):
cli.run((os.path.join(self.wd, 'a.yaml'), ))
self.assertEqual(ctx.returncode, 1)
def test_run_with_user_xdg_config_home_in_env(self):
self.addCleanup(os.environ.__delitem__, 'XDG_CONFIG_HOME')
with tempfile.TemporaryDirectory('w') as d:
os.environ['XDG_CONFIG_HOME'] = d
os.makedirs(os.path.join(d, 'yamllint'))
with open(os.path.join(d, 'yamllint', 'config'), 'w') as f:
f.write('extends: relaxed')
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', os.path.join(self.wd, 'warn.yaml')))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
def test_run_with_user_yamllint_config_file_in_env(self):
self.addCleanup(os.environ.__delitem__, 'YAMLLINT_CONFIG_FILE')
@ -368,7 +348,7 @@ class CommandLineTestCase(unittest.TestCase):
# as the first two runs don't use setlocale()
try:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
except locale.Error: # pragma: no cover
except locale.Error:
self.skipTest('locale en_US.UTF-8 not available')
locale.setlocale(locale.LC_ALL, (None, None))
@ -406,7 +386,7 @@ class CommandLineTestCase(unittest.TestCase):
with RunContext(self) as ctx:
cli.run(('--version', ))
self.assertEqual(ctx.returncode, 0)
self.assertRegex(ctx.stdout + ctx.stderr, r'yamllint \d+\.\d+')
self.assertRegexpMatches(ctx.stdout + ctx.stderr, r'yamllint \d+\.\d+')
def test_run_non_existing_file(self):
path = os.path.join(self.wd, 'i-do-not-exist.yaml')
@ -415,7 +395,7 @@ class CommandLineTestCase(unittest.TestCase):
cli.run(('-f', 'parsable', path))
self.assertEqual(ctx.returncode, -1)
self.assertEqual(ctx.stdout, '')
self.assertRegex(ctx.stderr, r'No such file or directory')
self.assertRegexpMatches(ctx.stderr, r'No such file or directory')
def test_run_one_problem_file(self):
path = os.path.join(self.wd, 'a.yaml')
@ -569,32 +549,17 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_format_colored_warning(self):
path = os.path.join(self.wd, 'warn.yaml')
with RunContext(self) as ctx:
cli.run((path, '--format', 'colored'))
expected_out = (
'\033[4m%s\033[0m\n'
' \033[2m1:1\033[0m \033[33mwarning\033[0m '
'missing document start "---" \033[2m(document-start)\033[0m\n'
'\n' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (0, expected_out, ''))
def test_run_format_github(self):
path = os.path.join(self.wd, 'a.yaml')
with RunContext(self) as ctx:
cli.run((path, '--format', 'github'))
expected_out = (
'::group::%s\n'
'::error file=%s,line=2,col=4::2:4 [trailing-spaces] trailing'
'::error file=%s,line=2,col=4::[trailing-spaces] trailing'
' spaces\n'
'::error file=%s,line=3,col=4::3:4 [new-line-at-end-of-file] no'
'::error file=%s,line=3,col=4::[new-line-at-end-of-file] no'
' new line character at the end of file\n'
'::endgroup::\n\n'
% (path, path, path))
% (path, path))
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
@ -608,13 +573,11 @@ class CommandLineTestCase(unittest.TestCase):
os.environ['GITHUB_WORKFLOW'] = 'something'
cli.run((path, ))
expected_out = (
'::group::%s\n'
'::error file=%s,line=2,col=4::2:4 [trailing-spaces] trailing'
'::error file=%s,line=2,col=4::[trailing-spaces] trailing'
' spaces\n'
'::error file=%s,line=3,col=4::3:4 [new-line-at-end-of-file] no'
'::error file=%s,line=3,col=4::[new-line-at-end-of-file] no'
' new line character at the end of file\n'
'::endgroup::\n\n'
% (path, path, path))
% (path, path))
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
@ -677,121 +640,3 @@ class CommandLineTestCase(unittest.TestCase):
'\n' % path)
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):
workspace = {'a.yml': 'hello: world\n'}
conf = ('---\n'
'extends: relaxed\n')
for conf_file in ('.yamllint', '.yamllint.yml', '.yamllint.yaml'):
with self.subTest(conf_file):
with temp_workspace(workspace):
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', '.'))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr),
(0, './a.yml:1:1: [warning] missing document '
'start "---" (document-start)\n', ''))
with temp_workspace({**workspace, **{conf_file: conf}}):
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', '.'))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr),
(0, '', ''))
def test_parent_config_file(self):
workspace = {'a/b/c/d/e/f/g/a.yml': 'hello: world\n'}
conf = ('---\n'
'extends: relaxed\n')
for conf_file in ('.yamllint', '.yamllint.yml', '.yamllint.yaml'):
with self.subTest(conf_file):
with temp_workspace(workspace):
with RunContext(self) as ctx:
os.chdir('a/b/c/d/e/f')
cli.run(('-f', 'parsable', '.'))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr),
(0, './g/a.yml:1:1: [warning] missing '
'document start "---" (document-start)\n',
''))
with temp_workspace({**workspace, **{conf_file: conf}}):
with RunContext(self) as ctx:
os.chdir('a/b/c/d/e/f')
cli.run(('-f', 'parsable', '.'))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr),
(0, '', ''))
def test_multiple_parent_config_file(self):
workspace = {'a/b/c/3spaces.yml': 'array:\n'
' - item\n',
'a/b/c/4spaces.yml': 'array:\n'
' - item\n',
'a/.yamllint': '---\n'
'extends: relaxed\n'
'rules:\n'
' indentation:\n'
' spaces: 4\n',
}
conf3 = ('---\n'
'extends: relaxed\n'
'rules:\n'
' indentation:\n'
' spaces: 3\n')
with temp_workspace(workspace):
with RunContext(self) as ctx:
os.chdir('a/b/c')
cli.run(('-f', 'parsable', '.'))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr),
(0, './3spaces.yml:2:4: [warning] wrong indentation: '
'expected 4 but found 3 (indentation)\n', ''))
with temp_workspace({**workspace, **{'a/b/.yamllint.yml': conf3}}):
with RunContext(self) as ctx:
os.chdir('a/b/c')
cli.run(('-f', 'parsable', '.'))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr),
(0, './4spaces.yml:2:5: [warning] wrong indentation: '
'expected 3 but found 4 (indentation)\n', ''))

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -13,7 +14,10 @@
# 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
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
import os
import shutil
import sys
@ -22,7 +26,6 @@ import unittest
from tests.common import build_temp_workspace
from yamllint.config import YamlLintConfigError
from yamllint import cli
from yamllint import config
@ -45,7 +48,7 @@ class SimpleConfigTestCase(unittest.TestCase):
config.YamlLintConfig('not: valid: yaml')
def test_unknown_rule(self):
with self.assertRaisesRegex(
with self.assertRaisesRegexp(
config.YamlLintConfigError,
'invalid config: no such rule: "this-one-does-not-exist"'):
config.YamlLintConfig('rules:\n'
@ -64,7 +67,7 @@ class SimpleConfigTestCase(unittest.TestCase):
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
def test_unknown_option(self):
with self.assertRaisesRegex(
with self.assertRaisesRegexp(
config.YamlLintConfigError,
'invalid config: unknown option "abcdef" for rule "colons"'):
config.YamlLintConfig('rules:\n'
@ -102,7 +105,7 @@ class SimpleConfigTestCase(unittest.TestCase):
self.assertEqual(c.rules['indentation']['check-multi-line-strings'],
False)
with self.assertRaisesRegex(
with self.assertRaisesRegexp(
config.YamlLintConfigError,
'invalid config: option "indent-sequences" of "indentation" '
'should be in '):
@ -122,7 +125,7 @@ class SimpleConfigTestCase(unittest.TestCase):
self.assertEqual(c.rules['hyphens'], False)
def test_validate_rule_conf(self):
class Rule:
class Rule(object):
ID = 'fake'
self.assertFalse(config.validate_rule_conf(Rule, False))
@ -190,41 +193,6 @@ class SimpleConfigTestCase(unittest.TestCase):
config.validate_rule_conf, Rule,
{'multiple': ['item4']})
def test_invalid_rule(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: rule "colons": should be either '
'"enable", "disable" or a dict'):
config.YamlLintConfig('rules:\n'
' colons: invalid\n')
def test_invalid_ignore(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: ignore should contain file patterns'):
config.YamlLintConfig('ignore: yes\n')
def test_invalid_rule_ignore(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: ignore should contain file patterns'):
config.YamlLintConfig('rules:\n'
' colons:\n'
' ignore: yes\n')
def test_invalid_locale(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: locale should be a string'):
config.YamlLintConfig('locale: yes\n')
def test_invalid_yaml_files(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: yaml-files should be a list of file '
'patterns'):
config.YamlLintConfig('yaml-files: yes\n')
class ExtendedConfigTestCase(unittest.TestCase):
def test_extend_on_object(self):
@ -369,26 +337,6 @@ 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_str(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)
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):
@ -440,10 +388,10 @@ class ExtendedLibraryConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['empty-lines']['max-end'], 0)
class IgnoreConfigTestCase(unittest.TestCase):
class IgnorePathConfigTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
super(IgnorePathConfigTestCase, cls).setUpClass()
bad_yaml = ('---\n'
'- key: val1\n'
@ -463,6 +411,22 @@ class IgnoreConfigTestCase(unittest.TestCase):
's/s/ign-trail/file.yaml': bad_yaml,
's/s/ign-trail/s/s/file.yaml': bad_yaml,
's/s/ign-trail/s/s/file2.lint-me-anyway.yaml': bad_yaml,
'.yamllint': 'ignore: |\n'
' *.dont-lint-me.yaml\n'
' /bin/\n'
' !/bin/*.lint-me-anyway.yaml\n'
'\n'
'extends: default\n'
'\n'
'rules:\n'
' key-duplicates:\n'
' ignore: |\n'
' /ign-dup\n'
' trailing-spaces:\n'
' ignore: |\n'
' ign-trail\n'
' !*.lint-me-anyway.yaml\n',
})
cls.backup_wd = os.getcwd()
@ -470,255 +434,13 @@ class IgnoreConfigTestCase(unittest.TestCase):
@classmethod
def tearDownClass(cls):
super().tearDownClass()
super(IgnorePathConfigTestCase, cls).tearDownClass()
os.chdir(cls.backup_wd)
shutil.rmtree(cls.wd)
def test_mutually_exclusive_ignore_keys(self):
self.assertRaises(
YamlLintConfigError,
config.YamlLintConfig, 'extends: default\n'
'ignore-from-file: .gitignore\n'
'ignore: |\n'
' *.dont-lint-me.yaml\n'
' /bin/\n')
def test_ignore_from_file_not_exist(self):
self.assertRaises(
FileNotFoundError,
config.YamlLintConfig, 'extends: default\n'
'ignore-from-file: not_found_file\n')
def test_ignore_from_file_incorrect_type(self):
self.assertRaises(
YamlLintConfigError,
config.YamlLintConfig, 'extends: default\n'
'ignore-from-file: 0\n')
self.assertRaises(
YamlLintConfigError,
config.YamlLintConfig, 'extends: default\n'
'ignore-from-file: [0]\n')
def test_no_ignore(self):
sys.stdout = StringIO()
with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.'))
out = sys.stdout.getvalue()
out = '\n'.join(sorted(out.splitlines()))
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((
'./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,
'./bin/file.yaml:3:3: ' + keydup,
'./bin/file.yaml:4:17: ' + trailing,
'./bin/file.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,
'./file.dont-lint-me.yaml:3:3: ' + keydup,
'./file.dont-lint-me.yaml:4:17: ' + trailing,
'./file.dont-lint-me.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,
'./ign-trail/file.yaml:4:17: ' + trailing,
'./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:4:17: ' + trailing,
'./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:4:17: ' + trailing,
'./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_str(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_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'
'ignore-from-file: .gitignore\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')
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: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,
'./ign-trail/file.yaml:4:17: ' + trailing,
'./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:4:17: ' + trailing,
'./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:4:17: ' + trailing,
'./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_ignored_from_file(self):
with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
f.write('ignore-from-file: [.gitignore, .yamlignore]\n'
'extends: default\n')
with open(os.path.join(self.wd, '.gitignore'), 'w') as f:
f.write('*.dont-lint-me.yaml\n'
'/bin/\n')
with open(os.path.join(self.wd, '.yamlignore'), 'w') as f:
f.write('!/bin/*.lint-me-anyway.yaml\n')
def test_run_with_ignored_path(self):
sys.stdout = StringIO()
with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.'))
@ -739,23 +461,18 @@ 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,
'./ign-trail/file.yaml:4:17: ' + trailing,
'./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:4:17: ' + trailing,
'./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:4:17: ' + trailing,
'./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,

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -31,10 +32,10 @@ class LinterTestCase(unittest.TestCase):
linter.run(b'test: document', self.fake_config())
def test_run_on_unicode(self):
linter.run('test: document', self.fake_config())
linter.run(u'test: document', self.fake_config())
def test_run_on_stream(self):
linter.run(io.StringIO('hello'), self.fake_config())
linter.run(io.StringIO(u'hello'), self.fake_config())
def test_run_on_int(self):
self.assertRaises(TypeError, linter.run, 42, self.fake_config())
@ -44,23 +45,13 @@ class LinterTestCase(unittest.TestCase):
['h', 'e', 'l', 'l', 'o'], self.fake_config())
def test_run_on_non_ascii_chars(self):
s = ('- hétérogénéité\n'
'# 19.99 €\n')
s = (u'- hétérogénéité\n'
u'# 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 = ('- お早う御座います。\n'
'# الأَبْجَدِيَّة العَرَبِيَّة\n')
s = (u'- お早う御座います。\n'
u'# الأَبْجَدِيَّة العَرَبِيَّة\n')
linter.run(s, self.fake_config())
linter.run(s.encode('utf-8'), self.fake_config())
def test_linter_problem_repr_without_rule(self):
problem = linter.LintProblem(1, 2, 'problem')
self.assertEqual(str(problem), '1:2: problem')
def test_linter_problem_repr_with_rule(self):
problem = linter.LintProblem(1, 2, 'problem', 'rule-id')
self.assertEqual(str(problem), '1:2: problem (rule-id)')

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -46,15 +47,16 @@ class ModuleTestCase(unittest.TestCase):
subprocess.check_output([PYTHON, '-m', 'yamllint'],
stderr=subprocess.STDOUT)
self.assertEqual(ctx.exception.returncode, 2)
self.assertRegex(ctx.exception.output.decode(), r'^usage: yamllint')
self.assertRegexpMatches(ctx.exception.output.decode(),
r'^usage: yamllint')
def test_run_module_on_bad_dir(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output([PYTHON, '-m', 'yamllint',
'/does/not/exist'],
stderr=subprocess.STDOUT)
self.assertRegex(ctx.exception.output.decode(),
r'No such file or directory')
self.assertRegexpMatches(ctx.exception.output.decode(),
r'No such file or directory')
def test_run_module_on_file(self):
out = subprocess.check_output(

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2020 Satoru SATOH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
import warnings
try:
from unittest import mock
except ImportError: # for python 2.7
mock = False
from tests.plugins import example
import yamllint.plugins
class FakeEntryPoint(object):
"""Fake object to mimic pkg_resources.EntryPoint.
"""
RULES_MAP = example.RULES_MAP
def load(self):
"""Fake method to return self.
"""
return self
class BrokenEntryPoint(FakeEntryPoint):
"""Fake object to mimic load failure of pkg_resources.EntryPoint.
"""
def load(self):
raise ImportError("This entry point should fail always!")
class PluginFunctionsTestCase(unittest.TestCase):
def test_validate_rule_module(self):
fun = yamllint.plugins.validate_rule_module
rule_mod = example.override_comments
self.assertFalse(fun(object()))
self.assertTrue(fun(rule_mod))
@unittest.skipIf(not mock, "unittest.mock is not available")
def test_validate_rule_module_using_mock(self):
fun = yamllint.plugins.validate_rule_module
rule_mod = example.override_comments
with mock.patch.object(rule_mod, "ID", False):
self.assertFalse(fun(rule_mod))
with mock.patch.object(rule_mod, "TYPE", False):
self.assertFalse(fun(rule_mod))
with mock.patch.object(rule_mod, "check", True):
self.assertFalse(fun(rule_mod))
def test_load_plugin_rules_itr(self):
fun = yamllint.plugins.load_plugin_rules_itr
self.assertEqual(list(fun([])), [])
self.assertEqual(sorted(fun([FakeEntryPoint(),
FakeEntryPoint()])),
sorted(FakeEntryPoint.RULES_MAP.items()))
with warnings.catch_warnings():
warnings.simplefilter("ignore")
self.assertEqual(list(fun([BrokenEntryPoint()])), [])

@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2020 Satoru SATOH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
try:
from unittest import mock
except ImportError: # for python 2.7
mock = False
from tests.plugins import example
import yamllint.rules
RULE_NEVER_EXISTS = "rule_never_exists"
PLUGIN_RULES = example.RULES_MAP
class TestCase(unittest.TestCase):
"""Test cases for yamllint.rules.__init__.*.
"""
def test_get_default_rule(self):
self.assertEqual(yamllint.rules.get(yamllint.rules.braces.ID),
yamllint.rules.braces)
def test_get_rule_does_not_exist(self):
with self.assertRaises(ValueError):
yamllint.rules.get(RULE_NEVER_EXISTS)
@unittest.skipIf(not mock, "unittest.mock is not available")
class TestCaseUsingMock(unittest.TestCase):
"""Test cases for yamllint.rules.__init__.* using mock.
"""
def test_get_default_rule_with_plugins(self):
with mock.patch.dict(yamllint.rules._EXTERNAL_RULES, PLUGIN_RULES):
self.assertEqual(yamllint.rules.get(yamllint.rules.braces.ID),
yamllint.rules.braces)
def test_get_plugin_rules(self):
plugin_rule_id = example.override_comments.ID
plugin_rule_mod = example.override_comments
with mock.patch.dict(yamllint.rules._EXTERNAL_RULES, PLUGIN_RULES):
self.assertEqual(yamllint.rules.get(plugin_rule_id),
plugin_rule_mod)
def test_get_rule_does_not_exist_with_plugins(self):
with mock.patch.dict(yamllint.rules._EXTERNAL_RULES, PLUGIN_RULES):
with self.assertRaises(ValueError):
yamllint.rules.get(RULE_NEVER_EXISTS)

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -13,6 +14,7 @@
# 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

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -21,10 +22,10 @@ indentation, etc."""
APP_NAME = 'yamllint'
APP_VERSION = '1.32.0'
APP_VERSION = '1.25.0'
APP_DESCRIPTION = __doc__
__author__ = 'Adrien Vergé'
__copyright__ = 'Copyright 2022, Adrien Vergé'
__author__ = u'Adrien Vergé'
__copyright__ = u'Copyright 2016, Adrien Vergé'
__license__ = 'GPLv3'
__version__ = APP_VERSION

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -13,7 +14,10 @@
# 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 __future__ import print_function
import argparse
import io
import locale
import os
import platform
@ -46,7 +50,7 @@ def supports_color():
hasattr(sys.stdout, 'isatty') and sys.stdout.isatty())
class Format:
class Format(object):
@staticmethod
def parsable(problem, filename):
return ('%(file)s:%(line)s:%(column)s: [%(level)s] %(message)s' %
@ -89,10 +93,6 @@ class Format:
line += 'line=' + format(problem.line) + ','
line += 'col=' + format(problem.column)
line += '::'
line += format(problem.line)
line += ':'
line += format(problem.column)
line += ' '
if problem.rule:
line += '[' + problem.rule + '] '
line += problem.desc
@ -103,25 +103,18 @@ def show_problems(problems, file, args_format, no_warn):
max_level = 0
first = True
if args_format == 'auto':
if ('GITHUB_ACTIONS' in os.environ and
'GITHUB_WORKFLOW' in os.environ):
args_format = 'github'
elif supports_color():
args_format = 'colored'
for problem in problems:
max_level = max(max_level, PROBLEM_LEVELS[problem.level])
if no_warn and (problem.level != 'error'):
continue
if args_format == 'parsable':
print(Format.parsable(problem, file))
elif args_format == 'github':
if first:
print('::group::%s' % file)
first = False
elif args_format == 'github' or (args_format == 'auto' and
'GITHUB_ACTIONS' in os.environ and
'GITHUB_WORKFLOW' in os.environ):
print(Format.github(problem, file))
elif args_format == 'colored':
elif args_format == 'colored' or \
(args_format == 'auto' and supports_color()):
if first:
print('\033[4m%s\033[0m' % file)
first = False
@ -132,28 +125,12 @@ def show_problems(problems, file, args_format, no_warn):
first = False
print(Format.standard(problem, file))
if not first and args_format == 'github':
print('::endgroup::')
if not first and args_format != 'parsable':
print('')
return max_level
def find_project_config_filepath(path='.'):
for filename in ('.yamllint', '.yamllint.yaml', '.yamllint.yml'):
filepath = os.path.join(path, filename)
if os.path.isfile(filepath):
return filepath
if os.path.abspath(path) == os.path.abspath(os.path.expanduser('~')):
return None
if os.path.abspath(path) == os.path.abspath(os.path.join(path, '..')):
return None
return find_project_config_filepath(path=os.path.join(path, '..'))
def run(argv=None):
parser = argparse.ArgumentParser(prog=APP_NAME,
description=APP_DESCRIPTION)
@ -170,8 +147,6 @@ 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'),
@ -198,7 +173,6 @@ def run(argv=None):
else:
user_global_config = os.path.expanduser('~/.config/yamllint/config')
project_config_filepath = find_project_config_filepath()
try:
if args.config_data is not None:
if args.config_data != '' and ':' not in args.config_data:
@ -206,8 +180,12 @@ def run(argv=None):
conf = YamlLintConfig(content=args.config_data)
elif args.config_file is not None:
conf = YamlLintConfig(file=args.config_file)
elif project_config_filepath:
conf = YamlLintConfig(file=project_config_filepath)
elif os.path.isfile('.yamllint'):
conf = YamlLintConfig(file='.yamllint')
elif os.path.isfile('.yamllint.yaml'):
conf = YamlLintConfig(file='.yamllint.yaml')
elif os.path.isfile('.yamllint.yml'):
conf = YamlLintConfig(file='.yamllint.yml')
elif os.path.isfile(user_global_config):
conf = YamlLintConfig(file=user_global_config)
else:
@ -219,20 +197,14 @@ 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 open(file, newline='') as f:
with io.open(file, newline='') as f:
problems = linter.run(f, conf, filepath)
except OSError as e:
except EnvironmentError as e:
print(e, file=sys.stderr)
sys.exit(-1)
prob_level = show_problems(problems, file, args_format=args.format,
@ -243,7 +215,7 @@ def run(argv=None):
if args.stdin:
try:
problems = linter.run(sys.stdin, conf, '')
except OSError as e:
except EnvironmentError as e:
print(e, file=sys.stderr)
sys.exit(-1)
prob_level = show_problems(problems, 'stdin', args_format=args.format,

@ -6,7 +6,6 @@ yaml-files:
- '.yamllint'
rules:
anchors: enable
braces: enable
brackets: enable
colons: enable
@ -20,7 +19,6 @@ rules:
level: warning
empty-lines: enable
empty-values: disable
float-values: disable
hyphens: enable
indentation: enable
key-duplicates: enable
@ -31,5 +29,7 @@ rules:
octal-values: disable
quoted-strings: disable
trailing-spaces: enable
random-failure: enable
override-comments: {forbid: true}
truthy:
level: warning

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -13,7 +14,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/>.
import fileinput
import os.path
import pathspec
@ -26,7 +26,7 @@ class YamlLintConfigError(Exception):
pass
class YamlLintConfig:
class YamlLintConfig(object):
def __init__(self, content=None, file=None):
assert (content is None) ^ (file is None)
@ -97,31 +97,12 @@ class YamlLintConfig:
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
if 'ignore' in conf and 'ignore-from-file' in conf:
raise YamlLintConfigError(
'invalid config: ignore and ignore-from-file keys cannot be '
'used together')
elif 'ignore-from-file' in conf:
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(ln, str) for ln in conf['ignore-from-file'])):
raise YamlLintConfigError(
'invalid config: ignore-from-file should contain '
'filename(s), either as a list or string')
with fileinput.input(conf['ignore-from-file']) as f:
self.ignore = pathspec.PathSpec.from_lines('gitwildmatch', f)
elif 'ignore' in conf:
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:
if 'ignore' in conf:
if not isinstance(conf['ignore'], str):
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)
@ -155,16 +136,11 @@ def validate_rule_conf(rule, conf):
if isinstance(conf, dict):
if ('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())
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:
if not isinstance(conf['ignore'], str):
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'
@ -175,7 +151,7 @@ def validate_rule_conf(rule, conf):
options = getattr(rule, 'CONF', {})
options_default = getattr(rule, 'DEFAULT', {})
for optkey in conf:
if optkey in ('ignore', 'ignore-from-file', 'level'):
if optkey in ('ignore', 'level'):
continue
if optkey not in options:
raise YamlLintConfigError(

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -14,7 +15,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
import io
import yaml
@ -30,11 +30,8 @@ PROBLEM_LEVELS = {
'error': 2,
}
DISABLE_RULE_PATTERN = re.compile(r'^# yamllint disable( rule:\S+)*\s*$')
ENABLE_RULE_PATTERN = re.compile(r'^# yamllint enable( rule:\S+)*\s*$')
class LintProblem:
class LintProblem(object):
"""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)
@ -84,9 +81,12 @@ def get_cosmetic_problems(buffer, conf, filepath):
self.all_rules = {r.ID for r in rules}
def process_comment(self, comment):
comment = str(comment)
try:
comment = str(comment)
except UnicodeError:
return # this certainly wasn't a yamllint directive comment
if DISABLE_RULE_PATTERN.match(comment):
if re.match(r'^# yamllint disable( rule:\S+)*\s*$', comment):
items = comment[18:].rstrip().split(' ')
rules = [item[5:] for item in items][1:]
if len(rules) == 0:
@ -96,7 +96,7 @@ def get_cosmetic_problems(buffer, conf, filepath):
if id in self.all_rules:
self.rules.add(id)
elif ENABLE_RULE_PATTERN.match(comment):
elif re.match(r'^# yamllint enable( rule:\S+)*\s*$', comment):
items = comment[17:].rstrip().split(' ')
rules = [item[5:] for item in items][1:]
if len(rules) == 0:
@ -110,7 +110,10 @@ def get_cosmetic_problems(buffer, conf, filepath):
class DisableLineDirective(DisableDirective):
def process_comment(self, comment):
comment = str(comment)
try:
comment = str(comment)
except UnicodeError:
return # this certainly wasn't a yamllint directive comment
if re.match(r'^# yamllint disable-line( rule:\S+)*\s*$', comment):
items = comment[23:].rstrip().split(' ')
@ -122,7 +125,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 an end of line is
# Use a cache to store problems and flush it only when a end of line is
# found. This allows the use of yamllint directive to disable some rules on
# some lines.
cache = []
@ -203,11 +206,15 @@ def _run(buffer, conf, filepath):
syntax_error.column <= problem.column):
yield syntax_error
# Discard the problem since it is at the same place as the syntax
# error and is probably redundant (and maybe it's just a 'warning',
# If there is already a yamllint error at the same place, discard
# it as it is probably redundant (and maybe it's just a 'warning',
# in which case the script won't even exit with a failure status).
if (syntax_error.line == problem.line and
syntax_error.column == problem.column):
syntax_error = None
continue
syntax_error = None
continue
yield problem
@ -223,12 +230,12 @@ def run(input, conf, filepath=None):
:param input: buffer, string or stream to read from
:param conf: yamllint configuration object
"""
if filepath is not None and conf.is_file_ignored(filepath):
if conf.is_file_ignored(filepath):
return ()
if isinstance(input, (bytes, str)):
if isinstance(input, (type(b''), type(u''))): # compat with Python 2 & 3
return _run(input, conf, filepath)
elif isinstance(input, io.IOBase):
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase
# We need to have everything in memory to parse correctly
content = input.read()
return _run(content, conf, filepath)

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -16,7 +17,7 @@
import yaml
class Line:
class Line(object):
def __init__(self, line_no, buffer, start, end):
self.line_no = line_no
self.start = start
@ -28,7 +29,7 @@ class Line:
return self.buffer[self.start:self.end]
class Token:
class Token(object):
def __init__(self, line_no, curr, prev, next, nextnext):
self.line_no = line_no
self.curr = curr
@ -37,7 +38,7 @@ class Token:
self.nextnext = nextnext
class Comment:
class Comment(object):
def __init__(self, line_no, column_no, buffer, pointer,
token_before=None, token_after=None, comment_before=None):
self.line_no = line_no
@ -132,7 +133,8 @@ def token_or_comment_generator(buffer):
yield Token(curr.start_mark.line + 1, curr, prev, next, nextnext)
yield from comments_between_tokens(curr, next)
for comment in comments_between_tokens(curr, next):
yield comment
prev = curr
curr = next

@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2020 Satoru SATOH
#
# 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/>.
"""
Plugin module utilizing setuptools (pkg_resources) to allow users to add their
own custom lint rules.
"""
import warnings
import pkg_resources
PACKAGE_GROUP = "yamllint.plugins.rules"
def validate_rule_module(rule_mod):
"""Test if given rule module is valid.
"""
return (getattr(rule_mod, "ID", False) and
getattr(rule_mod, "TYPE", False)
) and callable(getattr(rule_mod, "check", False))
def load_plugin_rules_itr(entry_points=None, group=PACKAGE_GROUP):
"""Load custom lint rule plugins."""
if not entry_points:
entry_points = pkg_resources.iter_entry_points(group)
rule_ids = set()
for entry in entry_points:
try:
rules = entry.load()
for rule_id, rule_mod in rules.RULES_MAP.items():
if rule_id in rule_ids or not validate_rule_module(rule_mod):
continue
print(rule_id, rule_mod)###
yield (rule_id, rule_mod)
rule_ids.add(rule_id)
# pkg_resources.EntryPoint.resolve may throw ImportError.
except (AttributeError, ImportError):
warnings.warn("Could not load the plugin: {}".format(entry),
RuntimeWarning)
def get_plugin_rules_map():
"""Get a mappings of plugin rule's IDs and rules."""
return dict((rule_id, rule_mod)
for rule_id, rule_mod in load_plugin_rules_itr())

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -13,8 +14,8 @@
# 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 yamllint.plugins
from yamllint.rules import (
anchors,
braces,
brackets,
colons,
@ -33,14 +34,12 @@ from yamllint.rules import (
new_line_at_end_of_file,
new_lines,
octal_values,
float_values,
quoted_strings,
trailing_spaces,
truthy,
)
_RULES = {
anchors.ID: anchors,
braces.ID: braces,
brackets.ID: brackets,
colons.ID: colons,
@ -51,7 +50,6 @@ _RULES = {
document_start.ID: document_start,
empty_lines.ID: empty_lines,
empty_values.ID: empty_values,
float_values.ID: float_values,
hyphens.ID: hyphens,
indentation.ID: indentation,
key_duplicates.ID: key_duplicates,
@ -65,9 +63,14 @@ _RULES = {
truthy.ID: truthy,
}
_EXTERNAL_RULES = yamllint.plugins.get_plugin_rules_map()
def get(id):
if id not in _RULES:
raise ValueError('no such rule: "%s"' % id)
return _RULES[id]
def get(rule_id):
if rule_id in _RULES:
return _RULES[rule_id]
if rule_id in _EXTERNAL_RULES:
return _EXTERNAL_RULES[rule_id]
raise ValueError('no such rule: "%s"' % rule_id)

@ -1,174 +0,0 @@
# 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.
* Set ``forbid-unused-anchors`` to ``true`` to avoid anchors being declared but
not used anywhere in the YAML document via alias.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
anchors:
forbid-undeclared-aliases: true
forbid-duplicated-anchors: false
forbid-unused-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]
#. With ``anchors: {forbid-unused-anchors: true}``
the following code snippet would **PASS**:
::
---
- &anchor
foo: bar
- *anchor
the following code snippet would **FAIL**:
::
---
- &anchor
foo: bar
- items:
- item1
- item2
"""
import yaml
from yamllint.linter import LintProblem
ID = 'anchors'
TYPE = 'token'
CONF = {'forbid-undeclared-aliases': bool,
'forbid-duplicated-anchors': bool,
'forbid-unused-anchors': bool}
DEFAULT = {'forbid-undeclared-aliases': True,
'forbid-duplicated-anchors': False,
'forbid-unused-anchors': False}
def check(conf, token, prev, next, nextnext, context):
if (conf['forbid-undeclared-aliases'] or
conf['forbid-duplicated-anchors'] or
conf['forbid-unused-anchors']):
if isinstance(token, (
yaml.StreamStartToken,
yaml.DocumentStartToken,
yaml.DocumentEndToken)):
context['anchors'] = {}
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-unused-anchors']:
# Unused anchors can only be detected at the end of Document.
# End of document can be either
# - end of stream
# - end of document sign '...'
# - start of a new document sign '---'
# If next token indicates end of document,
# check if the anchors have been used or not.
# If they haven't been used, report problem on those anchors.
if isinstance(next, (yaml.StreamEndToken,
yaml.DocumentStartToken,
yaml.DocumentEndToken)):
for anchor, info in context['anchors'].items():
if not info['used']:
yield LintProblem(info['line'] + 1,
info['column'] + 1,
f'found unused anchor "{anchor}"')
elif isinstance(token, yaml.AliasToken):
context['anchors'].get(token.value, {})['used'] = True
if (conf['forbid-undeclared-aliases'] or
conf['forbid-duplicated-anchors'] or
conf['forbid-unused-anchors']):
if isinstance(token, yaml.AnchorToken):
context['anchors'][token.value] = {
'line': token.start_mark.line,
'column': token.start_mark.column,
'used': False
}

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -21,8 +22,7 @@ braces (``{`` and ``}``).
* ``forbid`` is used to forbid the use of flow mappings which are denoted by
surrounding braces (``{`` and ``}``). Use ``true`` to forbid the use of flow
mappings completely. Use ``non-empty`` to forbid the use of all flow
mappings except for empty ones.
mappings completely.
* ``min-spaces-inside`` defines the minimal number of spaces required inside
braces.
* ``max-spaces-inside`` defines the maximal number of spaces allowed inside
@ -60,18 +60,6 @@ braces (``{`` and ``}``).
object: { key1: 4, key2: 8 }
#. With ``braces: {forbid: non-empty}``
the following code snippet would **PASS**:
::
object: {}
the following code snippet would **FAIL**:
::
object: { key1: 4, key2: 8 }
#. With ``braces: {min-spaces-inside: 0, max-spaces-inside: 0}``
the following code snippet would **PASS**:
@ -140,7 +128,7 @@ from yamllint.rules.common import spaces_after, spaces_before
ID = 'braces'
TYPE = 'token'
CONF = {'forbid': (bool, 'non-empty'),
CONF = {'forbid': bool,
'min-spaces-inside': int,
'max-spaces-inside': int,
'min-spaces-inside-empty': int,
@ -153,15 +141,7 @@ DEFAULT = {'forbid': False,
def check(conf, token, prev, next, nextnext, context):
if (conf['forbid'] is True and
isinstance(token, yaml.FlowMappingStartToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'forbidden flow mapping')
elif (conf['forbid'] == 'non-empty' and
isinstance(token, yaml.FlowMappingStartToken) and
not isinstance(next, yaml.FlowMappingEndToken)):
if conf['forbid'] and isinstance(token, yaml.FlowMappingStartToken):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'forbidden flow mapping')

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -21,8 +22,7 @@ inside brackets (``[`` and ``]``).
* ``forbid`` is used to forbid the use of flow sequences which are denoted by
surrounding brackets (``[`` and ``]``). Use ``true`` to forbid the use of
flow sequences completely. Use ``non-empty`` to forbid the use of all flow
sequences except for empty ones.
flow sequences completely.
* ``min-spaces-inside`` defines the minimal number of spaces required inside
brackets.
* ``max-spaces-inside`` defines the maximal number of spaces allowed inside
@ -61,18 +61,6 @@ inside brackets (``[`` and ``]``).
object: [ 1, 2, abc ]
#. With ``brackets: {forbid: non-empty}``
the following code snippet would **PASS**:
::
object: []
the following code snippet would **FAIL**:
::
object: [ 1, 2, abc ]
#. With ``brackets: {min-spaces-inside: 0, max-spaces-inside: 0}``
the following code snippet would **PASS**:
@ -141,7 +129,7 @@ from yamllint.rules.common import spaces_after, spaces_before
ID = 'brackets'
TYPE = 'token'
CONF = {'forbid': (bool, 'non-empty'),
CONF = {'forbid': bool,
'min-spaces-inside': int,
'max-spaces-inside': int,
'min-spaces-inside-empty': int,
@ -154,15 +142,7 @@ DEFAULT = {'forbid': False,
def check(conf, token, prev, next, nextnext, context):
if (conf['forbid'] is True and
isinstance(token, yaml.FlowSequenceStartToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'forbidden flow sequence')
elif (conf['forbid'] == 'non-empty' and
isinstance(token, yaml.FlowSequenceStartToken) and
not isinstance(next, yaml.FlowSequenceEndToken)):
if conf['forbid'] and isinstance(token, yaml.FlowSequenceStartToken):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'forbidden flow sequence')

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -92,9 +93,7 @@ DEFAULT = {'max-spaces-before': 0,
def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.ValueToken) and not (
isinstance(prev, yaml.AliasToken) and
token.start_mark.pointer - prev.end_mark.pointer == 1):
if isinstance(token, yaml.ValueToken):
problem = spaces_before(token, prev, next,
max=conf['max-spaces-before'],
max_desc='too many spaces before colon')

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -73,6 +74,8 @@ Use this rule to control the position and formatting of comments.
"""
import re
from yamllint.linter import LintProblem
@ -102,7 +105,7 @@ def check(conf, comment):
if (conf['ignore-shebangs'] and
comment.line_no == 1 and
comment.column_no == 1 and
comment.buffer[text_start] == '!'):
re.match(r'^!\S', comment.buffer[text_start:])):
return
# We can test for both \r and \r\n just by checking first char
# \r itself is a valid newline on some older OS.

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -117,7 +118,8 @@ def check(conf, comment):
# # comment
# - 1
# - 2
prev_line_indent = max(prev_line_indent, next_line_indent)
if prev_line_indent <= next_line_indent:
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:

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -99,13 +100,11 @@ def check(conf, token, prev, next, nextnext, context):
prev_is_end_or_stream_start = isinstance(
prev, (yaml.DocumentEndToken, yaml.StreamStartToken)
)
prev_is_directive = isinstance(prev, yaml.DirectiveToken)
if is_stream_end and not prev_is_end_or_stream_start:
yield LintProblem(token.start_mark.line, 1,
'missing document end "..."')
elif is_start and not (prev_is_end_or_stream_start
or prev_is_directive):
elif is_start and not prev_is_end_or_stream_start:
yield LintProblem(token.start_mark.line + 1, 1,
'missing document end "..."')

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Greg Dubicki
#
# This program is free software: you can redistribute it and/or modify

@ -1,158 +0,0 @@
# Copyright (C) 2022 the yamllint contributors
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to limit the permitted values for floating-point numbers.
YAML permits three classes of float expressions: approximation to real numbers,
positive and negative infinity and "not a number".
.. rubric:: Options
* Use ``require-numeral-before-decimal`` to require floats to start
with a numeral (ex ``0.0`` instead of ``.0``).
* Use ``forbid-scientific-notation`` to forbid scientific notation.
* Use ``forbid-nan`` to forbid NaN (not a number) values.
* Use ``forbid-inf`` to forbid infinite values.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
float-values:
forbid-inf: false
forbid-nan: false
forbid-scientific-notation: false
require-numeral-before-decimal: false
.. rubric:: Examples
#. With ``float-values: {require-numeral-before-decimal: true}``
the following code snippets would **PASS**:
::
anemometer:
angle: 0.0
the following code snippets would **FAIL**:
::
anemometer:
angle: .0
#. With ``float-values: {forbid-scientific-notation: true}``
the following code snippets would **PASS**:
::
anemometer:
angle: 0.00001
the following code snippets would **FAIL**:
::
anemometer:
angle: 10e-6
#. With ``float-values: {forbid-nan: true}``
the following code snippets would **FAIL**:
::
anemometer:
angle: .NaN
#. With ``float-values: {forbid-inf: true}``
the following code snippets would **FAIL**:
::
anemometer:
angle: .inf
"""
import re
import yaml
from yamllint.linter import LintProblem
ID = 'float-values'
TYPE = 'token'
CONF = {
'require-numeral-before-decimal': bool,
'forbid-scientific-notation': bool,
'forbid-nan': bool,
'forbid-inf': bool,
}
DEFAULT = {
'require-numeral-before-decimal': False,
'forbid-scientific-notation': False,
'forbid-nan': False,
'forbid-inf': False,
}
IS_NUMERAL_BEFORE_DECIMAL_PATTERN = (
re.compile('[-+]?(\\.[0-9]+)([eE][-+]?[0-9]+)?$')
)
IS_SCIENTIFIC_NOTATION_PATTERN = re.compile(
'[-+]?(\\.[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)$')
def check(conf, token, prev, next, nextnext, context):
if prev and isinstance(prev, yaml.tokens.TagToken):
return
if not isinstance(token, yaml.tokens.ScalarToken):
return
if token.style:
return
val = token.value
if conf['forbid-nan'] and IS_NAN_PATTERN.match(val):
yield LintProblem(
token.start_mark.line + 1,
token.start_mark.column + 1,
f'forbidden not a number value "{token.value}"',
)
if conf['forbid-inf'] and IS_INF_PATTERN.match(val):
yield LintProblem(
token.start_mark.line + 1,
token.start_mark.column + 1,
f'forbidden infinite value "{token.value}"',
)
if conf[
'forbid-scientific-notation'
] and IS_SCIENTIFIC_NOTATION_PATTERN.match(val):
yield LintProblem(
token.start_mark.line + 1,
token.start_mark.column + 1,
f'forbidden scientific notation "{token.value}"',
)
if conf[
'require-numeral-before-decimal'
] and IS_NUMERAL_BEFORE_DECIMAL_PATTERN.match(val):
yield LintProblem(
token.start_mark.line + 1,
token.start_mark.column + 1,
f'forbidden decimal missing 0 prefix "{token.value}"',
)

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -218,7 +219,7 @@ ROOT, B_MAP, F_MAP, B_SEQ, F_SEQ, B_ENT, KEY, VAL = range(8)
labels = ('ROOT', 'B_MAP', 'F_MAP', 'B_SEQ', 'F_SEQ', 'B_ENT', 'KEY', 'VAL')
class Parent:
class Parent(object):
def __init__(self, type, indent, line_indent=None):
self.type = type
self.indent = indent
@ -341,18 +342,14 @@ def _check(conf, token, prev, next, nextnext, context):
expected = detect_indent(expected, token)
if found_indentation != expected:
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)
yield LintProblem(token.start_mark.line + 1, found_indentation + 1,
'wrong indentation: expected %d but found %d' %
(expected, found_indentation))
if (isinstance(token, yaml.ScalarToken) and
conf['check-multi-line-strings']):
yield from check_scalar_indentation(conf, token, context)
for problem in check_scalar_indentation(conf, token, context):
yield problem
# Step 2.a:
@ -497,8 +494,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 unknown value (-1).
indent = -1
# So we choose an arbitrary value (2).
indent = 2
else:
indent = detect_indent(context['stack'][-1].indent,
next)
@ -580,7 +577,8 @@ def _check(conf, token, prev, next, nextnext, context):
def check(conf, token, prev, next, nextnext, context):
try:
yield from _check(conf, token, prev, next, nextnext, context)
for problem in _check(conf, token, prev, next, nextnext, context):
yield problem
except AssertionError:
yield LintProblem(token.start_mark.line + 1,
token.start_mark.column + 1,

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -64,7 +65,7 @@ TYPE = 'token'
MAP, SEQ = range(2)
class Parent:
class Parent(object):
def __init__(self, type):
self.type = type
self.keys = []
@ -83,8 +84,7 @@ def check(conf, token, prev, next, nextnext, context):
elif isinstance(token, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken)):
if len(context['stack']) > 0:
context['stack'].pop()
context['stack'].pop()
elif (isinstance(token, yaml.KeyToken) and
isinstance(next, yaml.ScalarToken)):
# This check is done because KeyTokens can be found inside flow

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Johannes F. Knauf
#
# This program is free software: you can redistribute it and/or modify
@ -17,8 +18,8 @@
Use this rule to enforce alphabetical ordering of keys in mappings. The sorting
order uses the Unicode code point number as a default. As a result, the
ordering is case-sensitive and not accent-friendly (see examples below).
This can be changed by setting the global ``locale`` option. This allows one
to sort case and accents properly.
This can be changed by setting the global ``locale`` option. This allows to
sort case and accents properly.
.. rubric:: Examples
@ -93,7 +94,7 @@ TYPE = 'token'
MAP, SEQ = range(2)
class Parent:
class Parent(object):
def __init__(self, type):
self.type = type
self.keys = []

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -16,6 +17,10 @@
"""
Use this rule to set a limit to lines length.
Note: with Python 2, the ``line-length`` rule may not work properly with
unicode characters because of the way strings are represented in bytes. We
recommend running yamllint with Python 3.
.. rubric:: Options
* ``max`` defines the maximal (inclusive) length of lines.
@ -139,11 +144,7 @@ def check(conf, line):
start += 1
if start != line.end:
if line.buffer[start] == '#':
while line.buffer[start] == '#':
start += 1
start += 1
elif line.buffer[start] == '-':
if line.buffer[start] in ('#', '-'):
start += 2
if line.buffer.find(' ', start, line.end) == -1:

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -17,7 +18,7 @@
Use this rule to require a new line character (``\\n``) at the end of files.
The POSIX standard `requires the last line to end with a new line character
<https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206>`_.
<http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206>`_.
All UNIX tools expect a new line at the end of files. Most text editors use
this convention too.
"""

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -18,11 +19,8 @@ Use this rule to force the type of new line characters.
.. rubric:: Options
* Set ``type`` to ``unix`` to enforce UNIX-typed new line characters (``\\n``),
set ``type`` to ``dos`` to enforce DOS-typed new line characters
(``\\r\\n``), or set ``type`` to ``platform`` to infer the type from the
system running yamllint (``\\n`` on POSIX / UNIX / Linux / Mac OS systems or
``\\r\\n`` on DOS / Windows systems).
* Set ``type`` to ``unix`` to use UNIX-typed new line characters (``\\n``), or
``dos`` to use DOS-typed new line characters (``\\r\\n``).
.. rubric:: Default values (when enabled)
@ -33,27 +31,24 @@ Use this rule to force the type of new line characters.
type: unix
"""
from os import linesep
from yamllint.linter import LintProblem
ID = 'new-lines'
TYPE = 'line'
CONF = {'type': ('unix', 'dos', 'platform')}
CONF = {'type': ('unix', 'dos')}
DEFAULT = {'type': 'unix'}
def check(conf, line):
if conf['type'] == 'unix':
newline_char = '\n'
elif conf['type'] == 'platform':
newline_char = linesep
elif conf['type'] == 'dos':
newline_char = '\r\n'
if line.start == 0 and len(line.buffer) > line.end:
if line.buffer[line.end:line.end + len(newline_char)] != newline_char:
yield LintProblem(1, line.end - line.start + 1,
'wrong new line character: expected {}'
.format(repr(newline_char).strip('\'')))
if conf['type'] == 'dos':
if (line.end + 2 > len(line.buffer) or
line.buffer[line.end:line.end + 2] != '\r\n'):
yield LintProblem(1, line.end - line.start + 1,
'wrong new line character: expected \\r\\n')
else:
if line.buffer[line.end] == '\r':
yield LintProblem(1, line.end - line.start + 1,
'wrong new line character: expected \\n')

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 ScienJus
#
# This program is free software: you can redistribute it and/or modify
@ -84,7 +85,9 @@ CONF = {'forbid-implicit-octal': bool,
DEFAULT = {'forbid-implicit-octal': True,
'forbid-explicit-octal': True}
IS_OCTAL_NUMBER_PATTERN = re.compile(r'^[0-7]+$')
def _is_octal_number(string):
return re.match(r'^[0-7]+$', string) is not None
def check(conf, token, prev, next, nextnext, context):
@ -96,7 +99,7 @@ def check(conf, token, prev, next, nextnext, context):
if not token.style:
val = token.value
if (val.isdigit() and len(val) > 1 and val[0] == '0' and
IS_OCTAL_NUMBER_PATTERN.match(val[1:])):
_is_octal_number(val[1:])):
yield LintProblem(
token.start_mark.line + 1, token.end_mark.column + 1,
'forbidden implicit octal value "%s"' %
@ -107,7 +110,7 @@ def check(conf, token, prev, next, nextnext, context):
if not token.style:
val = token.value
if (len(val) > 2 and val[:2] == '0o' and
IS_OCTAL_NUMBER_PATTERN.match(val[2:])):
_is_octal_number(val[2:])):
yield LintProblem(
token.start_mark.line + 1, token.end_mark.column + 1,
'forbidden explicit octal value "%s"' %

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 ClearScore
#
# This program is free software: you can redistribute it and/or modify
@ -30,8 +31,6 @@ used.
``required: false`` and ``required: only-when-needed``.
* ``extra-allowed`` is a list of PCRE regexes to allow quoted string values,
even if ``required: only-when-needed`` is set.
* ``allow-quoted-quotes`` allows (``true``) using disallowed quotes for strings
with allowed quotes inside. Default ``false``.
**Note**: Multi-line strings (with ``|`` or ``>``) will not be checked.
@ -45,7 +44,6 @@ used.
required: true
extra-required: []
extra-allowed: []
allow-quoted-quotes: false
.. rubric:: Examples
@ -115,26 +113,6 @@ used.
- "localhost"
- this is a string that needs to be QUOTED
#. With ``quoted-strings: {quote-type: double, allow-quoted-quotes: false}``
the following code snippet would **PASS**:
::
foo: "bar\\"baz"
the following code snippet would **FAIL**:
::
foo: 'bar"baz'
#. With ``quoted-strings: {quote-type: double, allow-quoted-quotes: true}``
the following code snippet would **PASS**:
::
foo: 'bar"baz'
"""
import re
@ -148,13 +126,11 @@ TYPE = 'token'
CONF = {'quote-type': ('any', 'single', 'double'),
'required': (True, False, 'only-when-needed'),
'extra-required': [str],
'extra-allowed': [str],
'allow-quoted-quotes': bool}
'extra-allowed': [str]}
DEFAULT = {'quote-type': 'any',
'required': True,
'extra-required': [],
'extra-allowed': [],
'allow-quoted-quotes': False}
'extra-allowed': []}
def VALIDATE(conf):
@ -166,18 +142,7 @@ def VALIDATE(conf):
return 'cannot use both "required: false" and "extra-allowed"'
DEFAULT_SCALAR_TAG = 'tag:yaml.org,2002:str'
# https://stackoverflow.com/a/36514274
yaml.resolver.Resolver.add_implicit_resolver(
'tag:yaml.org,2002:int',
re.compile(r'''^(?:[-+]?0b[0-1_]+
|[-+]?0o?[0-7_]+
|[-+]?0[0-7_]+
|[-+]?(?:0|[1-9][0-9_]*)
|[-+]?0x[0-9a-fA-F_]+
|[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X),
list('-+0123456789'))
DEFAULT_SCALAR_TAG = u'tag:yaml.org,2002:str'
def _quote_match(quote_type, token_style):
@ -202,12 +167,6 @@ def _quotes_are_needed(string):
return True
def _has_quoted_quotes(token):
return ((not token.plain) and
((token.style == "'" and '"' in token.value) or
(token.style == '"' and "'" in token.value)))
def check(conf, token, prev, next, nextnext, context):
if not (isinstance(token, yaml.tokens.ScalarToken) and
isinstance(prev, (yaml.BlockEntryToken, yaml.FlowEntryToken,
@ -228,7 +187,7 @@ def check(conf, token, prev, next, nextnext, context):
return
# Ignore multi-line strings
if not token.plain and token.style in ("|", ">"):
if (not token.plain) and (token.style == "|" or token.style == ">"):
return
quote_type = conf['quote-type']
@ -237,18 +196,13 @@ def check(conf, token, prev, next, nextnext, context):
if conf['required'] is True:
# Quotes are mandatory and need to match config
if (token.style is None or
not (_quote_match(quote_type, token.style) or
(conf['allow-quoted-quotes'] and _has_quoted_quotes(token)))):
if token.style is None or not _quote_match(quote_type, token.style):
msg = "string value is not quoted with %s quotes" % quote_type
elif conf['required'] is False:
# Quotes are not mandatory but when used need to match config
if (token.style and
not _quote_match(quote_type, token.style) and
not (conf['allow-quoted-quotes'] and
_has_quoted_quotes(token))):
if token.style and not _quote_match(quote_type, token.style):
msg = "string value is not quoted with %s quotes" % quote_type
elif not token.style:
@ -271,9 +225,7 @@ def check(conf, token, prev, next, nextnext, context):
quote_type)
# 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))):
elif token.style and not _quote_match(quote_type, token.style):
msg = "string value is not quoted with %s quotes" % quote_type
elif not token.style:

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Peter Ericson
#
# This program is free software: you can redistribute it and/or modify
@ -14,7 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to forbid non-explicitly typed truthy values other than allowed
Use this rule to forbid non-explictly 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

Loading…
Cancel
Save