Compare commits

...

93 Commits

Author SHA1 Message Date
Adrien Vergé
6a842229fd yamllint version 1.11.0 2018-02-21 13:42:06 +01:00
Adrien Vergé
8b9eab33bf CI: Fix failing tests for Python 3.6 because of flake8-import-order
See issue https://github.com/PyCQA/flake8-import-order/issues/149
2018-02-21 13:40:21 +01:00
xieenlong
22e792a433 Feature: checking octal numbers 2017-12-07 18:29:05 +01:00
Adrien Vergé
f713dc8be2 style: Fix E100 and E202 errors reported by pycodestyle 2017-12-07 18:28:53 +01:00
Adrien Vergé
a92743c8ca yamllint version 1.10.0 2017-11-05 10:17:55 +01:00
Adrien Vergé
501def327d tests: Use sys.executable instead of hard-coded 'python'
To test yamllint as a module, tests run commands like
`python -m yamllint`. But some environments (like continuous integration
of Debian or CentOS) don't always include the `python` executable (they
use `python3` instead).

Let's dynamically detect the Python executable path.
2017-11-05 10:06:46 +01:00
Adrien Vergé
ed5d319df8 tests: Use en_US.UTF-8 locale when C.UTF-8 not available
Some operating systems don't have the `C.UTF-8` locale installed yet
(for instance, CentOS 7). In such a case, fallback to `en_US.UTF-8` so
that tests can be run.

This follows commit 92ff315.
2017-11-05 10:02:22 +01:00
Adrien Vergé
6ec1e7b54a Distribution: Include tests in dist file
Since commit e948509 ("setup.py - don't distribute tests"), tests files
are not included in the `.tar.gz` bundle on a fresh repo clone. (On old
repos they were still included, because listed in
`yamllint.egg-info/SOURCES.txt`.)

Let's explicitly include them.
2017-11-05 09:50:46 +01:00
Adrien Vergé
c4475ece34 empty-values: Add forbid-in-flow-mappings conf
This allows preventing implicit `null` from empty values in flow
mappings.

For example:

    {a:}

    {a:, b: 2}

    {
      a: {
        b: ,
        c: {
          d: 4,
          e:
        }
      },
      f:
    }
2017-11-05 09:29:03 +01:00
Greg Dubicki
8537b0a164 Add rule: empty-values, to forbid implicit nulls
only in block mappings for now
2017-11-04 16:22:29 +01:00
Adrien Vergé
83ea74e2f8 CI: Compile documentation on Travis 2017-11-04 16:02:43 +01:00
Waylan Limberg
e43768f203 Better color support check.
Not all systems have `isatty` attribute on `sys.stdout` so check for
existance of attribute before checking value. Also don't use color in
Windows unless environ indicates support. Apparently, Windows can indicate
support by either the presence of `ANSICON` environ variable or if the
`TERM` environ variable is set to `ANSI`. Fixes #79.

No additional tests added, as the relevant tests use fcntl, which is a
Unix only lib. In fact, the tests won't even run in Windows.
2017-10-27 20:06:34 +02:00
Adrien Vergé
d422274563 style: Fix E722 errors reported by pycodestyle
Since a few days ago pycodestyle (formerly called pep8) has a new check:
E722 warning for bare except clauses.

Let's fix our code.
2017-10-27 17:22:35 +02:00
Adrien Vergé
2d931b5a81 yamllint version 1.9.0 2017-10-16 22:52:06 +02:00
Adrien Vergé
773bfc0f3c key-ordering: Add more test cases and documentation 2017-10-16 22:49:39 +02:00
Johannes F. Knauf
1543d0e435 New rule key-ordering
closes #67
2017-10-16 22:49:39 +02:00
Adrien Vergé
f82346dac7 indentation: Add more test cases for key following empty list 2017-10-16 22:17:58 +02:00
Tim Wade
ca540c113b Fix indentation rule for key following empty list
If a key-value pair follows an empty list, i.e.:

```yaml
a:
-
b: c
```

yamllint will complain:

```
warning  wrong indentation: expected 2 but found 0  (indentation)
```

This is because it is expecting the second key to be a continuation of
the block entry above:

```yaml
a:
-
  b: c
```

However, both are perfectly valid, though structurally different.
2017-10-16 22:17:58 +02:00
Adrien Vergé
c8fc170ff0 yamllint version 1.8.2 2017-10-10 12:30:00 +02:00
Adrien Vergé
c4a3e15ff0 docs(readthedocs): Fix builds on yamllint.readthedocs.io
Documentation builds on readthedocs.io partly fail because some modules
imported by yammlint cannot be imported in Sphinx automodule.

This commit fixes that using the tip at [1].

Closes #66

[1]: http://docs.readthedocs.io/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules
2017-09-03 16:08:42 +02:00
Sebastian Finke
db57127971 docs(integration): Fix pre-commit config file 2017-08-17 12:07:23 +02:00
blackillzone
c8e516be2f Add documentation for pre-commit 2017-07-19 14:56:21 +02:00
blackillzone
1c0dd48ccd Update pre-commit hook file 2017-07-19 14:56:21 +02:00
Adrien Vergé
f4edb85a04 fix(config): Be clearer about the ignore conf type 2017-07-19 09:48:00 +02:00
Adrien Vergé
d99bb9fec3 yamllint version 1.8.1 2017-07-04 22:23:02 +02:00
Adrien Vergé
3c4013fda1 docs(CHANGELOG): Add a changelog
Closes #57
2017-07-04 22:20:57 +02:00
Adrien Vergé
1a961bd4b0 chore(tests): Also run tests on Python 2.6 2017-07-04 22:07:32 +02:00
Adrien Vergé
7a8cfeed6d chore(deps): Require pathspec >= 0.5.3
This new version adds support for Python 2.6.
2017-07-04 22:07:21 +02:00
Adrien Vergé
f9709bc6e6 yamllint version 1.8.0 2017-06-28 15:30:39 +02:00
Adrien Vergé
5060917e40 style(cli): Space import sections 2017-06-28 15:20:24 +02:00
Adrien Vergé
a052cf7dba chore(tests): Add flake8-import-order linter plugin 2017-06-28 15:18:40 +02:00
Adrien Vergé
ae33716529 chore(tests): Also run tests on Python 3.6 2017-06-28 15:14:46 +02:00
Adrien Vergé
df26cc0438 feat(config): Add support to ignore paths on per-rule basis
Example of configuration to use this feature:

    # For all rules
    ignore: |
      *.dont-lint-me.yaml
      /bin/
      !/bin/*.lint-me-anyway.yaml

    rules:
      key-duplicates:
        ignore: |
          generated
          *.template.yaml
      trailing-spaces:
        ignore: |
          *.ignore-trailing-spaces.yaml
          /ascii-art/*

Closes #43.
2017-06-28 15:14:46 +02:00
Adrien Vergé
342d7b49dd tests(cli): Create a temp test workspace only once
Do not re-create it for every test in the class.
2017-06-28 15:11:24 +02:00
Adrien Vergé
7d638d47b9 tests(cli): Refactor temp test workspace recreation
Make it simpler and re-usable.
2017-06-28 15:11:24 +02:00
Adrien Vergé
db116eaaaf Merge pull request #51 from sedrubal/feature_use-argparse-mutually_exclusive_group
Use argparse mutually_exclusive_group for --config-file and --config-data
2017-05-31 22:43:44 +02:00
sedrubal
30dfa78923 Use argparse mutually_exclusive_group for --config-file and --config-data
This does the same as your solution 😉
2017-05-28 22:59:33 +02:00
Adrien Vergé
4ae829c062 yamllint version 1.7.0 2017-04-25 17:09:50 +02:00
Adrien Vergé
400aa084da Merge pull request #46 from krzysztof-magosa/master
Add information about Emacs integration
2017-03-26 19:49:50 +02:00
Krzysztof Magosa
a825645cbe Add information about Emacs integration 2017-03-25 18:53:09 +01:00
Adrien Vergé
1764e32def Merge pull request #45 from jayvdb/add-__main__
Add __main__
2017-03-21 11:36:58 +01:00
Adrien Vergé
d6a81f1b23 Add tests for python -m yamllint 2017-03-21 11:34:52 +01:00
John Vandenberg
38d14c7314 Add __main__
Allows execution using python -m yamllint
2017-03-21 16:00:58 +07:00
Adrien Vergé
ff1c9ad221 Merge pull request #38 from jhriggs/feature/empty_braces_brackets
Add min-spaces-inside-empty, max-spaces-inside-empty to braces and brackets
2017-03-13 13:38:16 +01:00
Jim Riggs
4b2b57aa32 Rules: Add min-spaces-inside-empty and max-spaces-inside-empty
Add min-spaces-inside-empty and max-spaces-inside-empty to braces and
brackets to allow separate handling for empty and non-empty objects.
2017-03-13 13:36:55 +01:00
Adrien Vergé
51b6d8377f Merge pull request #42 from polyzen/doc-ALE.vim
Doc: Add ALE Vim plugin
2017-02-28 20:42:35 +01:00
Daniel M. Capella
f507319419 Doc: Add ALE Vim plugin 2017-02-28 14:01:25 -05:00
Adrien Vergé
c0c8534501 Merge pull request #40 from jwilk/spelling
Fix typos
2017-02-28 07:59:06 +01:00
Jakub Wilk
2b26cbc56b Fix typos 2017-02-28 00:22:49 +01:00
Adrien Vergé
c037d3e586 yamllint version 1.6.1 2017-02-25 22:43:55 +01:00
Adrien Vergé
228c47ab77 fix(indentation): Fix seq indent detection with consistent spaces
In the case when the conf is as follows:

    indentation:
      spaces: consistent
      indent-sequences: true

and there is no indented block before the first block sequence, and this
block sequence is not indented, then the spaces number is computed as
zero (while it obviously shouldn't be).

This causes such a document to fail on 4th line, instead of 2nd:

    a:
    - b
    c:
      - d

This commit fixes that, and adds corresponding tests.

Fixes: #39
2017-02-25 22:43:55 +01:00
Adrien Vergé
413d7a8e4e Merge pull request #32 from mtnbikenc/typo-fix
Minor cosmetic typo
2017-01-11 21:04:02 +01:00
Russell Teague
c332c8e3d4 Minor cosmetic typo 2017-01-11 13:52:19 -05:00
Adrien Vergé
ea67ba3394 Merge pull request #31 from bootswithdefer/master
support for pre-commit from yelp
2016-12-28 19:13:39 +01:00
bootswithdefer
a7dbfb08b3 support for pre-commit from yelp 2016-12-28 19:11:32 +01:00
Adrien Vergé
42eda54014 yamllint version 1.6.0 2016-11-30 09:37:52 +01:00
Adrien Vergé
e909692f88 docs(truthy): Fix typo 2016-11-18 14:28:46 +01:00
Adrien Vergé
3bc72d4c40 feat(CI): Enforce strict checking of YAML files
Use the `--strict` flag to check all rules on local YAML files, to
prevent all problems (including warnings). This includes the newly added
`truthy` rule.
2016-11-18 12:02:30 +01:00
Adrien Vergé
21e81b6435 fix(rules): Use true/false, not yes/no
Although `yes` and `no` are recognized as booleans by the pyyaml parser,
the correct keywords are `true` and `false` (as highlighted by the newly
added `truthy` rule).

This commit replaces the use of `yes`/`no` by `true`/`false` and
advertise it in the docs, but also makes sure this change is
backward-compatible (so that `yes` and `no` still work).
2016-11-18 12:02:02 +01:00
Adrien Vergé
b97b6ad19b style(tests): Fix new flake8 errors
This change fixes new errors detected by the last version of pycodestyle
(2.2.0), which is a dependency of flake8:

    ./tests/test_spec_examples.py:51:1: E305 expected 2 blank lines
    after class or function definition, found 1
    ./tests/test_spec_examples.py:139:1: E305 expected 2 blank lines
    after class or function definition, found 1

See pycodestyle changelog at 2.2.0 and
https://github.com/PyCQA/pycodestyle/pull/593.
2016-11-17 12:24:38 +01:00
Adrien Vergé
2b7f5c5e72 docs(install): Update Debian version
yamllint is now backported in Debian 8 (Jessie):
https://tracker.debian.org/pkg/yamllint
https://packages.debian.org/source/jessie-backports/yamllint
2016-11-16 18:50:13 +01:00
Adrien Vergé
64369db9a2 docs(configuration): Fix typo 2016-11-07 18:11:33 +01:00
Adrien Vergé
2428f6eeaf Merge pull request #27 from jsok/strict-mode
Strict mode
2016-11-07 18:10:57 +01:00
Jonathan Sokolowski
bf386b3c90 docs: Explain strict mode return codes 2016-10-24 14:15:23 +11:00
Jonathan Sokolowski
03e0f5aa6b Add strict mode argument to CLI 2016-10-24 14:08:46 +11:00
Adrien Vergé
3b2a73d224 Merge pull request #25 from adamchainz/universal_wheels
Release as a universal wheel
2016-10-14 09:38:10 +02:00
Adam Chainz
3c525ab743 Release as a universal wheel
By releasing as a [Python wheel](http://pythonwheels.com/) as well as a
source distribution, you can speed up end user’s installs. After merging
this command, to release you just need to run `python setup.py clean
sdist bdist_wheel upload`.
2016-10-14 09:37:38 +02:00
Adrien Vergé
559ad5574b yamllint version 1.5.0 2016-10-08 11:56:46 +02:00
Adrien Vergé
adcb2d2953 Merge pull request #24 from adamchainz/dont_package_tests
setup.py - don't distribute tests
2016-10-08 11:54:46 +02:00
Adam Chainz
e948509fe5 setup.py - don't distribute tests
Found them installed in my `site-packages`, importable as `import tests` 😱

Tested with:

```
In [2]: find_packages()
Out[2]: ['tests', 'yamllint', 'tests.rules', 'yamllint.rules']

In [3]: find_packages(exclude=['tests', 'tests.*'])
Out[3]: ['yamllint', 'yamllint.rules']
```
2016-10-07 10:52:48 +01:00
Adrien Vergé
6dae8f5b6e feat(truthy): Allow explicit types
With this change, we don't require quotes for truthy values that are
explicitly typed. For instance, the following examples are all
considered valid:

    string1: !!str True
    string2: !!str yes
    string3: !!str off
    encoded: !!binary |
               True
               OFF
               pad==  # this decodes as 'N\xbb\x9e8Qii'
    boolean1: !!bool true
    boolean2: !!bool "false"
    boolean3: !!bool FALSE
    boolean4: !!bool True
    boolean5: !!bool off
    boolean6: !!bool NO
2016-10-02 08:15:53 +02:00
Adrien Vergé
073462a87d docs(rules): Fix missing truthy rule in index 2016-10-01 10:00:23 +02:00
Adrien Vergé
4b9ba9e201 docs(truthy): Enhance rule documentation 2016-10-01 10:00:23 +02:00
Peter Ericson
5294ff5552 truthy: Add tests for explicit booleans
From @adrienverge
2016-10-01 09:04:37 +02:00
Peter Ericson
1f472bc144 Add rule: truthy, to forbid truthy values that are not quoted 2016-10-01 09:03:59 +02:00
Adrien Vergé
c163135ee5 yamllint version 1.4.1 2016-09-27 09:46:40 +02:00
Adrien Vergé
f656cf42d2 fix(line-length): Wrap token scanning securely
With `allow-non-breakable-inline-mappings` enabled, every long line is
passed through `loader.peek_token()`. Even lines that are not valid
YAML. For this reason, this code must be wrapped in a `try`/`except`
block.

Closes: #21
2016-09-27 09:27:43 +02:00
Adrien Vergé
9b72a2d29a Merge branch 'adamchainz-readthedocs.io' 2016-09-21 10:47:01 +02:00
Adam Chainz
d7c17c7e7c Doc: Convert readthedocs links from .org to .io
As per [their blog post of the 27th
April](https://blog.readthedocs.com/securing-subdomains/) ‘Securing
subdomains’:

> Starting today, Read the Docs will start hosting projects from
> subdomains on the domain readthedocs.io, instead of on
> readthedocs.org. This change addresses some security concerns around
> site cookies while hosting user generated data on the same domain as
> our dashboard.

Test Plan: Manually visited all the links I’ve modified.
2016-09-21 10:36:00 +02:00
Adrien Vergé
60b72daad4 yamllint version 1.4.0 2016-09-19 13:53:03 +02:00
Adrien Vergé
773bb8a648 Merge pull request #17 from allanlewis/improve-unbreakable
line_length: Allow mapping values with long unbreakable lines
2016-09-19 13:51:44 +02:00
Adrien Vergé
d3cd8ba332 line-length: Generalize ...-inline-mappings for corner cases
This commit refactors the `allow-non-breakable-inline-mappings` logic to
use YAML tokens and avoid crashes or erroneous reports on cases like:

```yaml
- {a: "http://localhost/very/very/very/very/very/very/long/url"
   }
```

```yaml
dict:
  {a: long long long long long long long, b: nospace}
```

```yaml
- long_line: http://localhost/very/very/long/url
```

```yaml
long_line: and+some+space+at+the+end       <-- extra spaces
```

For reference see:
https://github.com/adrienverge/yamllint/pull/17#issuecomment-247805799
2016-09-19 12:39:52 +01:00
Allan Lewis
e56a7c788c line_length: Extract inline logic to new config option
This commit extracts the inline mappings logic defined in the previous
commit to a separate config option, as suggested by @adrienverge. I'll
squash this into the previous commit if the change is accepted. (I named
the option slightly differently to what was suggested as I think my
proposal reads better without consulting the docs: I'd be happy to
reconsider this.)
2016-09-19 12:39:52 +01:00
Allan Lewis
d017631aff line_length: Allow mapping values with long unbreakable lines 2016-09-12 16:31:58 +01:00
Adrien Vergé
5b98cd2053 feat(comments): Allow comments with multiple hash chars
This change make the `comments` rule accept comments that start with
multiple pound signs, e.g.:

    ##############################
    ## This is some documentation

Closes: #12
2016-08-12 11:58:57 +02:00
Adrien Vergé
82dd7dbf16 Merge pull request #16 from adrienverge/coloured_output_on_tty
feat(cli): Colour output only on TTY
2016-08-12 11:58:00 +02:00
Adrien Vergé
4533b8ae49 doc(config): Show relaxed conf contents
Closes: #15
2016-08-12 11:17:18 +02:00
Adrien Vergé
a2c68fdf9b feat(cli): Colour output only on TTY
When piping yamllint output to a file, "coloured" characters aren't
interpreted and pollute text formatting with glyphs like:

  �[4m./global.yaml�[0m
    �[2m1439:52�[0m   �[31merror�[0m    no new line character...

With this commit, stdout is checked: if it's a TTY then output is
coloured, otherwise output is simple text.

Closes: #14
2016-08-12 11:03:41 +02:00
Adrien Vergé
82ed191bc9 yamllint version 1.3.2 2016-06-28 12:06:58 +02:00
Adrien Vergé
92ff315fb4 Tests: Set proper LC_ALL when decoding UTF-8 is needed
Make sure the default localization conditions on the "test system"
support UTF-8 encoding.
2016-06-28 12:06:55 +02:00
Adrien Vergé
f4cebdc054 Tests: Run with LC_ALL=C for uniform tests
Use default (C) locale in all tests to make sure the localization
conditions are the same wherever tests are run.
2016-06-28 11:04:50 +02:00
Adrien Vergé
d174f9e3e3 yamllint version 1.3.1 2016-06-28 10:10:34 +02:00
Adrien Vergé
c8ba8f7e99 linter: Fix UnicodeError when parsing comments
And add tests when reading non-ASCII strings and comments (both from
Python strings and from files).

Fixes: #10
2016-06-28 09:58:23 +02:00
55 changed files with 2592 additions and 208 deletions

11
.pre-commit-hooks.yaml Normal file
View File

@@ -0,0 +1,11 @@
---
# For use with pre-commit.
# See usage instructions at http://pre-commit.com
- id: yamllint
name: yamllint
description: This hook runs yamllint.
entry: yamllint
language: python
types: [file, yaml]

View File

@@ -1,17 +1,26 @@
---
language: python
python:
- 2.6
- 2.7
- 3.3
- 3.4
- 3.5
- 3.6
- nightly
install:
- pip install pyyaml flake8 coveralls
- pip install pyyaml flake8 flake8-import-order coveralls sphinx
- if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi
- pip install .
script:
- flake8 .
- yamllint $(git ls-files '*.yaml' '*.yml')
- if [[ $TRAVIS_PYTHON_VERSION != 2.6 ]]; then flake8 .; fi
# Because of https://github.com/PyCQA/flake8-import-order/issues/149
# otherwise tests fail with Python 3.6:
- pip uninstall --yes enum34
- yamllint --strict $(git ls-files '*.yaml' '*.yml')
- coverage run --source=yamllint setup.py test
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then
python setup.py build_sphinx;
fi
after_success:
coveralls

43
CHANGELOG.rst Normal file
View File

@@ -0,0 +1,43 @@
Changelog
=========
1.11.0 (2018-02-21)
-------------------
- Add a new `octal-values` rule
1.10.0 (2017-11-05)
-------------------
- Fix colored output on Windows
- Check documentation compilation on continuous integration
- Add a new `empty-values` rule
- Make sure test files are included in dist bundle
- Tests: Use en_US.UTF-8 locale when C.UTF-8 not available
- Tests: Dynamically detect Python executable path
1.9.0 (2017-10-16)
------------------
- Add a new `key-ordering` rule
- Fix indentation rule for key following empty list
1.8.2 (2017-10-10)
------------------
- Be clearer about the `ignore` conf type
- Update pre-commit hook file
- Add documentation for pre-commit
1.8.1 (2017-07-04)
------------------
- Require pathspec >= 0.5.3
- Support Python 2.6
- Add a changelog
1.8.0 (2017-06-28)
------------------
- Refactor argparse with mutually_exclusive_group
- Add support to ignore paths in configuration

View File

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

View File

@@ -16,7 +16,7 @@ indentation, etc.
:target: https://coveralls.io/github/adrienverge/yamllint?branch=master
:alt: Code coverage status
.. image:: https://readthedocs.org/projects/yamllint/badge/?version=latest
:target: http://yamllint.readthedocs.org/en/latest/?badge=latest
:target: https://yamllint.readthedocs.io/en/latest/?badge=latest
:alt: Documentation status
Written in Python (compatible with Python 2 & 3).
@@ -24,7 +24,7 @@ Written in Python (compatible with Python 2 & 3).
Documentation
-------------
http://yamllint.readthedocs.io/
https://yamllint.readthedocs.io/
Overview
--------
@@ -44,7 +44,7 @@ On Fedora / CentOS:
sudo dnf install yamllint
On Debian 9+ / Ubuntu 16.04+:
On Debian 8+ / Ubuntu 16.04+:
.. code:: bash
@@ -82,7 +82,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! <http://yamllint.readthedocs.io/>`_
`Read more in the complete documentation! <https://yamllint.readthedocs.io/>`_
Features
^^^^^^^^
@@ -119,7 +119,28 @@ or for a whole block:
consectetur : adipiscing elit
# yamllint enable
`Read more in the complete documentation! <http://yamllint.readthedocs.io/>`_
Specific files can be ignored (totally or for some rules only) using a
``.gitignore``-style pattern:
.. code:: yaml
# For all rules
ignore: |
*.dont-lint-me.yaml
/bin/
!/bin/*.lint-me-anyway.yaml
rules:
key-duplicates:
ignore: |
generated
*.template.yaml
trailing-spaces:
ignore: |
*.ignore-trailing-spaces.yaml
/ascii-art/*
`Read more in the complete documentation! <https://yamllint.readthedocs.io/>`_
License
-------

View File

@@ -4,6 +4,7 @@
import sys
import os
from unittest.mock import MagicMock
sys.path.insert(0, os.path.abspath('..')) # noqa
@@ -40,3 +41,15 @@ htmlhelp_basename = 'yamllintdoc'
man_pages = [
('index', 'yamllint', '', [u'Adrien Vergé'], 1)
]
# -- Build with sphinx automodule without needing to install third-party libs
class Mock(MagicMock):
@classmethod
def __getattr__(cls, name):
return MagicMock()
MOCK_MODULES = ['pathspec', 'yaml']
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)

View File

@@ -31,7 +31,10 @@ Unless told otherwise, yamllint uses its ``default`` configuration:
Details on rules can be found on :doc:`the rules page <rules>`.
There is another pre-defined configuration named ``relaxed``. As its name
suggests, it is more tolerant.
suggests, it is more tolerant:
.. literalinclude:: ../yamllint/conf/relaxed.yaml
:language: yaml
It can be chosen using:
@@ -99,8 +102,69 @@ Errors and warnings
-------------------
Problems detected by yamllint can be raised either as errors or as warnings.
The CLI will output them (with different colors when using the ``standard``
output format).
In both cases, the script will output them (with different colors when using the
``standard`` output format), but the exit code can be different. More precisely,
the script will exit will a failure code *only when* there is one or more
error(s).
By default the script will exit with a return code ``1`` *only when* there is one or
more error(s).
However if strict mode is enabled with the ``-s`` (or ``--strict``) option, the
return code will be:
* ``0`` if no errors or warnings occur
* ``1`` if one or more errors occur
* ``2`` if no errors occur, but one or more warnings occur
Ignoring paths
--------------
It is possible to exclude specific files or directories, so that the linter
doesn't process them.
You can either totally ignore files (they won't be looked at):
.. code-block:: yaml
extends: default
ignore: |
/this/specific/file.yaml
/all/this/directory/
*.template.yaml
or ignore paths only for specific rules:
.. code-block:: yaml
extends: default
rules:
trailing-spaces:
ignore: |
/this-file-has-trailing-spaces-but-it-is-OK.yaml
/generated/*.yaml
Note that this ``.gitignore``-style path pattern allows complex path
exclusion/inclusion, see the `pathspec README file
<https://pypi.python.org/pypi/pathspec>`_ for more details.
Here is a more complex example:
.. code-block:: yaml
# For all rules
ignore: |
*.dont-lint-me.yaml
/bin/
!/bin/*.lint-me-anyway.yaml
extends: default
rules:
key-duplicates:
ignore: |
generated
*.template.yaml
trailing-spaces:
ignore: |
*.ignore-trailing-spaces.yaml
/ascii-art/*

View File

@@ -32,7 +32,7 @@ or:
- This line is waaaaaaaaaay too long but yamllint will not report anything about it.
This line will be checked by yamllint.
It it possible, although not recommend, to disabled **all** rules for a
It is possible, although not recommend, to disabled **all** rules for a
specific line:
.. code-block:: yaml
@@ -46,7 +46,7 @@ If you need to disable multiple rules, it is allowed to chain rules like this:
Disabling checks for all (or part of) the file
----------------------------------------------
To prevent yamllint from reporting problems for the whoe file, or for a block of
To prevent yamllint from reporting problems for the whole file, or for a block of
lines within the file, use ``# yamllint disable ...`` and ``# yamllint enable
...`` directive comments. For instance:
@@ -60,7 +60,7 @@ lines within the file, use ``# yamllint disable ...`` and ``# yamllint enable
- rest of the document...
It it possible, although not recommend, to disabled **all** rules:
It is possible, although not recommend, to disabled **all** rules:
.. code-block:: yaml

View File

@@ -26,3 +26,4 @@ Table of contents
disable_with_comments
development
text_editors
integration

17
docs/integration.rst Normal file
View File

@@ -0,0 +1,17 @@
Integration with other software
===============================
Integration with pre-commit
---------------------------
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
---
# Update the sha variable with the release version that you want, from the yamllint repo
- repo: https://github.com/adrienverge/yamllint.git
sha: v1.8.1
hooks:
- id: yamllint

View File

@@ -10,7 +10,7 @@ On Fedora / CentOS:
sudo dnf install yamllint
On Debian 9+ / Ubuntu 16.04+:
On Debian 8+ / Ubuntu 16.04+:
.. code:: bash

View File

@@ -59,6 +59,11 @@ empty-lines
.. automodule:: yamllint.rules.empty_lines
empty-values
------------
.. automodule:: yamllint.rules.empty_values
hyphens
-------
@@ -74,6 +79,11 @@ key-duplicates
.. automodule:: yamllint.rules.key_duplicates
key-ordering
--------------
.. automodule:: yamllint.rules.key_ordering
line-length
-----------
@@ -89,7 +99,17 @@ new-lines
.. automodule:: yamllint.rules.new_lines
octal-values
------------
.. automodule:: yamllint.rules.octal_values
trailing-spaces
---------------
.. automodule:: yamllint.rules.trailing_spaces
truthy
---------------
.. automodule:: yamllint.rules.truthy

View File

@@ -9,8 +9,12 @@ text editor.
Vim
---
Assuming that the `syntastic <https://github.com/scrooloose/syntastic>`_ plugin
is installed, add to your ``.vimrc``:
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/scrooloose/syntastic>`_
plugin, add this to your ``.vimrc``:
::
@@ -23,6 +27,12 @@ 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.
Emacs
-----
If you are `flycheck <https://github.com/flycheck/flycheck>`_ user, you can use
`flycheck-yamllint <https://github.com/krzysztof-magosa/flycheck-yamllint>`_ integration.
Other text editors
------------------

12
setup.cfg Normal file
View File

@@ -0,0 +1,12 @@
[bdist_wheel]
universal = 1
[flake8]
import-order-style = pep8
application-import-names = yamllint
[build_sphinx]
all-files = 1
source-dir = docs
build-dir = docs/_build
warning-is-error = 1

View File

@@ -42,11 +42,9 @@ setup(
'Topic :: Software Development :: Testing',
],
packages=find_packages(),
packages=find_packages(exclude=['tests', 'tests.*']),
entry_points={'console_scripts': ['yamllint=yamllint.cli:run']},
package_data={'yamllint': ['conf/*.yaml'],
'tests': ['yaml-1.2-spec-examples/*']},
install_requires=['pyyaml'],
tests_require=['nose'],
test_suite='nose.collector',
package_data={'yamllint': ['conf/*.yaml']},
install_requires=['pathspec >=0.5.3', 'pyyaml'],
test_suite='tests',
)

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import locale
locale.setlocale(locale.LC_ALL, 'C')

View File

@@ -14,7 +14,14 @@
# 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 os
import tempfile
import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except AssertionError:
import unittest2 as unittest
import yaml
@@ -49,3 +56,21 @@ class RuleTestCase(unittest.TestCase):
real_problems = list(linter.run(source, self.build_fake_config(conf)))
self.assertEqual(real_problems, expected_problems)
def build_temp_workspace(files):
tempdir = tempfile.mkdtemp(prefix='yamllint-tests-')
for path, content in files.items():
path = os.path.join(tempdir, path)
if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))
if type(content) is list:
os.mkdir(path)
else:
mode = 'wb' if isinstance(content, bytes) else 'w'
with open(path, mode) as f:
f.write(content)
return tempdir

View File

@@ -32,11 +32,19 @@ class ColonTestCase(RuleTestCase):
'dict7: { a: 1, b, c: 3 }\n', conf)
def test_min_spaces(self):
conf = 'braces: {max-spaces-inside: -1, min-spaces-inside: 0}'
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 0\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'dict: {}\n', conf)
conf = 'braces: {max-spaces-inside: -1, min-spaces-inside: 1}'
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'dict: {}\n', conf, problem=(2, 8))
self.check('---\n'
@@ -52,7 +60,11 @@ class ColonTestCase(RuleTestCase):
' b\n'
'}\n', conf)
conf = 'braces: {max-spaces-inside: -1, min-spaces-inside: 3}'
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 3\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'dict: { a: 1, b }\n', conf,
problem1=(2, 9), problem2=(2, 17))
@@ -60,7 +72,11 @@ class ColonTestCase(RuleTestCase):
'dict: { a: 1, b }\n', conf)
def test_max_spaces(self):
conf = 'braces: {max-spaces-inside: 0, min-spaces-inside: -1}'
conf = ('braces:\n'
' max-spaces-inside: 0\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'dict: {}\n', conf)
self.check('---\n'
@@ -79,7 +95,11 @@ class ColonTestCase(RuleTestCase):
' b\n'
'}\n', conf)
conf = 'braces: {max-spaces-inside: 3, min-spaces-inside: -1}'
conf = ('braces:\n'
' max-spaces-inside: 3\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'dict: { a: 1, b }\n', conf)
self.check('---\n'
@@ -87,7 +107,11 @@ class ColonTestCase(RuleTestCase):
problem1=(2, 11), problem2=(2, 23))
def test_min_and_max_spaces(self):
conf = 'braces: {max-spaces-inside: 0, min-spaces-inside: 0}'
conf = ('braces:\n'
' max-spaces-inside: 0\n'
' min-spaces-inside: 0\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'dict: {}\n', conf)
self.check('---\n'
@@ -95,14 +119,169 @@ class ColonTestCase(RuleTestCase):
self.check('---\n'
'dict: { a: 1, b}\n', conf, problem=(2, 10))
conf = 'braces: {max-spaces-inside: 1, min-spaces-inside: 1}'
conf = ('braces:\n'
' max-spaces-inside: 1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'dict: {a: 1, b, c: 3 }\n', conf, problem=(2, 8))
conf = 'braces: {max-spaces-inside: 2, min-spaces-inside: 0}'
conf = ('braces:\n'
' max-spaces-inside: 2\n'
' min-spaces-inside: 0\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'dict: {a: 1, b, c: 3 }\n', conf)
self.check('---\n'
'dict: { a: 1, b, c: 3 }\n', conf)
self.check('---\n'
'dict: { a: 1, b, c: 3 }\n', conf, problem=(2, 10))
def test_min_spaces_empty(self):
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 0\n'
' min-spaces-inside-empty: 0\n')
self.check('---\n'
'array: {}\n', conf)
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: {}\n', conf, problem=(2, 9))
self.check('---\n'
'array: { }\n', conf)
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: 3\n')
self.check('---\n'
'array: {}\n', conf, problem=(2, 9))
self.check('---\n'
'array: { }\n', conf)
def test_max_spaces_empty(self):
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 0\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: {}\n', conf)
self.check('---\n'
'array: { }\n', conf, problem=(2, 9))
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: {}\n', conf)
self.check('---\n'
'array: { }\n', conf)
self.check('---\n'
'array: { }\n', conf, problem=(2, 10))
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 3\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: {}\n', conf)
self.check('---\n'
'array: { }\n', conf)
self.check('---\n'
'array: { }\n', conf, problem=(2, 12))
def test_min_and_max_spaces_empty(self):
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 2\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: {}\n', conf, problem=(2, 9))
self.check('---\n'
'array: { }\n', conf)
self.check('---\n'
'array: { }\n', conf)
self.check('---\n'
'array: { }\n', conf, problem=(2, 11))
def test_mixed_empty_nonempty(self):
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: 0\n'
' min-spaces-inside-empty: 0\n')
self.check('---\n'
'array: { a: 1, b }\n', conf)
self.check('---\n'
'array: {a: 1, b}\n', conf,
problem1=(2, 9), problem2=(2, 16))
self.check('---\n'
'array: {}\n', conf)
self.check('---\n'
'array: { }\n', conf,
problem1=(2, 9))
conf = ('braces:\n'
' max-spaces-inside: 0\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: { a: 1, b }\n', conf,
problem1=(2, 9), problem2=(2, 17))
self.check('---\n'
'array: {a: 1, b}\n', conf)
self.check('---\n'
'array: {}\n', conf,
problem1=(2, 9))
self.check('---\n'
'array: { }\n', conf)
conf = ('braces:\n'
' max-spaces-inside: 2\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: { a: 1, b }\n', conf)
self.check('---\n'
'array: {a: 1, b }\n', conf,
problem1=(2, 9), problem2=(2, 18))
self.check('---\n'
'array: {}\n', conf,
problem1=(2, 9))
self.check('---\n'
'array: { }\n', conf)
self.check('---\n'
'array: { }\n', conf,
problem1=(2, 11))
conf = ('braces:\n'
' max-spaces-inside: 1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: { a: 1, b }\n', conf)
self.check('---\n'
'array: {a: 1, b}\n', conf,
problem1=(2, 9), problem2=(2, 16))
self.check('---\n'
'array: {}\n', conf,
problem1=(2, 9))
self.check('---\n'
'array: { }\n', conf)

View File

@@ -32,11 +32,19 @@ class ColonTestCase(RuleTestCase):
'array7: [ a, b, c ]\n', conf)
def test_min_spaces(self):
conf = 'brackets: {max-spaces-inside: -1, min-spaces-inside: 0}'
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 0\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: []\n', conf)
conf = 'brackets: {max-spaces-inside: -1, min-spaces-inside: 1}'
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: []\n', conf, problem=(2, 9))
self.check('---\n'
@@ -51,7 +59,11 @@ class ColonTestCase(RuleTestCase):
' b\n'
']\n', conf)
conf = 'brackets: {max-spaces-inside: -1, min-spaces-inside: 3}'
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 3\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: [ a, b ]\n', conf,
problem1=(2, 10), problem2=(2, 15))
@@ -59,7 +71,11 @@ class ColonTestCase(RuleTestCase):
'array: [ a, b ]\n', conf)
def test_max_spaces(self):
conf = 'brackets: {max-spaces-inside: 0, min-spaces-inside: -1}'
conf = ('brackets:\n'
' max-spaces-inside: 0\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
@@ -78,7 +94,11 @@ class ColonTestCase(RuleTestCase):
' b\n'
']\n', conf)
conf = 'brackets: {max-spaces-inside: 3, min-spaces-inside: -1}'
conf = ('brackets:\n'
' max-spaces-inside: 3\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: [ a, b ]\n', conf)
self.check('---\n'
@@ -86,7 +106,11 @@ class ColonTestCase(RuleTestCase):
problem1=(2, 12), problem2=(2, 21))
def test_min_and_max_spaces(self):
conf = 'brackets: {max-spaces-inside: 0, min-spaces-inside: 0}'
conf = ('brackets:\n'
' max-spaces-inside: 0\n'
' min-spaces-inside: 0\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
@@ -94,14 +118,169 @@ class ColonTestCase(RuleTestCase):
self.check('---\n'
'array: [ a, b]\n', conf, problem=(2, 11))
conf = 'brackets: {max-spaces-inside: 1, min-spaces-inside: 1}'
conf = ('brackets:\n'
' max-spaces-inside: 1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: [a, b, c ]\n', conf, problem=(2, 9))
conf = 'brackets: {max-spaces-inside: 2, min-spaces-inside: 0}'
conf = ('brackets:\n'
' max-spaces-inside: 2\n'
' min-spaces-inside: 0\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: [a, b, c ]\n', conf)
self.check('---\n'
'array: [ a, b, c ]\n', conf)
self.check('---\n'
'array: [ a, b, c ]\n', conf, problem=(2, 11))
def test_min_spaces_empty(self):
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 0\n'
' min-spaces-inside-empty: 0\n')
self.check('---\n'
'array: []\n', conf)
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: []\n', conf, problem=(2, 9))
self.check('---\n'
'array: [ ]\n', conf)
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: 3\n')
self.check('---\n'
'array: []\n', conf, problem=(2, 9))
self.check('---\n'
'array: [ ]\n', conf)
def test_max_spaces_empty(self):
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 0\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
'array: [ ]\n', conf, problem=(2, 9))
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
'array: [ ]\n', conf)
self.check('---\n'
'array: [ ]\n', conf, problem=(2, 10))
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 3\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
'array: [ ]\n', conf)
self.check('---\n'
'array: [ ]\n', conf, problem=(2, 12))
def test_min_and_max_spaces_empty(self):
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 2\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: []\n', conf, problem=(2, 9))
self.check('---\n'
'array: [ ]\n', conf)
self.check('---\n'
'array: [ ]\n', conf)
self.check('---\n'
'array: [ ]\n', conf, problem=(2, 11))
def test_mixed_empty_nonempty(self):
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: 0\n'
' min-spaces-inside-empty: 0\n')
self.check('---\n'
'array: [ a, b ]\n', conf)
self.check('---\n'
'array: [a, b]\n', conf,
problem1=(2, 9), problem2=(2, 13))
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
'array: [ ]\n', conf,
problem1=(2, 9))
conf = ('brackets:\n'
' max-spaces-inside: 0\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: [ a, b ]\n', conf,
problem1=(2, 9), problem2=(2, 14))
self.check('---\n'
'array: [a, b]\n', conf)
self.check('---\n'
'array: []\n', conf,
problem1=(2, 9))
self.check('---\n'
'array: [ ]\n', conf)
conf = ('brackets:\n'
' max-spaces-inside: 2\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: [ a, b ]\n', conf)
self.check('---\n'
'array: [a, b ]\n', conf,
problem1=(2, 9), problem2=(2, 15))
self.check('---\n'
'array: []\n', conf,
problem1=(2, 9))
self.check('---\n'
'array: [ ]\n', conf)
self.check('---\n'
'array: [ ]\n', conf,
problem1=(2, 11))
conf = ('brackets:\n'
' max-spaces-inside: 1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: [ a, b ]\n', conf)
self.check('---\n'
'array: [a, b]\n', conf,
problem1=(2, 9), problem2=(2, 13))
self.check('---\n'
'array: []\n', conf,
problem1=(2, 9))
self.check('---\n'
'array: [ ]\n', conf)

View File

@@ -35,11 +35,15 @@ class CommentsTestCase(RuleTestCase):
' #comment 3 bis\n'
' # comment 3 ter\n'
'\n'
'################################\n'
'## comment 4\n'
'##comment 5\n'
'\n'
'string: "Une longue phrase." # this is French\n', conf)
def test_starting_space(self):
conf = ('comments:\n'
' require-starting-space: yes\n'
' require-starting-space: true\n'
' min-spaces-from-content: -1\n'
'comments-indentation: disable\n')
self.check('---\n'
@@ -52,7 +56,11 @@ class CommentsTestCase(RuleTestCase):
'# comment 2\n'
'# comment 3\n'
' # comment 3 bis\n'
' # comment 3 ter\n', conf)
' # comment 3 ter\n'
'\n'
'################################\n'
'## comment 4\n'
'## comment 5\n', conf)
self.check('---\n'
'#comment\n'
'\n'
@@ -63,13 +71,18 @@ class CommentsTestCase(RuleTestCase):
'# comment 2\n'
'#comment 3\n'
' #comment 3 bis\n'
' # comment 3 ter\n', conf,
' # comment 3 ter\n'
'\n'
'################################\n'
'## comment 4\n'
'##comment 5\n', conf,
problem1=(2, 2), problem2=(6, 13),
problem4=(9, 2), problem5=(10, 4))
problem3=(9, 2), problem4=(10, 4),
problem5=(15, 3))
def test_spaces_from_content(self):
conf = ('comments:\n'
' require-starting-space: no\n'
' require-starting-space: false\n'
' min-spaces-from-content: 2\n')
self.check('---\n'
'# comment\n'
@@ -91,7 +104,7 @@ class CommentsTestCase(RuleTestCase):
def test_both(self):
conf = ('comments:\n'
' require-starting-space: yes\n'
' require-starting-space: true\n'
' min-spaces-from-content: 2\n'
'comments-indentation: disable\n')
self.check('---\n'
@@ -106,17 +119,22 @@ class CommentsTestCase(RuleTestCase):
' #comment 3 bis\n'
' # comment 3 ter\n'
'\n'
'################################\n'
'## comment 4\n'
'##comment 5\n'
'\n'
'string: "Une longue phrase." # this is French\n', conf,
problem1=(2, 2),
problem2=(4, 7),
problem3=(6, 11), problem4=(6, 12),
problem5=(9, 2),
problem6=(10, 4),
problem7=(13, 30))
problem7=(15, 3),
problem8=(17, 30))
def test_empty_comment(self):
conf = ('comments:\n'
' require-starting-space: yes\n'
' require-starting-space: true\n'
' min-spaces-from-content: 2\n')
self.check('---\n'
'# This is paragraph 1.\n'
@@ -128,13 +146,21 @@ class CommentsTestCase(RuleTestCase):
def test_first_line(self):
conf = ('comments:\n'
' require-starting-space: yes\n'
' require-starting-space: true\n'
' min-spaces-from-content: 2\n')
self.check('# comment\n', conf)
def test_last_line(self):
conf = ('comments:\n'
' require-starting-space: true\n'
' min-spaces-from-content: 2\n'
'new-line-at-end-of-file: disable\n')
self.check('# comment with no newline char:\n'
'#', conf)
def test_multi_line_scalar(self):
conf = ('comments:\n'
' require-starting-space: yes\n'
' require-starting-space: true\n'
' min-spaces-from-content: 2\n'
'trailing-spaces: disable\n')
self.check('---\n'

View File

@@ -102,13 +102,13 @@ class CommentsIndentationTestCase(RuleTestCase):
' a: 1\n'
' # b: 2\n'
'# this object is useless\n'
'obj2: no\n', conf)
'obj2: "no"\n', conf)
self.check('---\n'
'obj1:\n'
' a: 1\n'
'# this object is useless\n'
' # b: 2\n'
'obj2: no\n', conf, problem=(5, 3))
'obj2: "no"\n', conf, problem=(5, 3))
self.check('---\n'
'obj1:\n'
' a: 1\n'

View File

@@ -31,7 +31,7 @@ class DocumentEndTestCase(RuleTestCase):
' document: end\n', conf)
def test_required(self):
conf = 'document-end: {present: yes}'
conf = 'document-end: {present: true}'
self.check('', conf)
self.check('\n', conf)
self.check('---\n'
@@ -43,7 +43,7 @@ class DocumentEndTestCase(RuleTestCase):
' document: end\n', conf, problem=(3, 1))
def test_forbidden(self):
conf = 'document-end: {present: no}'
conf = 'document-end: {present: false}'
self.check('---\n'
'with:\n'
' document: end\n'
@@ -53,7 +53,7 @@ class DocumentEndTestCase(RuleTestCase):
' document: end\n', conf)
def test_multiple_documents(self):
conf = ('document-end: {present: yes}\n'
conf = ('document-end: {present: true}\n'
'document-start: disable\n')
self.check('---\n'
'first: document\n'

View File

@@ -28,7 +28,7 @@ class DocumentStartTestCase(RuleTestCase):
'key: val\n', conf)
def test_required(self):
conf = ('document-start: {present: yes}\n'
conf = ('document-start: {present: true}\n'
'empty-lines: disable\n')
self.check('', conf)
self.check('\n', conf)
@@ -44,7 +44,7 @@ class DocumentStartTestCase(RuleTestCase):
'key: val\n', conf)
def test_forbidden(self):
conf = ('document-start: {present: no}\n'
conf = ('document-start: {present: false}\n'
'empty-lines: disable\n')
self.check('', conf)
self.check('key: val\n', conf)
@@ -62,7 +62,7 @@ class DocumentStartTestCase(RuleTestCase):
'key: val\n', conf, problem=(2, 1))
def test_multiple_documents(self):
conf = 'document-start: {present: yes}'
conf = 'document-start: {present: true}'
self.check('---\n'
'first: document\n'
'...\n'
@@ -85,7 +85,7 @@ class DocumentStartTestCase(RuleTestCase):
'third: document\n', conf, problem=(4, 1, 'syntax'))
def test_directives(self):
conf = 'document-start: {present: yes}'
conf = 'document-start: {present: true}'
self.check('%YAML 1.2\n'
'---\n'
'doc: ument\n'

View File

@@ -0,0 +1,261 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Greg Dubicki
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.common import RuleTestCase
class EmptyValuesTestCase(RuleTestCase):
rule_id = 'empty-values'
def test_disabled(self):
conf = ('empty-values: disable\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'foo:\n', conf)
self.check('---\n'
'foo:\n'
' bar:\n', conf)
self.check('---\n'
'{a:}\n', conf)
self.check('---\n'
'foo: {a:}\n', conf)
self.check('---\n'
'- {a:}\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf)
self.check('---\n'
'{a: {b: , c: {d: 4, e:}}, f:}\n', conf)
def test_in_block_mappings_disabled(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n', conf)
self.check('---\n'
'foo:\n'
'bar: aaa\n', conf)
def test_in_block_mappings_single_line(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'implicitly-null:\n', conf, problem1=(2, 17))
self.check('---\n'
'implicitly-null:with-colons:in-key:\n', conf,
problem1=(2, 36))
self.check('---\n'
'implicitly-null:with-colons:in-key2:\n', conf,
problem1=(2, 37))
def test_in_block_mappings_all_lines(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
'bar:\n'
'foobar:\n', conf, problem1=(2, 5),
problem2=(3, 5), problem3=(4, 8))
def test_in_block_mappings_explicit_end_of_document(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
'...\n', conf, problem1=(2, 5))
def test_in_block_mappings_not_end_of_document(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
'bar:\n'
' aaa\n', conf, problem1=(2, 5))
def test_in_block_mappings_different_level(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
' bar:\n'
'aaa: bbb\n', conf, problem1=(3, 6))
def test_in_block_mappings_empty_flow_mapping(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'foo: {a:}\n', conf)
self.check('---\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf)
def test_in_block_mappings_empty_block_sequence(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
' -\n', conf)
def test_in_block_mappings_not_empty_or_explicit_null(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
' bar:\n'
' aaa\n', conf)
self.check('---\n'
'explicitly-null: null\n', conf)
self.check('---\n'
'explicitly-null:with-colons:in-key: null\n', conf)
self.check('---\n'
'false-null: nulL\n', conf)
self.check('---\n'
'empty-string: \'\'\n', conf)
self.check('---\n'
'nullable-boolean: false\n', conf)
self.check('---\n'
'nullable-int: 0\n', conf)
self.check('---\n'
'First occurrence: &anchor Foo\n'
'Second occurrence: *anchor\n', conf)
def test_in_block_mappings_various_explicit_null(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'null-alias: ~\n', conf)
self.check('---\n'
'null-key1: {?: val}\n', conf)
self.check('---\n'
'null-key2: {? !!null "": val}\n', conf)
def test_in_block_mappings_comments(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n'
'comments: disable\n')
self.check('---\n'
'empty: # comment\n'
'foo:\n'
' bar: # comment\n', conf,
problem1=(2, 7),
problem2=(4, 7))
def test_in_flow_mappings_disabled(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: false}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'{a:}\n', conf)
self.check('---\n'
'foo: {a:}\n', conf)
self.check('---\n'
'- {a:}\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf)
self.check('---\n'
'{a: {b: , c: {d: 4, e:}}, f:}\n', conf)
def test_in_flow_mappings_single_line(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'{a:}\n', conf,
problem=(2, 4))
self.check('---\n'
'foo: {a:}\n', conf,
problem=(2, 9))
self.check('---\n'
'- {a:}\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf,
problem1=(2, 6),
problem2=(3, 6),
problem3=(4, 12),
problem4=(5, 12))
self.check('---\n'
'{a: {b: , c: {d: 4, e:}}, f:}\n', conf,
problem1=(2, 8),
problem2=(2, 23),
problem3=(2, 29))
def test_in_flow_mappings_multi_line(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'foo: {\n'
' a:\n'
'}\n', conf,
problem=(3, 5))
self.check('---\n'
'{\n'
' a: {\n'
' b: ,\n'
' c: {\n'
' d: 4,\n'
' e:\n'
' }\n'
' },\n'
' f:\n'
'}\n', conf,
problem1=(4, 7),
problem2=(7, 9),
problem3=(10, 5))
def test_in_flow_mappings_various_explicit_null(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'{explicit-null: null}\n', conf)
self.check('---\n'
'{null-alias: ~}\n', conf)
self.check('---\n'
'null-key1: {?: val}\n', conf)
self.check('---\n'
'null-key2: {? !!null "": val}\n', conf)
def test_in_flow_mappings_comments(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n'
'comments: disable\n')
self.check('---\n'
'{\n'
' a: {\n'
' b: , # comment\n'
' c: {\n'
' d: 4, # comment\n'
' e: # comment\n'
' }\n'
' },\n'
' f: # comment\n'
'}\n', conf,
problem1=(4, 7),
problem2=(7, 9),
problem3=(10, 5))

View File

@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.common import RuleTestCase
from yamllint.parser import token_or_comment_generator, Comment
from yamllint.rules.indentation import check
@@ -549,7 +550,7 @@ class IndentationTestCase(RuleTestCase):
'...\n', conf)
def test_one_space(self):
conf = 'indentation: {spaces: 1, indent-sequences: no}'
conf = 'indentation: {spaces: 1, indent-sequences: false}'
self.check('---\n'
'object:\n'
' k1:\n'
@@ -562,7 +563,7 @@ class IndentationTestCase(RuleTestCase):
' - name: Linux\n'
' date: 1991\n'
'...\n', conf)
conf = 'indentation: {spaces: 1, indent-sequences: yes}'
conf = 'indentation: {spaces: 1, indent-sequences: true}'
self.check('---\n'
'object:\n'
' k1:\n'
@@ -577,7 +578,7 @@ class IndentationTestCase(RuleTestCase):
'...\n', conf)
def test_two_spaces(self):
conf = 'indentation: {spaces: 2, indent-sequences: no}'
conf = 'indentation: {spaces: 2, indent-sequences: false}'
self.check('---\n'
'object:\n'
' k1:\n'
@@ -589,8 +590,11 @@ class IndentationTestCase(RuleTestCase):
' date: 1969\n'
' - name: Linux\n'
' date: 1991\n'
' k4:\n'
' -\n'
' k5: v3\n'
'...\n', conf)
conf = 'indentation: {spaces: 2, indent-sequences: yes}'
conf = 'indentation: {spaces: 2, indent-sequences: true}'
self.check('---\n'
'object:\n'
' k1:\n'
@@ -605,7 +609,7 @@ class IndentationTestCase(RuleTestCase):
'...\n', conf)
def test_three_spaces(self):
conf = 'indentation: {spaces: 3, indent-sequences: no}'
conf = 'indentation: {spaces: 3, indent-sequences: false}'
self.check('---\n'
'object:\n'
' k1:\n'
@@ -618,7 +622,7 @@ class IndentationTestCase(RuleTestCase):
' - name: Linux\n'
' date: 1991\n'
'...\n', conf)
conf = 'indentation: {spaces: 3, indent-sequences: yes}'
conf = 'indentation: {spaces: 3, indent-sequences: true}'
self.check('---\n'
'object:\n'
' k1:\n'
@@ -632,7 +636,7 @@ class IndentationTestCase(RuleTestCase):
' date: 1991\n'
'...\n', conf)
def test_consistent(self):
def test_consistent_spaces(self):
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: whatever}\n'
'document-start: disable\n')
@@ -713,6 +717,142 @@ class IndentationTestCase(RuleTestCase):
'- b\n'
'- c\n', conf)
def test_consistent_spaces_and_indent_sequences(self):
conf = 'indentation: {spaces: consistent, indent-sequences: true}'
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem1=(3, 1))
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem1=(7, 5))
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf, problem1=(7, 1))
conf = 'indentation: {spaces: consistent, indent-sequences: false}'
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem1=(7, 5))
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem1=(7, 3))
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf, problem1=(3, 3))
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: consistent}')
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem1=(7, 5))
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf, problem1=(7, 1))
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf)
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem1=(7, 5))
conf = 'indentation: {spaces: consistent, indent-sequences: whatever}'
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf)
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf)
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf)
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem1=(7, 5))
def test_indent_sequences_whatever(self):
conf = 'indentation: {spaces: 4, indent-sequences: whatever}'
self.check('---\n'
@@ -1072,6 +1212,20 @@ class IndentationTestCase(RuleTestCase):
' - name: Linux\n'
' date: 1991\n'
'...\n', conf, problem=(5, 10, 'syntax'))
conf = 'indentation: {spaces: 2, indent-sequences: true}'
self.check('---\n'
'a:\n'
'-\n' # empty list
'b: c\n'
'...\n', conf, problem=(3, 1))
conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
self.check('---\n'
'a:\n'
' -\n' # empty list
'b:\n'
'-\n'
'c: d\n'
'...\n', conf, problem=(5, 1))
def test_over_indented(self):
conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
@@ -1128,9 +1282,23 @@ class IndentationTestCase(RuleTestCase):
' - subel\n'
'...\n', conf,
problem=(2, 3))
conf = 'indentation: {spaces: 2, indent-sequences: false}'
self.check('---\n'
'a:\n'
' -\n' # empty list
'b: c\n'
'...\n', conf, problem=(3, 3))
conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
self.check('---\n'
'a:\n'
'-\n' # empty list
'b:\n'
' -\n'
'c: d\n'
'...\n', conf, problem=(5, 3))
def test_multi_lines(self):
conf = 'indentation: {spaces: consistent, indent-sequences: yes}'
conf = 'indentation: {spaces: consistent, indent-sequences: true}'
self.check('---\n'
'long_string: >\n'
' bla bla blah\n'
@@ -1438,7 +1606,7 @@ class IndentationTestCase(RuleTestCase):
'- !!map # Block collection\n'
' foo: bar\n', conf)
conf = 'indentation: {spaces: consistent, indent-sequences: no}'
conf = 'indentation: {spaces: consistent, indent-sequences: false}'
self.check('---\n'
'sequence: !!seq\n'
'- entry\n'
@@ -1505,7 +1673,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_basics_plain(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: no}\n'
' check-multi-line-strings: false}\n'
'document-start: disable\n')
self.check('multi\n'
'line\n', conf)
@@ -1534,7 +1702,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_check_multi_line_plain(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
' check-multi-line-strings: true}\n'
'document-start: disable\n')
self.check('multi\n'
' line\n', conf, problem=(2, 2))
@@ -1557,7 +1725,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_basics_quoted(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: no}\n'
' check-multi-line-strings: false}\n'
'document-start: disable\n')
self.check('"multi\n'
' line"\n', conf)
@@ -1588,7 +1756,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_check_multi_line_quoted(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
' check-multi-line-strings: true}\n'
'document-start: disable\n')
self.check('"multi\n'
'line"\n', conf, problem=(2, 1))
@@ -1644,7 +1812,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_basics_folded_style(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: no}\n'
' check-multi-line-strings: false}\n'
'document-start: disable\n')
self.check('>\n'
' multi\n'
@@ -1682,7 +1850,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_check_multi_line_folded_style(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
' check-multi-line-strings: true}\n'
'document-start: disable\n')
self.check('>\n'
' multi\n'
@@ -1723,7 +1891,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_basics_literal_style(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: no}\n'
' check-multi-line-strings: false}\n'
'document-start: disable\n')
self.check('|\n'
' multi\n'
@@ -1761,7 +1929,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_check_multi_line_literal_style(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
' check-multi-line-strings: true}\n'
'document-start: disable\n')
self.check('|\n'
' multi\n'
@@ -1805,7 +1973,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_paragraph_plain(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
' check-multi-line-strings: true}\n'
'document-start: disable\n')
self.check('- long text: very "long"\n'
' \'string\' with\n'
@@ -1827,7 +1995,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_paragraph_double_quoted(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
' check-multi-line-strings: true}\n'
'document-start: disable\n')
self.check('- long text: "very \\"long\\"\n'
' \'string\' with\n'
@@ -1855,7 +2023,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_paragraph_single_quoted(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
' check-multi-line-strings: true}\n'
'document-start: disable\n')
self.check('- long text: \'very "long"\n'
' \'\'string\'\' with\n'
@@ -1883,7 +2051,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_paragraph_folded(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
' check-multi-line-strings: true}\n'
'document-start: disable\n')
self.check('- long text: >\n'
' very "long"\n'
@@ -1901,7 +2069,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_paragraph_literal(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
' check-multi-line-strings: true}\n'
'document-start: disable\n')
self.check('- long text: |\n'
' very "long"\n'
@@ -1919,7 +2087,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_consistent(self):
conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n'
' check-multi-line-strings: true}\n'
'document-start: disable\n')
self.check('multi\n'
'line\n', conf)

View File

@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Johannes F. Knauf
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.common import RuleTestCase
class KeyOrderingTestCase(RuleTestCase):
rule_id = 'key-ordering'
def test_disabled(self):
conf = 'key-ordering: disable'
self.check('---\n'
'block mapping:\n'
' secondkey: a\n'
' firstkey: b\n', conf)
self.check('---\n'
'flow mapping:\n'
' {secondkey: a, firstkey: b}\n', conf)
self.check('---\n'
'second: before_first\n'
'at: root\n', conf)
self.check('---\n'
'nested but OK:\n'
' second: {first: 1}\n'
' third:\n'
' second: 2\n', conf)
def test_enabled(self):
conf = 'key-ordering: enable'
self.check('---\n'
'block mapping:\n'
' secondkey: a\n'
' firstkey: b\n', conf,
problem=(4, 3))
self.check('---\n'
'flow mapping:\n'
' {secondkey: a, firstkey: b}\n', conf,
problem=(3, 18))
self.check('---\n'
'second: before_first\n'
'at: root\n', conf,
problem=(3, 1))
self.check('---\n'
'nested but OK:\n'
' second: {first: 1}\n'
' third:\n'
' second: 2\n', conf)
def test_word_length(self):
conf = 'key-ordering: enable'
self.check('---\n'
'a: 1\n'
'ab: 1\n'
'abc: 1\n', conf)
self.check('---\n'
'a: 1\n'
'abc: 1\n'
'ab: 1\n', conf,
problem=(4, 1))
def test_key_duplicates(self):
conf = ('key-duplicates: disable\n'
'key-ordering: enable')
self.check('---\n'
'key: 1\n'
'key: 2\n', conf)
def test_case(self):
conf = 'key-ordering: enable'
self.check('---\n'
'T-shirt: 1\n'
'T-shirts: 2\n'
't-shirt: 3\n'
't-shirts: 4\n', conf)
self.check('---\n'
'T-shirt: 1\n'
't-shirt: 2\n'
'T-shirts: 3\n'
't-shirts: 4\n', conf,
problem=(4, 1))
def test_accents(self):
conf = 'key-ordering: enable'
self.check('---\n'
'hair: true\n'
'hais: true\n'
'haïr: true\n'
'haïssable: true\n', conf)
self.check('---\n'
'haïr: true\n'
'hais: true\n', conf,
problem=(3, 1))
self.check('---\n'
'haïr: true\n'
'hais: true\n', conf,
problem=(3, 1))
def test_key_tokens_in_flow_sequences(self):
conf = 'key-ordering: enable'
self.check('---\n'
'[\n'
' key: value, mappings, in, flow: sequence\n'
']\n', conf)

View File

@@ -32,6 +32,9 @@ class LineLengthTestCase(RuleTestCase):
self.check('---\n' + 81 * 'a' + '\n', conf)
self.check(1000 * 'b', conf)
self.check('---\n' + 1000 * 'b' + '\n', conf)
self.check('content: |\n'
' {% this line is' + 99 * ' really' + ' long %}\n',
conf)
def test_default(self):
conf = ('line-length: {max: 80}\n'
@@ -63,7 +66,7 @@ class LineLengthTestCase(RuleTestCase):
self.check('---\n' + 81 * ' ' + '\n', conf, problem=(2, 81))
def test_non_breakable_word(self):
conf = 'line-length: {max: 20, allow-non-breakable-words: yes}'
conf = 'line-length: {max: 20, allow-non-breakable-words: true}'
self.check('---\n' + 30 * 'A' + '\n', conf)
self.check('---\n'
'this:\n'
@@ -84,8 +87,11 @@ class LineLengthTestCase(RuleTestCase):
'another:\n'
' - https://localhost/very/very/long/url\n'
'...\n', conf)
self.check('---\n'
'long_line: http://localhost/very/very/long/url\n', conf,
problem=(2, 21))
conf = 'line-length: {max: 20, allow-non-breakable-words: no}'
conf = 'line-length: {max: 20, allow-non-breakable-words: false}'
self.check('---\n' + 30 * 'A' + '\n', conf, problem=(2, 21))
self.check('---\n'
'this:\n'
@@ -106,3 +112,46 @@ class LineLengthTestCase(RuleTestCase):
'another:\n'
' - https://localhost/very/very/long/url\n'
'...\n', conf, problem=(5, 21))
self.check('---\n'
'long_line: http://localhost/very/very/long/url\n'
'...\n', conf, problem=(2, 21))
conf = ('line-length: {max: 20, allow-non-breakable-words: true}\n'
'trailing-spaces: disable')
self.check('---\n'
'loooooooooong+word+and+some+space+at+the+end \n',
conf, problem=(2, 21))
def test_non_breakable_inline_mappings(self):
conf = 'line-length: {max: 20, ' \
'allow-non-breakable-inline-mappings: true}'
self.check('---\n'
'long_line: http://localhost/very/very/long/url\n'
'long line: http://localhost/very/very/long/url\n', conf)
self.check('---\n'
'- long line: http://localhost/very/very/long/url\n', conf)
self.check('---\n'
'long_line: http://localhost/short/url + word\n'
'long line: http://localhost/short/url + word\n',
conf, problem1=(2, 21), problem2=(3, 21))
conf = ('line-length: {max: 20,'
' allow-non-breakable-inline-mappings: true}\n'
'trailing-spaces: disable')
self.check('---\n'
'long_line: and+some+space+at+the+end \n',
conf, problem=(2, 21))
self.check('---\n'
'long line: and+some+space+at+the+end \n',
conf, problem=(2, 21))
self.check('---\n'
'- long line: and+some+space+at+the+end \n',
conf, problem=(2, 21))
# See https://github.com/adrienverge/yamllint/issues/21
conf = 'line-length: {allow-non-breakable-inline-mappings: true}'
self.check('---\n'
'content: |\n'
' {% this line is' + 99 * ' really' + ' long %}\n',
conf, problem=(3, 81))

View File

@@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.common import RuleTestCase
class OctalValuesTestCase(RuleTestCase):
rule_id = 'octal-values'
def test_disabled(self):
conf = ('octal-values: disable\n'
'new-line-at-end-of-file: disable\n'
'document-start: disable\n')
self.check('user-city: 010', conf)
self.check('user-city: 0o10', conf)
def test_implicit_octal_values(self):
conf = ('octal-values: {forbid-implicit-octal: true}\n'
'new-line-at-end-of-file: disable\n'
'document-start: disable\n')
self.check('user-city: 010', conf, problem=(1, 15))
self.check('user-city: abc', conf)
self.check('user-city: 010,0571', conf)
self.check("user-city: '010'", conf)
self.check('user-city: "010"', conf)
self.check('user-city:\n'
' - 010', conf, problem=(2, 8))
self.check('user-city: [010]', conf, problem=(1, 16))
self.check('user-city: {beijing: 010}', conf, problem=(1, 25))
self.check('explicit-octal: 0o10', conf)
self.check('not-number: 0abc', conf)
self.check('zero: 0', conf)
self.check('hex-value: 0x10', conf)
self.check('number-values:\n'
' - 0.10\n'
' - .01\n'
' - 0e3\n', conf)
def test_explicit_octal_values(self):
conf = ('octal-values: {forbid-explicit-octal: true}\n'
'new-line-at-end-of-file: disable\n'
'document-start: disable\n')
self.check('user-city: 0o10', conf, problem=(1, 16))
self.check('user-city: abc', conf)
self.check('user-city: 0o10,0571', conf)
self.check("user-city: '0o10'", conf)
self.check('user-city:\n'
' - 0o10', conf, problem=(2, 9))
self.check('user-city: [0o10]', conf, problem=(1, 17))
self.check('user-city: {beijing: 0o10}', conf, problem=(1, 26))
self.check('implicit-octal: 010', conf)
self.check('not-number: 0oabc', conf)
self.check('zero: 0', conf)
self.check('hex-value: 0x10', conf)
self.check('number-values:\n'
' - 0.10\n'
' - .01\n'
' - 0e3\n', conf)
self.check('user-city: "010"', conf)

View File

@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Peter Ericson
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.common import RuleTestCase
class TruthyTestCase(RuleTestCase):
rule_id = 'truthy'
def test_disabled(self):
conf = 'truthy: disable'
self.check('---\n'
'1: True\n', conf)
self.check('---\n'
'True: 1\n', conf)
def test_enabled(self):
conf = 'truthy: enable\n'
self.check('---\n'
'1: True\n'
'True: 1\n',
conf, problem1=(2, 4), problem2=(3, 1))
self.check('---\n'
'1: "True"\n'
'"True": 1\n', conf)
self.check('---\n'
'[\n'
' true, false,\n'
' "false", "FALSE",\n'
' "true", "True",\n'
' True, FALSE,\n'
' on, OFF,\n'
' NO, Yes\n'
']\n', conf,
problem1=(6, 3), problem2=(6, 9),
problem3=(7, 3), problem4=(7, 7),
problem5=(8, 3), problem6=(8, 7))
def test_explicit_types(self):
conf = 'truthy: enable\n'
self.check('---\n'
'string1: !!str True\n'
'string2: !!str yes\n'
'string3: !!str off\n'
'encoded: !!binary |\n'
' True\n'
' OFF\n'
' pad==\n' # this decodes as 'N\xbb\x9e8Qii'
'boolean1: !!bool true\n'
'boolean2: !!bool "false"\n'
'boolean3: !!bool FALSE\n'
'boolean4: !!bool True\n'
'boolean5: !!bool off\n'
'boolean6: !!bool NO\n',
conf)

View File

@@ -18,54 +18,64 @@ try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
import fcntl
import locale
import os
import pty
import shutil
import tempfile
import unittest
import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except AssertionError:
import unittest2 as unittest
from tests.common import build_temp_workspace
from yamllint import cli
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
class CommandLineTestCase(unittest.TestCase):
def setUp(self):
self.wd = tempfile.mkdtemp(prefix='yamllint-tests-')
@classmethod
def setUpClass(cls):
super(CommandLineTestCase, cls).setUpClass()
# .yaml file at root
with open(os.path.join(self.wd, 'a.yaml'), 'w') as f:
f.write('---\n'
'- 1 \n'
'- 2')
cls.wd = build_temp_workspace({
# .yaml file at root
'a.yaml': '---\n'
'- 1 \n'
'- 2',
# file with only one warning
'warn.yaml': 'key: value\n',
# .yml file at root
'empty.yml': '',
# file in dir
'sub/ok.yaml': '---\n'
'key: value\n',
# file in very nested dir
's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml': '---\n'
'key: value\n'
'key: other value\n',
# empty dir
'empty-dir': [],
# non-YAML file
'no-yaml.json': '---\n'
'key: value\n',
# non-ASCII chars
'non-ascii/utf-8': (
u'---\n'
u'- hétérogénéité\n'
u'# 19.99 €\n'
u'- お早う御座います。\n'
u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'),
})
# .yml file at root
open(os.path.join(self.wd, 'empty.yml'), 'w').close()
@classmethod
def tearDownClass(cls):
super(CommandLineTestCase, cls).tearDownClass()
# file in dir
os.mkdir(os.path.join(self.wd, 'sub'))
with open(os.path.join(self.wd, 'sub', 'ok.yaml'), 'w') as f:
f.write('---\n'
'key: value\n')
# file in very nested dir
dir = self.wd
for i in range(15):
dir = os.path.join(dir, 's')
os.mkdir(dir)
with open(os.path.join(dir, 'file.yaml'), 'w') as f:
f.write('---\n'
'key: value\n'
'key: other value\n')
# empty dir
os.mkdir(os.path.join(self.wd, 'empty-dir'))
# non-YAML file
with open(os.path.join(self.wd, 'no-yaml.json'), 'w') as f:
f.write('---\n'
'key: value\n')
def tearDown(self):
shutil.rmtree(self.wd)
shutil.rmtree(cls.wd)
def test_find_files_recursively(self):
self.assertEqual(
@@ -73,7 +83,8 @@ class CommandLineTestCase(unittest.TestCase):
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/ok.yaml')],
os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')],
)
items = [os.path.join(self.wd, 'sub/ok.yaml'),
@@ -128,8 +139,11 @@ class CommandLineTestCase(unittest.TestCase):
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^Options --config-file and '
r'--config-data cannot be used')
self.assertRegexpMatches(
err.splitlines()[-1],
r'^yamllint: error: argument -d\/--config-data: '
r'not allowed with argument -c\/--config-file$'
)
def test_run_with_bad_config(self):
sys.stdout, sys.stderr = StringIO(), StringIO()
@@ -235,6 +249,24 @@ class CommandLineTestCase(unittest.TestCase):
'(new-line-at-end-of-file)\n') % (file, file))
self.assertEqual(err, '')
def test_run_one_warning(self):
file = os.path.join(self.wd, 'warn.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
self.assertEqual(ctx.exception.code, 0)
def test_run_warning_in_strict_mode(self):
file = os.path.join(self.wd, 'warn.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', '--strict', file))
self.assertEqual(ctx.exception.code, 2)
def test_run_one_ok_file(self):
file = os.path.join(self.wd, 'sub', 'ok.yaml')
@@ -261,6 +293,29 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual(out, '')
self.assertEqual(err, '')
def test_run_non_ascii_file(self):
file = os.path.join(self.wd, 'non-ascii', 'utf-8')
# Make sure the default localization conditions on this "system"
# support UTF-8 encoding.
loc = locale.getlocale()
try:
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
except locale.Error:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
locale.setlocale(locale.LC_ALL, loc)
self.assertEqual(ctx.exception.code, 0)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertEqual(err, '')
def test_run_multiple_files(self):
items = [os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 's')]
@@ -278,7 +333,7 @@ class CommandLineTestCase(unittest.TestCase):
'(key-duplicates)\n') % file)
self.assertEqual(err, '')
def test_run_colored_output(self):
def test_run_piped_output_nocolor(self):
file = os.path.join(self.wd, 'a.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
@@ -288,6 +343,38 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual(ctx.exception.code, 1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
'%s\n'
' 2:4 error trailing spaces (trailing-spaces)\n'
' 3:4 error no new line character at the end of file '
'(new-line-at-end-of-file)\n'
'\n' % file))
self.assertEqual(err, '')
def test_run_colored_output(self):
file = os.path.join(self.wd, 'a.yaml')
# Create a pseudo-TTY and redirect stdout to it
master, slave = pty.openpty()
sys.stdout = sys.stderr = os.fdopen(slave, 'w')
with self.assertRaises(SystemExit) as ctx:
cli.run((file, ))
sys.stdout.flush()
self.assertEqual(ctx.exception.code, 1)
# Read output from TTY
output = os.fdopen(master, 'r')
flag = fcntl.fcntl(master, fcntl.F_GETFD)
fcntl.fcntl(master, fcntl.F_SETFL, flag | os.O_NONBLOCK)
out = output.read().replace('\r\n', '\n')
sys.stdout.close()
sys.stderr.close()
output.close()
self.assertEqual(out, (
'\033[4m%s\033[0m\n'
' \033[2m2:4\033[0m \033[31merror\033[0m '
@@ -296,4 +383,3 @@ class CommandLineTestCase(unittest.TestCase):
'no new line character at the end of file '
'\033[2m(new-line-at-end-of-file)\033[0m\n'
'\n' % file))
self.assertEqual(err, '')

View File

@@ -14,8 +14,22 @@
# 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 cStringIO import StringIO
except ImportError:
from io import StringIO
import os
import shutil
import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except AssertionError:
import unittest2 as unittest
from tests.common import build_temp_workspace
from yamllint import cli
from yamllint import config
@@ -30,7 +44,7 @@ class SimpleConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['colons']['max-spaces-before'], 0)
self.assertEqual(new.rules['colons']['max-spaces-after'], 1)
self.assertEqual(len(new.enabled_rules()), 1)
self.assertEqual(len(new.enabled_rules(None)), 1)
def test_invalid_conf(self):
with self.assertRaises(config.YamlLintConfigError):
@@ -62,6 +76,45 @@ class SimpleConfigTestCase(unittest.TestCase):
' max-spaces-after: 1\n'
' abcdef: yes\n')
def test_yes_no_for_booleans(self):
c = config.YamlLintConfig('rules:\n'
' indentation:\n'
' spaces: 2\n'
' indent-sequences: true\n'
' check-multi-line-strings: false\n')
self.assertEqual(c.rules['indentation']['indent-sequences'], True)
self.assertEqual(c.rules['indentation']['check-multi-line-strings'],
False)
c = config.YamlLintConfig('rules:\n'
' indentation:\n'
' spaces: 2\n'
' indent-sequences: yes\n'
' check-multi-line-strings: false\n')
self.assertEqual(c.rules['indentation']['indent-sequences'], True)
self.assertEqual(c.rules['indentation']['check-multi-line-strings'],
False)
c = config.YamlLintConfig('rules:\n'
' indentation:\n'
' spaces: 2\n'
' indent-sequences: whatever\n'
' check-multi-line-strings: false\n')
self.assertEqual(c.rules['indentation']['indent-sequences'],
'whatever')
self.assertEqual(c.rules['indentation']['check-multi-line-strings'],
False)
with self.assertRaisesRegexp(
config.YamlLintConfigError,
'invalid config: option "indent-sequences" of "indentation" '
'should be in '):
c = config.YamlLintConfig('rules:\n'
' indentation:\n'
' spaces: 2\n'
' indent-sequences: YES!\n'
' check-multi-line-strings: false\n')
def test_validate_rule_conf(self):
class Rule(object):
ID = 'fake'
@@ -131,7 +184,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['colons']['max-spaces-after'], 1)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 2)
self.assertEqual(len(new.enabled_rules(None)), 2)
def test_extend_remove_rule(self):
old = config.YamlLintConfig('rules:\n'
@@ -148,7 +201,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['colons'], False)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 1)
self.assertEqual(len(new.enabled_rules(None)), 1)
def test_extend_edit_rule(self):
old = config.YamlLintConfig('rules:\n'
@@ -168,7 +221,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['colons']['max-spaces-after'], 4)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 2)
self.assertEqual(len(new.enabled_rules(None)), 2)
def test_extend_reenable_rule(self):
old = config.YamlLintConfig('rules:\n'
@@ -186,7 +239,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['colons']['max-spaces-after'], 1)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 2)
self.assertEqual(len(new.enabled_rules(None)), 2)
class ExtendedLibraryConfigTestCase(unittest.TestCase):
@@ -231,3 +284,94 @@ class ExtendedLibraryConfigTestCase(unittest.TestCase):
self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys()))
for rule in new.rules:
self.assertEqual(new.rules[rule], old.rules[rule])
class IgnorePathConfigTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
super(IgnorePathConfigTestCase, cls).setUpClass()
bad_yaml = ('---\n'
'- key: val1\n'
' key: val2\n'
'- trailing space \n'
'- lonely hyphen\n')
cls.wd = build_temp_workspace({
'bin/file.lint-me-anyway.yaml': bad_yaml,
'bin/file.yaml': bad_yaml,
'file-at-root.yaml': bad_yaml,
'file.dont-lint-me.yaml': bad_yaml,
'ign-dup/file.yaml': bad_yaml,
'ign-dup/sub/dir/file.yaml': bad_yaml,
'ign-trail/file.yaml': bad_yaml,
'include/ign-dup/sub/dir/file.yaml': bad_yaml,
'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()
os.chdir(cls.wd)
@classmethod
def tearDownClass(cls):
super(IgnorePathConfigTestCase, cls).tearDownClass()
os.chdir(cls.backup_wd)
shutil.rmtree(cls.wd)
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
def test_run_with_ignored_path(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,
'./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,
)))

View File

@@ -15,8 +15,12 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import io
import unittest
import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except AssertionError:
import unittest2 as unittest
from yamllint.config import YamlLintConfig
from yamllint import linter
@@ -44,3 +48,15 @@ class LinterTestCase(unittest.TestCase):
def test_run_on_list(self):
self.assertRaises(TypeError, linter.run,
['h', 'e', 'l', 'l', 'o'], self.fake_config())
def test_run_on_non_ascii_chars(self):
s = (u'- hétérogénéité\n'
u'# 19.99 €\n')
linter.run(s, self.fake_config())
linter.run(s.encode('utf-8'), self.fake_config())
linter.run(s.encode('iso-8859-15'), self.fake_config())
s = (u'- お早う御座います。\n'
u'# الأَبْجَدِيَّة العَرَبِيَّة\n')
linter.run(s, self.fake_config())
linter.run(s.encode('utf-8'), self.fake_config())

91
tests/test_module.py Normal file
View File

@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 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 os
import shutil
import subprocess
import tempfile
import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except AssertionError:
import unittest2 as unittest
PYTHON = sys.executable or 'python'
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
class ModuleTestCase(unittest.TestCase):
def setUp(self):
self.wd = tempfile.mkdtemp(prefix='yamllint-tests-')
# file with only one warning
with open(os.path.join(self.wd, 'warn.yaml'), 'w') as f:
f.write('key: value\n')
# file in dir
os.mkdir(os.path.join(self.wd, 'sub'))
with open(os.path.join(self.wd, 'sub', 'nok.yaml'), 'w') as f:
f.write('---\n'
'list: [ 1, 1, 2, 3, 5, 8] \n')
def tearDown(self):
shutil.rmtree(self.wd)
def test_run_module_no_args(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output([PYTHON, '-m', 'yamllint'],
stderr=subprocess.STDOUT)
self.assertEqual(ctx.exception.returncode, 2)
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.assertRegexpMatches(ctx.exception.output.decode(),
r'No such file or directory')
def test_run_module_on_file(self):
out = subprocess.check_output(
[PYTHON, '-m', 'yamllint', os.path.join(self.wd, 'warn.yaml')])
lines = out.decode().splitlines()
self.assertIn('/warn.yaml', lines[0])
self.assertEqual('\n'.join(lines[1:]),
' 1:1 warning missing document start "---"'
' (document-start)\n')
def test_run_module_on_dir(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output([PYTHON, '-m', 'yamllint', self.wd])
self.assertEqual(ctx.exception.returncode, 1)
files = ctx.exception.output.decode().split('\n\n')
self.assertIn(
'/warn.yaml\n'
' 1:1 warning missing document start "---"'
' (document-start)',
files[0])
self.assertIn(
'/sub/nok.yaml\n'
' 2:9 error too many spaces inside brackets'
' (brackets)\n'
' 2:27 error trailing spaces (trailing-spaces)',
files[1])

View File

@@ -14,7 +14,12 @@
# 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 sys
try:
assert sys.version_info >= (2, 7)
import unittest
except AssertionError:
import unittest2 as unittest
import yaml

View File

@@ -48,6 +48,7 @@ from tests.common import RuleTestCase
class SpecificationTestCase(RuleTestCase):
rule_id = None
conf_general = ('document-start: disable\n'
'comments: {min-spaces-from-content: 1}\n'
'braces: {min-spaces-inside: 1, max-spaces-inside: 1}\n'
@@ -66,7 +67,7 @@ conf_overrides = {
'example-2.18': ('empty-lines: {max-end: 1}\n'),
'example-2.19': ('empty-lines: {max-end: 1}\n'),
'example-2.28': ('empty-lines: {max-end: 3}\n'),
'example-5.3': ('indentation: {indent-sequences: no}\n'
'example-5.3': ('indentation: {indent-sequences: false}\n'
'colons: {max-spaces-before: 1}\n'),
'example-6.4': ('trailing-spaces: disable\n'),
'example-6.5': ('trailing-spaces: disable\n'),
@@ -114,11 +115,13 @@ conf_overrides = {
'example-8.14': ('colons: {max-spaces-before: 1}\n'),
'example-8.16': ('indentation: {spaces: 1}\n'),
'example-8.17': ('indentation: disable\n'),
'example-8.20': ('indentation: {indent-sequences: no}\n'
'example-8.20': ('indentation: {indent-sequences: false}\n'
'colons: {max-spaces-before: 1}\n'),
'example-8.22': ('indentation: disable\n'),
'example-10.1': ('colons: {max-spaces-before: 2}\n'),
'example-10.2': ('indentation: {indent-sequences: no}\n'),
'example-10.2': ('indentation: {indent-sequences: false}\n'),
'example-10.8': ('truthy: disable\n'),
'example-10.9': ('truthy: disable\n'),
}
files = os.listdir(os.path.join(os.path.dirname(os.path.realpath(__file__)),
@@ -131,6 +134,7 @@ def _gen_test(buffer, conf):
self.check(buffer, conf)
return test
# The following tests are blacklisted (i.e. will not be checked against
# yamllint), because pyyaml is currently not able to parse the contents
# (using yaml.parse()).

View File

@@ -22,7 +22,7 @@ indentation, etc."""
APP_NAME = 'yamllint'
APP_VERSION = '1.3.0'
APP_VERSION = '1.11.0'
APP_DESCRIPTION = __doc__
__author__ = u'Adrien Vergé'

4
yamllint/__main__.py Normal file
View File

@@ -0,0 +1,4 @@
from yamllint.cli import run
if __name__ == '__main__':
run()

View File

@@ -15,13 +15,15 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
import os.path
import sys
import os
import sys
import platform
import argparse
from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION
from yamllint.config import YamlLintConfig, YamlLintConfigError
from yamllint.linter import PROBLEM_LEVELS
from yamllint import linter
@@ -36,6 +38,15 @@ def find_files_recursively(items):
yield item
def supports_color():
supported_platform = not (platform.system() == 'Windows' and not
('ANSICON' in os.environ or
('TERM' in os.environ and
os.environ['TERM'] == 'ANSI')))
return (supported_platform and
hasattr(sys.stdout, 'isatty') and sys.stdout.isatty())
class Format(object):
@staticmethod
def parsable(problem, filename):
@@ -48,6 +59,17 @@ class Format(object):
@staticmethod
def standard(problem, filename):
line = ' %d:%d' % (problem.line, problem.column)
line += max(12 - len(line), 0) * ' '
line += problem.level
line += max(21 - len(line), 0) * ' '
line += problem.desc
if problem.rule:
line += ' (%s)' % problem.rule
return line
@staticmethod
def standard_color(problem, filename):
line = ' \033[2m%d:%d\033[0m' % (problem.line, problem.column)
line += max(20 - len(line), 0) * ' '
if problem.level == 'warning':
@@ -66,14 +88,20 @@ def run(argv=None):
description=APP_DESCRIPTION)
parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+',
help='files to check')
parser.add_argument('-c', '--config-file', dest='config_file',
action='store', help='path to a custom configuration')
parser.add_argument('-d', '--config-data', dest='config_data',
action='store',
help='custom configuration (as YAML source)')
config_group = parser.add_mutually_exclusive_group()
config_group.add_argument('-c', '--config-file', dest='config_file',
action='store',
help='path to a custom configuration')
config_group.add_argument('-d', '--config-data', dest='config_data',
action='store',
help='custom configuration (as YAML source)')
parser.add_argument('-f', '--format',
choices=('parsable', 'standard'), default='standard',
help='format for parsing output')
parser.add_argument('-s', '--strict',
action='store_true',
help='return non-zero exit code on warnings '
'as well as errors')
parser.add_argument('-v', '--version', action='version',
version='%s %s' % (APP_NAME, APP_VERSION))
@@ -81,11 +109,6 @@ def run(argv=None):
args = parser.parse_args(argv)
if args.config_file is not None and args.config_data is not None:
print('Options --config-file and --config-data cannot be used '
'simultaneously.', file=sys.stderr)
sys.exit(-1)
# User-global config is supposed to be in ~/.config/yamllint/config
if 'XDG_CONFIG_HOME' in os.environ:
user_global_config = os.path.join(
@@ -110,29 +133,42 @@ def run(argv=None):
print(e, file=sys.stderr)
sys.exit(-1)
return_code = 0
max_level = 0
for file in find_files_recursively(args.files):
filepath = file[2:] if file.startswith('./') else file
try:
first = True
with open(file) as f:
for problem in linter.run(f, conf):
for problem in linter.run(f, conf, filepath):
if args.format == 'parsable':
print(Format.parsable(problem, file))
else:
elif supports_color():
if first:
print('\033[4m%s\033[0m' % file)
first = False
print(Format.standard_color(problem, file))
else:
if first:
print(file)
first = False
print(Format.standard(problem, file))
if return_code == 0 and problem.level == 'error':
return_code = 1
max_level = max(max_level, PROBLEM_LEVELS[problem.level])
if not first and args.format != 'parsable':
print('')
except EnvironmentError as e:
print(e, file=sys.stderr)
return_code = -1
sys.exit(-1)
if max_level == PROBLEM_LEVELS['error']:
return_code = 1
elif max_level == PROBLEM_LEVELS['warning']:
return_code = 2 if args.strict else 0
else:
return_code = 0
sys.exit(return_code)

View File

@@ -4,9 +4,13 @@ rules:
braces:
min-spaces-inside: 0
max-spaces-inside: 0
min-spaces-inside-empty: -1
max-spaces-inside-empty: -1
brackets:
min-spaces-inside: 0
max-spaces-inside: 0
min-spaces-inside-empty: -1
max-spaces-inside-empty: -1
colons:
max-spaces-before: 0
max-spaces-after: 1
@@ -16,29 +20,39 @@ rules:
max-spaces-after: 1
comments:
level: warning
require-starting-space: yes
require-starting-space: true
min-spaces-from-content: 2
comments-indentation:
level: warning
document-end: disable
document-start:
level: warning
present: yes
present: true
empty-lines:
max: 2
max-start: 0
max-end: 0
empty-values:
forbid-in-block-mappings: false
forbid-in-flow-mappings: false
hyphens:
max-spaces-after: 1
indentation:
spaces: consistent
indent-sequences: yes
check-multi-line-strings: no
indent-sequences: true
check-multi-line-strings: false
key-duplicates: enable
key-ordering: disable
line-length:
max: 80
allow-non-breakable-words: yes
allow-non-breakable-words: true
allow-non-breakable-inline-mappings: false
new-line-at-end-of-file: enable
new-lines:
type: unix
octal-values:
forbid-implicit-octal: false
forbid-explicit-octal: false
trailing-spaces: enable
truthy:
level: warning

View File

@@ -25,3 +25,5 @@ rules:
indent-sequences: consistent
line-length:
level: warning
allow-non-breakable-inline-mappings: true
truthy: disable

View File

@@ -16,6 +16,7 @@
import os.path
import pathspec
import yaml
import yamllint.rules
@@ -29,6 +30,8 @@ class YamlLintConfig(object):
def __init__(self, content=None, file=None):
assert (content is None) ^ (file is None)
self.ignore = None
if file is not None:
with open(file) as f:
content = f.read()
@@ -36,9 +39,14 @@ class YamlLintConfig(object):
self.parse(content)
self.validate()
def enabled_rules(self):
def is_file_ignored(self, filepath):
return self.ignore and self.ignore.match_file(filepath)
def enabled_rules(self, filepath):
return [yamllint.rules.get(id) for id, val in self.rules.items()
if val is not False]
if val is not False and (
filepath is None or 'ignore' not in val or
not val['ignore'].match_file(filepath))]
def extend(self, base_config):
assert isinstance(base_config, YamlLintConfig)
@@ -53,6 +61,9 @@ class YamlLintConfig(object):
self.rules = base_config.rules
if base_config.ignore is not None:
self.ignore = base_config.ignore
def parse(self, raw_content):
try:
conf = yaml.safe_load(raw_content)
@@ -73,6 +84,13 @@ class YamlLintConfig(object):
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
if 'ignore' in conf:
if type(conf['ignore']) != str:
raise YamlLintConfigError(
'invalid config: ignore should contain file patterns')
self.ignore = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
def validate(self):
for id in self.rules:
try:
@@ -90,6 +108,14 @@ def validate_rule_conf(rule, conf):
conf = {}
if type(conf) == dict:
if ('ignore' in conf and
type(conf['ignore']) != pathspec.pathspec.PathSpec):
if type(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'
elif conf['level'] not in ('error', 'warning'):
@@ -98,7 +124,7 @@ def validate_rule_conf(rule, conf):
options = getattr(rule, 'CONF', {})
for optkey in conf:
if optkey == 'level':
if optkey in ('ignore', 'level'):
continue
if optkey not in options:
raise YamlLintConfigError(

View File

@@ -21,6 +21,16 @@ import yaml
from yamllint import parser
PROBLEM_LEVELS = {
0: None,
1: 'warning',
2: 'error',
None: 0,
'warning': 1,
'error': 2,
}
class LintProblem(object):
"""Represents a linting problem found by yamllint."""
def __init__(self, line, column, desc='<no description>', rule=None):
@@ -53,8 +63,8 @@ class LintProblem(object):
return '%d:%d: %s' % (self.line, self.column, self.message)
def get_costemic_problems(buffer, conf):
rules = conf.enabled_rules()
def get_cosmetic_problems(buffer, conf, filepath):
rules = conf.enabled_rules(filepath)
# Split token rules from line rules
token_rules = [r for r in rules if r.TYPE == 'token']
@@ -71,7 +81,10 @@ def get_costemic_problems(buffer, conf):
self.all_rules = set([r.ID for r in rules])
def process_comment(self, comment):
comment = repr(comment)
try:
comment = str(comment)
except UnicodeError:
return # this certainly wasn't a yamllint directive comment
if re.match(r'^# yamllint disable( rule:\S+)*\s*$', comment):
rules = [item[5:] for item in comment[18:].split(' ')][1:]
@@ -95,7 +108,10 @@ def get_costemic_problems(buffer, conf):
class DisableLineDirective(DisableDirective):
def process_comment(self, comment):
comment = repr(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):
rules = [item[5:] for item in comment[23:].split(' ')][1:]
@@ -169,7 +185,7 @@ def get_syntax_error(buffer):
return problem
def _run(buffer, conf):
def _run(buffer, conf, filepath):
assert hasattr(buffer, '__getitem__'), \
'_run() argument must be a buffer, not a stream'
@@ -177,7 +193,7 @@ def _run(buffer, conf):
# right line
syntax_error = get_syntax_error(buffer)
for problem in get_costemic_problems(buffer, conf):
for problem in get_cosmetic_problems(buffer, conf, filepath):
# Insert the syntax error (if any) at the right place...
if (syntax_error and syntax_error.line <= problem.line and
syntax_error.column <= problem.column):
@@ -199,7 +215,7 @@ def _run(buffer, conf):
yield syntax_error
def run(input, conf):
def run(input, conf, filepath=None):
"""Lints a YAML source.
Returns a generator of LintProblem objects.
@@ -207,11 +223,14 @@ def run(input, conf):
:param input: buffer, string or stream to read from
:param conf: yamllint configuration object
"""
if conf.is_file_ignored(filepath):
return ()
if type(input) in (type(b''), type(u'')): # compat with Python 2 & 3
return _run(input, conf)
return _run(input, conf, filepath)
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)
return _run(content, conf, filepath)
else:
raise TypeError('input should be a string or a stream')

View File

@@ -49,7 +49,7 @@ class Comment(object):
self.token_after = token_after
self.comment_before = comment_before
def __repr__(self):
def __str__(self):
end = self.buffer.find('\n', self.pointer)
if end == -1:
end = self.buffer.find('\0', self.pointer)

View File

@@ -24,13 +24,17 @@ from yamllint.rules import (
document_end,
document_start,
empty_lines,
empty_values,
hyphens,
indentation,
key_duplicates,
key_ordering,
line_length,
new_line_at_end_of_file,
new_lines,
octal_values,
trailing_spaces,
truthy,
)
_RULES = {
@@ -43,13 +47,17 @@ _RULES = {
document_end.ID: document_end,
document_start.ID: document_start,
empty_lines.ID: empty_lines,
empty_values.ID: empty_values,
hyphens.ID: hyphens,
indentation.ID: indentation,
key_duplicates.ID: key_duplicates,
key_ordering.ID: key_ordering,
line_length.ID: line_length,
new_line_at_end_of_file.ID: new_line_at_end_of_file,
new_lines.ID: new_lines,
octal_values.ID: octal_values,
trailing_spaces.ID: trailing_spaces,
truthy.ID: truthy,
}

View File

@@ -23,6 +23,10 @@ Use this rule to control the number of spaces inside braces (``{`` and ``}``).
braces.
* ``max-spaces-inside`` defines the maximal number of spaces allowed inside
braces.
* ``min-spaces-inside-empty`` defines the minimal number of spaces required
inside empty braces.
* ``max-spaces-inside-empty`` defines the maximal number of spaces allowed
inside empty braces.
.. rubric:: Examples
@@ -59,6 +63,30 @@ Use this rule to control the number of spaces inside braces (``{`` and ``}``).
::
object: {key1: 4, key2: 8 }
#. With ``braces: {min-spaces-inside-empty: 0, max-spaces-inside-empty: 0}``
the following code snippet would **PASS**:
::
object: {}
the following code snippet would **FAIL**:
::
object: { }
#. With ``braces: {min-spaces-inside-empty: 1, max-spaces-inside-empty: -1}``
the following code snippet would **PASS**:
::
object: { }
the following code snippet would **FAIL**:
::
object: {}
"""
@@ -70,11 +98,27 @@ from yamllint.rules.common import spaces_after, spaces_before
ID = 'braces'
TYPE = 'token'
CONF = {'min-spaces-inside': int,
'max-spaces-inside': int}
'max-spaces-inside': int,
'min-spaces-inside-empty': int,
'max-spaces-inside-empty': int}
def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.FlowMappingStartToken):
if (isinstance(token, yaml.FlowMappingStartToken) and
isinstance(next, yaml.FlowMappingEndToken)):
problem = spaces_after(token, prev, next,
min=(conf['min-spaces-inside-empty']
if conf['min-spaces-inside-empty'] != -1
else conf['min-spaces-inside']),
max=(conf['max-spaces-inside-empty']
if conf['max-spaces-inside-empty'] != -1
else conf['max-spaces-inside']),
min_desc='too few spaces inside empty braces',
max_desc='too many spaces inside empty braces')
if problem is not None:
yield problem
elif isinstance(token, yaml.FlowMappingStartToken):
problem = spaces_after(token, prev, next,
min=conf['min-spaces-inside'],
max=conf['max-spaces-inside'],

View File

@@ -24,6 +24,10 @@ Use this rule to control the number of spaces inside brackets (``[`` and
brackets.
* ``max-spaces-inside`` defines the maximal number of spaces allowed inside
brackets.
* ``min-spaces-inside-empty`` defines the minimal number of spaces required
inside empty brackets.
* ``max-spaces-inside-empty`` defines the maximal number of spaces allowed
inside empty brackets.
.. rubric:: Examples
@@ -60,6 +64,30 @@ Use this rule to control the number of spaces inside brackets (``[`` and
::
object: [1, 2, abc ]
#. With ``brackets: {min-spaces-inside-empty: 0, max-spaces-inside-empty: 0}``
the following code snippet would **PASS**:
::
object: []
the following code snippet would **FAIL**:
::
object: [ ]
#. With ``brackets: {min-spaces-inside-empty: 1, max-spaces-inside-empty: -1}``
the following code snippet would **PASS**:
::
object: [ ]
the following code snippet would **FAIL**:
::
object: []
"""
@@ -71,11 +99,28 @@ from yamllint.rules.common import spaces_after, spaces_before
ID = 'brackets'
TYPE = 'token'
CONF = {'min-spaces-inside': int,
'max-spaces-inside': int}
'max-spaces-inside': int,
'min-spaces-inside-empty': int,
'max-spaces-inside-empty': int}
def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.FlowSequenceStartToken):
if (isinstance(token, yaml.FlowSequenceStartToken) and
isinstance(next, yaml.FlowSequenceEndToken)):
problem = spaces_after(token, prev, next,
min=(conf['min-spaces-inside-empty']
if conf['min-spaces-inside-empty'] != -1
else conf['min-spaces-inside']),
max=(conf['max-spaces-inside-empty']
if conf['max-spaces-inside-empty'] != -1
else conf['max-spaces-inside']),
min_desc='too few spaces inside empty brackets',
max_desc=('too many spaces inside empty '
'brackets'))
if problem is not None:
yield problem
elif isinstance(token, yaml.FlowSequenceStartToken):
problem = spaces_after(token, prev, next,
min=conf['min-spaces-inside'],
max=conf['max-spaces-inside'],

View File

@@ -20,14 +20,14 @@ Use this rule to control the position and formatting of comments.
.. rubric:: Options
* Use ``require-starting-space`` to require a space character right after the
``#``. Set to ``yes`` to enable, ``no`` to disable.
``#``. Set to ``true`` to enable, ``false`` to disable.
* ``min-spaces-from-content`` is used to visually separate inline comments from
content. It defines the minimal required number of spaces between a comment
and its preceding content.
.. rubric:: Examples
#. With ``comments: {require-starting-space: yes}``
#. With ``comments: {require-starting-space: true}``
the following code snippet would **PASS**:
::
@@ -35,6 +35,12 @@ Use this rule to control the position and formatting of comments.
# This sentence
# is a block comment
the following code snippet would **PASS**:
::
##############################
## This is some documentation
the following code snippet would **FAIL**:
::
@@ -71,9 +77,13 @@ def check(conf, comment):
yield LintProblem(comment.line_no, comment.column_no,
'too few spaces before comment')
if (conf['require-starting-space'] and
comment.pointer + 1 < len(comment.buffer) and
comment.buffer[comment.pointer + 1] != ' ' and
comment.buffer[comment.pointer + 1] != '\n'):
yield LintProblem(comment.line_no, comment.column_no + 1,
'missing starting space in comment')
if conf['require-starting-space']:
text_start = comment.pointer + 1
while (comment.buffer[text_start] == '#' and
text_start < len(comment.buffer)):
text_start += 1
if (text_start < len(comment.buffer) and
comment.buffer[text_start] not in (' ', '\n', '\0')):
yield LintProblem(comment.line_no,
comment.column_no + text_start - comment.pointer,
'missing starting space in comment')

View File

@@ -19,12 +19,12 @@ Use this rule to require or forbid the use of document end marker (``...``).
.. rubric:: Options
* Set ``present`` to ``yes`` when the document end marker is required, or to
``no`` when it is forbidden.
* Set ``present`` to ``true`` when the document end marker is required, or to
``false`` when it is forbidden.
.. rubric:: Examples
#. With ``document-end: {present: yes}``
#. With ``document-end: {present: true}``
the following code snippet would **PASS**:
::
@@ -49,7 +49,7 @@ Use this rule to require or forbid the use of document end marker (``...``).
- is: another one
...
#. With ``document-end: {present: no}``
#. With ``document-end: {present: false}``
the following code snippet would **PASS**:
::

View File

@@ -19,12 +19,12 @@ Use this rule to require or forbid the use of document start marker (``---``).
.. rubric:: Options
* Set ``present`` to ``yes`` when the document start marker is required, or to
``no`` when it is forbidden.
* Set ``present`` to ``true`` when the document start marker is required, or to
``false`` when it is forbidden.
.. rubric:: Examples
#. With ``document-start: {present: yes}``
#. With ``document-start: {present: true}``
the following code snippet would **PASS**:
::
@@ -45,7 +45,7 @@ Use this rule to require or forbid the use of document start marker (``---``).
- this
- is: another one
#. With ``document-start: {present: no}``
#. With ``document-start: {present: false}``
the following code snippet would **PASS**:
::

View File

@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Greg Dubicki
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to prevent nodes with empty content, that implicitly result in
``null`` values.
.. rubric:: Options
* Use ``forbid-in-block-mappings`` to prevent empty values in block mappings.
* Use ``forbid-in-flow-mappings`` to prevent empty values in flow mappings.
.. rubric:: Examples
#. With ``empty-values: {forbid-in-block-mappings: true}``
the following code snippets would **PASS**:
::
some-mapping:
sub-element: correctly indented
::
explicitly-null: null
the following code snippets would **FAIL**:
::
some-mapping:
sub-element: incorrectly indented
::
implicitly-null:
#. With ``empty-values: {forbid-in-flow-mappings: true}``
the following code snippet would **PASS**:
::
{prop: null}
{a: 1, b: 2, c: 3}
the following code snippets would **FAIL**:
::
{prop: }
::
{a: 1, b:, c: 3}
"""
import yaml
from yamllint.linter import LintProblem
ID = 'empty-values'
TYPE = 'token'
CONF = {'forbid-in-block-mappings': bool,
'forbid-in-flow-mappings': bool}
def check(conf, token, prev, next, nextnext, context):
if conf['forbid-in-block-mappings']:
if isinstance(token, yaml.ValueToken) and isinstance(next, (
yaml.KeyToken, yaml.BlockEndToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'empty value in block mapping')
if conf['forbid-in-flow-mappings']:
if isinstance(token, yaml.ValueToken) and isinstance(next, (
yaml.FlowEntryToken, yaml.FlowMappingEndToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'empty value in flow mapping')

View File

@@ -25,12 +25,12 @@ Use this rule to control the indentation.
same within the file.
* ``indent-sequences`` defines whether block sequences should be indented or
not (when in a mapping, this indentation is not mandatory -- some people
perceive the ``-`` as part of the indentation). Possible values: ``yes``,
``no``, ``whatever`` and ``consistent``. ``consistent`` requires either all
block sequences to be indented, or none to be. ``whatever`` means either
perceive the ``-`` as part of the indentation). Possible values: ``true``,
``false``, ``whatever`` and ``consistent``. ``consistent`` requires either
all block sequences to be indented, or none to be. ``whatever`` means either
indenting or not indenting individual block sequences is OK.
* ``check-multi-line-strings`` defines whether to lint indentation in
multi-line strings. Set to ``yes`` to enable, ``no`` to disable.
multi-line strings. Set to ``true`` to enable, ``false`` to disable.
.. rubric:: Examples
@@ -99,7 +99,7 @@ Use this rule to control the indentation.
Russian:
dolls
#. With ``indentation: {spaces: 2, indent-sequences: no}``
#. With ``indentation: {spaces: 2, indent-sequences: false}``
the following code snippet would **PASS**:
::
@@ -152,7 +152,7 @@ Use this rule to control the indentation.
- spaghetti
- sauce
#. With ``indentation: {spaces: 4, check-multi-line-strings: yes}``
#. With ``indentation: {spaces: 4, check-multi-line-strings: true}``
the following code snippet would **PASS**:
::
@@ -399,6 +399,10 @@ def _check(conf, token, prev, next, nextnext, context):
# - item 1
# - item 2
indent = next.start_mark.column
elif next.start_mark.column == token.start_mark.column:
# -
# key: value
indent = next.start_mark.column
else:
# -
# item 1
@@ -469,7 +473,19 @@ def _check(conf, token, prev, next, nextnext, context):
if context['indent-sequences'] is False:
indent = context['stack'][-1].indent
elif context['indent-sequences'] is True:
indent = detect_indent(context['stack'][-1].indent, next)
if (context['spaces'] == 'consistent' and
next.start_mark.column -
context['stack'][-1].indent == 0):
# In this case, the block sequence item is not indented
# (while it should be), but we don't know yet the
# indentation it should have (because `spaces` is
# `consistent` and its value has not been computed yet
# -- this is probably the beginning of the document).
# So we choose an arbitrary value (2).
indent = 2
else:
indent = detect_indent(context['stack'][-1].indent,
next)
else: # 'whatever' or 'consistent'
if next.start_mark.column == context['stack'][-1].indent:
# key:

View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Johannes F. Knauf
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to enforce alphabetical ordering of keys in mappings. The sorting
order uses the Unicode code point number. As a result, the ordering is
case-sensitive and not accent-friendly (see examples below).
.. rubric:: Examples
#. With ``key-ordering: {}``
the following code snippet would **PASS**:
::
- key 1: v
key 2: val
key 3: value
- {a: 1, b: 2, c: 3}
- T-shirt: 1
T-shirts: 2
t-shirt: 3
t-shirts: 4
- hair: true
hais: true
haïr: true
haïssable: true
the following code snippet would **FAIL**:
::
- key 2: v
key 1: val
the following code snippet would **FAIL**:
::
- {b: 1, a: 2}
the following code snippet would **FAIL**:
::
- T-shirt: 1
t-shirt: 2
T-shirts: 3
t-shirts: 4
the following code snippet would **FAIL**:
::
- haïr: true
hais: true
"""
import yaml
from yamllint.linter import LintProblem
ID = 'key-ordering'
TYPE = 'token'
CONF = {}
MAP, SEQ = range(2)
class Parent(object):
def __init__(self, type):
self.type = type
self.keys = []
def check(conf, token, prev, next, nextnext, context):
if 'stack' not in context:
context['stack'] = []
if isinstance(token, (yaml.BlockMappingStartToken,
yaml.FlowMappingStartToken)):
context['stack'].append(Parent(MAP))
elif isinstance(token, (yaml.BlockSequenceStartToken,
yaml.FlowSequenceStartToken)):
context['stack'].append(Parent(SEQ))
elif isinstance(token, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken)):
context['stack'].pop()
elif (isinstance(token, yaml.KeyToken) and
isinstance(next, yaml.ScalarToken)):
# This check is done because KeyTokens can be found inside flow
# sequences... strange, but allowed.
if len(context['stack']) > 0 and context['stack'][-1].type == MAP:
if any(next.value < key for key in context['stack'][-1].keys):
yield LintProblem(
next.start_mark.line + 1, next.start_mark.column + 1,
'wrong ordering of key "%s" in mapping' % next.value)
else:
context['stack'][-1].keys.append(next.value)

View File

@@ -22,7 +22,9 @@ Use this rule to set a limit to lines length.
* ``max`` defines the maximal (inclusive) length of lines.
* ``allow-non-breakable-words`` is used to allow non breakable words (without
spaces inside) to overflow the limit. This is useful for long URLs, for
instance. Use ``yes`` to allow, ``no`` to forbid.
instance. Use ``true`` to allow, ``false`` to forbid.
* ``allow-non-breakable-inline-mappings`` implies ``allow-non-breakable-words``
and extends it to also allow non-breakable words in inline mappings.
.. rubric:: Examples
@@ -42,7 +44,7 @@ Use this rule to set a limit to lines length.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
#. With ``line-length: {max: 60, allow-non-breakable-words: yes}``
#. With ``line-length: {max: 60, allow-non-breakable-words: true}``
the following code snippet would **PASS**:
::
@@ -61,7 +63,20 @@ Use this rule to set a limit to lines length.
- this line is waaaaaaaaaaaaaay too long but could be easily split...
#. With ``line-length: {max: 60, allow-non-breakable-words: no}``
and the following code snippet would also **FAIL**:
::
- foobar: http://localhost/very/very/very/very/very/very/very/very/long/url
#. With ``line-length: {max: 60, allow-non-breakable-words: true,
allow-non-breakable-inline-mappings: true}``
the following code snippet would **PASS**:
::
- foobar: http://localhost/very/very/very/very/very/very/very/very/long/url
#. With ``line-length: {max: 60, allow-non-breakable-words: false}``
the following code snippet would **FAIL**:
::
@@ -73,17 +88,39 @@ Use this rule to set a limit to lines length.
"""
import yaml
from yamllint.linter import LintProblem
ID = 'line-length'
TYPE = 'line'
CONF = {'max': int,
'allow-non-breakable-words': bool}
'allow-non-breakable-words': bool,
'allow-non-breakable-inline-mappings': bool}
def check_inline_mapping(line):
loader = yaml.SafeLoader(line.content)
try:
while loader.peek_token():
if isinstance(loader.get_token(), yaml.BlockMappingStartToken):
while loader.peek_token():
if isinstance(loader.get_token(), yaml.ValueToken):
t = loader.get_token()
if isinstance(t, yaml.ScalarToken):
return (
' ' not in line.content[t.start_mark.column:])
except yaml.scanner.ScannerError:
pass
return False
def check(conf, line):
if line.end - line.start > conf['max']:
conf['allow-non-breakable-words'] |= \
conf['allow-non-breakable-inline-mappings']
if conf['allow-non-breakable-words']:
start = line.start
while start < line.end and line.buffer[start] == ' ':
@@ -96,6 +133,10 @@ def check(conf, line):
if line.buffer.find(' ', start, line.end) == -1:
return
if (conf['allow-non-breakable-inline-mappings'] and
check_inline_mapping(line)):
return
yield LintProblem(line.line_no, conf['max'] + 1,
'line too long (%d > %d characters)' %
(line.end - line.start, conf['max']))

View File

@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 ScienJus
#
# 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 prevent values with octal numbers. In YAML, numbers that
start with ``0`` are interpreted as octal, but this is not always wanted.
For instance ``010`` is the city code of Beijing, and should not be
converted to ``8``.
.. rubric:: Examples
#. With ``octal-values: {forbid-implicit-octal: true}``
the following code snippets would **PASS**:
::
user:
city-code: '010'
the following code snippets would **PASS**:
::
user:
city-code: 010,021
the following code snippets would **FAIL**:
::
user:
city-code: 010
#. With ``octal-values: {forbid-explicit-octal: true}``
the following code snippets would **PASS**:
::
user:
city-code: '0o10'
the following code snippets would **FAIL**:
::
user:
city-code: 0o10
"""
import yaml
from yamllint.linter import LintProblem
ID = 'octal-values'
TYPE = 'token'
CONF = {'forbid-implicit-octal': bool,
'forbid-explicit-octal': bool}
def check(conf, token, prev, next, nextnext, context):
if prev and isinstance(prev, yaml.tokens.TagToken):
return
if conf['forbid-implicit-octal']:
if isinstance(token, yaml.tokens.ScalarToken):
if not token.style:
val = token.value
if val.isdigit() and len(val) > 1 and val[0] == '0':
yield LintProblem(
token.start_mark.line + 1, token.end_mark.column + 1,
'forbidden implicit octal value "%s"' %
token.value)
if conf['forbid-explicit-octal']:
if isinstance(token, yaml.tokens.ScalarToken):
if not token.style:
val = token.value
if len(val) > 2 and val[:2] == '0o' and val[2:].isdigit():
yield LintProblem(
token.start_mark.line + 1, token.end_mark.column + 1,
'forbidden explicit octal value "%s"' %
token.value)

93
yamllint/rules/truthy.py Normal file
View File

@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Peter Ericson
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to forbid truthy values that are not quoted nor explicitly typed.
This would prevent YAML parsers from transforming ``[yes, FALSE, Off]`` into
``[true, false, false]`` or ``{y: 1, yes: 2, on: 3, true: 4, True: 5}`` into
``{y: 1, true: 5}``.
.. rubric:: Examples
#. With ``truthy: {}``
the following code snippet would **PASS**:
::
boolean: true
object: {"True": 1, 1: "True"}
"yes": 1
"on": 2
"true": 3
"True": 4
explicit:
string1: !!str True
string2: !!str yes
string3: !!str off
encoded: !!binary |
True
OFF
pad== # this decodes as 'N\xbb\x9e8Qii'
boolean1: !!bool true
boolean2: !!bool "false"
boolean3: !!bool FALSE
boolean4: !!bool True
boolean5: !!bool off
boolean6: !!bool NO
the following code snippet would **FAIL**:
::
object: {True: 1, 1: True}
the following code snippet would **FAIL**:
::
yes: 1
on: 2
true: 3
True: 4
"""
import yaml
from yamllint.linter import LintProblem
ID = 'truthy'
TYPE = 'token'
CONF = {}
TRUTHY = ['YES', 'Yes', 'yes',
'NO', 'No', 'no',
'TRUE', 'True', # 'true' is a boolean
'FALSE', 'False', # 'false' is a boolean
'ON', 'On', 'on',
'OFF', 'Off', 'off']
def check(conf, token, prev, next, nextnext, context):
if prev and isinstance(prev, yaml.tokens.TagToken):
return
if isinstance(token, yaml.tokens.ScalarToken):
if token.value in TRUTHY and token.style is None:
yield LintProblem(token.start_mark.line + 1,
token.start_mark.column + 1,
"truthy value is not quoted")