Compare commits

..

343 Commits

Author SHA1 Message Date
Dimitri Papadopoulos a68c3aa69e document-end: Fix spurious "missing document end"
DocumentStartToken is preceded by DirectiveToken.
2 years ago
Nick Cao 7f2c071545
build: Fix license identifier according to license notice in source files 2 years ago
Adrien Vergé b05e028c58 yamllint version 1.32.0 2 years ago
Georgi Georgiev e636848ddc
config: Look for configuration file in parent directories
Inspired be ESLint's search, it looks for configuration files in all
parent directories up until it reaches the user's home or root.

closes #571
2 years ago
Adrien Vergé 019c87d36d anchors: Update code style to use single quotes
Like the rest of the project does.
2 years ago
Adrien Vergé 977f4908b5 anchors: Add missing quotes in unused anchor error message
Existing `anchors` options use quotes around the anchor name:

    2:3       error    found undeclared alias "unknown"  (anchors)
    4:3       error    found duplicated anchor "dup"  (anchors)

Let's do the same in the newly-added option `forbid-unused-anchors`:

    5:3       error    found unused anchor "not used"  (anchors)
2 years ago
amimas f874b6607c anchors: Add new option to detect unused anchors
According to the YAML specification [^1]:

- > An anchored node need not be referenced by any alias nodes

This means that it's OK to declare anchors but don't have any alias
referencing them. However users could want to avoid this, so a new
option (e.g. `forbid-unused-anchors`) is implemented in this change.
It is disabled by default.

[^1]: https://yaml.org/spec/1.2.2/#692-node-anchors
2 years ago
Adrien Vergé 98f2281f56 yamllint version 1.31.0 2 years ago
Adrien Vergé 15eafeb80a build: Migrate from setup.py to pyproject.toml
Using `setup.py` is deprecated and the new recommanded way is to declare
a `pyproject.toml` file (see PEP 517 [^1]).

This commit proposes to use setuptools to achieve that, because
setuptools is already used by yamllint, is standard and referenced by
the official Python packaging documentation [^2], and seems to be the
most lightweight solution. An alternative could have been to use Poetry,
see the dedicated pull request and discussion [^3].

For some period, the `setup.py` file will be kept (although almost
empty), to allow old tools versions to keep working.

Closes https://github.com/adrienverge/yamllint/issues/509.

[^1]: https://peps.python.org/pep-0517/
[^2]: https://packaging.python.org/en/latest/tutorials/installing-packages/
[^3]: https://github.com/adrienverge/yamllint/pull/557
2 years ago
Adrien Vergé 16eae28a50 build: Stop using setup.py to generate documentation
Because `setup.py` is deprecated, let's switch from:

    python setup.py build_sphinx

to:

    make -C docs html

to build Sphinx documentation.

The generated HTML files in `docs/_build/html` are exactly the same (I
compared with `diff -qr`).

Also add `-W` (turn warnings into errors) to the `sphinx-build` options
to keep the previous behavior.
2 years ago
Adrien Vergé 771c3a0412 README: Update CI status badge
It is a leftover from commit 66bf76a "CI: Switch to GitHub Actions".
2 years ago
Adrien Vergé b92fc9cb31 colons: Prevent error when space before is mandatory
In the rare case when the key before `:` is an alias (e.g. `{*x : 4}`),
the space before `:` is required (although this requirement is not
enforced by PyYAML), the reason being that a colon can be part of an
anchor name. Consequently, this commit adapts the `colons` rule to avoid
failures when this happens.

See this comment from Tina Müller for more details:
https://github.com/adrienverge/yamllint/pull/550#discussion_r1155297373
2 years ago
Adrien Vergé e90e0a0eb5 anchors: Fix invalid YAML in aliases test cases
Although accepted by PyYAML, `{*x: 4}` is not valid YAML: it should be
noted `{*x : 4}`. The reason is that a colon can be part of an anchor
name. See this comment from Tina Müller for more details:
https://github.com/adrienverge/yamllint/pull/550#discussion_r1155297373

Even if it's not a problem for yamllint, let's fix our tests to include
valid YAML snippets.
2 years ago
Andrew Imeson 6bfd6756e2 docs: Update links that redirect 2 years ago
Andrew Imeson 6b45be1afc CI: Check for broken links in docs 2 years ago
Adrien Vergé 9d0f59876d yamllint version 1.30.0 2 years ago
Adrien Vergé ebd6b90d3e anchors: Add new rule to detect undeclared or duplicated anchors
According to the YAML specification [^1]:

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

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

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

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

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

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

Fixes #395
Closes #420

[^1]: https://yaml.org/spec/1.2.2/#71-alias-nodes
2 years ago
Andrew Imeson 8aaa226830
docs: Update pre-commit hook example
Update syntax of pre-commit hook docs to work with newer pre-commit versions.

Closes #551, #553
2 years ago
Adrien Vergé 15f8204427 linter: Prevent testing is_file_ignored() with filepath == None
As reported in https://github.com/adrienverge/yamllint/pull/548, there
might be a problem with	pathspec 0.11.1 which does't allow calling
`match_file()` with argument `None` anymore.

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

Error:

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

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

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

Directly yield from an iterable instead of iterating to yield items.
2 years ago
Dimitri Papadopoulos 5b21a3d9ea Remove Unicode marker before strings
All strings are Unicode in Python 3. No need for u'€', just use '€'.
2 years ago
Dimitri Papadopoulos 5fbf44c203 docs: Fix typos 2 years ago
Michael Käufl c9c5e0b1c7 CI: Add support for Python 3.11 2 years ago
Adrien Vergé a6e0e1213a CI: Fix pip install problem with Pygments version
Recently `python setup.py build_sphinx` started failing with:

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

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

The easiest fix is to change the install order.
2 years ago
Adrien Vergé eb7b7ca627 docs: Fix Sphinx error on non-YAML code snippet
This problem was just introduced by commit cec4f33 "Clarify disable-line
and parser errors, workaround" and produced this error when building
documentation:

    docs/disable_with_comments.rst:120:Could not lex literal_block as
    "yaml". Highlighting skipped.
2 years ago
Andrew Imeson cec4f3383a cocs: Clarify disable-line and parser errors, workaround
Lots of user confusion expecting `disable-line` to work around parser
errors caused by templating syntax.

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

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

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

Fixes issue #485
2 years ago
Dimitri Papadopoulos 47cd8f2e9e No need to inherit from `object` in Python 3 2 years ago
Dimitri Papadopoulos 4d271f3daf ci: Security hardening for GitHub Actions
https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs

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

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

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

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

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

Fixes https://github.com/adrienverge/yamllint/issues/495
2 years ago
Dimitri Papadopoulos Orfanos e8391de711
Drop support for Python 3.6
Python 3.6 reached end-of-life on 2021-12-23.
https://peps.python.org/pep-0494/
2 years ago
Dimitri Papadopoulos a5adec1570 ci: Update GitHub Actions
https://github.com/actions/checkout
https://github.com/actions/setup-python
2 years ago
Adrien Vergé 9cce294041 yamllint version 1.28.0 2 years ago
andrewnaguib 2f8ad7003a config: Implement for `ignore-from-file` option
Closes https://github.com/adrienverge/yamllint/issues/360
Co-authored-by: Adrien Vergé <@adrienverge>
2 years ago
Roman Geraskin fb0c0a5247 quoted-strings: fix docs example 2 years ago
Roman Geraskin 352e1a975e
quoted-strings: Add allow-quoted-quotes option
Allows strings like `'foo"bar'` on `quote-type: double` and vice versa.
2 years ago
Dimitri Papadopoulos e319a17344 octal values: simpler test for match objects
From the Python 3 documentation:
	Match objects always have a boolean value of True.
	Since match() and search() return None when there is no match,
	you can test whether there was a match with a simple if statement:
		match = re.search(pattern, string)
		if match:
		    process(match)
2 years ago
Dimitri Papadopoulos 6b6fdba3bf linter: pre-compile disable/enable rules regexes
Not only this should improve performance, but I find the code more
readable.
2 years ago
Dimitri Papadopoulos Orfanos 868350681a
License: Update to latest version of GPLv3
http:// → https://
2 years ago
Andrew Imeson 94c1c2bcf2 docs: Update ALE vim plugin link 2 years ago
Andrew Imeson 0130e15c8c docs: Simplify GitHub Actions example 2 years ago
Dimitri Papadopoulos ae3158cd1f No need to inherit from `object` in Python 3 2 years ago
Dimitri Papadopoulos 4c7b47daf3 Most __future__ imports are specific to Python 2 2 years ago
Dimitri Papadopoulos Orfanos 3346843edc
docs: Better compress PNG image 2 years ago
Dimitri Papadopoulos Orfanos ea70520216
Changelog: Fix typo found by codespell 2 years ago
Adrien Vergé a09ad89268 yamllint version 1.27.1 3 years ago
Adrien Vergé 8d543a4b9c key-duplicates: Fix failing test for missing space after colon
Commit c268a82 "key-duplicates: Don't crash on redundant closing
brackets or braces" fixed a problem but introduced another one: it
crashes on systems with (I guess) an old version of PyYAML. This is
probably linked to the "Allow colon in a plain scalar in a flow context"
issue on PyYAML [1].
For example, this problem happens on CentOS 8:

    FAIL: test_disabled (tests.rules.test_key_duplicates.KeyDuplicatesTestCase)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "…/tests/rules/test_key_duplicates.py", line 90, in test_disabled
        '{a:1, b:2}}\n', conf, problem=(2, 11, 'syntax'))
      File "…/tests/common.py", line 54, in check
        self.assertEqual(real_problems, expected_problems)
    AssertionError: Lists differ: …
    - [2:3: syntax error: found unexpected ':' (syntax)]
    + [2:11: <no description>]

I propose to simply fix the *space following a colon* problem, since
it's not related to what the original author @tamere-allo-peter tried to
fix.

[1]: https://github.com/yaml/pyyaml/pull/45
3 years ago
Adrien Vergé 8a364e2fde yamllint version 1.27.0 3 years ago
Adrien Vergé dc2d0991e0 float-values: Refactor tests to be less verbose
The goal is to be more concise thus more readable, as well as consistent
with most other tests.
3 years ago
Adrien Vergé e6af957131 float-values: Add missing quotes in problems messages
To be consistent with other existing messages, e.g.:
- forbidden not a number value ".NaN"
- found forbidden document start "---"
- missing document start "---"
- truthy value should be one of ["true"]
- forbidden implicit octal value "0777"
3 years ago
Derek Brown 8ac7d58693 float-values: Add a new rule to check floating-point numbers 3 years ago
Andrew Imeson 40cab7f999 docs: Bump application copyright year 3 years ago
Andrew Imeson 34a4f76e8b docs: Remove repeated word 'copyright'
Fixes #476 - credit to @chrillep and @adrienverge
3 years ago
Jan Wille 7d9c824b83 new-lines: add `type: platform` config option
The new option infers the correct newline character(s) from the
operating system running yamllint.
3 years ago
Jan Wille 157b06871d new-lines: refactor to reduce duplicate code
Both options where using identical code.  Now the newline character
is determined beforehand depending on the selected option and then
the same code can be used for all options
3 years ago
Jan Wille af843b675a new-lines: explicitly check \n for type: unix
To be more consistent with the other types, unix now also checks against
the expected newline character (`\n`) instead of checking if a wrong
character (`\r`) is present
3 years ago
Christian Widlund 695fc5f1f1 docs: Add plugin section for Visual Studio Code 3 years ago
Christian Widlund 632665c3e6 docs: Add plugin section for IntelliJ 3 years ago
Derek Brown 5658cf7f42 octal-values: Pre-compile regex for performance 3 years ago
Matt Clay bdbec7dc4d linter: Remove redundant conditional
Remove the redundant conditional used when reporting a syntax error
at the same location as a cosmetic problem. Also reword the comment
explaining the logic to more accurately describe the situation.

This eliminates an unreachable `syntax_error = None` assignment.
3 years ago
Matt Clay 9700525496 linter: Remove unreachable exception handler
Remove two `try/except UnicodeError` exception handlers which were
added in commit c8ba8f7e99 for
Python 2.x compatibility. Now that Python 2.x is no longer
supported, the `except` is unreachable and is no longer needed.
3 years ago
Matt Clay 327f92e472 tests: Increase test coverage
- Add a `temp_workspace` context manager to simplify writing new tests.
- Add `# pragma: no cover` to unit test code paths used for skipping tests.
  These code paths are only covered when tests are skipped.
  That makes it impractical to reach full code coverage on the unit test code.
  Having full coverage of unit tests is helpful for identifying unused tests.
- Test the `octal-values` rule with a custom tag.
- Test the cli `-d` option with the `default` config.
- Test support for the `XDG_CONFIG_HOME` env var.
- Test warning message output.
- Test support for `.yamllint.yml` config files.
- Test support for `.yamllint.yaml` config files.
- Test error handling of a rule with a non-enable|disable|dict value.
- Test error handling of `ignore` with a non-pattern value.
- Test error handling of a rule `ignore` with a non-pattern value.
- Test error handling of `locale` with a non-string value.
- Test error handling of `yaml-files` with a non-list value.
- Test extending config containing `ignore`.
- Test `LintProblem.__repr__` without a rule.
- Test `LintProblem.__repr__` with a rule.
3 years ago
Adrien Vergé 89b75b7c05 refactor: Remove UTF-8 headers in Python files
The `# -*- coding: utf-8 -*-` headers were useful for Python 2, and
aren't needed for Python 3 where UTF-8 is the default.

yamllint support of Python 2 was dropped in early 2021, see commit
a3fc64d "End support for Python 2".

Let's drop these headers.
3 years ago
Andrew Imeson e49a101160 Add rstcheck to CI to lint docs 3 years ago
Jérôme Alet c268a82c5a
key-duplicates: Don't crash on redundant closing brackets or braces
Don't break on empty `context` stack when invalid YAML:

    [ a, b, c ] ]
    {a: 1, b: 2} }
3 years ago
Andrew Imeson 2f423117c1
docs: Attempt to clarify configuration file location
Closes #96, Closes #212
3 years ago
Andrew Imeson f58448cb21 Fix spelling of "across" in test output 3 years ago
Andrew Imeson 7974d518cd Fix grammar in key_ordering docs to make Lintian happy
Fixes part of #76
3 years ago
Andrew Imeson 8a320aaf2c Make man page show up in apropos
Set the 'description' attribute so that Sphinx builds the manpage with
the 'NAME' section. This is necessary for `apropos` to be able to find
yamllint

Fixes part of #76
3 years ago
Andrew Imeson c34c962691 Remove the repeated word "automatically" in GHA doc 3 years ago
Madison Swain-Bowden 4f1bbc33dc
docs: Fix link syntax on integration.rst 3 years ago
Jérôme Alet bb567ba395
comments: Allow whitespace after the shebang marker
Basically, any character is now allowed after the shebang marker.

Closes #428.

Whitespace after the #! marker on shebang lines is authorized and
optional, as explained on Wikipedia's entry for shebang line as can be
seen from the extracts below :

> White space after #! is optional

and

> It has been claimed[20] that some old versions of Unix expect the
> normal shebang to be followed by a space and a slash (#! /), but this
> appears to be untrue;[21] rather, blanks after the shebang have
> traditionally been allowed, and sometimes documented with a space
3 years ago
Trevor Royer d0392b34ca
github format: Update output to utilize groups
Resolves #421

Update the github formatting to utilize groups in the output and provide
the line/column number for the error in the output log.
3 years ago
Trevor Royer 7246a0c800
cli: Separate --format=auto logic
Moved the auto arg_format selection out of the main if block into a
separate logic section to improve readability.

No logic changes.
3 years ago
Dmytro Bondar 9e6dfacceb Fix github actions workflow
- install correct python version
- set `fail-fast: false` to run all jobs
- remove hard-coded value for HOME directory
3 years ago
Dmytro Bondar 11e8d8ff37 Add support for Python 3.10, drop Python 3.5
- Add support for Python 3.10 released on 2021-10-04
- Drop support for Python 3.5 since it has reached end-of-life
3 years ago
Adrien Vergé f2e2e0c366 docs: Update CONTRIBUTING.rst
Be more precise in contributing instructions.
3 years ago
Adrien Vergé 058fef7559 yamllint version 1.26.3 3 years ago
Adrien Vergé f47d5318cf Restore setuptools requirement for Python < 3.8
This reverts commit 8f68248 "Remove runtime dep 'setuptools' for Python
< 3.8". It looks like removing setuptools induces problems on some
systems, see for example the linked discussion.

Fixes https://github.com/adrienverge/yamllint/issues/380.
3 years ago
Adrien Vergé 33ce0fa960 yamllint version 1.26.2 3 years ago
Kyle Finley 43744902e9
setup: update python_requires to comply with PEP 345/440
According to PEP 345 Requires-Python
(https://www.python.org/dev/peps/pep-0345/#requires-python), the value
of this field must be a valid Version Specifier
(https://www.python.org/dev/peps/pep-0345/#version-specifiers). Which
in turn expects this to comply with PEP 440
(https://www.python.org/dev/peps/pep-0440/).

While not an issue for those that directly use `pip`, this will cause
issues for `poetry` users in the next release (if their current stance
is maintained). Discussion of the issue and there stance can be found
here: https://github.com/python-poetry/poetry/issues/4095.
4 years ago
Adrien Vergé 85ccd625a3 yamllint version 1.26.1 4 years ago
Patryk Małek e53ea093e2
line_length: skip all hash signs starting comment 4 years ago
Adrien Vergé 5d8ef2ea23 CI: Simplify 'pip' commands 4 years ago
Adrien Vergé 4515269233 CI: Fix failing 'coverage' command because of $PATH
Very probably due to:
https://github.com/actions/virtual-environments/issues/2455#issuecomment-787511010
4 years ago
Adrien Vergé 66bf76a362 CI: Switch to GitHub Actions
Because Travis CI is dead.
4 years ago
Daniel M. Capella 8f682481c7
Remove runtime dep 'setuptools' for Python < 3.8
> In recent versions of setuptools and Python, console-script entry
points are using stdlib importlib by default, thus setuptools is no
longer needed as a runtime dependency.

https://github.com/pypa/setuptools/pull/2197
https://github.com/pypa/setuptools/blob/main/CHANGES.rst#v4730
https://docs.python.org/3/library/importlib.metadata.html
4 years ago
Adrien Vergé 0fff4e29e4 yamllint version 1.26.0 4 years ago
Adrien Vergé 1b378ed5b9 quoted-strings: Fix explicit octal recognition
PyYAML implements YAML spec version 1.1, not 1.2. Hence, values starting
with `0o` are not considered as numbers: they are just strings, so they
need quotes when `quoted-strings: {required: true}`.

>>> import yaml
>>> yaml.resolver.Resolver().resolve(yaml.nodes.ScalarNode, '100', (True, False))
'tag:yaml.org,2002:int'
>>> yaml.resolver.Resolver().resolve(yaml.nodes.ScalarNode, '0100', (True, False))
'tag:yaml.org,2002:int'
>>> yaml.resolver.Resolver().resolve(yaml.nodes.ScalarNode, '0o100', (True, False))
'tag:yaml.org,2002:str'

Let's try to prevent that.

Fixes https://github.com/adrienverge/yamllint/issues/351.
4 years ago
Adrien Vergé a3fc64d134 End support for Python 2
As planned and advertized, yamllint drops support for Python 2 on 2021.
4 years ago
Rusty Geldmacher ee4d163ff8
Allow only non-empty brackets/braces
We'd like to disallow brackets and braces in our YAML, but there's a
catch: the only way to describe an empty array or hash in YAML is to
supply an empty one (`[]` or `{}`). Otherwise, the value will be null.

This commit adds a `non-empty` option to `forbid` for brackets and
braces. When it is set, all flow and sequence mappings will cause errors
_except_ for empty ones.
4 years ago
Jason Mobarak 22335b294d Add support for Python 3.9, drop Python 3.4
Add support for Python 3.9 since it was officially released in October
and drop support for Python 3.4 since it is end-of-life (EOL).
4 years ago
Rex Ledesma 0f9dffde23
docs: Add configuration for integration with Arcanist 4 years ago
Mathieu Couette cef0b48993 tests: Add unittest aliases to Python 2.7 4 years ago
Mathieu Couette 11b1f1c14e tests: Fix indentation issues 4 years ago
Mathieu Couette 9ee8c27ac9 tests: Replace deprecated aliases
https://docs.python.org/3/library/unittest.html#deprecated-aliases
4 years ago
Florian Bruhin 8eebab68ab Fix typo in changelog 4 years ago
Per Lundberg 2103bd73de README.rst: fix typo 4 years ago
Adrien Vergé 85c8631183 tests: Stop using deprecated 'python setup.py test'
Using `python setup.py test` is now deprecated [1], users are encouraged
to be explicit about the test command.

Running yamllint tests using the Python standard library (`unittest`)
can be done using:

    python -m unittest discover

Why not nose, tox or pytest? Because they would add a dependency, make
tests running more complicated and verbose for new users, and their
benefit is not worth for this simple project (only 2 runtime
dependencies: PyYAML and pathspec).

Resolves https://github.com/adrienverge/yamllint/issues/328.

[1]: https://github.com/pypa/setuptools/pull/1878
4 years ago
Adrien Vergé 16e0f9d7b2 yamllint version 1.25.0 4 years ago
Mathieu Couette 1a4f9fe00f
gitignore: Add /.eggs
Quick PR to ignore the `/.eggs` folder, which appears to be generated every
time the `python setup.py test` command is run.

The content of the `./.eggs/README.txt` file:

> This directory contains eggs that were downloaded by setuptools to build,
> test, and run plug-ins.
> 
> This directory caches those eggs to prevent repeated downloads.
> 
> However, it is safe to delete this directory.
4 years ago
Mathieu Couette 027d1b0a9a
directives: Fix DOS lines messing with rule IDs
Fixes #325

The linter allows a directive to contain trailing whitespace characters like
\r, but does not trim them before iterating on the rules. As a result, the last
rule in the list contains the trailing whitespace characters and never matches
any existing rule.

I added the necessary trimming, as well as a test with 2 checks to go along
with it.
4 years ago
Andrew Imeson 67cb4eb24d Auto-change output format if GitHub Actions detected 4 years ago
Andrew Imeson 50c7453824 Add support for GitHub Annotations output format
Support the format used by GitHub Actions to annotate pull
requests with linter failures
4 years ago
Satoru SATOH 549b136a04 fix: add runtime dependency to setuptools
yamllint depends on pkg_resources.load_entry_point from setuptools to
make its command working, so this runtime dependency to setuptools is
necessary to be listed.
4 years ago
Satoru SATOH 333ae52c78 Add 'forbid' configurations to the braces and brackets rules
Add 'forbid' configuration parameters to the braces and brackets rules
to allow users to forbid the use of flow style collections, flow
mappings and flow sequences.
4 years ago
Julien Falque 0a88c55194
quoted-strings: Fix detecting strings with hashtag as requiring quotes 4 years ago
Julien Falque ac19d1e427
octal-values: Prevent detection of 8 and 9 as octal values 4 years ago
Adrien Vergé 597e88bb7b docs: Make 'yaml-file' config documentation clearer
Related to https://github.com/adrienverge/yamllint/issues/311.
4 years ago
Satoru SATOH 29d2b50d50 enhancement: add some metadata to provide extra info in its PyPI page
Add some metadata (project_urls) to provide extra info in its PyPI page.

Signed-off-by: Satoru SATOH <satoru.satoh@gmail.com>
4 years ago
Satoru SATOH 4171cdafc9 Move setuptools' packaging configuration from setup.py to setup.cfg
Move setuptools' packaging configuration from setup.py to setup.cfg to
simplify setup.py and make its packaging more dedeclarative.

Signed-off-by: Satoru SATOH <satoru.satoh@gmail.com>
4 years ago
Sorin Sbarnea d274543b72
docs: Add Python API usage example
Fixes: #297
4 years ago
Kirill Deyko 8da98f2122
commas: Fix example in documentation
Error in the example snippet, it would NOT pass otherwise actually:
```
$ cat test.yml
strange var:
  [10, 20,30, {x: 1, y: 2}]

$ yamllint -d "{extends: default, rules: {commas: {min-spaces-after: 1, max-spaces-after: 1}}}" test.yml
test.yml
  1:1       warning  missing document start "---"  (document-start)
  2:11      error    too few spaces after comma  (commas)
```
4 years ago
Benjamin Wuethrich b65769c9d2
docs: Add default values to rules with options 5 years ago
Wolfgang Walther b80997eba6
CI: Add build environment without UTF-8 locales to travis-ci
Preventing regressions like #285
5 years ago
Adrien Vergé 8b758d4e7e yamllint version 1.24.2 5 years ago
Wolfgang Walther b5b436a3a4
Add global "locale" config option and make key-ordering rule locale-aware
Support sorting by locale with strcoll(). Properly handle case and accents.

Note: this is a second implementation, for context see:
https://github.com/adrienverge/yamllint/pull/280
https://github.com/adrienverge/yamllint/issues/285
https://github.com/adrienverge/yamllint/pull/288
5 years ago
Adrien Vergé 0fceca2354 yamllint version 1.24.1 5 years ago
Adrien Vergé 9403f1f3ec Revert "Add global "locale" config option"
This reverts commit 9e90c77, because it caused a bug that affected
different people just after being released:
https://github.com/adrienverge/yamllint/issues/285
https://github.com/adrienverge/yamllint/issues/286
5 years ago
Adrien Vergé 0016390e78 yamllint version 1.24.0 5 years ago
Wolfgang Walther 9e90c777cb
Add global "locale" config option and make key-ordering rule locale-aware
Support sorting by locale with strcoll(). Properly handle case and accents.
5 years ago
Jonathan Sokolowski a2218988ee
config: Do no match directories that look like YAML files
Fixes #279
5 years ago
Adrien Vergé 954fdd5e8f style: Fix 'noqa' for flake8 3.8.0
There was a change in behavior of E402, see:
https://gitlab.com/pycqa/flake8/-/issues/638#note_345108633
5 years ago
Sorin Sbarnea bbcad943b6
style: Ignore flake8 warnings W503 and W504
Avoid W503/W504 with current code as the current code not compliant
and they are contradictory.
5 years ago
Adrien Vergé 30c90dbf70 Add contribution instructions in CONTRIBUTING.rst
Closes https://github.com/adrienverge/yamllint/issues/263.
5 years ago
Brad Solomon 512fe17047
Fix bug with CRLF in new-lines and require-starting-space
Pound-signs followed by a lone CRLF should not
raise if require-starting-space is specified.

If require-starting-space is true, *and* either:
- new-lines: disbale, or
- newlines: type: dos
is specified, a line with `#\r` or `#\r\n` should
not raise a false positive.

This commit also uses a Set for O(1) membership testing
and uses the correct escape sequence for the nul byte.

If we find a CRLF when looking for Unix newlines, yamllint
should always raise, regardless of logic with
require-starting-space.

Closes: Issue #171.
5 years ago
Will Badart 278a79f093 Mention YAMLLINT_CONFIG_FILE in the documentation 5 years ago
Brad Solomon e98aacf62c Add Python 3.8 to PyPI/trove classifier data
3.8 is now formally supported in .travis.yml
as of this commit.
5 years ago
Will Badart 94c0416f6b
Specify config with environment variable YAMLLINT_CONFIG_FILE
Add option to specify config file with environment variable.
Add test case.
5 years ago
Adrien Vergé a54cbce1b6 yamllint version 1.23.0 5 years ago
Adrien Vergé b711fd993e quoted-strings: Add options extra-required and extra-allowed
Add ability to:
- require strings to be quoted if they match a pattern (PCRE regex)
- allow quoted strings if they match a pattern, while `require:
  only-when-needed` is enforced.

Co-Authored-By: Leo Feyer (https://github.com/adrienverge/yamllint/pull/246)
5 years ago
Adrien Vergé d68022b846 config: Allow generic types inside lists
For example it's possible to define a conf like:

    rule:
      foo: [str],
      bar: [int, bool, 'magic'],
5 years ago
Adrien Vergé 851d34b9fd config: Allow rules to validate their configuration 5 years ago
Adrien Vergé 483a8d89a5 yamllint version 1.22.1 5 years ago
Adrien Vergé fa87913566 quoted-strings: Fix only-when-needed on corner cases
Change implementation of `required: only-when-needed`, because
maintaining a list of `START_TOKENS` and just looking at the first
character of string values has proven to be partially broken.

Cf. discussion at
https://github.com/adrienverge/yamllint/pull/246#issuecomment-612354097.

Fixes https://github.com/adrienverge/yamllint/issues/242 and
https://github.com/adrienverge/yamllint/pull/244.
5 years ago
Adrien Vergé 961c496b4f yamllint version 1.22.0 5 years ago
Adrien Vergé ce7d3fcc7b quoted-strings: Remove test_quotes_required()
It is exactly the same tests as `test_quote_type_any()`.
5 years ago
Adrien Vergé 0bffba1e13 quoted-strings: Remove test_single_quotes_required()
It is exactly the same tests as `test_quote_type_single()`.
5 years ago
Adrien Vergé 2d8639c3a1 quoted-strings: Fix broken rule for list items
The rule worked for values like:

    flow-map: {a: foo, b: "bar"}
    block-map:
      a: foo
      b: "bar"

But not for:

    flow-seq: [foo, "bar"]
    block-seq:
      - foo
      - "bar"

Also add tests to make sure there will be no regression.

Fixes: #208.
5 years ago
Adrien Vergé e284d74be1 quoted-strings: Rename tests names for clarity
And move only-when-needed tests at the end for readability.
5 years ago
Adrien Vergé 1a13837e84 docs: Sunset Python 2
Keep supporting Python 2.7 for one extra year after upstream dropped it:
https://www.python.org/doc/sunset-python-2/
5 years ago
Adrien Vergé 46ed0c02be truthy: Add missing test removed from PR
See https://github.com/adrienverge/yamllint/pull/247#discussion_r405421376.
5 years ago
ilyam8 6ce11dedb4 truthy: add `check-keys` option 5 years ago
Adrien Vergé 542ae758f5 yamllint version 1.21.0 5 years ago
Rui Pinge 3a6a09b7b6 Add support for redundant quotes in quoted-strings rule
Co-Authored-By: Adrien Vergé
5 years ago
Rui Pinge 15aea73fbe
Fix quoted-strings rules not working for string values matching scalars 5 years ago
Martin Packman 91763f5476 Fix new-lines rule on Python 3
Use io.open() when reading files in cli which has the same behaviour
in Python 2 and Python 3, and supply the newline='' parameter which
handles but does not translate line endings.

Add dos.yml test file with windows newlines.

Also add to file finding test expected output.

Add test for new-lines rule through the cli.

Validates files are read with the correct universal newlines setting.

Fixes adrienverge/yamllint#228
5 years ago
Martin Packman 5b049e4229 Add RunContext helper for cli tests
Single context manager that includes exit code and output streams.

Use new RunContext throughout test_cli.

Largely non-functional change, saving some repetition of setup.

Also improve some failures by bundling multiple assertions into one.
5 years ago
Adrien Vergé 044c7f0248 cli: Test unicode chars in paths too 5 years ago
Adrien Vergé 734d5d5f73 CI: Run tests on Python 3.8
Python 3.8 was released in October 2019.
5 years ago
dhutty fd86455076 CI: Disable building on Python 3.4
As can be seen in https://travis-ci.org/adrienverge/yamllint/builds/631325436?utm_source=github_status&utm_medium=notification
The dependency, pathspec, requires Python '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*' but the running Python is 3.4.8

This commit stops Travis building yamllint against 3.4 so that CI can pass again.
5 years ago
Adrien Vergé 13a0f11e7c yamllint version 1.20.0 5 years ago
Sylvestre Ledru 43b95e99d1 Use 'syntax' as rule name upon syntax errors 5 years ago
ffapitalle 8fa9eb3ced Add --no-warnings option to suppress warning messages
Use `--no-warnings` option to hide warning messages. It only shows
problems marked as errors.
5 years ago
Adrien Vergé da3788e95a yamllint version 1.19.0 5 years ago
Joel Baranick fb400dc64b Allow disabling all checks for a file
Allow disabling of a file, even if it is invalid YAML (syntax error) by
including `# yamllint disable-file` in the first line.
5 years ago
Adrien Vergé 92324ae730 yamllint version 1.18.0 5 years ago
Imran Iqbal 7359785ea0 fix(default.yaml): disable `empty-values` & `octal-values` by default
* Close #204
5 years ago
Hossein Zolfi 579a975b70 docs: Fix pre-commit config file
* pre-commit show warning for unsupported key (sha)
* Demonstrate how to use custom yamllint
5 years ago
Imran Iqbal f3d9196aa0 docs(configuration): improve `yaml-files` code example
* A straight copy/paste of the existing example into the `.yamllint` file results in a `yamllint` error!
5 years ago
Ibrahim AshShohail 881d301883 feat: Support reading config from .yamllint.yml and .yamllint.yaml
Signed-off-by: Ibrahim AshShohail <me@ibrasho.com>
5 years ago
Adrien Vergé b62b424dd4 feat: Lint .yamllint by default 5 years ago
Adrien Vergé ce0336e430 yamllint version 1.17.0 6 years ago
grzesuav 063c854658 feat: Make YAML file extensions configurable 6 years ago
xatier 673bdbd324 fix(truthy): Fix extra whitespace 6 years ago
Remi Pointel cb5fe2c050 add OpenBSD installation instructions. 6 years ago
Adrien Vergé 930c8eea94 docs: Simplify installation instruction in the README 6 years ago
Adrien Vergé f6a24552d9 yamllint version 1.16.0 6 years ago
Adrien Vergé 0ba193331b truthy: Validate options passed to 'allowed-values'
Make sure values passed in allowed values are correct ones. This is
possible thanks to previous commit, and should prevent users from
writing incorrect configurations.
6 years ago
Adrien Vergé f65553c4f7 config: Validate config options with list of enums
Allow rules to declare a list of valid values for an option.

For example, a rule like:

    CONF = {'allowed-values': list}

... allowed any value to be passed in the list (including bad ones).

It is now possible to declare:

    CONF = {'allowed-values': ['value1', 'value2', 'value3']}

... so that the list passed to the options must contain only values in
`['value1', 'value2', 'value3']`.
6 years ago
Adrien Vergé 0fef4c14e7 truthy: Try to make docs on allowed-values more explicit
Edit documentation for the `truthy` rule, in order to:
- add quotes to examples (`'yes'` instead of `yes`) to avoid
  misconfigurations,
- group truthy values in the `allowed-values` option paragraph, for
  easier reading.
6 years ago
Ondrej Vaško 4ef7e05f3a truthy: Add allowed-values configuration option
Allows using key `allowed-values` for `truthy` section in configuration file (#150).

This allows to use configuration `truthy: allowed-values: ["yes", "no",
"..."]`, to set custom allowed truthy values.

This is especially useful for people using ansible, where values like
`yes` or `no` are valid and officially supported, but yamllint reports
them as illegal.

Implemented by difference of set of TRUTHY constants and configured
allowed values.

Signed-off-by: Ondrej Vasko <ondrej.vaskoo@gmail.com>
6 years ago
xatier 43c50379e0 Sort import orders 6 years ago
Adrien Vergé fec2c2fba7 fix(parser): Correctly handle DOS new lines in 'line' rules
Do not consider the trailing `\r` of a line a part of it.
6 years ago
Mateusz Piotrowski 2a66ec2e5e Add FreeBSD installation instructions 6 years ago
Adrien Vergé 37700ab3e6 yamllint version 1.15.0 6 years ago
Adrien Vergé f66661e36d docs(cli): Add a paragraph about standard input
See commit 05dfcbc "cli: Add command line option - to read from standard
input", cc @miguelbarao.
6 years ago
Adrien Vergé d6b89e94e4 chore(docs): Fix conf.py styling 6 years ago
Miguel Barao 05dfcbc109 cli: Add command line option - to read from standard input
If YAML files are given as arguments, parses these files.
If yamllint is run with - option, stdin.
If no arguments are given, just fail.
6 years ago
Adrien Vergé 16b939958d yamllint version 1.14.0 6 years ago
Adrien Vergé b4740dc1fb comments: Fix ignore-shebangs option on corner cases 6 years ago
Mattias Bengtsson b77f78f677 Support ignoring shebangs
Some usages of YAML (like Ansible) supports running the file as a script.

Support (by default) an ignore-shebangs setting for the comments module.

Fixes #116 - comments rule with require-starting-space: true should special case shebang
6 years ago
Adrien Vergé 0f073f7a09 config: Do not require all rule options to be set
Before, it was required to specify all the options when customizing a
rule. For instance, one could use `empty-lines: enable` or `empty-lines:
{max: 1, max-start: 2, max-end: 2}`, but not just `empty-lines: {max:
1}` (it would fail with *invalid config: missing option "max-start" for
rule "empty-lines"*).

This was a minor problem for users, but it prevented the addition of new
options to existing rules, see [1] for an example. If a new option was
added, updating yamllint for all users that customize the rule would
produce a crash (*invalid config: missing option ...*).

To avoid that, let's embed default values inside the rules themselves,
instead of keeping them in `conf/default.yaml`.

This refactor should not have any impact on existing projects. I've
manually checked that it did not change the output of tests, on
different projects:
- ansible/ansible: `test/runner/ansible-test sanity --python 3.7 --test yamllint`
- ansible/molecule: `yamllint -s test/ molecule/`
- Neo23x0/sigma: `make test-yaml`
- markstory/lint-review: `yamllint .`

[1]: https://github.com/adrienverge/yamllint/pull/151
6 years ago
cclauss bc7ac81707 Travis CI: Add Python 3.7 and 3.8a
* Adds Python 3.7.1 and a current nightly build of Python 3.8 alpha.
* Python 3.3 reached its end of life in 2017 https://devguide.python.org/#branchstatus
6 years ago
Adrien Vergé a56a1015f0 style(docs): Fix RST lint errors reported by doc8 6 years ago
Adrien Vergé 6cf5eecdac chore(CI): Lint RST (reStructuredText) files 6 years ago
Hugo f4c56b8216 Upgrade Python syntax with pyupgrade
https://github.com/asottile/pyupgrade
6 years ago
Hugo 5852566ff0 Upgrade unit tests to use more useful asserts 6 years ago
Hugo 4a7986b4cf Remove redundant parentheses 6 years ago
Hugo 3d1ad9a176 Add explicit Trove classifers for PyPI 6 years ago
Hugo 8da6e36bf1 Add python_requires to help pip 6 years ago
Hugo c281d48507 Drop support for EOL Python 2.6 6 years ago
Adrien Vergé 8bdddf6e89 docs: Warn about Python 2 and problems with line-length
Closes #146.
6 years ago
Adrien Vergé c8032c086b line-length: Add tests for lines containing unicode characters
Some unicode characters span accross multiple bytes. Python 3 is OK with
that, but Python 2 reports an incorrect number of characters.

Related to https://github.com/adrienverge/yamllint/issues/146
6 years ago
Adrien Vergé ea045c41b7 CI: Drop Python 3.3 support
The `pkg_resources` package inside `setuptools` explicitly [disallows
Python 3.3](7392f01ffc (diff-81de4a30a55fcc3fb944f8387ea9ec94)):

    if (3, 0) < sys.version_info < (3, 4):
        raise RuntimeError("Python 3.4 or later is required")

It's time to drop support for 3.3.
6 years ago
Adrien Vergé c803dd5f6d docs(CHANGELOG): Fix RST format for code snippets 6 years ago
Adrien Vergé 318a12bbe6 yamllint version 1.13.0 6 years ago
Adrien Vergé 66adaee66c docs: Add documentation on the new -f colored option 6 years ago
sedrubal 5062b91cac cli: Add -f colored to force colors
`-f standard` shows non-colored output,
`-f colored` shows colored output,
`-f auto` is the new default, it chooses `standard` or `colored`
depending on terminal capabilities.
6 years ago
sedrubal 3ef85739e3 Use isinstance(x, y) instead of type(x) == y
Fixes pylint C0123.
6 years ago
Adrien Vergé dc4a9f4fff yamllint version 1.12.1 6 years ago
Adrien Vergé 8354d50016 quoted-strings: Fix broken rule
Original implementation was completely broken. Documentation and actual
behavior were different. Numbers and booleans were detected as wrong, as
well as explicit types.

Fixes #136 and #130.
6 years ago
Adrien Vergé 524d721f0d Update .gitignore to exclude build/ 6 years ago
Adrien Vergé e864f57d37 docs: Fix missing quoted-strings module in documentation 6 years ago
Adrien Vergé d41b64aa97 yamllint version 1.12.0 6 years ago
Guido Wischrop (mgm tp) aaa8777f1d Add quoted-strings rule
* taken from https://github.com/adrienverge/yamllint/pull/110 (submitted by @jurajseffer)
* small fixes for generic and multi-line strings
* fixes for comments from @adrienverge
6 years ago
Adrien Vergé 479f580202 CI: Fix tests failing on Travis for Python 2.6
Because installing dependencies for `coveralls` now fails with:

    Collecting pycparser (from cffi>=1.7->cryptography>=1.3.4; python_version <= "2.7" and extra == "secure"->urllib3[secure]; python_version < "3"->coveralls)
    [...]
    pycparser requires Python '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' but the running Python is 2.6.9
6 years ago
Justin Foreman e4e99f0aba docs: Update README for CentOS dependency 7 years ago
Adrien Vergé 203cfc20f0 docs: Remove sudo from pip installation instructions 7 years ago
Adrien Vergé 51c30505b5 docs: Add Mac OS installation instructions
See https://github.com/adrienverge/yamllint/issues/91 and
https://github.com/Homebrew/homebrew-core/blob/af2bbe9/Formula/yamllint.rb
7 years ago
Adrien Vergé ff9ebde608 docs: Remove old Debian / Ubuntu installation instructions 7 years ago
Adrien Vergé 506e066410 yamllint version 1.11.1 7 years ago
Adrien Vergé 54f21c0514 parser: Fix crash with latest PyYAML
There is a backwards-incompatible change in PyYAML that induces a crash
if `check_token()` is not called before `peek_token()`. See commit
a02d17a in PyYAML or https://github.com/yaml/pyyaml/pull/150.

Closes #105.
7 years ago
Adam Johnson 36b4776778 Clarify documentation on the 'truthy' rule
I like the 'truthy' rule but its documentation and message have confused several of my colleagues. I've tried rewriting it to be clearer.
7 years ago
Adrien Vergé 3bdc1b6e1b CI: Don't install Sphinx if Python 2
Recently builds started to fail with:

    Collecting sphinx
      Downloading Sphinx-1.7.2-py2.py3-none-any.whl (1.9MB)
        100% |████████████████████████████████| 1.9MB 731kB/s
    Sphinx requires Python '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
    but the running Python is 2.6.9
7 years ago
Adrien Vergé c16934117b CI: Remove Travis hack for enum34 crashing on Python 3.6
Revert commit 8b9eab3, it is not needed anymore.
7 years ago
Eimert 8ab680635b docs: Make `ignore` examples clearer
[Solved](https://github.com/metacloud/molecule/issues/1228), when
yamllint is used by molecule.
7 years ago
Anthony Sottile 503bde9e70 pre-commit is now served over https! 7 years ago
Nick Burke 1b379628d7 key-duplicates: Handle merge keys (<<)
Merge keys are described here: http://yaml.org/type/merge.html
They shouldn't be considered as duplicated keys.

Fixes https://github.com/adrienverge/yamllint/issues/88
7 years ago
Adrien Vergé 6a842229fd yamllint version 1.11.0 7 years ago
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
7 years ago
xieenlong 22e792a433 Feature: checking octal numbers 7 years ago
Adrien Vergé f713dc8be2 style: Fix E100 and E202 errors reported by pycodestyle 7 years ago
Adrien Vergé a92743c8ca yamllint version 1.10.0 7 years ago
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.
7 years ago
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.
7 years ago
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.
7 years ago
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:
    }
7 years ago
Greg Dubicki 8537b0a164 Add rule: empty-values, to forbid implicit nulls
only in block mappings for now
7 years ago
Adrien Vergé 83ea74e2f8 CI: Compile documentation on Travis 7 years ago
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.
7 years ago
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.
7 years ago
Adrien Vergé 2d931b5a81 yamllint version 1.9.0 7 years ago
Adrien Vergé 773bfc0f3c key-ordering: Add more test cases and documentation 7 years ago
Johannes F. Knauf 1543d0e435 New rule key-ordering
closes #67
7 years ago
Adrien Vergé f82346dac7 indentation: Add more test cases for key following empty list 7 years ago
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.
7 years ago
Adrien Vergé c8fc170ff0 yamllint version 1.8.2 7 years ago
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
7 years ago
Sebastian Finke db57127971 docs(integration): Fix pre-commit config file 8 years ago
blackillzone c8e516be2f Add documentation for pre-commit 8 years ago
blackillzone 1c0dd48ccd Update pre-commit hook file 8 years ago
Adrien Vergé f4edb85a04 fix(config): Be clearer about the `ignore` conf type 8 years ago
Adrien Vergé d99bb9fec3 yamllint version 1.8.1 8 years ago
Adrien Vergé 3c4013fda1 docs(CHANGELOG): Add a changelog
Closes #57
8 years ago
Adrien Vergé 1a961bd4b0 chore(tests): Also run tests on Python 2.6 8 years ago
Adrien Vergé 7a8cfeed6d chore(deps): Require pathspec >= 0.5.3
This new version adds support for Python 2.6.
8 years ago
Adrien Vergé f9709bc6e6 yamllint version 1.8.0 8 years ago
Adrien Vergé 5060917e40 style(cli): Space import sections 8 years ago
Adrien Vergé a052cf7dba chore(tests): Add flake8-import-order linter plugin 8 years ago
Adrien Vergé ae33716529 chore(tests): Also run tests on Python 3.6 8 years ago
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.
8 years ago
Adrien Vergé 342d7b49dd tests(cli): Create a temp test workspace only once
Do not re-create it for every test in the class.
8 years ago
Adrien Vergé 7d638d47b9 tests(cli): Refactor temp test workspace recreation
Make it simpler and re-usable.
8 years ago
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
8 years ago
sedrubal 30dfa78923
Use argparse mutually_exclusive_group for --config-file and --config-data
This does the same as your solution 😉
8 years ago
Adrien Vergé 4ae829c062 yamllint version 1.7.0 8 years ago
Adrien Vergé 400aa084da Merge pull request #46 from krzysztof-magosa/master
Add information about Emacs integration
8 years ago
Krzysztof Magosa a825645cbe Add information about Emacs integration 8 years ago
Adrien Vergé 1764e32def Merge pull request #45 from jayvdb/add-__main__
Add __main__
8 years ago
Adrien Vergé d6a81f1b23 Add tests for `python -m yamllint` 8 years ago
John Vandenberg 38d14c7314 Add __main__
Allows execution using python -m yamllint
8 years ago
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
8 years ago
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.
8 years ago
Adrien Vergé 51b6d8377f Merge pull request #42 from polyzen/doc-ALE.vim
Doc: Add ALE Vim plugin
8 years ago
Daniel M. Capella f507319419
Doc: Add ALE Vim plugin 8 years ago
Adrien Vergé c0c8534501 Merge pull request #40 from jwilk/spelling
Fix typos
8 years ago
Jakub Wilk 2b26cbc56b Fix typos 8 years ago
Adrien Vergé c037d3e586 yamllint version 1.6.1 8 years ago
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
8 years ago
Adrien Vergé 413d7a8e4e Merge pull request #32 from mtnbikenc/typo-fix
Minor cosmetic typo
8 years ago
Russell Teague c332c8e3d4
Minor cosmetic typo 8 years ago
Adrien Vergé ea67ba3394 Merge pull request #31 from bootswithdefer/master
support for pre-commit from yelp
8 years ago
bootswithdefer a7dbfb08b3 support for pre-commit from yelp 8 years ago
Adrien Vergé 42eda54014 yamllint version 1.6.0 8 years ago
Adrien Vergé e909692f88 docs(truthy): Fix typo 8 years ago
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.
8 years ago
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).
8 years ago
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.
8 years ago
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
8 years ago
Adrien Vergé 64369db9a2 docs(configuration): Fix typo 8 years ago
Adrien Vergé 2428f6eeaf Merge pull request #27 from jsok/strict-mode
Strict mode
8 years ago
Jonathan Sokolowski bf386b3c90 docs: Explain strict mode return codes 8 years ago
Jonathan Sokolowski 03e0f5aa6b Add strict mode argument to CLI 8 years ago
Adrien Vergé 3b2a73d224 Merge pull request #25 from adamchainz/universal_wheels
Release as a universal wheel
8 years ago
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`.
8 years ago
Adrien Vergé 559ad5574b yamllint version 1.5.0 8 years ago
Adrien Vergé adcb2d2953 Merge pull request #24 from adamchainz/dont_package_tests
setup.py - don't distribute tests
8 years ago
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']
```
8 years ago
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
8 years ago
Adrien Vergé 073462a87d docs(rules): Fix missing truthy rule in index 8 years ago
Adrien Vergé 4b9ba9e201 docs(truthy): Enhance rule documentation 8 years ago
Peter Ericson 5294ff5552 truthy: Add tests for explicit booleans
From @adrienverge
8 years ago
Peter Ericson 1f472bc144 Add rule: truthy, to forbid truthy values that are not quoted 8 years ago
Adrien Vergé c163135ee5 yamllint version 1.4.1 8 years ago
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
8 years ago
Adrien Vergé 9b72a2d29a Merge branch 'adamchainz-readthedocs.io' 8 years ago
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.
8 years ago
Adrien Vergé 60b72daad4 yamllint version 1.4.0 8 years ago
Adrien Vergé 773bb8a648 Merge pull request #17 from allanlewis/improve-unbreakable
line_length: Allow mapping values with long unbreakable lines
8 years ago
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
8 years ago
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.)
8 years ago
Allan Lewis d017631aff line_length: Allow mapping values with long unbreakable lines 8 years ago
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
9 years ago
Adrien Vergé 82dd7dbf16 Merge pull request #16 from adrienverge/coloured_output_on_tty
feat(cli): Colour output only on TTY
9 years ago
Adrien Vergé 4533b8ae49 doc(config): Show `relaxed` conf contents
Closes: #15
9 years ago
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
9 years ago
Adrien Vergé 82ed191bc9 yamllint version 1.3.2 9 years ago
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.
9 years ago
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.
9 years ago
Adrien Vergé d174f9e3e3 yamllint version 1.3.1 9 years ago
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
9 years ago
Adrien Vergé 63dd8313f8 yamllint version 1.3.0 9 years ago
Adrien Vergé 7be5867675 linter: Remove dead code
There is *always* a `Line` element at the end of file, even if the
newline character (`\n`) is missing.
9 years ago
Adrien Vergé 6061a2c4cc Rules: common: Remove dead code 9 years ago
Adrien Vergé 09118e417c Doc: Add license information on README page 9 years ago
Adrien Vergé 71b90ae208 Doc: Add new features in README 9 years ago
Adrien Vergé 8844855353 Doc: Remove old Debian install commands from README 9 years ago
Adrien Vergé 0eb310e102 Allow disabling yamllint checks using comments
Implement problem report disabling with comments in YAML source, for
instance:

    # The following mapping contains the same key twice,
    # but I know what I'm doing:
    key: value 1
    key: value 2  # yamllint disable-line rule:key-duplicates

or:

    # yamllint disable rule:colons
    - Lorem       : ipsum
      dolor       : sit amet,
      consectetur : adipiscing elit
    # yamllint enable rule:colons

Closes: #8
9 years ago
Adrien Vergé cdd094220c parser: Add tests for `Comment.is_inline()` 9 years ago
Adrien Vergé 7a7d98c96a parser: Iterate over lines + tokens + comments
Instead of iterating over lines and tokens (and find comments between
tokens in the comment rules), add a new `Comment` type and set rules
with `type = 'comment'`.
9 years ago
Adrien Vergé 9f99f25db5 linter: Assert that `_run()` is called with a buffer 9 years ago
Adrien Vergé 8c839a20c2 Config: Detect user config using `os.path.expanduser()`
Instead of `$HOME`, since the former works when `$HOME` is not set.

[1]: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=828033#10
9 years ago
Adrien Vergé 8e6e851c5b yamllint version 1.2.2 9 years ago
Adrien Vergé edd4cca02f Merge pull request #9 from michelebariani/master
Patch allow-non-breakable-words on '-'
9 years ago
Michele Bariani 867970258e Patch allow-non-breakable-words on '-' 9 years ago
Adrien Vergé d0cb5998c4 Merge pull request #7 from jwilk/spelling
Fix typos
9 years ago
Jakub Wilk a5c97220e7 Fix typos 9 years ago
Adrien Vergé 598e5e4370 Doc: Fix typo on configuration page intro 9 years ago
Adrien Vergé 03076ee214 Doc: Add a pointer to rules on configuration page intro 9 years ago
Adrien Vergé eabd349902 Config: Allow a user-global configuration file
Instead of just looking for `.yamllint` in the current working
directory, also look for `~/.config/yamllint/config` (using
`$XDG_CONFIG_HOME` or `$HOME`, see [1] and [2] for information).

[1]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-0.6.html
[2]: https://wiki.archlinux.org/index.php/XDG_Base_Directory_support

Closes: #6
9 years ago

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

@ -0,0 +1,61 @@
---
name: CI
on: # yamllint disable-line rule:truthy
push:
pull_request:
branches:
- master
permissions:
contents: read
jobs:
lint:
name: Linters
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
- run:
pip install flake8 flake8-import-order sphinx rstcheck[sphinx] doc8
- run: pip install .
- run: flake8 .
- run: doc8 $(git ls-files '*.rst')
- run: rstcheck --ignore-directives automodule $(git ls-files '*.rst')
- run: yamllint --strict $(git ls-files '*.yaml' '*.yml')
- run: make -C docs html
- name: Check for broken links in documentation
run: make -C docs linkcheck
test:
name: Tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version:
- '3.7'
- '3.8'
- '3.9'
- '3.10'
- '3.11'
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Append GitHub Actions system path
run: echo "$HOME/.local/bin" >> $GITHUB_PATH
- run: pip install coverage
- run: pip install .
# https://github.com/AndreMiras/coveralls-python-action/issues/18
- run: echo -e "[run]\nrelative_files = True" > .coveragerc
- run: coverage run -m unittest discover
- name: Coveralls
uses: AndreMiras/coveralls-python-action@develop

2
.gitignore vendored

@ -3,3 +3,5 @@ __pycache__
/docs/_build
/dist
/yamllint.egg-info
/build
/.eggs

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

@ -1,17 +0,0 @@
---
language: python
python:
- 2.7
- 3.3
- 3.4
- 3.5
- nightly
install:
- pip install pyyaml flake8 coveralls
- pip install .
script:
- flake8 .
- yamllint $(git ls-files '*.yaml' '*.yml')
- coverage run --source=yamllint setup.py test
after_success:
coveralls

@ -0,0 +1,279 @@
Changelog
=========
1.32.0 (2023-05-22)
-------------------
- Look for configuration file in parent directories
- Rule ``anchors``: add new option ``forbid-unused-anchors``
1.31.0 (2023-04-21)
-------------------
- Build: migrate from ``setup.py`` to ``pyproject.toml``
- Docs: update some outdated URLs
- Rule ``colons``: prevent error when space before is mandatory
1.30.0 (2023-03-22)
-------------------
- Rule ``anchors``: add new rule to detect undeclared or duplicated anchors
- Python API: prevent using ``is_file_ignored()`` with null ``filepath``
- Docs: fix misleading Python API example
- Docs: fix plain text code snippet example
- Docs: update pre-commit hook example
1.29.0 (2023-01-10)
-------------------
- Add support for Python 3.11, drop support for Python 3.6
- Rule ``float-values``: fix bug on strings containing fordidden values
- Stop releasing universal wheels
- Use proper Python 3 I/O type for file reading
- Rule ``indentation``: fix ``indent-sequences`` in nested collections
- Docs: clarify ``disable-line`` and parser errors, give a workaround
- Refactors to apply some pyupgrade suggestions
- Allow using a list of strings in ``ignore`` configuration
- Add ``--list-files`` command line option
1.28.0 (2022-09-12)
-------------------
- Better compress PNG image in documentation
- Remove ``__future__`` imports specific to Python 2
- Remove inheritance from ``object`` specific to Python 2
- Simplify GitHub Actions example in documentation
- Update ALE vim plugin link in documentation
- Update license to latest version of GPLv3
- Pre-compile disable/enable rules regexes
- Rule ``quoted-strings``: add ``allow-quoted-quotes`` option
- Add option ``ignore-from-file`` in config
1.27.1 (2022-07-08)
-------------------
- Fix failing test on ``key-duplicates`` for old PyYAML versions
1.27.0 (2022-07-08)
-------------------
- Add support for Python 3.10, drop Python 3.5
- Fix GitHub Actions workflow
- Refactor ``--format=auto`` logic
- Update GitHub format output to use groups
- Rule ``comments``: allow whitespace after the shebang marker
- Multiple minor fixes in documentation
- Configure Sphinx to make man page show up in apropos
- Attempt to clarify configuration file location in documentation
- Rule ``key-duplicates``: don't crash on redundant closing brackets or braces
- Use ``rstcheck`` to lint documentation on the CI
- Remove UTF-8 headers in Python files, since Python 2 isn't supported
- Add various tests to increase coverage
- Rule ``octal-values``: pre-compile regex for performance
- Add sections for Visual Studio Code and IntelliJ in documentation
- Rule ``new-lines``: add the ``type: platform`` config option
- Add the new rule ``float-values``
1.26.3 (2021-08-21)
-------------------
- Restore runtime dependency ``setuptools`` for Python < 3.8
1.26.2 (2021-08-03)
-------------------
- Fix ``python_requires`` to comply with PEP 345 and PEP 440
1.26.1 (2021-04-06)
-------------------
- Remove runtime dependency ``setuptools`` for Python < 3.8
- Fix ``line_length`` to skip all hash signs starting comment
1.26.0 (2021-01-29)
-------------------
- End support for Python 2 and Python 3.4, add support for Python 3.9
- Add ``forbid: non-empty`` option to ``braces`` and ``brackets`` rules
- Fix ``quoted-strings`` for explicit octal recognition
- Add documentation for integration with Arcanist
- Fix typos in changelog and README
- Stop using deprecated ``python setup.py test`` in tests
1.25.0 (2020-09-29)
-------------------
- Run tests on Travis both with and without UTF-8 locales
- Improve documentation with default values to rules with options
- Improve documentation with a Python API usage example
- Fix documentation on ``commas`` examples
- Packaging: move setuptools' configuration from ``setup.py`` to ``setup.cfg``
- Packaging: add extra info in PyPI metadata
- Improve documentation on ``yaml-files``
- Fix ``octal-values`` to prevent detection of ``8`` and ``9`` as octal values
- Fix ``quoted-strings`` Fix detecting strings with hashtag as requiring quotes
- Add ``forbid`` configuration to the ``braces`` and ``brackets`` rules
- Fix runtime dependencies missing ``setuptools``
- Add a new output format for GitHub Annotations (``--format github``)
- Fix DOS lines messing with rule IDs in directives
1.24.2 (2020-07-16)
-------------------
- Add ``locale`` config option and make ``key-ordering`` locale-aware
1.24.1 (2020-07-15)
-------------------
- Revert ``locale`` config option from version 1.24.0 because of a bug
1.24.0 (2020-07-15)
-------------------
- Specify config with environment variable ``YAMLLINT_CONFIG_FILE``
- Fix bug with CRLF in ``new-lines`` and ``require-starting-space``
- Do not run linter on directories whose names look like YAML files
- Add ``locale`` config option and make ``key-ordering`` locale-aware
1.23.0 (2020-04-17)
-------------------
- Allow rules to validate their configuration
- Add options ``extra-required`` and ``extra-allowed`` to ``quoted-strings``
1.22.1 (2020-04-15)
-------------------
- Fix ``quoted-strings`` rule with ``only-when-needed`` on corner cases
1.22.0 (2020-04-13)
-------------------
- Add ``check-keys`` option to the ``truthy`` rule
- Fix ``quoted-strings`` rule not working on sequences items
- Sunset Python 2
1.21.0 (2020-03-24)
-------------------
- Fix ``new-lines`` rule on Python 3 with DOS line endings
- Fix ``quoted-strings`` rule not working for string values matching scalars
- Add ``required: only-when-needed`` option to the ``quoted-strings`` rule
1.20.0 (2019-12-26)
-------------------
- Add --no-warnings option to suppress warning messages
- Use 'syntax' as rule name upon syntax errors
1.19.0 (2019-11-19)
-------------------
- Allow disabling all checks for a file with ``# yamllint disable-file``
1.18.0 (2019-10-15)
-------------------
- Lint ``.yamllint`` config file by default
- Also read config from ``.yamllint.yml`` and ``.yamllint.yaml``
- Improve documentation for ``yaml-files``
- Update documentation for ``pre-commit``
- Explicitly disable ``empty-values`` and ``octal-values`` rules
1.17.0 (2019-08-12)
-------------------
- Simplify installation instructions in the README
- Add OpenBSD installation instructions
- Make YAML file extensions configurable
1.16.0 (2019-06-07)
-------------------
- Add FreeBSD installation instructions
- Fix the ``line`` rule to correctly handle DOS new lines
- Add the ``allowed-values`` option to the ``truthy`` rule
- Allow configuration options to be a list of enums
1.15.0 (2019-02-11)
-------------------
- Allow linting from standard input with ``yamllint -``
1.14.0 (2019-01-14)
-------------------
- Fix documentation code snippets
- Drop Python 2.6 and 3.3 support, add Python 3.7 support
- Update documentation and tests for ``line-length`` + Unicode + Python 2
- Allow rule configurations to lack options
- Add a new ``ignore-shebangs`` option for the ``comments`` rule
1.13.0 (2018-11-14)
-------------------
- Use ``isinstance(x, y)`` instead of ``type(x) == y``
- Add a new ``-f colored`` option
- Update documentation about colored output when run from CLI
1.12.1 (2018-10-17)
-------------------
- Fix the ``quoted-strings`` rule, broken implementation
- Fix missing documentation for the ``quoted-strings`` rule
1.12.0 (2018-10-04)
-------------------
- Add a new ``quoted-strings`` rule
- Update installation documentation for pip, CentOS, Debian, Ubuntu, Mac OS
1.11.1 (2018-04-06)
-------------------
- Handle merge keys (``<<``) in the ``key-duplicates`` rule
- Update documentation about pre-commit
- Make examples for ``ignore`` rule clearer
- Clarify documentation on the 'truthy' rule
- Fix crash in parser due to a change in PyYAML > 3.12
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

@ -0,0 +1,48 @@
Contributing
============
Pull requests are the best way to propose changes to the codebase.
Contributions are welcome, but they have to meet some criteria.
Pull Request Process
--------------------
1. Fork this Git repository and create your branch from ``master``.
2. Make sure the tests pass:
.. code:: bash
pip install --user .
python -m unittest discover # all tests...
python -m unittest tests/rules/test_commas.py # or just some tests (faster)
3. If you add code that should be tested, add tests.
4. Make sure the linters pass:
.. code:: bash
flake8 .
If you added/modified documentation:
.. code:: bash
doc8 $(git ls-files '*.rst')
If you touched YAML files:
.. code:: bash
yamllint --strict $(git ls-files '*.yaml' '*.yml')
5. If relevant, update documentation (either in ``docs`` directly or in rules
files themselves).
6. Write a `good commit message
<http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html>`_.
If the pull request has multiple commits, each must be atomic (single
irreducible change that makes sense on its own).
7. Then, open a pull request.

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

@ -1,3 +0,0 @@
include LICENSE
include README.rst
include docs/*

@ -8,26 +8,26 @@ repetition and cosmetic problems such as lines length, trailing spaces,
indentation, etc.
.. image::
https://travis-ci.org/adrienverge/yamllint.svg?branch=master
:target: https://travis-ci.org/adrienverge/yamllint
https://github.com/adrienverge/yamllint/actions/workflows/ci.yaml/badge.svg?branch=master
:target: https://github.com/adrienverge/yamllint/actions/workflows/ci.yaml?query=branch%3Amaster
:alt: CI tests status
.. image::
https://coveralls.io/repos/github/adrienverge/yamllint/badge.svg?branch=master
: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).
Written in Python (compatible with Python 3 only).
Documentation
-------------
http://yamllint.readthedocs.org/
https://yamllint.readthedocs.io/
Short overview
--------------
Overview
--------
Screenshot
^^^^^^^^^^
@ -38,30 +38,15 @@ Screenshot
Installation
^^^^^^^^^^^^
On Fedora / CentOS:
Using pip, the Python package manager:
.. code:: bash
sudo dnf install yamllint
pip install --user yamllint
On Debian 9+ / Ubuntu 16.04+:
.. code:: bash
sudo apt-get install yamllint
On older Debian / Ubuntu versions:
.. code:: bash
sudo add-apt-repository -y ppa:adrienverge/ppa && sudo apt-get update
sudo apt-get install yamllint
Alternatively using pip, the Python package manager:
.. code:: bash
sudo pip install yamllint
yamllint is also packaged for all major operating systems, see installation
examples (``dnf``, ``apt-get``...) `in the documentation
<https://yamllint.readthedocs.io/en/stable/quickstart.html>`_.
Usage
^^^^^
@ -82,15 +67,19 @@ Usage
yamllint -d relaxed file.yaml
# Use a custom lint configuration
yamllint -c ~/myconfig file.yml
yamllint -c /path/to/myconfig file-to-lint.yaml
.. code:: bash
# Output a parsable format (for syntax checking in editors like Vim, emacs...)
yamllint -f parsable file.yaml
Configuration example
^^^^^^^^^^^^^^^^^^^^^
`Read more in the complete documentation! <https://yamllint.readthedocs.io/>`__
Features
^^^^^^^^
Here is a yamllint configuration file example:
.. code:: yaml
@ -104,3 +93,48 @@ Configuration example
# don't bother me with this rule
indentation: disable
Within a YAML file, special comments can be used to disable checks for a single
line:
.. code:: yaml
This line is waaaaaaaaaay too long # yamllint disable-line
or for a whole block:
.. code:: yaml
# yamllint disable rule:colons
- Lorem : ipsum
dolor : sit amet,
consectetur : adipiscing elit
# yamllint enable
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
-------
`GPL version 3 <LICENSE>`_

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

@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-
# yamllint documentation build configuration file, created by
# sphinx-quickstart on Thu Jan 21 21:18:52 2016.
import sys
import os
from unittest.mock import MagicMock
sys.path.insert(0, os.path.abspath('..')) # noqa
sys.path.insert(0, os.path.abspath('..'))
from yamllint import __copyright__, APP_NAME, APP_VERSION
from yamllint import __copyright__, APP_NAME, APP_VERSION # noqa
# -- General configuration ------------------------------------------------
@ -20,7 +20,7 @@ source_suffix = '.rst'
master_doc = 'index'
project = APP_NAME
copyright = __copyright__
copyright = __copyright__.lstrip('Copyright ')
version = APP_VERSION
release = APP_VERSION
@ -38,5 +38,17 @@ htmlhelp_basename = 'yamllintdoc'
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'yamllint', '', [u'Adrien Vergé'], 1)
('index', 'yamllint', 'Linter for YAML files', ['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)

@ -1,16 +1,27 @@
Configuration
=============
yamllint uses a set of *rules* to check sources files for problems. Each rule is
independent from the others, and can be enabled, disabled or tweaked. All these
settings can be gathered in a configuration file.
yamllint uses a set of :doc:`rules <rules>` to check source files for problems.
Each rule is independent from the others, and can be enabled, disabled or
tweaked. All these settings can be gathered in a configuration file.
To use a custom configuration file, either name it ``.yamllint`` in your working
directory, or use the ``-c`` option:
To use a custom configuration file, use the ``-c`` option:
.. code:: bash
yamllint -c ~/myconfig file.yaml
yamllint -c /path/to/myconfig file-to-lint.yaml
If ``-c`` is not provided, yamllint will look for a configuration file in the
following locations (by order of preference):
- a file named ``.yamllint``, ``.yamllint.yaml``, or ``.yamllint.yml`` in the
current working directory, or a parent directory (the search for this file is
terminated at the user's home or filesystem root)
- a filename referenced by ``$YAMLLINT_CONFIG_FILE``, if set
- a file named ``$XDG_CONFIG_HOME/yamllint/config`` or
``~/.config/yamllint/config``, if present
Finally if no config file is found, the default configuration is applied.
Default configuration
---------------------
@ -23,7 +34,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:
@ -34,9 +48,9 @@ It can be chosen using:
Extending the default configuration
-----------------------------------
When writing a custom configuration file, you don't need to redefine every rule.
Just extend the ``default`` configuration (or any already-existing configuration
file).
When writing a custom configuration file, you don't need to redefine every
rule. Just extend the ``default`` configuration (or any already-existing
configuration file).
For instance, if you just want to disable the ``comments-indentation`` rule,
your file could look like this:
@ -91,8 +105,151 @@ 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 ``colored``
output format, or ``auto`` when run from a terminal).
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
If the script is invoked with the ``--no-warnings`` option, it won't output
warning level problems, only error level ones.
YAML files extensions
---------------------
To configure what yamllint should consider as YAML files when listing
directories, set ``yaml-files`` configuration option. The default is:
.. code-block:: yaml
yaml-files:
- '*.yaml'
- '*.yml'
- '.yamllint'
The same rules as for ignoring paths apply (``.gitignore``-style path pattern,
see below).
If you need to know the exact list of files that yamllint would process,
without really linting them, you can use ``--list-files``:
.. code:: bash
yamllint --list-files .
Ignoring paths
--------------
It is possible to exclude specific files or directories, so that the linter
doesn't process them. They can be provided either as a list of paths, or as a
bulk string.
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:
- /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
# or:
rules:
trailing-spaces:
ignore:
- /this-file-has-trailing-spaces-but-it-is-OK.yaml
- /generated/*.yaml
Note that this ``.gitignore``-style path pattern allows complex path
exclusion/inclusion, see the `pathspec README file
<https://pypi.org/project/pathspec/>`_ for more details. Here is a more complex
example:
.. 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/*
You can also use the ``.gitignore`` file (or any list of files) through:
.. code-block:: yaml
ignore-from-file: .gitignore
or:
.. code-block:: yaml
ignore-from-file: [.gitignore, .yamlignore]
.. note:: However, this is mutually exclusive with the ``ignore`` key.
If you need to know the exact list of files that yamllint would process,
without really linting them, you can use ``--list-files``:
.. code:: bash
yamllint --list-files .
Setting the locale
------------------
It is possible to set the ``locale`` option globally. This is passed to Python's
`locale.setlocale
<https://docs.python.org/3/library/locale.html#locale.setlocale>`_,
so an empty string ``""`` will use the system default locale, while e.g.
``"en_US.UTF-8"`` will use that.
Currently this only affects the ``key-ordering`` rule. The default will order
by Unicode code point number, while locales will sort case and accents
properly as well.
.. code-block:: yaml
extends: default
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).
locale: en_US.UTF-8

@ -2,7 +2,17 @@ Development
===========
yamllint provides both a script and a Python module. The latter can be used to
write your own linting tools:
write your own linting tools.
Basic example of running the linter from Python:
.. code-block:: python
import yamllint
yaml_config = yamllint.config.YamlLintConfig("extends: default")
for p in yamllint.linter.run(open("example.yaml", "r"), yaml_config):
print(p.desc, p.line, p.rule)
.. automodule:: yamllint.linter
:members:

@ -0,0 +1,136 @@
Disable with comments
=====================
Disabling checks for a specific line
------------------------------------
To prevent yamllint from reporting problems for a specific line, add a
directive comment (``# yamllint disable-line ...``) on that line, or on the
line above. For instance:
.. code-block:: yaml
# The following mapping contains the same key twice,
# but I know what I'm doing:
key: value 1
key: value 2 # yamllint disable-line rule:key-duplicates
- This line is waaaaaaaaaay too long but yamllint will not report anything about it. # yamllint disable-line rule:line-length
This line will be checked by yamllint.
or:
.. code-block:: yaml
# The following mapping contains the same key twice,
# but I know what I'm doing:
key: value 1
# yamllint disable-line rule:key-duplicates
key: value 2
# yamllint disable-line rule:line-length
- This line is waaaaaaaaaay too long but yamllint will not report anything about it.
This line will be checked by yamllint.
It is possible, although not recommend, to disabled **all** rules for a
specific line:
.. code-block:: yaml
# yamllint disable-line
- { all : rules ,are disabled for this line}
You can't make yamllint ignore invalid YAML syntax on a line (which generates a
`syntax error`), such as when templating a YAML file with Jinja. In some cases,
you can workaround this by putting the templating syntax in a YAML comment. See
`Putting template flow control in comments`_.
If you need to disable multiple rules, it is allowed to chain rules like this:
``# yamllint disable-line rule:hyphens rule:commas rule:indentation``.
Disabling checks for all (or part of) the file
----------------------------------------------
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:
.. code-block:: yaml
# yamllint disable rule:colons
- Lorem : ipsum
dolor : sit amet,
consectetur : adipiscing elit
# yamllint enable rule:colons
- rest of the document...
It is possible, although not recommend, to disabled **all** rules:
.. code-block:: yaml
# yamllint disable
- Lorem :
ipsum:
dolor : [ sit,amet]
- consectetur : adipiscing elit
# yamllint enable
If you need to disable multiple rules, it is allowed to chain rules like this:
``# yamllint disable rule:hyphens rule:commas rule:indentation``.
Disabling all checks for a file
-------------------------------
To prevent yamllint from reporting problems for a specific file, add the
directive comment ``# yamllint disable-file`` as the first line of the file.
For instance:
.. code-block:: yaml
# yamllint disable-file
# The following mapping contains the same key twice, but I know what I'm doing:
key: value 1
key: value 2
- This line is waaaaaaaaaay too long but yamllint will not report anything about it.
or:
.. code-block:: jinja
# yamllint disable-file
# This file is not valid YAML because it is a Jinja template
{% if extra_info %}
key1: value1
{% endif %}
key2: value2
Putting template flow control in comments
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Alternatively for templating you can wrap the template statements in comments
to make it a valid YAML file. As long as the templating language doesn't use
the same comment symbol, it should be a valid template and valid YAML (pre and
post-template processing).
Example of a Jinja2 code that cannot be parsed as YAML because it contains
invalid tokens ``{%`` and ``%}``:
.. code-block:: text
# This file IS NOT valid YAML and will produce syntax errors
{% if extra_info %}
key1: value1
{% endif %}
key2: value2
But it can be fixed using YAML comments:
.. code-block:: yaml
# This file IS valid YAML because the Jinja is in a YAML comment
# {% if extra_info %}
key1: value1
# {% endif %}
key2: value2

@ -11,7 +11,7 @@ Screenshot
.. note::
The default output format is inspired by `eslint <http://eslint.org/>`_, a
The default output format is inspired by `eslint <https://eslint.org/>`_, a
great linting tool for Javascript.
Table of contents
@ -23,5 +23,7 @@ Table of contents
quickstart
configuration
rules
disable_with_comments
development
text_editors
integration

@ -0,0 +1,67 @@
Integration with other software
===============================
Integration with pre-commit
---------------------------
You can integrate yamllint in the `pre-commit <https://pre-commit.com/>`_ tool.
Here is an example, to add in your .pre-commit-config.yaml
.. code:: yaml
---
# Update the rev variable with the release version that you want, from the yamllint repo
# You can pass your custom .yamllint with args attribute.
repos:
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.29.0
hooks:
- id: yamllint
args: [--strict, -c=/path/to/.yamllint]
Integration with GitHub Actions
-------------------------------
yamllint auto-detects when it's running inside of `GitHub
Actions <https://github.com/features/actions>`_ and automatically uses the
suited output format to decorate code with linting errors. You can also force
the GitHub Actions output with ``yamllint --format github``.
A minimal example workflow using GitHub Actions:
.. code:: yaml
---
on: push # yamllint disable-line rule:truthy
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install yamllint
run: pip install yamllint
- name: Lint YAML files
run: yamllint .
Integration with Arcanist
-------------------------
You can configure yamllint to run on ``arc lint``. Here is an example
``.arclint`` file that makes use of this configuration.
.. code:: json
{
"linters": {
"yamllint": {
"type": "script-and-regex",
"script-and-regex.script": "yamllint",
"script-and-regex.regex": "/^(?P<line>\\d+):(?P<offset>\\d+) +(?P<severity>warning|error) +(?P<message>.*) +\\((?P<name>.*)\\)$/m",
"include": "(\\.(yml|yaml)$)"
}
}
}

@ -4,37 +4,49 @@ Quickstart
Installing yamllint
-------------------
On Fedora / CentOS:
On Fedora / CentOS (note: `EPEL <https://docs.fedoraproject.org/en-US/epel/>`_ is
required on CentOS):
.. code:: bash
sudo dnf install yamllint
On Debian 9+ / Ubuntu 16.04+:
On Debian 8+ / Ubuntu 16.04+:
.. code:: bash
sudo apt-get install yamllint
On older Debian / Ubuntu versions:
On Mac OS 10.11+:
.. code:: bash
sudo add-apt-repository -y ppa:adrienverge/ppa && sudo apt-get update
sudo apt-get install yamllint
brew install yamllint
On FreeBSD:
.. code:: sh
pkg install py36-yamllint
On OpenBSD:
.. code:: sh
doas pkg_add py3-yamllint
Alternatively using pip, the Python package manager:
.. code:: bash
sudo pip install yamllint
pip install --user yamllint
If you prefer installing from source, you can run, from the source directory:
.. code:: bash
python setup.py sdist
sudo pip install dist/yamllint-*.tar.gz
python -m build
pip install --user dist/yamllint-*.tar.gz
Running yamllint
----------------
@ -51,6 +63,12 @@ You can also lint all YAML files in a whole directory:
yamllint .
Or lint a YAML stream from standard input:
.. code:: bash
echo -e 'this: is\nvalid: YAML' | yamllint -
The output will look like (colors are not displayed here):
::
@ -69,6 +87,10 @@ The output will look like (colors are not displayed here):
10:1 error too many blank lines (4 > 2) (empty-lines)
11:4 error too many spaces inside braces (braces)
By default, the output of yamllint is colored when run from a terminal, and
pure text in other cases. Add the ``-f standard`` arguments to force
non-colored output. Use the ``-f colored`` arguments to force colored output.
Add the ``-f parsable`` arguments if you need an output format parsable by a
machine (for instance for :doc:`syntax highlighting in text editors
<text_editors>`). The output will then look like:

@ -14,6 +14,11 @@ This page describes the rules and their options.
:local:
:depth: 1
anchors
-------
.. automodule:: yamllint.rules.anchors
braces
------
@ -59,6 +64,17 @@ empty-lines
.. automodule:: yamllint.rules.empty_lines
empty-values
------------
.. automodule:: yamllint.rules.empty_values
float-values
------------
.. automodule:: yamllint.rules.float_values
hyphens
-------
@ -74,6 +90,11 @@ key-duplicates
.. automodule:: yamllint.rules.key_duplicates
key-ordering
--------------
.. automodule:: yamllint.rules.key_ordering
line-length
-----------
@ -89,7 +110,22 @@ new-lines
.. automodule:: yamllint.rules.new_lines
octal-values
------------
.. automodule:: yamllint.rules.octal_values
quoted-strings
--------------
.. automodule:: yamllint.rules.quoted_strings
trailing-spaces
---------------
.. automodule:: yamllint.rules.trailing_spaces
truthy
---------------
.. automodule:: yamllint.rules.truthy

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 31 KiB

@ -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/dense-analysis/ale>`_ plugin is
installed, yamllint is supported by default. It is automatically enabled when
editing YAML files.
If you instead use the `syntastic <https://github.com/vim-syntastic/syntastic>`_
plugin, add this to your ``.vimrc``:
::
@ -19,10 +23,26 @@ is installed, add to your ``.vimrc``:
Neovim
------
Assuming that the `neomake <https://github.com/benekastah/neomake>`_ plugin is
Assuming that the `neomake <https://github.com/neomake/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.
Visual Studio Code
------------------
https://marketplace.visualstudio.com/items?itemName=fnando.linter
IntelliJ
--------
https://plugins.jetbrains.com/plugin/15349-yamllint
Other text editors
------------------

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

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -14,39 +13,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from setuptools import setup, find_packages
from setuptools import setup
from yamllint import (__author__, __license__,
APP_NAME, APP_VERSION, APP_DESCRIPTION)
setup(
name=APP_NAME,
version=APP_VERSION,
author=__author__,
description=APP_DESCRIPTION.split('\n')[0],
long_description=APP_DESCRIPTION,
license=__license__,
keywords=['yaml', 'lint', 'linter', 'syntax', 'checker'],
url='https://github.com/adrienverge/yamllint',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
'Topic :: Software Development',
'Topic :: Software Development :: Debuggers',
'Topic :: Software Development :: Quality Assurance',
'Topic :: Software Development :: Testing',
],
packages=find_packages(),
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',
)
# This is only kept for backward-compatibility with older versions that don't
# support new packaging standards (e.g. PEP 517 or PEP 660):
setup()

@ -0,0 +1,19 @@
# 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')

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -14,6 +13,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import contextlib
import os
import shutil
import tempfile
import unittest
import yaml
@ -49,3 +52,35 @@ 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).encode('utf-8')
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
@contextlib.contextmanager
def temp_workspace(files):
"""Provide a temporary workspace that is automatically cleaned up."""
backup_wd = os.getcwd()
wd = build_temp_workspace(files)
try:
os.chdir(wd)
yield
finally:
os.chdir(backup_wd)
shutil.rmtree(wd)

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

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -31,12 +30,74 @@ class ColonTestCase(RuleTestCase):
'dict6: { a: 1, b, c: 3 }\n'
'dict7: { a: 1, b, c: 3 }\n', conf)
def test_forbid(self):
conf = ('braces:\n'
' forbid: false\n')
self.check('---\n'
'dict: {}\n', conf)
self.check('---\n'
'dict: {a}\n', conf)
self.check('---\n'
'dict: {a: 1}\n', conf)
self.check('---\n'
'dict: {\n'
' a: 1\n'
'}\n', conf)
conf = ('braces:\n'
' forbid: true\n')
self.check('---\n'
'dict:\n'
' a: 1\n', conf)
self.check('---\n'
'dict: {}\n', conf, problem=(2, 8))
self.check('---\n'
'dict: {a}\n', conf, problem=(2, 8))
self.check('---\n'
'dict: {a: 1}\n', conf, problem=(2, 8))
self.check('---\n'
'dict: {\n'
' a: 1\n'
'}\n', conf, problem=(2, 8))
conf = ('braces:\n'
' forbid: non-empty\n')
self.check('---\n'
'dict:\n'
' a: 1\n', conf)
self.check('---\n'
'dict: {}\n', conf)
self.check('---\n'
'dict: {\n'
'}\n', conf)
self.check('---\n'
'dict: {\n'
'# commented: value\n'
'# another: value2\n'
'}\n', conf)
self.check('---\n'
'dict: {a}\n', conf, problem=(2, 8))
self.check('---\n'
'dict: {a: 1}\n', conf, problem=(2, 8))
self.check('---\n'
'dict: {\n'
' a: 1\n'
'}\n', conf, problem=(2, 8))
def test_min_spaces(self):
conf = 'braces: {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 +113,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 +125,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 +148,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 +160,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 +172,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)

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -31,12 +30,72 @@ class ColonTestCase(RuleTestCase):
'array6: [ a, b, c ]\n'
'array7: [ a, b, c ]\n', conf)
def test_forbid(self):
conf = ('brackets:\n'
' forbid: false\n')
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
'array: [a, b]\n', conf)
self.check('---\n'
'array: [\n'
' a,\n'
' b\n'
']\n', conf)
conf = ('brackets:\n'
' forbid: true\n')
self.check('---\n'
'array:\n'
' - a\n'
' - b\n', conf)
self.check('---\n'
'array: []\n', conf, problem=(2, 9))
self.check('---\n'
'array: [a, b]\n', conf, problem=(2, 9))
self.check('---\n'
'array: [\n'
' a,\n'
' b\n'
']\n', conf, problem=(2, 9))
conf = ('brackets:\n'
' forbid: non-empty\n')
self.check('---\n'
'array:\n'
' - a\n'
' - b\n', conf)
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
'array: [\n\n'
']\n', conf)
self.check('---\n'
'array: [\n'
'# a comment\n'
']\n', conf)
self.check('---\n'
'array: [a, b]\n', conf, problem=(2, 9))
self.check('---\n'
'array: [\n'
' a,\n'
' b\n'
']\n', conf, problem=(2, 9))
def test_min_spaces(self):
conf = 'brackets: {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 +110,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 +122,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 +145,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 +157,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 +169,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)

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

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

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -35,11 +34,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 +55,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 +70,59 @@ 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_shebang(self):
conf = ('comments:\n'
' require-starting-space: true\n'
' ignore-shebangs: false\n'
'comments-indentation: disable\n'
'document-start: disable\n')
self.check('#!/bin/env my-interpreter\n',
conf, problem1=(1, 2))
self.check('# comment\n'
'#!/bin/env my-interpreter\n', conf,
problem1=(2, 2))
self.check('#!/bin/env my-interpreter\n'
'---\n'
'#comment\n'
'#!/bin/env my-interpreter\n'
'', conf,
problem1=(1, 2), problem2=(3, 2), problem3=(4, 2))
self.check('#! is a valid shebang too\n',
conf, problem1=(1, 2))
self.check('key: #!/not/a/shebang\n',
conf, problem1=(1, 8))
def test_ignore_shebang(self):
conf = ('comments:\n'
' require-starting-space: true\n'
' ignore-shebangs: true\n'
'comments-indentation: disable\n'
'document-start: disable\n')
self.check('#!/bin/env my-interpreter\n', conf)
self.check('# comment\n'
'#!/bin/env my-interpreter\n', conf,
problem1=(2, 2))
self.check('#!/bin/env my-interpreter\n'
'---\n'
'#comment\n'
'#!/bin/env my-interpreter\n', conf,
problem2=(3, 2), problem3=(4, 2))
self.check('#! is a valid shebang too\n', conf)
self.check('key: #!/not/a/shebang\n',
conf, problem1=(1, 8))
def test_spaces_from_content(self):
conf = ('comments:\n'
' require-starting-space: no\n'
' require-starting-space: false\n'
' min-spaces-from-content: 2\n')
self.check('---\n'
'# comment\n'
@ -91,7 +144,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 +159,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'
@ -126,15 +184,44 @@ class CommentsTestCase(RuleTestCase):
'inline: comment #\n'
'foo: bar\n', conf)
def test_empty_comment_crlf_dos_newlines(self):
conf = ('comments:\n'
' require-starting-space: true\n'
' min-spaces-from-content: 2\n'
'new-lines:\n'
' type: dos\n')
self.check('---\r\n'
'# This is paragraph 1.\r\n'
'#\r\n'
'# This is paragraph 2.\r\n', conf)
def test_empty_comment_crlf_disabled_newlines(self):
conf = ('comments:\n'
' require-starting-space: true\n'
' min-spaces-from-content: 2\n'
'new-lines: disable\n')
self.check('---\r\n'
'# This is paragraph 1.\r\n'
'#\r\n'
'# This is paragraph 2.\r\n', conf)
def test_first_line(self):
conf = ('comments:\n'
' require-starting-space: 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'

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -58,7 +57,7 @@ class CommentsIndentationTestCase(RuleTestCase):
'# line 2\n', conf, problem=(2, 2))
self.check('---\n'
' # line 1\n'
' # line 2\n', conf, problem1=(2, 3), problem2=(3, 3))
' # line 2\n', conf, problem1=(2, 3))
self.check('---\n'
'obj:\n'
' # normal\n'
@ -102,13 +101,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'
@ -143,3 +142,15 @@ class CommentsIndentationTestCase(RuleTestCase):
'# hey\n'
'# normal\n'
' #\n', conf, problem=(4, 2))
def test_inline_comment(self):
conf = 'comments-indentation: enable'
self.check('---\n'
'- a # inline\n'
'# ok\n', conf)
self.check('---\n'
'- a # inline\n'
' # not ok\n', conf, problem=(3, 2))
self.check('---\n'
' # not ok\n'
'- a # inline\n', conf, problem=(2, 2))

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -18,8 +17,7 @@ import unittest
import yaml
from yamllint.rules.common import (Comment, get_line_indent,
get_comments_between_tokens)
from yamllint.rules.common import get_line_indent
class CommonTestCase(unittest.TestCase):
@ -43,54 +41,3 @@ class CommonTestCase(unittest.TestCase):
self.assertEqual(get_line_indent(tokens[i]), 0)
for i in (13, 16, 18, 22, 24):
self.assertEqual(get_line_indent(tokens[i]), 2)
def check_comments(self, buffer, *expected):
yaml_loader = yaml.BaseLoader(buffer)
comments = []
next = yaml_loader.peek_token()
while next is not None:
curr = yaml_loader.get_token()
next = yaml_loader.peek_token()
for comment in get_comments_between_tokens(curr, next):
comments.append(comment)
self.assertEqual(comments, list(expected))
def test_get_comments_between_tokens(self):
self.check_comments('# comment\n',
Comment(1, 1, '# comment', 0))
self.check_comments('---\n'
'# comment\n'
'...\n',
Comment(2, 1, '# comment', 0))
self.check_comments('---\n'
'# no newline char',
Comment(2, 1, '# no newline char', 0))
self.check_comments('# just comment',
Comment(1, 1, '# just comment', 0))
self.check_comments('\n'
' # indented comment\n',
Comment(2, 4, '# indented comment', 0))
self.check_comments('\n'
'# trailing spaces \n',
Comment(2, 1, '# trailing spaces ', 0))
self.check_comments('# comment one\n'
'\n'
'key: val # key=val\n'
'\n'
'# this is\n'
'# a block \n'
'# comment\n'
'\n'
'other:\n'
' - foo # equals\n'
' # bar\n',
Comment(1, 1, '# comment one', 0),
Comment(3, 11, '# key=val', 0),
Comment(5, 1, '# this is', 0),
Comment(6, 1, '# a block ', 0),
Comment(7, 1, '# comment', 0),
Comment(10, 10, '# equals', 0),
Comment(11, 10, '# bar', 0))

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

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

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -78,3 +77,22 @@ class EmptyLinesTestCase(RuleTestCase):
'document-start: disable\n')
self.check('non empty\n', conf)
self.check('non empty\n\n', conf, problem=(2, 1))
def test_with_dos_newlines(self):
conf = ('empty-lines: {max: 2, max-start: 0, max-end: 0}\n'
'new-lines: {type: dos}\n'
'document-start: disable\n')
self.check('---\r\n', conf)
self.check('---\r\ntext\r\n\r\ntext\r\n', conf)
self.check('\r\n---\r\ntext\r\n\r\ntext\r\n', conf,
problem=(1, 1))
self.check('\r\n\r\n\r\n---\r\ntext\r\n\r\ntext\r\n', conf,
problem=(3, 1))
self.check('---\r\ntext\r\n\r\n\r\n\r\ntext\r\n', conf,
problem=(5, 1))
self.check('---\r\ntext\r\n\r\n\r\n\r\n\r\n\r\n\r\ntext\r\n', conf,
problem=(8, 1))
self.check('---\r\ntext\r\n\r\ntext\r\n\r\n', conf,
problem=(5, 1))
self.check('---\r\ntext\r\n\r\ntext\r\n\r\n\r\n\r\n', conf,
problem=(7, 1))

@ -0,0 +1,260 @@
# 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))

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

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

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -15,7 +14,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.common import RuleTestCase
from yamllint.parser import token_generator
from yamllint.parser import token_or_comment_generator, Comment
from yamllint.rules.indentation import check
@ -38,7 +38,8 @@ class IndentationStackTestCase(RuleTestCase):
'check-multi-line-strings': False}
context = {}
output = ''
for elem in token_generator(source):
for elem in [t for t in token_or_comment_generator(source)
if not isinstance(t, Comment)]:
list(check(conf, elem.curr, elem.prev, elem.next, elem.nextnext,
context))
@ -49,8 +50,8 @@ class IndentationStackTestCase(RuleTestCase):
.replace('Mapping', 'Map'))
if token_type in ('StreamStart', 'StreamEnd'):
continue
output += '%9s %s\n' % (token_type,
self.format_stack(context['stack']))
output += '{:>9} {}\n'.format(token_type,
self.format_stack(context['stack']))
return output
def test_simple_mapping(self):
@ -548,7 +549,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'
@ -561,7 +562,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'
@ -576,7 +577,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'
@ -588,8 +589,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'
@ -604,7 +608,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'
@ -617,7 +621,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'
@ -631,7 +635,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')
@ -712,6 +716,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'
@ -1071,6 +1211,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}'
@ -1127,9 +1281,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'
@ -1202,6 +1370,45 @@ class IndentationTestCase(RuleTestCase):
' key: value\n'
'...\n', conf, problem=(2, 2))
def test_nested_collections_with_spaces_consistent(self):
"""Tests behavior of {spaces: consistent} in nested collections to
ensure wrong-indentation is properly caught--especially when the
expected indent value is initially unknown. For details, see
https://github.com/adrienverge/yamllint/issues/485.
"""
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: true}')
self.check('---\n'
'- item:\n'
' - elem\n'
'- item:\n'
' - elem\n'
'...\n', conf, problem=(3, 3))
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: false}')
self.check('---\n'
'- item:\n'
' - elem\n'
'- item:\n'
' - elem\n'
'...\n', conf, problem=(5, 5))
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: consistent}')
self.check('---\n'
'- item:\n'
' - elem\n'
'- item:\n'
' - elem\n'
'...\n', conf, problem=(5, 5))
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: whatever}')
self.check('---\n'
'- item:\n'
' - elem\n'
'- item:\n'
' - elem\n'
'...\n', conf)
def test_return(self):
conf = 'indentation: {spaces: consistent}'
self.check('---\n'
@ -1437,7 +1644,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'
@ -1504,7 +1711,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)
@ -1533,7 +1740,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))
@ -1556,7 +1763,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)
@ -1587,7 +1794,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))
@ -1643,7 +1850,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'
@ -1681,7 +1888,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'
@ -1722,7 +1929,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'
@ -1760,7 +1967,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'
@ -1804,7 +2011,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'
@ -1826,7 +2033,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'
@ -1854,7 +2061,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'
@ -1882,7 +2089,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'
@ -1900,7 +2107,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'
@ -1918,7 +2125,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)

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -78,6 +77,19 @@ class KeyDuplicatesTestCase(RuleTestCase):
' duplicates with\n'
' many styles\n'
': 1\n', conf)
self.check('---\n'
'Merge Keys are OK:\n'
'anchor_one: &anchor_one\n'
' one: one\n'
'anchor_two: &anchor_two\n'
' two: two\n'
'anchor_reference:\n'
' <<: *anchor_one\n'
' <<: *anchor_two\n', conf)
self.check('---\n'
'{a: 1, b: 2}}\n', conf, problem=(2, 13, 'syntax'))
self.check('---\n'
'[a, b, c]]\n', conf, problem=(2, 10, 'syntax'))
def test_enabled(self):
conf = 'key-duplicates: enable'
@ -147,6 +159,19 @@ class KeyDuplicatesTestCase(RuleTestCase):
': 1\n', conf,
problem1=(3, 1), problem2=(4, 1), problem3=(5, 3),
problem4=(7, 3))
self.check('---\n'
'Merge Keys are OK:\n'
'anchor_one: &anchor_one\n'
' one: one\n'
'anchor_two: &anchor_two\n'
' two: two\n'
'anchor_reference:\n'
' <<: *anchor_one\n'
' <<: *anchor_two\n', conf)
self.check('---\n'
'{a: 1, b: 2}}\n', conf, problem=(2, 13, 'syntax'))
self.check('---\n'
'[a, b, c]]\n', conf, problem=(2, 10, 'syntax'))
def test_key_tokens_in_flow_sequences(self):
conf = 'key-duplicates: enable'

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

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -32,6 +31,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 +65,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'
@ -78,8 +80,17 @@ class LineLengthTestCase(RuleTestCase):
' # http://localhost/very/long/url\n'
' comment\n'
'...\n', conf)
self.check('---\n'
'this:\n'
'is:\n'
'another:\n'
' - https://localhost/very/very/long/url\n'
'...\n', conf)
self.check('---\n'
'long_line: http://localhost/very/very/long/url\n', conf,
problem=(2, 21))
conf = 'line-length: {max: 20, allow-non-breakable-words: 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'
@ -94,3 +105,94 @@ class LineLengthTestCase(RuleTestCase):
' # http://localhost/very/long/url\n'
' comment\n'
'...\n', conf, problem=(5, 21))
self.check('---\n'
'this:\n'
'is:\n'
'another:\n'
' - https://localhost/very/very/long/url\n'
'...\n', conf, problem=(5, 21))
self.check('---\n'
'long_line: http://localhost/very/very/long/url\n'
'...\n', conf, problem=(2, 21))
conf = 'line-length: {max: 20, allow-non-breakable-words: true}'
self.check('---\n'
'# http://www.verylongurlurlurlurlurlurlurlurl.com\n'
'key:\n'
' subkey: value\n', conf)
self.check('---\n'
'## http://www.verylongurlurlurlurlurlurlurlurl.com\n'
'key:\n'
' subkey: value\n', conf)
self.check('---\n'
'# # http://www.verylongurlurlurlurlurlurlurlurl.com\n'
'key:\n'
' subkey: value\n', conf,
problem=(2, 21))
self.check('---\n'
'#A http://www.verylongurlurlurlurlurlurlurlurl.com\n'
'key:\n'
' subkey: value\n', conf,
problem1=(2, 2, 'comments'),
problem2=(2, 21, 'line-length'))
conf = ('line-length: {max: 20, allow-non-breakable-words: true}\n'
'trailing-spaces: disable')
self.check('---\n'
'loooooooooong+word+and+some+space+at+the+end \n',
conf, problem=(2, 21))
def test_non_breakable_inline_mappings(self):
conf = 'line-length: {max: 20, ' \
'allow-non-breakable-inline-mappings: true}'
self.check('---\n'
'long_line: http://localhost/very/very/long/url\n'
'long line: http://localhost/very/very/long/url\n', conf)
self.check('---\n'
'- long line: http://localhost/very/very/long/url\n', conf)
self.check('---\n'
'long_line: http://localhost/short/url + word\n'
'long line: http://localhost/short/url + word\n',
conf, problem1=(2, 21), problem2=(3, 21))
conf = ('line-length: {max: 20,'
' allow-non-breakable-inline-mappings: true}\n'
'trailing-spaces: disable')
self.check('---\n'
'long_line: and+some+space+at+the+end \n',
conf, problem=(2, 21))
self.check('---\n'
'long line: and+some+space+at+the+end \n',
conf, problem=(2, 21))
self.check('---\n'
'- long line: and+some+space+at+the+end \n',
conf, problem=(2, 21))
# See https://github.com/adrienverge/yamllint/issues/21
conf = 'line-length: {allow-non-breakable-inline-mappings: true}'
self.check('---\n'
'content: |\n'
' {% this line is' + 99 * ' really' + ' long %}\n',
conf, problem=(3, 81))
def test_unicode(self):
conf = 'line-length: {max: 53}'
self.check('---\n'
'# This is a test to check if “line-length” works nice\n'
'with: “unicode characters” that span across bytes! ↺\n',
conf)
conf = 'line-length: {max: 51}'
self.check('---\n'
'# This is a test to check if “line-length” works nice\n'
'with: “unicode characters” that span across bytes! ↺\n',
conf, problem1=(2, 52), problem2=(3, 52))
def test_with_dos_newlines(self):
conf = ('line-length: {max: 10}\n'
'new-lines: {type: dos}\n'
'new-line-at-end-of-file: disable\n')
self.check('---\r\nABCD EFGHI', conf)
self.check('---\r\nABCD EFGHI\r\n', conf)
self.check('---\r\nABCD EFGHIJ', conf, problem=(2, 11))
self.check('---\r\nABCD EFGHIJ\r\n', conf, problem=(2, 11))

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

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -14,6 +13,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from unittest import mock
from tests.common import RuleTestCase
@ -31,17 +32,65 @@ class NewLinesTestCase(RuleTestCase):
self.check('---\r\ntext\r\n', conf)
def test_unix_type(self):
conf = 'new-lines: {type: unix}'
conf = ('new-line-at-end-of-file: disable\n'
'new-lines: {type: unix}\n')
self.check('', conf)
self.check('\r', conf)
self.check('\n', conf)
self.check('\r\n', conf, problem=(1, 1))
self.check('---\ntext\n', conf)
self.check('---\r\ntext\r\n', conf, problem=(1, 4))
def test_unix_type_required_st_sp(self):
# If we find a CRLF when looking for Unix newlines, yamllint
# should always raise, regardless of logic with
# require-starting-space.
conf = ('new-line-at-end-of-file: disable\n'
'new-lines: {type: unix}\n'
'comments:\n'
' require-starting-space: true\n')
self.check('---\r\n#\r\n', conf, problem=(1, 4))
def test_dos_type(self):
conf = 'new-lines: {type: dos}\n'
conf = ('new-line-at-end-of-file: disable\n'
'new-lines: {type: dos}\n')
self.check('', conf)
self.check('\r', conf)
self.check('\n', conf, problem=(1, 1))
self.check('\r\n', conf)
self.check('---\ntext\n', conf, problem=(1, 4))
self.check('---\r\ntext\r\n', conf)
def test_platform_type(self):
conf = ('new-line-at-end-of-file: disable\n'
'new-lines: {type: platform}\n')
self.check('', conf)
# mock the Linux new-line-character
with mock.patch('yamllint.rules.new_lines.linesep', '\n'):
self.check('\n', conf)
self.check('\r\n', conf, problem=(1, 1))
self.check('---\ntext\n', conf)
self.check('---\r\ntext\r\n', conf, problem=(1, 4))
self.check('---\r\ntext\n', conf, problem=(1, 4))
# FIXME: the following tests currently don't work
# because only the first line is checked for line-endings
# see: issue #475
# ---
# self.check('---\ntext\r\nfoo\n', conf, problem=(2, 4))
# self.check('---\ntext\r\n', conf, problem=(2, 4))
# mock the Windows new-line-character
with mock.patch('yamllint.rules.new_lines.linesep', '\r\n'):
self.check('\r\n', conf)
self.check('\n', conf, problem=(1, 1))
self.check('---\r\ntext\r\n', conf)
self.check('---\ntext\n', conf, problem=(1, 4))
self.check('---\ntext\r\n', conf, problem=(1, 4))
# FIXME: the following tests currently don't work
# because only the first line is checked for line-endings
# see: issue #475
# ---
# self.check('---\r\ntext\nfoo\r\n', conf, problem=(2, 4))
# self.check('---\r\ntext\n', conf, problem=(2, 4))

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

@ -0,0 +1,558 @@
# Copyright (C) 2018 ClearScore
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.common import RuleTestCase
from yamllint import config
class QuotedTestCase(RuleTestCase):
rule_id = 'quoted-strings'
def test_disabled(self):
conf = 'quoted-strings: disable'
self.check('---\n'
'foo: bar\n', conf)
self.check('---\n'
'foo: "bar"\n', conf)
self.check('---\n'
'foo: \'bar\'\n', conf)
self.check('---\n'
'bar: 123\n', conf)
self.check('---\n'
'bar: "123"\n', conf)
def test_quote_type_any(self):
conf = 'quoted-strings: {quote-type: any}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n' # fails
'string2: "foo"\n'
'string3: "true"\n'
'string4: "123"\n'
'string5: \'bar\'\n'
'string6: !!str genericstring\n'
'string7: !!str 456\n'
'string8: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n'
'block-seq:\n'
' - foo\n' # fails
' - "foo"\n'
'flow-seq: [foo, "foo"]\n' # fails
'flow-map: {a: foo, b: "foo"}\n', # fails
conf, problem1=(4, 10), problem2=(17, 5),
problem3=(19, 12), problem4=(20, 15))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n' # fails
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n'
' word 2"\n',
conf, problem1=(9, 3))
def test_quote_type_single(self):
conf = 'quoted-strings: {quote-type: single}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n' # fails
'string2: "foo"\n' # fails
'string3: "true"\n' # fails
'string4: "123"\n' # fails
'string5: \'bar\'\n'
'string6: !!str genericstring\n'
'string7: !!str 456\n'
'string8: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n'
'block-seq:\n'
' - foo\n' # fails
' - "foo"\n' # fails
'flow-seq: [foo, "foo"]\n' # fails
'flow-map: {a: foo, b: "foo"}\n', # fails
conf, problem1=(4, 10), problem2=(5, 10), problem3=(6, 10),
problem4=(7, 10), problem5=(17, 5), problem6=(18, 5),
problem7=(19, 12), problem8=(19, 17), problem9=(20, 15),
problem10=(20, 23))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n' # fails
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n'
' word 2"\n',
conf, problem1=(9, 3), problem2=(12, 3))
def test_quote_type_double(self):
conf = 'quoted-strings: {quote-type: double}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n' # fails
'string2: "foo"\n'
'string3: "true"\n'
'string4: "123"\n'
'string5: \'bar\'\n' # fails
'string6: !!str genericstring\n'
'string7: !!str 456\n'
'string8: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n'
'block-seq:\n'
' - foo\n' # fails
' - "foo"\n'
'flow-seq: [foo, "foo"]\n' # fails
'flow-map: {a: foo, b: "foo"}\n', # fails
conf, problem1=(4, 10), problem2=(8, 10), problem3=(17, 5),
problem4=(19, 12), problem5=(20, 15))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n' # fails
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n'
' word 2"\n',
conf, problem1=(9, 3))
def test_any_quotes_not_required(self):
conf = 'quoted-strings: {quote-type: any, required: false}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n'
'string2: "foo"\n'
'string3: "true"\n'
'string4: "123"\n'
'string5: \'bar\'\n'
'string6: !!str genericstring\n'
'string7: !!str 456\n'
'string8: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n'
'block-seq:\n'
' - foo\n' # fails
' - "foo"\n'
'flow-seq: [foo, "foo"]\n' # fails
'flow-map: {a: foo, b: "foo"}\n', # fails
conf)
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n'
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n'
' word 2"\n',
conf)
def test_single_quotes_not_required(self):
conf = 'quoted-strings: {quote-type: single, required: false}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n'
'string2: "foo"\n' # fails
'string3: "true"\n' # fails
'string4: "123"\n' # fails
'string5: \'bar\'\n'
'string6: !!str genericstring\n'
'string7: !!str 456\n'
'string8: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n'
'block-seq:\n'
' - foo\n' # fails
' - "foo"\n'
'flow-seq: [foo, "foo"]\n' # fails
'flow-map: {a: foo, b: "foo"}\n', # fails
conf, problem1=(5, 10), problem2=(6, 10), problem3=(7, 10),
problem4=(18, 5), problem5=(19, 17), problem6=(20, 23))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n'
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n' # fails
' word 2"\n',
conf, problem1=(12, 3))
def test_only_when_needed(self):
conf = 'quoted-strings: {required: only-when-needed}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n'
'string2: "foo"\n' # fails
'string3: "true"\n'
'string4: "123"\n'
'string5: \'bar\'\n' # fails
'string6: !!str genericstring\n'
'string7: !!str 456\n'
'string8: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n'
'block-seq:\n'
' - foo\n'
' - "foo"\n' # fails
'flow-seq: [foo, "foo"]\n' # fails
'flow-map: {a: foo, b: "foo"}\n', # fails
conf, problem1=(5, 10), problem2=(8, 10), problem3=(18, 5),
problem4=(19, 17), problem5=(20, 23))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n'
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n' # fails
' word 2"\n',
conf, problem1=(12, 3))
def test_only_when_needed_single_quotes(self):
conf = ('quoted-strings: {quote-type: single,\n'
' required: only-when-needed}\n')
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n'
'string2: "foo"\n' # fails
'string3: "true"\n' # fails
'string4: "123"\n' # fails
'string5: \'bar\'\n' # fails
'string6: !!str genericstring\n'
'string7: !!str 456\n'
'string8: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n'
'block-seq:\n'
' - foo\n'
' - "foo"\n' # fails
'flow-seq: [foo, "foo"]\n' # fails
'flow-map: {a: foo, b: "foo"}\n', # fails
conf, problem1=(5, 10), problem2=(6, 10), problem3=(7, 10),
problem4=(8, 10), problem5=(18, 5), problem6=(19, 17),
problem7=(20, 23))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n'
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n' # fails
' word 2"\n',
conf, problem1=(12, 3))
def test_only_when_needed_corner_cases(self):
conf = 'quoted-strings: {required: only-when-needed}\n'
self.check('---\n'
'- ""\n'
'- "- item"\n'
'- "key: value"\n'
'- "%H:%M:%S"\n'
'- "%wheel ALL=(ALL) NOPASSWD: ALL"\n'
'- \'"quoted"\'\n'
'- "\'foo\' == \'bar\'"\n'
'- "\'Mac\' in ansible_facts.product_name"\n'
'- \'foo # bar\'\n',
conf)
self.check('---\n'
'k1: ""\n'
'k2: "- item"\n'
'k3: "key: value"\n'
'k4: "%H:%M:%S"\n'
'k5: "%wheel ALL=(ALL) NOPASSWD: ALL"\n'
'k6: \'"quoted"\'\n'
'k7: "\'foo\' == \'bar\'"\n'
'k8: "\'Mac\' in ansible_facts.product_name"\n',
conf)
self.check('---\n'
'- ---\n'
'- "---"\n' # fails
'- ----------\n'
'- "----------"\n' # fails
'- :wq\n'
'- ":wq"\n', # fails
conf, problem1=(3, 3), problem2=(5, 3), problem3=(7, 3))
self.check('---\n'
'k1: ---\n'
'k2: "---"\n' # fails
'k3: ----------\n'
'k4: "----------"\n' # fails
'k5: :wq\n'
'k6: ":wq"\n', # fails
conf, problem1=(3, 5), problem2=(5, 5), problem3=(7, 5))
def test_only_when_needed_extras(self):
conf = ('quoted-strings:\n'
' required: true\n'
' extra-allowed: [^http://]\n')
self.assertRaises(config.YamlLintConfigError, self.check, '', conf)
conf = ('quoted-strings:\n'
' required: true\n'
' extra-required: [^http://]\n')
self.assertRaises(config.YamlLintConfigError, self.check, '', conf)
conf = ('quoted-strings:\n'
' required: false\n'
' extra-allowed: [^http://]\n')
self.assertRaises(config.YamlLintConfigError, self.check, '', conf)
conf = ('quoted-strings:\n'
' required: true\n')
self.check('---\n'
'- 123\n'
'- "123"\n'
'- localhost\n' # fails
'- "localhost"\n'
'- http://localhost\n' # fails
'- "http://localhost"\n'
'- ftp://localhost\n' # fails
'- "ftp://localhost"\n',
conf, problem1=(4, 3), problem2=(6, 3), problem3=(8, 3))
conf = ('quoted-strings:\n'
' required: only-when-needed\n'
' extra-allowed: [^ftp://]\n'
' extra-required: [^http://]\n')
self.check('---\n'
'- 123\n'
'- "123"\n'
'- localhost\n'
'- "localhost"\n' # fails
'- http://localhost\n' # fails
'- "http://localhost"\n'
'- ftp://localhost\n'
'- "ftp://localhost"\n',
conf, problem1=(5, 3), problem2=(6, 3))
conf = ('quoted-strings:\n'
' required: false\n'
' extra-required: [^http://, ^ftp://]\n')
self.check('---\n'
'- 123\n'
'- "123"\n'
'- localhost\n'
'- "localhost"\n'
'- http://localhost\n' # fails
'- "http://localhost"\n'
'- ftp://localhost\n' # fails
'- "ftp://localhost"\n',
conf, problem1=(6, 3), problem2=(8, 3))
conf = ('quoted-strings:\n'
' required: only-when-needed\n'
' extra-allowed: [^ftp://, ";$", " "]\n')
self.check('---\n'
'- localhost\n'
'- "localhost"\n' # fails
'- ftp://localhost\n'
'- "ftp://localhost"\n'
'- i=i+1\n'
'- "i=i+1"\n' # fails
'- i=i+2;\n'
'- "i=i+2;"\n'
'- foo\n'
'- "foo"\n' # fails
'- foo bar\n'
'- "foo bar"\n',
conf, problem1=(3, 3), problem2=(7, 3), problem3=(11, 3))
def test_octal_values(self):
conf = 'quoted-strings: {required: true}\n'
self.check('---\n'
'- 100\n'
'- 0100\n'
'- 0o100\n'
'- 777\n'
'- 0777\n'
'- 0o777\n'
'- 800\n'
'- 0800\n'
'- 0o800\n'
'- "0800"\n'
'- "0o800"\n',
conf,
problem1=(9, 3), problem2=(10, 3))
def test_allow_quoted_quotes(self):
conf = ('quoted-strings: {quote-type: single,\n'
' required: false,\n'
' allow-quoted-quotes: false}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n', # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: single,\n'
' required: false,\n'
' allow-quoted-quotes: true}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n',
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: single,\n'
' required: true,\n'
' allow-quoted-quotes: false}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n', # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: single,\n'
' required: true,\n'
' allow-quoted-quotes: true}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n',
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: single,\n'
' required: only-when-needed,\n'
' allow-quoted-quotes: false}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n', # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: single,\n'
' required: only-when-needed,\n'
' allow-quoted-quotes: true}\n')
self.check('---\n'
'foo1: "[barbaz]"\n' # fails
'foo2: "[bar\'baz]"\n',
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: false,\n'
' allow-quoted-quotes: false}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n", # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: false,\n'
' allow-quoted-quotes: true}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n",
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: true,\n'
' allow-quoted-quotes: false}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n", # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: true,\n'
' allow-quoted-quotes: true}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n",
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: only-when-needed,\n'
' allow-quoted-quotes: false}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n", # fails
conf, problem1=(2, 7), problem2=(3, 7))
conf = ('quoted-strings: {quote-type: double,\n'
' required: only-when-needed,\n'
' allow-quoted-quotes: true}\n')
self.check("---\n"
"foo1: '[barbaz]'\n" # fails
"foo2: '[bar\"baz]'\n",
conf, problem1=(2, 7))
conf = ('quoted-strings: {quote-type: any}\n')
self.check("---\n"
"foo1: '[barbaz]'\n"
"foo2: '[bar\"baz]'\n",
conf)

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

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

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -14,79 +13,137 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
from io import StringIO
import fcntl
import locale
import os
import pty
import shutil
import sys
import tempfile
import unittest
import sys
from tests.common import build_temp_workspace, temp_workspace
from yamllint import cli
from yamllint import config
class CommandLineTestCase(unittest.TestCase):
def setUp(self):
self.wd = tempfile.mkdtemp(prefix='yamllint-tests-')
# .yaml file at root
with open(os.path.join(self.wd, 'a.yaml'), 'w') as f:
f.write('---\n'
'- 1 \n'
'- 2')
# .yml file at root
open(os.path.join(self.wd, 'empty.yml'), 'w').close()
# 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)
class RunContext:
"""Context manager for ``cli.run()`` to capture exit code and streams."""
def __init__(self, case):
self.stdout = self.stderr = None
self._raises_ctx = case.assertRaises(SystemExit)
def __enter__(self):
self._raises_ctx.__enter__()
sys.stdout = self.outstream = StringIO()
sys.stderr = self.errstream = StringIO()
return self
def __exit__(self, *exc_info):
self.stdout, sys.stdout = self.outstream.getvalue(), sys.__stdout__
self.stderr, sys.stderr = self.errstream.getvalue(), sys.__stderr__
return self._raises_ctx.__exit__(*exc_info)
@property
def returncode(self):
return self._raises_ctx.exception.code
# Check system's UTF-8 availability
def utf8_available():
try:
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
locale.setlocale(locale.LC_ALL, (None, None))
return True
except locale.Error: # pragma: no cover
return False
class CommandLineTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
super(CommandLineTestCase, cls).setUpClass()
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',
# directory that looks like a yaml file
'sub/directory.yaml/not-yaml.txt': '',
'sub/directory.yaml/empty.yml': '',
# 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': (
'---\n'
'- hétérogénéité\n'
'# 19.99 €\n'
'- お早う御座います。\n'
'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'),
# dos line endings yaml
'dos.yml': '---\r\n'
'dos: true',
# different key-ordering by locale
'c.yaml': '---\n'
'A: true\n'
'a: true',
'en.yaml': '---\n'
'a: true\n'
'A: true'
})
@classmethod
def tearDownClass(cls):
super(CommandLineTestCase, cls).tearDownClass()
shutil.rmtree(cls.wd)
@unittest.skipIf(not utf8_available() and sys.version_info < (3, 7),
'UTF-8 paths not supported')
def test_find_files_recursively(self):
conf = config.YamlLintConfig('extends: default')
self.assertEqual(
sorted(cli.find_files_recursively([self.wd])),
sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 'en.yaml'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/ok.yaml')],
os.path.join(self.wd, 'sub/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')],
)
items = [os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'empty-dir')]
self.assertEqual(
sorted(cli.find_files_recursively(items)),
sorted(cli.find_files_recursively(items, conf)),
[os.path.join(self.wd, 'sub/ok.yaml')],
)
items = [os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 's')]
self.assertEqual(
sorted(cli.find_files_recursively(items)),
sorted(cli.find_files_recursively(items, conf)),
[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')],
)
@ -94,157 +151,371 @@ class CommandLineTestCase(unittest.TestCase):
items = [os.path.join(self.wd, 'sub'),
os.path.join(self.wd, '/etc/another/file')]
self.assertEqual(
sorted(cli.find_files_recursively(items)),
sorted(cli.find_files_recursively(items, conf)),
[os.path.join(self.wd, '/etc/another/file'),
os.path.join(self.wd, 'sub/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/ok.yaml')],
)
def test_run_with_bad_arguments(self):
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(())
conf = config.YamlLintConfig('extends: default\n'
'yaml-files:\n'
' - \'*.yaml\' \n')
self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'en.yaml'),
os.path.join(self.wd, '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, 'warn.yaml')]
)
self.assertNotEqual(ctx.exception.code, 0)
conf = config.YamlLintConfig('extends: default\n'
'yaml-files:\n'
' - \'*.yml\'\n')
self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 'sub/directory.yaml/empty.yml')]
)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^usage')
conf = config.YamlLintConfig('extends: default\n'
'yaml-files:\n'
' - \'*.json\'\n')
self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'no-yaml.json')]
)
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('--unknown-arg', ))
conf = config.YamlLintConfig('extends: default\n'
'yaml-files:\n'
' - \'*\'\n')
self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 'en.yaml'),
os.path.join(self.wd, 'no-yaml.json'),
os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/directory.yaml/not-yaml.txt'),
os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
self.assertNotEqual(ctx.exception.code, 0)
conf = config.YamlLintConfig('extends: default\n'
'yaml-files:\n'
' - \'*.yaml\'\n'
' - \'*\'\n'
' - \'**\'\n')
self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 'en.yaml'),
os.path.join(self.wd, 'no-yaml.json'),
os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/directory.yaml/not-yaml.txt'),
os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^usage')
conf = config.YamlLintConfig('extends: default\n'
'yaml-files:\n'
' - \'s/**\'\n'
' - \'**/utf-8\'\n')
self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8')]
)
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-c', './conf.yaml', '-d', 'relaxed', 'file'))
def test_run_with_bad_arguments(self):
with RunContext(self) as ctx:
cli.run(())
self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '')
self.assertRegex(ctx.stderr, r'^usage')
self.assertNotEqual(ctx.exception.code, 0)
with RunContext(self) as ctx:
cli.run(('--unknown-arg', ))
self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '')
self.assertRegex(ctx.stderr, r'^usage')
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')
with RunContext(self) as ctx:
cli.run(('-c', './conf.yaml', '-d', 'relaxed', 'file'))
self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '')
self.assertRegex(
ctx.stderr.splitlines()[-1],
r'^yamllint: error: argument -d\/--config-data: '
r'not allowed with argument -c\/--config-file$'
)
# checks if reading from stdin and files are mutually exclusive
with RunContext(self) as ctx:
cli.run(('-', 'file'))
self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '')
self.assertRegex(ctx.stderr, r'^usage')
def test_run_with_bad_config(self):
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
with RunContext(self) as ctx:
cli.run(('-d', 'rules: {a: b}', 'file'))
self.assertEqual(ctx.exception.code, -1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^invalid config: no such rule')
self.assertEqual(ctx.returncode, -1)
self.assertEqual(ctx.stdout, '')
self.assertRegex(ctx.stderr, r'^invalid config: no such rule')
def test_run_with_empty_config(self):
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
with RunContext(self) as ctx:
cli.run(('-d', '', 'file'))
self.assertEqual(ctx.returncode, -1)
self.assertEqual(ctx.stdout, '')
self.assertRegex(ctx.stderr, r'^invalid config: not a dict')
self.assertEqual(ctx.exception.code, -1)
def test_run_with_implicit_extends_config(self):
path = os.path.join(self.wd, 'warn.yaml')
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^invalid config: not a dict')
with RunContext(self) as ctx:
cli.run(('-d', 'default', '-f', 'parsable', path))
expected_out = ('%s:1:1: [warning] missing document start "---" '
'(document-start)\n' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (0, expected_out, ''))
def test_run_with_config_file(self):
with open(os.path.join(self.wd, 'config'), 'w') as f:
f.write('rules: {trailing-spaces: disable}')
with RunContext(self) as ctx:
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
self.assertEqual(ctx.returncode, 0)
with open(os.path.join(self.wd, 'config'), 'w') as f:
f.write('rules: {trailing-spaces: enable}')
with RunContext(self) as ctx:
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
self.assertEqual(ctx.returncode, 1)
@unittest.skipIf(os.environ.get('GITHUB_RUN_ID'), '$HOME not overridable')
def test_run_with_user_global_config_file(self):
home = os.path.join(self.wd, 'fake-home')
dir = os.path.join(home, '.config', 'yamllint')
os.makedirs(dir)
config = os.path.join(dir, 'config')
self.addCleanup(os.environ.update, HOME=os.environ['HOME'])
os.environ['HOME'] = home
with open(config, 'w') as f:
f.write('rules: {trailing-spaces: disable}')
with RunContext(self) as ctx:
cli.run((os.path.join(self.wd, 'a.yaml'), ))
self.assertEqual(ctx.returncode, 0)
with open(config, 'w') as f:
f.write('rules: {trailing-spaces: enable}')
with RunContext(self) as ctx:
cli.run((os.path.join(self.wd, 'a.yaml'), ))
self.assertEqual(ctx.returncode, 1)
def test_run_with_user_xdg_config_home_in_env(self):
self.addCleanup(os.environ.__delitem__, 'XDG_CONFIG_HOME')
with tempfile.TemporaryDirectory('w') as d:
os.environ['XDG_CONFIG_HOME'] = d
os.makedirs(os.path.join(d, 'yamllint'))
with open(os.path.join(d, 'yamllint', 'config'), 'w') as f:
f.write('extends: relaxed')
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', os.path.join(self.wd, 'warn.yaml')))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
def test_run_with_user_yamllint_config_file_in_env(self):
self.addCleanup(os.environ.__delitem__, 'YAMLLINT_CONFIG_FILE')
with tempfile.NamedTemporaryFile('w') as f:
os.environ['YAMLLINT_CONFIG_FILE'] = f.name
f.write('rules: {trailing-spaces: disable}')
f.flush()
with RunContext(self) as ctx:
cli.run((os.path.join(self.wd, 'a.yaml'), ))
self.assertEqual(ctx.returncode, 0)
with tempfile.NamedTemporaryFile('w') as f:
os.environ['YAMLLINT_CONFIG_FILE'] = f.name
f.write('rules: {trailing-spaces: enable}')
f.flush()
with RunContext(self) as ctx:
cli.run((os.path.join(self.wd, 'a.yaml'), ))
self.assertEqual(ctx.returncode, 1)
def test_run_with_locale(self):
# check for availability of locale, otherwise skip the test
# reset to default before running the test,
# as the first two runs don't use setlocale()
try:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
except locale.Error: # pragma: no cover
self.skipTest('locale en_US.UTF-8 not available')
locale.setlocale(locale.LC_ALL, (None, None))
# C + en.yaml should fail
with RunContext(self) as ctx:
cli.run(('-d', 'rules: { key-ordering: enable }',
os.path.join(self.wd, 'en.yaml')))
self.assertEqual(ctx.returncode, 1)
# C + c.yaml should pass
with RunContext(self) as ctx:
cli.run(('-d', 'rules: { key-ordering: enable }',
os.path.join(self.wd, 'c.yaml')))
self.assertEqual(ctx.returncode, 0)
# the next two runs use setlocale() inside,
# so we need to clean up afterwards
self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None))
# en_US + en.yaml should pass
with RunContext(self) as ctx:
cli.run(('-d', 'locale: en_US.UTF-8\n'
'rules: { key-ordering: enable }',
os.path.join(self.wd, 'en.yaml')))
self.assertEqual(ctx.returncode, 0)
# en_US + c.yaml should fail
with RunContext(self) as ctx:
cli.run(('-d', 'locale: en_US.UTF-8\n'
'rules: { key-ordering: enable }',
os.path.join(self.wd, 'c.yaml')))
self.assertEqual(ctx.returncode, 1)
def test_run_version(self):
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
with RunContext(self) as ctx:
cli.run(('--version', ))
self.assertEqual(ctx.exception.code, 0)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertRegexpMatches(out + err, r'yamllint \d+\.\d+')
self.assertEqual(ctx.returncode, 0)
self.assertRegex(ctx.stdout + ctx.stderr, r'yamllint \d+\.\d+')
def test_run_non_existing_file(self):
file = os.path.join(self.wd, 'i-do-not-exist.yaml')
path = os.path.join(self.wd, 'i-do-not-exist.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
self.assertEqual(ctx.exception.code, -1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'No such file or directory')
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', path))
self.assertEqual(ctx.returncode, -1)
self.assertEqual(ctx.stdout, '')
self.assertRegex(ctx.stderr, r'No such file or directory')
def test_run_one_problem_file(self):
file = os.path.join(self.wd, 'a.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
self.assertEqual(ctx.exception.code, 1)
path = os.path.join(self.wd, 'a.yaml')
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', path))
self.assertEqual(ctx.returncode, 1)
self.assertEqual(ctx.stdout, (
'%s:2:4: [error] trailing spaces (trailing-spaces)\n'
'%s:3:4: [error] no new line character at the end of file '
'(new-line-at-end-of-file)\n') % (file, file))
self.assertEqual(err, '')
'(new-line-at-end-of-file)\n' % (path, path)))
self.assertEqual(ctx.stderr, '')
def test_run_one_ok_file(self):
file = os.path.join(self.wd, 'sub', 'ok.yaml')
def test_run_one_warning(self):
path = os.path.join(self.wd, 'warn.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', path))
self.assertEqual(ctx.returncode, 0)
self.assertEqual(ctx.exception.code, 0)
def test_run_warning_in_strict_mode(self):
path = os.path.join(self.wd, 'warn.yaml')
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertEqual(err, '')
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', '--strict', path))
self.assertEqual(ctx.returncode, 2)
def test_run_one_ok_file(self):
path = os.path.join(self.wd, 'sub', 'ok.yaml')
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', path))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
def test_run_empty_file(self):
file = os.path.join(self.wd, 'empty.yml')
path = os.path.join(self.wd, 'empty.yml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', path))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
self.assertEqual(ctx.exception.code, 0)
@unittest.skipIf(not utf8_available(), 'C.UTF-8 not available')
def test_run_non_ascii_file(self):
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
self.addCleanup(locale.setlocale, locale.LC_ALL, (None, None))
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertEqual(err, '')
path = os.path.join(self.wd, 'non-ascii', 'éçäγλνπ¥', 'utf-8')
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', path))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
def test_run_multiple_files(self):
items = [os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 's')]
file = items[1] + '/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'
path = items[1] + '/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
with RunContext(self) as ctx:
cli.run(['-f', 'parsable'] + items)
self.assertEqual((ctx.returncode, ctx.stderr), (1, ''))
self.assertEqual(ctx.stdout, (
'%s:3:1: [error] duplication of key "key" in mapping '
'(key-duplicates)\n') % path)
self.assertEqual(ctx.exception.code, 1)
def test_run_piped_output_nocolor(self):
path = os.path.join(self.wd, 'a.yaml')
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
'%s:3:1: [error] duplication of key "key" in mapping '
'(key-duplicates)\n') % file)
self.assertEqual(err, '')
with RunContext(self) as ctx:
cli.run((path, ))
self.assertEqual((ctx.returncode, ctx.stderr), (1, ''))
self.assertEqual(ctx.stdout, (
'%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' % path))
def test_run_colored_output(self):
file = os.path.join(self.wd, 'a.yaml')
def test_run_default_format_output_in_tty(self):
path = 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')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run((file, ))
cli.run((path, ))
sys.stdout.flush()
self.assertEqual(ctx.exception.code, 1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
# 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 '
@ -252,5 +523,275 @@ class CommandLineTestCase(unittest.TestCase):
' \033[2m3:4\033[0m \033[31merror\033[0m '
'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, '')
'\n' % path))
def test_run_default_format_output_without_tty(self):
path = os.path.join(self.wd, 'a.yaml')
with RunContext(self) as ctx:
cli.run((path, ))
expected_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' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_auto_output_without_tty_output(self):
path = os.path.join(self.wd, 'a.yaml')
with RunContext(self) as ctx:
cli.run((path, '--format', 'auto'))
expected_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' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_format_colored(self):
path = os.path.join(self.wd, 'a.yaml')
with RunContext(self) as ctx:
cli.run((path, '--format', 'colored'))
expected_out = (
'\033[4m%s\033[0m\n'
' \033[2m2:4\033[0m \033[31merror\033[0m '
'trailing spaces \033[2m(trailing-spaces)\033[0m\n'
' \033[2m3:4\033[0m \033[31merror\033[0m '
'no new line character at the end of file '
'\033[2m(new-line-at-end-of-file)\033[0m\n'
'\n' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_format_colored_warning(self):
path = os.path.join(self.wd, 'warn.yaml')
with RunContext(self) as ctx:
cli.run((path, '--format', 'colored'))
expected_out = (
'\033[4m%s\033[0m\n'
' \033[2m1:1\033[0m \033[33mwarning\033[0m '
'missing document start "---" \033[2m(document-start)\033[0m\n'
'\n' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (0, expected_out, ''))
def test_run_format_github(self):
path = os.path.join(self.wd, 'a.yaml')
with RunContext(self) as ctx:
cli.run((path, '--format', 'github'))
expected_out = (
'::group::%s\n'
'::error file=%s,line=2,col=4::2:4 [trailing-spaces] trailing'
' spaces\n'
'::error file=%s,line=3,col=4::3:4 [new-line-at-end-of-file] no'
' new line character at the end of file\n'
'::endgroup::\n\n'
% (path, path, path))
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_github_actions_detection(self):
path = os.path.join(self.wd, 'a.yaml')
self.addCleanup(os.environ.__delitem__, 'GITHUB_ACTIONS')
self.addCleanup(os.environ.__delitem__, 'GITHUB_WORKFLOW')
with RunContext(self) as ctx:
os.environ['GITHUB_ACTIONS'] = 'something'
os.environ['GITHUB_WORKFLOW'] = 'something'
cli.run((path, ))
expected_out = (
'::group::%s\n'
'::error file=%s,line=2,col=4::2:4 [trailing-spaces] trailing'
' spaces\n'
'::error file=%s,line=3,col=4::3:4 [new-line-at-end-of-file] no'
' new line character at the end of file\n'
'::endgroup::\n\n'
% (path, path, path))
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_read_from_stdin(self):
# prepares stdin with an invalid yaml string so that we can check
# for its specific error, and be assured that stdin was read
self.addCleanup(setattr, sys, 'stdin', sys.__stdin__)
sys.stdin = StringIO(
'I am a string\n'
'therefore: I am an error\n')
with RunContext(self) as ctx:
cli.run(('-', '-f', 'parsable'))
expected_out = (
'stdin:2:10: [error] syntax error: '
'mapping values are not allowed here (syntax)\n')
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_no_warnings(self):
path = os.path.join(self.wd, 'a.yaml')
with RunContext(self) as ctx:
cli.run((path, '--no-warnings', '-f', 'auto'))
expected_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' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
path = os.path.join(self.wd, 'warn.yaml')
with RunContext(self) as ctx:
cli.run((path, '--no-warnings', '-f', 'auto'))
self.assertEqual(ctx.returncode, 0)
def test_run_no_warnings_and_strict(self):
path = os.path.join(self.wd, 'warn.yaml')
with RunContext(self) as ctx:
cli.run((path, '--no-warnings', '-s'))
self.assertEqual(ctx.returncode, 2)
def test_run_non_universal_newline(self):
path = os.path.join(self.wd, 'dos.yml')
with RunContext(self) as ctx:
cli.run(('-d', 'rules:\n new-lines:\n type: dos', path))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
with RunContext(self) as ctx:
cli.run(('-d', 'rules:\n new-lines:\n type: unix', path))
expected_out = (
'%s\n'
' 1:4 error wrong new line character: expected \\n'
' (new-lines)\n'
'\n' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_list_files(self):
with RunContext(self) as ctx:
cli.run(('--list-files', self.wd))
self.assertEqual(ctx.returncode, 0)
self.assertEqual(
sorted(ctx.stdout.splitlines()),
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 'en.yaml'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
config = '{ignore: "*.yml", yaml-files: ["*.*"]}'
with RunContext(self) as ctx:
cli.run(('--list-files', '-d', config, self.wd))
self.assertEqual(ctx.returncode, 0)
self.assertEqual(
sorted(ctx.stdout.splitlines()),
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'en.yaml'),
os.path.join(self.wd, 'no-yaml.json'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/directory.yaml/not-yaml.txt'),
os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
class CommandLineConfigTestCase(unittest.TestCase):
def test_config_file(self):
workspace = {'a.yml': 'hello: world\n'}
conf = ('---\n'
'extends: relaxed\n')
for conf_file in ('.yamllint', '.yamllint.yml', '.yamllint.yaml'):
with self.subTest(conf_file):
with temp_workspace(workspace):
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', '.'))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr),
(0, './a.yml:1:1: [warning] missing document '
'start "---" (document-start)\n', ''))
with temp_workspace({**workspace, **{conf_file: conf}}):
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', '.'))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr),
(0, '', ''))
def test_parent_config_file(self):
workspace = {'a/b/c/d/e/f/g/a.yml': 'hello: world\n'}
conf = ('---\n'
'extends: relaxed\n')
for conf_file in ('.yamllint', '.yamllint.yml', '.yamllint.yaml'):
with self.subTest(conf_file):
with temp_workspace(workspace):
with RunContext(self) as ctx:
os.chdir('a/b/c/d/e/f')
cli.run(('-f', 'parsable', '.'))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr),
(0, './g/a.yml:1:1: [warning] missing '
'document start "---" (document-start)\n',
''))
with temp_workspace({**workspace, **{conf_file: conf}}):
with RunContext(self) as ctx:
os.chdir('a/b/c/d/e/f')
cli.run(('-f', 'parsable', '.'))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr),
(0, '', ''))
def test_multiple_parent_config_file(self):
workspace = {'a/b/c/3spaces.yml': 'array:\n'
' - item\n',
'a/b/c/4spaces.yml': 'array:\n'
' - item\n',
'a/.yamllint': '---\n'
'extends: relaxed\n'
'rules:\n'
' indentation:\n'
' spaces: 4\n',
}
conf3 = ('---\n'
'extends: relaxed\n'
'rules:\n'
' indentation:\n'
' spaces: 3\n')
with temp_workspace(workspace):
with RunContext(self) as ctx:
os.chdir('a/b/c')
cli.run(('-f', 'parsable', '.'))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr),
(0, './3spaces.yml:2:4: [warning] wrong indentation: '
'expected 4 but found 3 (indentation)\n', ''))
with temp_workspace({**workspace, **{'a/b/.yamllint.yml': conf3}}):
with RunContext(self) as ctx:
os.chdir('a/b/c')
cli.run(('-f', 'parsable', '.'))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr),
(0, './4spaces.yml:2:5: [warning] wrong indentation: '
'expected 3 but found 4 (indentation)\n', ''))

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -14,8 +13,17 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from io import StringIO
import os
import shutil
import sys
import tempfile
import unittest
from tests.common import build_temp_workspace
from yamllint.config import YamlLintConfigError
from yamllint import cli
from yamllint import config
@ -30,30 +38,33 @@ 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):
config.YamlLintConfig('not: valid: yaml')
def test_unknown_rule(self):
with self.assertRaisesRegexp(
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: no such rule: "this-one-does-not-exist"'):
config.YamlLintConfig('rules:\n'
' this-one-does-not-exist: enable\n')
def test_missing_option(self):
with self.assertRaisesRegexp(
config.YamlLintConfigError,
'invalid config: missing option "max-spaces-before" '
'for rule "colons"'):
config.YamlLintConfig('rules:\n'
c = config.YamlLintConfig('rules:\n'
' colons: enable\n')
self.assertEqual(c.rules['colons']['max-spaces-before'], 0)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
c = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-after: 1\n')
' max-spaces-before: 9\n')
self.assertEqual(c.rules['colons']['max-spaces-before'], 9)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
def test_unknown_option(self):
with self.assertRaisesRegexp(
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: unknown option "abcdef" for rule "colons"'):
config.YamlLintConfig('rules:\n'
@ -62,17 +73,61 @@ 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.assertTrue(c.rules['indentation']['indent-sequences'])
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.assertTrue(c.rules['indentation']['indent-sequences'])
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.assertRaisesRegex(
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_enable_disable_keywords(self):
c = config.YamlLintConfig('rules:\n'
' colons: enable\n'
' hyphens: disable\n')
self.assertEqual(c.rules['colons'], {'level': 'error',
'max-spaces-after': 1,
'max-spaces-before': 0})
self.assertEqual(c.rules['hyphens'], False)
def test_validate_rule_conf(self):
class Rule(object):
class Rule:
ID = 'fake'
self.assertEqual(config.validate_rule_conf(Rule, False), False)
self.assertEqual(config.validate_rule_conf(Rule, 'disable'), False)
self.assertFalse(config.validate_rule_conf(Rule, False))
self.assertEqual(config.validate_rule_conf(Rule, {}),
{'level': 'error'})
self.assertEqual(config.validate_rule_conf(Rule, 'enable'),
{'level': 'error'})
config.validate_rule_conf(Rule, {'level': 'error'})
config.validate_rule_conf(Rule, {'level': 'warning'})
@ -80,22 +135,22 @@ class SimpleConfigTestCase(unittest.TestCase):
config.validate_rule_conf, Rule, {'level': 'warn'})
Rule.CONF = {'length': int}
Rule.DEFAULT = {'length': 80}
config.validate_rule_conf(Rule, {'length': 8})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {})
config.validate_rule_conf(Rule, {})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'height': 8})
Rule.CONF = {'a': bool, 'b': int}
Rule.DEFAULT = {'a': True, 'b': -42}
config.validate_rule_conf(Rule, {'a': True, 'b': 0})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'a': True})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'b': 0})
config.validate_rule_conf(Rule, {'a': True})
config.validate_rule_conf(Rule, {'b': 0})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'a': 1, 'b': 0})
Rule.CONF = {'choice': (True, 88, 'str')}
Rule.DEFAULT = {'choice': 88}
config.validate_rule_conf(Rule, {'choice': True})
config.validate_rule_conf(Rule, {'choice': 88})
config.validate_rule_conf(Rule, {'choice': 'str'})
@ -107,16 +162,72 @@ class SimpleConfigTestCase(unittest.TestCase):
config.validate_rule_conf, Rule, {'choice': 'abc'})
Rule.CONF = {'choice': (int, 'hardcoded')}
Rule.DEFAULT = {'choice': 1337}
config.validate_rule_conf(Rule, {'choice': 42})
config.validate_rule_conf(Rule, {'choice': 'hardcoded'})
config.validate_rule_conf(Rule, {})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': False})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': 'abc'})
Rule.CONF = {'multiple': ['item1', 'item2', 'item3']}
Rule.DEFAULT = {'multiple': ['item1']}
config.validate_rule_conf(Rule, {'multiple': []})
config.validate_rule_conf(Rule, {'multiple': ['item2']})
config.validate_rule_conf(Rule, {'multiple': ['item2', 'item3']})
config.validate_rule_conf(Rule, {})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule,
{'multiple': 'item1'})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule,
{'multiple': ['']})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule,
{'multiple': ['item1', 4]})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule,
{'multiple': ['item4']})
def test_invalid_rule(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: rule "colons": should be either '
'"enable", "disable" or a dict'):
config.YamlLintConfig('rules:\n'
' colons: invalid\n')
def test_invalid_ignore(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: ignore should contain file patterns'):
config.YamlLintConfig('ignore: yes\n')
def test_invalid_rule_ignore(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: ignore should contain file patterns'):
config.YamlLintConfig('rules:\n'
' colons:\n'
' ignore: yes\n')
def test_invalid_locale(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: locale should be a string'):
config.YamlLintConfig('locale: yes\n')
def test_invalid_yaml_files(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: yaml-files should be a list of file '
'patterns'):
config.YamlLintConfig('yaml-files: yes\n')
class ExtendedConfigTestCase(unittest.TestCase):
def test_extend_add_rule(self):
def test_extend_on_object(self):
old = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
@ -131,62 +242,152 @@ 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)
def test_extend_remove_rule(self):
old = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n'
' hyphens:\n'
' max-spaces-after: 2\n')
new = config.YamlLintConfig('rules:\n'
' colons: disable\n')
new.extend(old)
self.assertEqual(len(new.enabled_rules(None)), 2)
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons'], False)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
def test_extend_on_file(self):
with tempfile.NamedTemporaryFile('w') as f:
f.write('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n'
'rules:\n'
' hyphens:\n'
' max-spaces-after: 2\n')
self.assertEqual(len(new.enabled_rules()), 1)
self.assertEqual(sorted(c.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(c.rules['colons']['max-spaces-before'], 0)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
self.assertEqual(c.rules['hyphens']['max-spaces-after'], 2)
def test_extend_edit_rule(self):
old = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n'
' hyphens:\n'
' max-spaces-after: 2\n')
new = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 3\n'
' max-spaces-after: 4\n')
new.extend(old)
self.assertEqual(len(c.enabled_rules(None)), 2)
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons']['max-spaces-before'], 3)
self.assertEqual(new.rules['colons']['max-spaces-after'], 4)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
def test_extend_remove_rule(self):
with tempfile.NamedTemporaryFile('w') as f:
f.write('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n'
' hyphens:\n'
' max-spaces-after: 2\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n'
'rules:\n'
' colons: disable\n')
self.assertEqual(sorted(c.rules.keys()), ['colons', 'hyphens'])
self.assertFalse(c.rules['colons'])
self.assertEqual(c.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(c.enabled_rules(None)), 1)
self.assertEqual(len(new.enabled_rules()), 2)
def test_extend_edit_rule(self):
with tempfile.NamedTemporaryFile('w') as f:
f.write('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n'
' hyphens:\n'
' max-spaces-after: 2\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n'
'rules:\n'
' colons:\n'
' max-spaces-before: 3\n'
' max-spaces-after: 4\n')
self.assertEqual(sorted(c.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(c.rules['colons']['max-spaces-before'], 3)
self.assertEqual(c.rules['colons']['max-spaces-after'], 4)
self.assertEqual(c.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(c.enabled_rules(None)), 2)
def test_extend_reenable_rule(self):
old = config.YamlLintConfig('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n'
' hyphens: disable\n')
new = config.YamlLintConfig('rules:\n'
' hyphens:\n'
' max-spaces-after: 2\n')
new.extend(old)
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons']['max-spaces-before'], 0)
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)
with tempfile.NamedTemporaryFile('w') as f:
f.write('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n'
' hyphens: disable\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n'
'rules:\n'
' hyphens:\n'
' max-spaces-after: 2\n')
self.assertEqual(sorted(c.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(c.rules['colons']['max-spaces-before'], 0)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
self.assertEqual(c.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(c.enabled_rules(None)), 2)
def test_extend_recursive_default_values(self):
with tempfile.NamedTemporaryFile('w') as f:
f.write('rules:\n'
' braces:\n'
' max-spaces-inside: 1248\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n'
'rules:\n'
' braces:\n'
' min-spaces-inside-empty: 2357\n')
self.assertEqual(c.rules['braces']['min-spaces-inside'], 0)
self.assertEqual(c.rules['braces']['max-spaces-inside'], 1248)
self.assertEqual(c.rules['braces']['min-spaces-inside-empty'], 2357)
self.assertEqual(c.rules['braces']['max-spaces-inside-empty'], -1)
with tempfile.NamedTemporaryFile('w') as f:
f.write('rules:\n'
' colons:\n'
' max-spaces-before: 1337\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n'
'rules:\n'
' colons: enable\n')
self.assertEqual(c.rules['colons']['max-spaces-before'], 1337)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
with tempfile.NamedTemporaryFile('w') as f1, \
tempfile.NamedTemporaryFile('w') as f2:
f1.write('rules:\n'
' colons:\n'
' max-spaces-before: 1337\n')
f1.flush()
f2.write('extends: ' + f1.name + '\n'
'rules:\n'
' colons: disable\n')
f2.flush()
c = config.YamlLintConfig('extends: ' + f2.name + '\n'
'rules:\n'
' colons: enable\n')
self.assertEqual(c.rules['colons']['max-spaces-before'], 0)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
def test_extended_ignore_str(self):
with tempfile.NamedTemporaryFile('w') as f:
f.write('ignore: |\n'
' *.template.yaml\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n')
self.assertEqual(c.ignore.match_file('test.template.yaml'), True)
self.assertEqual(c.ignore.match_file('test.yaml'), False)
def test_extended_ignore_list(self):
with tempfile.NamedTemporaryFile('w') as f:
f.write('ignore:\n'
' - "*.template.yaml"\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n')
self.assertEqual(c.ignore.match_file('test.template.yaml'), True)
self.assertEqual(c.ignore.match_file('test.yaml'), False)
class ExtendedLibraryConfigTestCase(unittest.TestCase):
@ -218,6 +419,9 @@ 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])
self.assertEqual(new.rules['empty-lines']['max'], 42)
self.assertEqual(new.rules['empty-lines']['max-start'], 43)
self.assertEqual(new.rules['empty-lines']['max-end'], 44)
def test_extend_config_override_rule_partly(self):
old = config.YamlLintConfig('extends: default')
@ -231,3 +435,329 @@ 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])
self.assertEqual(new.rules['empty-lines']['max'], 2)
self.assertEqual(new.rules['empty-lines']['max-start'], 42)
self.assertEqual(new.rules['empty-lines']['max-end'], 0)
class IgnoreConfigTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
super().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,
})
cls.backup_wd = os.getcwd()
os.chdir(cls.wd)
@classmethod
def tearDownClass(cls):
super().tearDownClass()
os.chdir(cls.backup_wd)
shutil.rmtree(cls.wd)
def test_mutually_exclusive_ignore_keys(self):
self.assertRaises(
YamlLintConfigError,
config.YamlLintConfig, 'extends: default\n'
'ignore-from-file: .gitignore\n'
'ignore: |\n'
' *.dont-lint-me.yaml\n'
' /bin/\n')
def test_ignore_from_file_not_exist(self):
self.assertRaises(
FileNotFoundError,
config.YamlLintConfig, 'extends: default\n'
'ignore-from-file: not_found_file\n')
def test_ignore_from_file_incorrect_type(self):
self.assertRaises(
YamlLintConfigError,
config.YamlLintConfig, 'extends: default\n'
'ignore-from-file: 0\n')
self.assertRaises(
YamlLintConfigError,
config.YamlLintConfig, 'extends: default\n'
'ignore-from-file: [0]\n')
def test_no_ignore(self):
sys.stdout = StringIO()
with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.'))
out = sys.stdout.getvalue()
out = '\n'.join(sorted(out.splitlines()))
keydup = '[error] duplication of key "key" in mapping (key-duplicates)'
trailing = '[error] trailing spaces (trailing-spaces)'
hyphen = '[error] too many spaces after hyphen (hyphens)'
self.assertEqual(out, '\n'.join((
'./bin/file.lint-me-anyway.yaml:3:3: ' + keydup,
'./bin/file.lint-me-anyway.yaml:4:17: ' + trailing,
'./bin/file.lint-me-anyway.yaml:5:5: ' + hyphen,
'./bin/file.yaml:3:3: ' + keydup,
'./bin/file.yaml:4:17: ' + trailing,
'./bin/file.yaml:5:5: ' + hyphen,
'./file-at-root.yaml:3:3: ' + keydup,
'./file-at-root.yaml:4:17: ' + trailing,
'./file-at-root.yaml:5:5: ' + hyphen,
'./file.dont-lint-me.yaml:3:3: ' + keydup,
'./file.dont-lint-me.yaml:4:17: ' + trailing,
'./file.dont-lint-me.yaml:5:5: ' + hyphen,
'./ign-dup/file.yaml:3:3: ' + keydup,
'./ign-dup/file.yaml:4:17: ' + trailing,
'./ign-dup/file.yaml:5:5: ' + hyphen,
'./ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./ign-trail/file.yaml:3:3: ' + keydup,
'./ign-trail/file.yaml:4:17: ' + trailing,
'./ign-trail/file.yaml:5:5: ' + hyphen,
'./include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/file.yaml:4:17: ' + trailing,
'./s/s/ign-trail/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
)))
def test_run_with_ignore_str(self):
with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
f.write('extends: default\n'
'ignore: |\n'
' *.dont-lint-me.yaml\n'
' /bin/\n'
' !/bin/*.lint-me-anyway.yaml\n'
'rules:\n'
' key-duplicates:\n'
' ignore: |\n'
' /ign-dup\n'
' trailing-spaces:\n'
' ignore: |\n'
' ign-trail\n'
' !*.lint-me-anyway.yaml\n')
sys.stdout = StringIO()
with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.'))
out = sys.stdout.getvalue()
out = '\n'.join(sorted(out.splitlines()))
docstart = '[warning] missing document start "---" (document-start)'
keydup = '[error] duplication of key "key" in mapping (key-duplicates)'
trailing = '[error] trailing spaces (trailing-spaces)'
hyphen = '[error] too many spaces after hyphen (hyphens)'
self.assertEqual(out, '\n'.join((
'./.yamllint:1:1: ' + docstart,
'./bin/file.lint-me-anyway.yaml:3:3: ' + keydup,
'./bin/file.lint-me-anyway.yaml:4:17: ' + trailing,
'./bin/file.lint-me-anyway.yaml:5:5: ' + hyphen,
'./file-at-root.yaml:3:3: ' + keydup,
'./file-at-root.yaml:4:17: ' + trailing,
'./file-at-root.yaml:5:5: ' + hyphen,
'./ign-dup/file.yaml:4:17: ' + trailing,
'./ign-dup/file.yaml:5:5: ' + hyphen,
'./ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./ign-trail/file.yaml:3:3: ' + keydup,
'./ign-trail/file.yaml:5:5: ' + hyphen,
'./include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
)))
def test_run_with_ignore_list(self):
with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
f.write('extends: default\n'
'ignore:\n'
' - "*.dont-lint-me.yaml"\n'
' - "/bin/"\n'
' - "!/bin/*.lint-me-anyway.yaml"\n'
'rules:\n'
' key-duplicates:\n'
' ignore:\n'
' - "/ign-dup"\n'
' trailing-spaces:\n'
' ignore:\n'
' - "ign-trail"\n'
' - "!*.lint-me-anyway.yaml"\n')
sys.stdout = StringIO()
with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.'))
out = sys.stdout.getvalue()
out = '\n'.join(sorted(out.splitlines()))
docstart = '[warning] missing document start "---" (document-start)'
keydup = '[error] duplication of key "key" in mapping (key-duplicates)'
trailing = '[error] trailing spaces (trailing-spaces)'
hyphen = '[error] too many spaces after hyphen (hyphens)'
self.assertEqual(out, '\n'.join((
'./.yamllint:1:1: ' + docstart,
'./bin/file.lint-me-anyway.yaml:3:3: ' + keydup,
'./bin/file.lint-me-anyway.yaml:4:17: ' + trailing,
'./bin/file.lint-me-anyway.yaml:5:5: ' + hyphen,
'./file-at-root.yaml:3:3: ' + keydup,
'./file-at-root.yaml:4:17: ' + trailing,
'./file-at-root.yaml:5:5: ' + hyphen,
'./ign-dup/file.yaml:4:17: ' + trailing,
'./ign-dup/file.yaml:5:5: ' + hyphen,
'./ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./ign-trail/file.yaml:3:3: ' + keydup,
'./ign-trail/file.yaml:5:5: ' + hyphen,
'./include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
)))
def test_run_with_ignore_from_file(self):
with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
f.write('extends: default\n'
'ignore-from-file: .gitignore\n')
with open(os.path.join(self.wd, '.gitignore'), 'w') as f:
f.write('*.dont-lint-me.yaml\n'
'/bin/\n'
'!/bin/*.lint-me-anyway.yaml\n')
sys.stdout = StringIO()
with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.'))
out = sys.stdout.getvalue()
out = '\n'.join(sorted(out.splitlines()))
docstart = '[warning] missing document start "---" (document-start)'
keydup = '[error] duplication of key "key" in mapping (key-duplicates)'
trailing = '[error] trailing spaces (trailing-spaces)'
hyphen = '[error] too many spaces after hyphen (hyphens)'
self.assertEqual(out, '\n'.join((
'./.yamllint:1:1: ' + docstart,
'./bin/file.lint-me-anyway.yaml:3:3: ' + keydup,
'./bin/file.lint-me-anyway.yaml:4:17: ' + trailing,
'./bin/file.lint-me-anyway.yaml:5:5: ' + hyphen,
'./file-at-root.yaml:3:3: ' + keydup,
'./file-at-root.yaml:4:17: ' + trailing,
'./file-at-root.yaml:5:5: ' + hyphen,
'./ign-dup/file.yaml:3:3: ' + keydup,
'./ign-dup/file.yaml:4:17: ' + trailing,
'./ign-dup/file.yaml:5:5: ' + hyphen,
'./ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./ign-trail/file.yaml:3:3: ' + keydup,
'./ign-trail/file.yaml:4:17: ' + trailing,
'./ign-trail/file.yaml:5:5: ' + hyphen,
'./include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/file.yaml:4:17: ' + trailing,
'./s/s/ign-trail/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
)))
def test_run_with_ignored_from_file(self):
with open(os.path.join(self.wd, '.yamllint'), 'w') as f:
f.write('ignore-from-file: [.gitignore, .yamlignore]\n'
'extends: default\n')
with open(os.path.join(self.wd, '.gitignore'), 'w') as f:
f.write('*.dont-lint-me.yaml\n'
'/bin/\n')
with open(os.path.join(self.wd, '.yamlignore'), 'w') as f:
f.write('!/bin/*.lint-me-anyway.yaml\n')
sys.stdout = StringIO()
with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.'))
out = sys.stdout.getvalue()
out = '\n'.join(sorted(out.splitlines()))
docstart = '[warning] missing document start "---" (document-start)'
keydup = '[error] duplication of key "key" in mapping (key-duplicates)'
trailing = '[error] trailing spaces (trailing-spaces)'
hyphen = '[error] too many spaces after hyphen (hyphens)'
self.assertEqual(out, '\n'.join((
'./.yamllint:1:1: ' + docstart,
'./bin/file.lint-me-anyway.yaml:3:3: ' + keydup,
'./bin/file.lint-me-anyway.yaml:4:17: ' + trailing,
'./bin/file.lint-me-anyway.yaml:5:5: ' + hyphen,
'./file-at-root.yaml:3:3: ' + keydup,
'./file-at-root.yaml:4:17: ' + trailing,
'./file-at-root.yaml:5:5: ' + hyphen,
'./ign-dup/file.yaml:3:3: ' + keydup,
'./ign-dup/file.yaml:4:17: ' + trailing,
'./ign-dup/file.yaml:5:5: ' + hyphen,
'./ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./ign-trail/file.yaml:3:3: ' + keydup,
'./ign-trail/file.yaml:4:17: ' + trailing,
'./ign-trail/file.yaml:5:5: ' + hyphen,
'./include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/file.yaml:4:17: ' + trailing,
'./s/s/ign-trail/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
)))

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -15,7 +14,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import io
import unittest
from yamllint.config import YamlLintConfig
@ -33,10 +31,10 @@ class LinterTestCase(unittest.TestCase):
linter.run(b'test: document', self.fake_config())
def test_run_on_unicode(self):
linter.run(u'test: document', self.fake_config())
linter.run('test: document', self.fake_config())
def test_run_on_stream(self):
linter.run(io.StringIO(u'hello'), self.fake_config())
linter.run(io.StringIO('hello'), self.fake_config())
def test_run_on_int(self):
self.assertRaises(TypeError, linter.run, 42, self.fake_config())
@ -44,3 +42,25 @@ 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 = ('- hétérogénéité\n'
'# 19.99 €\n')
linter.run(s, self.fake_config())
linter.run(s.encode('utf-8'), self.fake_config())
linter.run(s.encode('iso-8859-15'), self.fake_config())
s = ('- お早う御座います。\n'
'# الأَبْجَدِيَّة العَرَبِيَّة\n')
linter.run(s, self.fake_config())
linter.run(s.encode('utf-8'), self.fake_config())
def test_linter_problem_repr_without_rule(self):
problem = linter.LintProblem(1, 2, 'problem')
self.assertEqual(str(problem), '1:2: problem')
def test_linter_problem_repr_with_rule(self):
problem = linter.LintProblem(1, 2, 'problem', 'rule-id')
self.assertEqual(str(problem), '1:2: problem (rule-id)')

@ -0,0 +1,84 @@
# 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
import unittest
PYTHON = sys.executable or 'python'
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.assertRegex(ctx.exception.output.decode(), r'^usage: yamllint')
def test_run_module_on_bad_dir(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output([PYTHON, '-m', 'yamllint',
'/does/not/exist'],
stderr=subprocess.STDOUT)
self.assertRegex(ctx.exception.output.decode(),
r'No such file or directory')
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])

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -18,8 +17,9 @@ import unittest
import yaml
from yamllint.parser import (line_generator, token_generator,
token_or_line_generator, Line, Token)
from yamllint.parser import (line_generator, token_or_comment_generator,
token_or_comment_or_line_generator,
Line, Token, Comment)
class ParserTestCase(unittest.TestCase):
@ -61,26 +61,84 @@ class ParserTestCase(unittest.TestCase):
self.assertEqual(e[2].line_no, 3)
self.assertEqual(e[2].content, 'at the end')
def test_token_generator(self):
e = list(token_generator(''))
def test_token_or_comment_generator(self):
e = list(token_or_comment_generator(''))
self.assertEqual(len(e), 2)
self.assertEqual(e[0].prev, None)
self.assertIsNone(e[0].prev)
self.assertIsInstance(e[0].curr, yaml.Token)
self.assertIsInstance(e[0].next, yaml.Token)
self.assertEqual(e[1].prev, e[0].curr)
self.assertEqual(e[1].curr, e[0].next)
self.assertEqual(e[1].next, None)
self.assertIsNone(e[1].next)
e = list(token_generator('---\n'
'k: v\n'))
e = list(token_or_comment_generator('---\n'
'k: v\n'))
self.assertEqual(len(e), 9)
self.assertIsInstance(e[3].curr, yaml.KeyToken)
self.assertIsInstance(e[5].curr, yaml.ValueToken)
def test_token_or_line_generator(self):
e = list(token_or_line_generator('---\n'
'k: v\n'))
self.assertEqual(len(e), 12)
e = list(token_or_comment_generator('# start comment\n'
'- a\n'
'- key: val # key=val\n'
'# this is\n'
'# a block \n'
'# comment\n'
'- c\n'
'# end comment\n'))
self.assertEqual(len(e), 21)
self.assertIsInstance(e[1], Comment)
self.assertEqual(e[1], Comment(1, 1, '# start comment', 0))
self.assertEqual(e[11], Comment(3, 13, '# key=val', 0))
self.assertEqual(e[12], Comment(4, 1, '# this is', 0))
self.assertEqual(e[13], Comment(5, 1, '# a block ', 0))
self.assertEqual(e[14], Comment(6, 1, '# comment', 0))
self.assertEqual(e[18], Comment(8, 1, '# end comment', 0))
e = list(token_or_comment_generator('---\n'
'# no newline char'))
self.assertEqual(e[2], Comment(2, 1, '# no newline char', 0))
e = list(token_or_comment_generator('# just comment'))
self.assertEqual(e[1], Comment(1, 1, '# just comment', 0))
e = list(token_or_comment_generator('\n'
' # indented comment\n'))
self.assertEqual(e[1], Comment(2, 4, '# indented comment', 0))
e = list(token_or_comment_generator('\n'
'# trailing spaces \n'))
self.assertEqual(e[1], Comment(2, 1, '# trailing spaces ', 0))
e = [c for c in
token_or_comment_generator('# block\n'
'# comment\n'
'- data # inline comment\n'
'# block\n'
'# comment\n'
'- k: v # inline comment\n'
'- [ l, ist\n'
'] # inline comment\n'
'- { m: ap\n'
'} # inline comment\n'
'# block comment\n'
'- data # inline comment\n')
if isinstance(c, Comment)]
self.assertEqual(len(e), 10)
self.assertFalse(e[0].is_inline())
self.assertFalse(e[1].is_inline())
self.assertTrue(e[2].is_inline())
self.assertFalse(e[3].is_inline())
self.assertFalse(e[4].is_inline())
self.assertTrue(e[5].is_inline())
self.assertTrue(e[6].is_inline())
self.assertTrue(e[7].is_inline())
self.assertFalse(e[8].is_inline())
self.assertTrue(e[9].is_inline())
def test_token_or_comment_or_line_generator(self):
e = list(token_or_comment_or_line_generator('---\n'
'k: v # k=v\n'))
self.assertEqual(len(e), 13)
self.assertIsInstance(e[0], Token)
self.assertIsInstance(e[0].curr, yaml.StreamStartToken)
self.assertIsInstance(e[1], Token)
@ -89,5 +147,6 @@ class ParserTestCase(unittest.TestCase):
self.assertIsInstance(e[3].curr, yaml.BlockMappingStartToken)
self.assertIsInstance(e[4].curr, yaml.KeyToken)
self.assertIsInstance(e[6].curr, yaml.ValueToken)
self.assertIsInstance(e[8], Line)
self.assertIsInstance(e[11], Line)
self.assertIsInstance(e[8], Comment)
self.assertIsInstance(e[9], Line)
self.assertIsInstance(e[12], Line)

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -14,7 +13,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from io import open
import os
from tests.common import RuleTestCase
@ -48,13 +46,14 @@ 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'
'brackets: {min-spaces-inside: 1, max-spaces-inside: 1}\n')
conf_overrides = {
'example-2.2': ('colons: {max-spaces-after: 2}\n'),
'example-2.4': ('colons: {max-spaces-after: 3}\n'),
'example-2.2': 'colons: {max-spaces-after: 2}\n',
'example-2.4': 'colons: {max-spaces-after: 3}\n',
'example-2.5': ('empty-lines: {max-end: 2}\n'
'brackets: {min-spaces-inside: 0, max-spaces-inside: 2}\n'
'commas: {max-spaces-before: -1}\n'),
@ -62,63 +61,65 @@ conf_overrides = {
'indentation: disable\n'),
'example-2.12': ('empty-lines: {max-end: 1}\n'
'colons: {max-spaces-before: -1}\n'),
'example-2.16': ('empty-lines: {max-end: 1}\n'),
'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-2.16': 'empty-lines: {max-end: 1}\n',
'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: false}\n'
'colons: {max-spaces-before: 1}\n'),
'example-6.4': ('trailing-spaces: disable\n'),
'example-6.5': ('trailing-spaces: disable\n'),
'example-6.6': ('trailing-spaces: disable\n'),
'example-6.7': ('trailing-spaces: disable\n'),
'example-6.8': ('trailing-spaces: disable\n'),
'example-6.4': 'trailing-spaces: disable\n',
'example-6.5': 'trailing-spaces: disable\n',
'example-6.6': 'trailing-spaces: disable\n',
'example-6.7': 'trailing-spaces: disable\n',
'example-6.8': 'trailing-spaces: disable\n',
'example-6.10': ('empty-lines: {max-end: 2}\n'
'trailing-spaces: disable\n'
'comments-indentation: disable\n'),
'example-6.11': ('empty-lines: {max-end: 1}\n'
'comments-indentation: disable\n'),
'example-6.13': ('comments-indentation: disable\n'),
'example-6.14': ('comments-indentation: disable\n'),
'example-6.23': ('colons: {max-spaces-before: 1}\n'),
'example-6.13': 'comments-indentation: disable\n',
'example-6.14': 'comments-indentation: disable\n',
'example-6.23': 'colons: {max-spaces-before: 1}\n',
'example-7.4': ('colons: {max-spaces-before: 1}\n'
'indentation: disable\n'),
'example-7.5': ('trailing-spaces: disable\n'),
'example-7.6': ('trailing-spaces: disable\n'),
'example-7.7': ('indentation: disable\n'),
'example-7.5': 'trailing-spaces: disable\n',
'example-7.6': 'trailing-spaces: disable\n',
'example-7.7': 'indentation: disable\n',
'example-7.8': ('colons: {max-spaces-before: 1}\n'
'indentation: disable\n'),
'example-7.9': ('trailing-spaces: disable\n'),
'example-7.9': 'trailing-spaces: disable\n',
'example-7.11': ('colons: {max-spaces-before: 1}\n'
'indentation: disable\n'),
'example-7.13': ('brackets: {min-spaces-inside: 0, max-spaces-inside: 1}\n'
'commas: {max-spaces-before: 1, min-spaces-after: 0}\n'),
'example-7.14': ('indentation: disable\n'),
'example-7.14': 'indentation: disable\n',
'example-7.15': ('braces: {min-spaces-inside: 0, max-spaces-inside: 1}\n'
'commas: {max-spaces-before: 1, min-spaces-after: 0}\n'
'colons: {max-spaces-before: 1}\n'),
'example-7.16': ('indentation: disable\n'),
'example-7.17': ('indentation: disable\n'),
'example-7.18': ('indentation: disable\n'),
'example-7.19': ('indentation: disable\n'),
'example-7.16': 'indentation: disable\n',
'example-7.17': 'indentation: disable\n',
'example-7.18': 'indentation: disable\n',
'example-7.19': 'indentation: disable\n',
'example-7.20': ('colons: {max-spaces-before: 1}\n'
'indentation: disable\n'),
'example-8.1': ('empty-lines: {max-end: 1}\n'),
'example-8.2': ('trailing-spaces: disable\n'),
'example-8.1': 'empty-lines: {max-end: 1}\n',
'example-8.2': 'trailing-spaces: disable\n',
'example-8.5': ('comments-indentation: disable\n'
'trailing-spaces: disable\n'),
'example-8.6': ('empty-lines: {max-end: 1}\n'),
'example-8.7': ('empty-lines: {max-end: 1}\n'),
'example-8.8': ('trailing-spaces: disable\n'),
'example-8.9': ('empty-lines: {max-end: 1}\n'),
'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.6': 'empty-lines: {max-end: 1}\n',
'example-8.7': 'empty-lines: {max-end: 1}\n',
'example-8.8': 'trailing-spaces: disable\n',
'example-8.9': 'empty-lines: {max-end: 1}\n',
'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: 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-8.22': 'indentation: disable\n',
'example-10.1': 'colons: {max-spaces-before: 2}\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 +132,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()).

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

@ -0,0 +1,432 @@
# 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 YamllintDirectivesTestCase(RuleTestCase):
conf = ('commas: disable\n'
'trailing-spaces: {}\n'
'colons: {max-spaces-before: 1}\n')
def test_disable_directive(self):
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(4, 8, 'colons'),
problem3=(6, 7, 'colons'),
problem4=(6, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'# yamllint disable\n'
'- bad : colon\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem=(3, 18, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'# yamllint disable\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'# yamllint enable\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(8, 7, 'colons'),
problem2=(8, 26, 'trailing-spaces'))
def test_disable_directive_with_rules(self):
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'# yamllint disable rule:trailing-spaces\n'
'- bad : colon\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(5, 8, 'colons'),
problem3=(7, 7, 'colons'))
self.check('---\n'
'- [valid , YAML]\n'
'# yamllint disable rule:trailing-spaces\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'# yamllint enable rule:trailing-spaces\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(5, 8, 'colons'),
problem2=(8, 7, 'colons'),
problem3=(8, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'# yamllint disable rule:trailing-spaces\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'# yamllint enable\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(5, 8, 'colons'),
problem2=(8, 7, 'colons'),
problem3=(8, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'# yamllint disable\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'# yamllint enable rule:trailing-spaces\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem=(8, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'# yamllint disable rule:colons\n'
'- trailing spaces \n'
'# yamllint disable rule:trailing-spaces\n'
'- bad : colon\n'
'- [valid , YAML]\n'
'# yamllint enable rule:colons\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(4, 18, 'trailing-spaces'),
problem2=(9, 7, 'colons'))
def test_disable_line_directive(self):
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'# yamllint disable-line\n'
'- bad : colon\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(7, 7, 'colons'),
problem3=(7, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'- bad : colon # yamllint disable-line\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(6, 7, 'colons'),
problem3=(6, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML] # yamllint disable-line\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(4, 8, 'colons'),
problem3=(6, 7, 'colons'),
problem4=(6, 26, 'trailing-spaces'))
def test_disable_line_directive_with_rules(self):
self.check('---\n'
'- [valid , YAML]\n'
'# yamllint disable-line rule:colons\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(4, 18, 'trailing-spaces'),
problem2=(5, 8, 'colons'),
problem3=(7, 7, 'colons'),
problem4=(7, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces # yamllint disable-line rule:colons \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 55, 'trailing-spaces'),
problem2=(4, 8, 'colons'),
problem3=(6, 7, 'colons'),
problem4=(6, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'# yamllint disable-line rule:colons\n'
'- bad : colon\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(7, 7, 'colons'),
problem3=(7, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'- bad : colon # yamllint disable-line rule:colons\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(6, 7, 'colons'),
problem3=(6, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'# yamllint disable-line rule:colons\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(4, 8, 'colons'),
problem3=(7, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'# yamllint disable-line rule:colons rule:trailing-spaces\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(4, 8, 'colons'))
def test_disable_directive_with_rules_and_dos_lines(self):
conf = self.conf + 'new-lines: {type: dos}\n'
self.check('---\r\n'
'- [valid , YAML]\r\n'
'# yamllint disable rule:trailing-spaces\r\n'
'- trailing spaces \r\n'
'- bad : colon\r\n'
'- [valid , YAML]\r\n'
'# yamllint enable rule:trailing-spaces\r\n'
'- bad : colon and spaces \r\n'
'- [valid , YAML]\r\n',
conf,
problem1=(5, 8, 'colons'),
problem2=(8, 7, 'colons'),
problem3=(8, 26, 'trailing-spaces'))
self.check('---\r\n'
'- [valid , YAML]\r\n'
'- trailing spaces \r\n'
'- bad : colon\r\n'
'- [valid , YAML]\r\n'
'# yamllint disable-line rule:colons\r\n'
'- bad : colon and spaces \r\n'
'- [valid , YAML]\r\n',
conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(4, 8, 'colons'),
problem3=(7, 26, 'trailing-spaces'))
def test_directive_on_last_line(self):
conf = 'new-line-at-end-of-file: {}'
self.check('---\n'
'no new line',
conf,
problem=(2, 12, 'new-line-at-end-of-file'))
self.check('---\n'
'# yamllint disable\n'
'no new line',
conf)
self.check('---\n'
'no new line # yamllint disable',
conf)
def test_indented_directive(self):
conf = 'brackets: {min-spaces-inside: 0, max-spaces-inside: 0}'
self.check('---\n'
'- a: 1\n'
' b:\n'
' c: [ x]\n',
conf,
problem=(4, 12, 'brackets'))
self.check('---\n'
'- a: 1\n'
' b:\n'
' # yamllint disable-line rule:brackets\n'
' c: [ x]\n',
conf)
def test_directive_on_itself(self):
conf = ('comments: {min-spaces-from-content: 2}\n'
'comments-indentation: {}\n')
self.check('---\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf,
problem1=(2, 8, 'comments'),
problem2=(4, 2, 'comments-indentation'))
self.check('---\n'
'# yamllint disable\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf)
self.check('---\n'
'- a: 1 # yamllint disable-line\n'
' b:\n'
' # yamllint disable-line\n'
' # wrong indentation\n'
' c: [x]\n',
conf)
self.check('---\n'
'- a: 1 # yamllint disable-line rule:comments\n'
' b:\n'
' # yamllint disable-line rule:comments-indentation\n'
' # wrong indentation\n'
' c: [x]\n',
conf)
self.check('---\n'
'# yamllint disable\n'
'- a: 1 # comment too close\n'
' # yamllint enable rule:comments-indentation\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf,
problem=(6, 2, 'comments-indentation'))
def test_disable_file_directive(self):
conf = ('comments: {min-spaces-from-content: 2}\n'
'comments-indentation: {}\n')
self.check('# yamllint disable-file\n'
'---\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf)
self.check('# yamllint disable-file\n'
'---\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf)
self.check('#yamllint disable-file\n'
'---\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf)
self.check('#yamllint disable-file \n'
'---\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf)
self.check('---\n'
'# yamllint disable-file\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf,
problem1=(3, 8, 'comments'),
problem2=(5, 2, 'comments-indentation'))
self.check('# yamllint disable-file: rules cannot be specified\n'
'---\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf,
problem1=(3, 8, 'comments'),
problem2=(5, 2, 'comments-indentation'))
self.check('AAAA yamllint disable-file\n'
'---\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf,
problem1=(1, 1, 'document-start'),
problem2=(3, 8, 'comments'),
problem3=(5, 2, 'comments-indentation'))
def test_disable_file_directive_not_at_first_position(self):
self.check('# yamllint disable-file\n'
'---\n'
'- bad : colon and spaces \n',
self.conf)
self.check('---\n'
'# yamllint disable-file\n'
'- bad : colon and spaces \n',
self.conf,
problem1=(3, 7, 'colons'),
problem2=(3, 26, 'trailing-spaces'))
def test_disable_file_directive_with_syntax_error(self):
self.check('# This file is not valid YAML (it is a Jinja template)\n'
'{% if extra_info %}\n'
'key1: value1\n'
'{% endif %}\n'
'key2: value2\n',
self.conf,
problem=(2, 2, 'syntax'))
self.check('# yamllint disable-file\n'
'# This file is not valid YAML (it is a Jinja template)\n'
'{% if extra_info %}\n'
'key1: value1\n'
'{% endif %}\n'
'key2: value2\n',
self.conf)
def test_disable_file_directive_with_dos_lines(self):
self.check('# yamllint disable-file\r\n'
'---\r\n'
'- bad : colon and spaces \r\n',
self.conf)
self.check('# yamllint disable-file\r\n'
'# This file is not valid YAML (it is a Jinja template)\r\n'
'{% if extra_info %}\r\n'
'key1: value1\r\n'
'{% endif %}\r\n'
'key2: value2\r\n',
self.conf)

@ -1,50 +0,0 @@
#!/bin/bash
set -euf
DIR=$(dirname "$(readlink -f $0)")
fix_one_problem_in_file() {
local filename=$1
local error
error=$(yamllint -f parsable "$filename" | grep 'wrong indentation: expected' \
| head -n 1)
if [ -z "$error" ]; then
return 1
fi
local line=$(echo $error | cut -d: -f2)
local expected=$(echo $error | cut -d: -f5 | sed 's/.* expected //;s/ but found.*//')
local found=$(echo $error | cut -d: -f5 | sed 's/.*but found //;s/(inde.*//')
"$DIR/yaml-remove-indentation" "$filename" $line $expected $found
return 0
}
reformat_yaml() {
local in=$1
local out=$2
python -c 'import sys, yaml; yaml.dump(yaml.load(sys.stdin), sys.stdout)' <"$in" >"$out"
}
fix_one_file() {
local filename=$1
local backup=$(mktemp originalXXXXX)
cp "$filename" "$backup"
echo "FIXING $file"
while fix_one_problem_in_file "$filename"; do continue; done
echo "CHECKING $file"
local tmp_old=$(mktemp oldXXXXX)
local tmp_new=$(mktemp newXXXXX)
reformat_yaml "$backup" "$tmp_old"
reformat_yaml "$filename" "$tmp_new"
if ! diff -q "$tmp_old" "$tmp_new" &>/dev/null; then
echo "error: after reformating, the file contents is detected different."
echo "diff $backup $filename"
echo "diff $tmp_old $tmp_new"
exit 1
fi
rm "$backup" "$tmp_old" "$tmp_new"
}
for file in "$@"; do
fix_one_file "$file"
done

@ -1,37 +0,0 @@
#!/usr/bin/env python3
import sys
file = sys.argv[1]
line = int(sys.argv[2]) - 1
indent_expected = int(sys.argv[3])
indent_found = int(sys.argv[4])
with open(file) as f:
lines = f.readlines()
before = lines[:line]
is_a_list = lines[line].strip()[0] == '-'
i = line
while (i < len(lines) and
(lines[i].strip() == '' or
(not is_a_list and lines[i].startswith(indent_found * ' ')) or
(is_a_list and (lines[i].startswith(indent_found * ' ' + '-') or
lines[i].startswith(indent_found * ' ' + ' '))))):
i += 1
contents = lines[line:i]
after = lines[i:]
new_contents = []
for line in contents:
if line.strip() != '':
line = (indent_expected * ' ') + line[indent_found:]
new_contents.append(line)
with open(file, 'w') as f:
f.write(''.join(before))
f.write(''.join(new_contents))
f.write(''.join(after))

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

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

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -14,29 +13,40 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function
import os.path
import sys
import argparse
import locale
import os
import platform
import sys
from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION
from yamllint.config import YamlLintConfig, YamlLintConfigError
from yamllint import linter
from yamllint.config import YamlLintConfig, YamlLintConfigError
from yamllint.linter import PROBLEM_LEVELS
def find_files_recursively(items):
def find_files_recursively(items, conf):
for item in items:
if os.path.isdir(item):
for root, dirnames, filenames in os.walk(item):
for filename in [f for f in filenames
if f.endswith(('.yml', '.yaml'))]:
yield os.path.join(root, filename)
for f in filenames:
filepath = os.path.join(root, f)
if conf.is_yaml_file(filepath):
yield filepath
else:
yield item
class Format(object):
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:
@staticmethod
def parsable(problem, filename):
return ('%(file)s:%(line)s:%(column)s: [%(level)s] %(message)s' %
@ -48,6 +58,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':
@ -60,32 +81,124 @@ class Format(object):
line += ' \033[2m(%s)\033[0m' % problem.rule
return line
@staticmethod
def github(problem, filename):
line = '::'
line += problem.level
line += ' file=' + filename + ','
line += 'line=' + format(problem.line) + ','
line += 'col=' + format(problem.column)
line += '::'
line += format(problem.line)
line += ':'
line += format(problem.column)
line += ' '
if problem.rule:
line += '[' + problem.rule + '] '
line += problem.desc
return line
def show_problems(problems, file, args_format, no_warn):
max_level = 0
first = True
if args_format == 'auto':
if ('GITHUB_ACTIONS' in os.environ and
'GITHUB_WORKFLOW' in os.environ):
args_format = 'github'
elif supports_color():
args_format = 'colored'
for problem in problems:
max_level = max(max_level, PROBLEM_LEVELS[problem.level])
if no_warn and (problem.level != 'error'):
continue
if args_format == 'parsable':
print(Format.parsable(problem, file))
elif args_format == 'github':
if first:
print('::group::%s' % file)
first = False
print(Format.github(problem, file))
elif args_format == 'colored':
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 not first and args_format == 'github':
print('::endgroup::')
if not first and args_format != 'parsable':
print('')
return max_level
def find_project_config_filepath(path='.'):
for filename in ('.yamllint', '.yamllint.yaml', '.yamllint.yml'):
filepath = os.path.join(path, filename)
if os.path.isfile(filepath):
return filepath
if os.path.abspath(path) == os.path.abspath(os.path.expanduser('~')):
return None
if os.path.abspath(path) == os.path.abspath(os.path.join(path, '..')):
return None
return find_project_config_filepath(path=os.path.join(path, '..'))
def run(argv=None):
parser = argparse.ArgumentParser(prog=APP_NAME,
description=APP_DESCRIPTION)
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)')
files_group = parser.add_mutually_exclusive_group(required=True)
files_group.add_argument('files', metavar='FILE_OR_DIR', nargs='*',
default=(),
help='files to check')
files_group.add_argument('-', action='store_true', dest='stdin',
help='read from standard input')
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('--list-files', action='store_true', dest='list_files',
help='list files to lint and exit')
parser.add_argument('-f', '--format',
choices=('parsable', 'standard'), default='standard',
help='format for parsing output')
choices=('parsable', 'standard', 'colored', 'github',
'auto'),
default='auto', 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('--no-warnings',
action='store_true',
help='output only error level problems')
parser.add_argument('-v', '--version', action='version',
version='%s %s' % (APP_NAME, APP_VERSION))
# TODO: read from stdin when no filename?
version='{} {}'.format(APP_NAME, APP_VERSION))
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)
if 'YAMLLINT_CONFIG_FILE' in os.environ:
user_global_config = os.path.expanduser(
os.environ['YAMLLINT_CONFIG_FILE'])
# User-global config is supposed to be in ~/.config/yamllint/config
elif 'XDG_CONFIG_HOME' in os.environ:
user_global_config = os.path.join(
os.environ['XDG_CONFIG_HOME'], 'yamllint', 'config')
else:
user_global_config = os.path.expanduser('~/.config/yamllint/config')
project_config_filepath = find_project_config_filepath()
try:
if args.config_data is not None:
if args.config_data != '' and ':' not in args.config_data:
@ -93,37 +206,55 @@ def run(argv=None):
conf = YamlLintConfig(content=args.config_data)
elif args.config_file is not None:
conf = YamlLintConfig(file=args.config_file)
elif os.path.isfile('.yamllint'):
conf = YamlLintConfig(file='.yamllint')
elif project_config_filepath:
conf = YamlLintConfig(file=project_config_filepath)
elif os.path.isfile(user_global_config):
conf = YamlLintConfig(file=user_global_config)
else:
conf = YamlLintConfig('extends: default')
except YamlLintConfigError as e:
print(e, file=sys.stderr)
sys.exit(-1)
return_code = 0
if conf.locale is not None:
locale.setlocale(locale.LC_ALL, conf.locale)
if args.list_files:
for file in find_files_recursively(args.files, conf):
if not conf.is_file_ignored(file):
print(file)
sys.exit(0)
max_level = 0
for file in find_files_recursively(args.files):
for file in find_files_recursively(args.files, conf):
filepath = file[2:] if file.startswith('./') else file
try:
first = True
with open(file) as f:
for problem in linter.run(f, conf):
if args.format == 'parsable':
print(Format.parsable(problem, file))
else:
if first:
print('\033[4m%s\033[0m' % file)
first = False
print(Format.standard(problem, file))
if return_code == 0 and problem.level == 'error':
return_code = 1
if not first and args.format != 'parsable':
print('')
except EnvironmentError as e:
with open(file, newline='') as f:
problems = linter.run(f, conf, filepath)
except OSError as e:
print(e, file=sys.stderr)
return_code = -1
sys.exit(-1)
prob_level = show_problems(problems, file, args_format=args.format,
no_warn=args.no_warnings)
max_level = max(max_level, prob_level)
# read yaml from stdin
if args.stdin:
try:
problems = linter.run(sys.stdin, conf, '')
except OSError as e:
print(e, file=sys.stderr)
sys.exit(-1)
prob_level = show_problems(problems, 'stdin', args_format=args.format,
no_warn=args.no_warnings)
max_level = max(max_level, prob_level)
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)

@ -1,44 +1,35 @@
---
yaml-files:
- '*.yaml'
- '*.yml'
- '.yamllint'
rules:
braces:
min-spaces-inside: 0
max-spaces-inside: 0
brackets:
min-spaces-inside: 0
max-spaces-inside: 0
colons:
max-spaces-before: 0
max-spaces-after: 1
commas:
max-spaces-before: 0
min-spaces-after: 1
max-spaces-after: 1
anchors: enable
braces: enable
brackets: enable
colons: enable
commas: enable
comments:
level: warning
require-starting-space: yes
min-spaces-from-content: 2
comments-indentation:
level: warning
document-end: disable
document-start:
level: warning
present: yes
empty-lines:
max: 2
max-start: 0
max-end: 0
hyphens:
max-spaces-after: 1
indentation:
spaces: consistent
indent-sequences: yes
check-multi-line-strings: no
empty-lines: enable
empty-values: disable
float-values: disable
hyphens: enable
indentation: enable
key-duplicates: enable
line-length:
max: 80
allow-non-breakable-words: yes
key-ordering: disable
line-length: enable
new-line-at-end-of-file: enable
new-lines:
type: unix
new-lines: enable
octal-values: disable
quoted-strings: disable
trailing-spaces: enable
truthy:
level: warning

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

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -14,8 +13,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import fileinput
import os.path
import pathspec
import yaml
import yamllint.rules
@ -25,10 +26,17 @@ class YamlLintConfigError(Exception):
pass
class YamlLintConfig(object):
class YamlLintConfig:
def __init__(self, content=None, file=None):
assert (content is None) ^ (file is None)
self.ignore = None
self.yaml_files = pathspec.PathSpec.from_lines(
'gitwildmatch', ['*.yaml', '*.yml', '.yamllint'])
self.locale = None
if file is not None:
with open(file) as f:
content = f.read()
@ -36,15 +44,23 @@ 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 is_yaml_file(self, filepath):
return self.yaml_files.match_file(os.path.basename(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)
for rule in self.rules:
if (type(self.rules[rule]) == dict and
if (isinstance(self.rules[rule], dict) and
rule in base_config.rules and
base_config.rules[rule] is not False):
base_config.rules[rule].update(self.rules[rule])
@ -53,16 +69,24 @@ 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)
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
if type(conf) != dict:
if not isinstance(conf, dict):
raise YamlLintConfigError('invalid config: not a dict')
self.rules = conf.get('rules', {})
for rule in self.rules:
if self.rules[rule] == 'enable':
self.rules[rule] = {}
elif self.rules[rule] == 'disable':
self.rules[rule] = False
# Does this conf override another conf that we need to load?
if 'extends' in conf:
@ -73,6 +97,47 @@ class YamlLintConfig(object):
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
if 'ignore' in conf and 'ignore-from-file' in conf:
raise YamlLintConfigError(
'invalid config: ignore and ignore-from-file keys cannot be '
'used together')
elif 'ignore-from-file' in conf:
if isinstance(conf['ignore-from-file'], str):
conf['ignore-from-file'] = [conf['ignore-from-file']]
if not (isinstance(conf['ignore-from-file'], list) and all(
isinstance(ln, str) for ln in conf['ignore-from-file'])):
raise YamlLintConfigError(
'invalid config: ignore-from-file should contain '
'filename(s), either as a list or string')
with fileinput.input(conf['ignore-from-file']) as f:
self.ignore = pathspec.PathSpec.from_lines('gitwildmatch', f)
elif 'ignore' in conf:
if isinstance(conf['ignore'], str):
self.ignore = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
elif (isinstance(conf['ignore'], list) and
all(isinstance(line, str) for line in conf['ignore'])):
self.ignore = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'])
else:
raise YamlLintConfigError(
'invalid config: ignore should contain file patterns')
if 'yaml-files' in conf:
if not (isinstance(conf['yaml-files'], list)
and all(isinstance(i, str) for i in conf['yaml-files'])):
raise YamlLintConfigError(
'invalid config: yaml-files '
'should be a list of file patterns')
self.yaml_files = pathspec.PathSpec.from_lines('gitwildmatch',
conf['yaml-files'])
if 'locale' in conf:
if not isinstance(conf['locale'], str):
raise YamlLintConfigError(
'invalid config: locale should be a string')
self.locale = conf['locale']
def validate(self):
for id in self.rules:
try:
@ -84,12 +149,23 @@ class YamlLintConfig(object):
def validate_rule_conf(rule, conf):
if conf is False or conf == 'disable':
if conf is False: # disable
return False
elif conf == 'enable':
conf = {}
if type(conf) == dict:
if isinstance(conf, dict):
if ('ignore' in conf and
not isinstance(conf['ignore'], pathspec.pathspec.PathSpec)):
if isinstance(conf['ignore'], str):
conf['ignore'] = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
elif (isinstance(conf['ignore'], list) and
all(isinstance(line, str) for line in conf['ignore'])):
conf['ignore'] = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'])
else:
raise YamlLintConfigError(
'invalid config: ignore should contain file patterns')
if 'level' not in conf:
conf['level'] = 'error'
elif conf['level'] not in ('error', 'warning'):
@ -97,29 +173,49 @@ def validate_rule_conf(rule, conf):
'invalid config: level should be "error" or "warning"')
options = getattr(rule, 'CONF', {})
options_default = getattr(rule, 'DEFAULT', {})
for optkey in conf:
if optkey == 'level':
if optkey in ('ignore', 'ignore-from-file', 'level'):
continue
if optkey not in options:
raise YamlLintConfigError(
'invalid config: unknown option "%s" for rule "%s"' %
(optkey, rule.ID))
if type(options[optkey]) == tuple:
# Example: CONF = {option: (bool, 'mixed')}
# → {option: true} → {option: mixed}
if isinstance(options[optkey], tuple):
if (conf[optkey] not in options[optkey] and
type(conf[optkey]) not in options[optkey]):
raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be in %s'
% (optkey, rule.ID, options[optkey]))
# Example: CONF = {option: ['flag1', 'flag2', int]}
# → {option: [flag1]} → {option: [42, flag1, flag2]}
elif isinstance(options[optkey], list):
if (type(conf[optkey]) is not list or
any(flag not in options[optkey] and
type(flag) not in options[optkey]
for flag in conf[optkey])):
raise YamlLintConfigError(
('invalid config: option "%s" of "%s" should only '
'contain values in %s')
% (optkey, rule.ID, str(options[optkey])))
# Example: CONF = {option: int}
# → {option: 42}
else:
if type(conf[optkey]) != options[optkey]:
if not isinstance(conf[optkey], options[optkey]):
raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be %s'
% (optkey, rule.ID, options[optkey].__name__))
for optkey in options:
if optkey not in conf:
raise YamlLintConfigError(
'invalid config: missing option "%s" for rule "%s"' %
(optkey, rule.ID))
conf[optkey] = options_default[optkey]
if hasattr(rule, 'VALIDATE'):
res = rule.VALIDATE(conf)
if res:
raise YamlLintConfigError('invalid config: %s: %s' %
(rule.ID, res))
else:
raise YamlLintConfigError(('invalid config: rule "%s": should be '
'either "enable", "disable" or a dict')

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -14,12 +13,28 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
import io
import yaml
from yamllint import parser
class LintProblem(object):
PROBLEM_LEVELS = {
0: None,
1: 'warning',
2: 'error',
None: 0,
'warning': 1,
'error': 2,
}
DISABLE_RULE_PATTERN = re.compile(r'^# yamllint disable( rule:\S+)*\s*$')
ENABLE_RULE_PATTERN = re.compile(r'^# yamllint enable( rule:\S+)*\s*$')
class LintProblem:
"""Represents a linting problem found by yamllint."""
def __init__(self, line, column, desc='<no description>', rule=None):
#: Line on which the problem was found (starting at 1)
@ -35,7 +50,7 @@ class LintProblem(object):
@property
def message(self):
if self.rule is not None:
return '%s (%s)' % (self.desc, self.rule)
return '{} ({})'.format(self.desc, self.rule)
return self.desc
def __eq__(self, other):
@ -51,18 +66,71 @@ 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']
comment_rules = [r for r in rules if r.TYPE == 'comment']
line_rules = [r for r in rules if r.TYPE == 'line']
context = {}
for rule in token_rules:
context[rule.ID] = {}
for elem in parser.token_or_line_generator(buffer):
class DisableDirective:
def __init__(self):
self.rules = set()
self.all_rules = {r.ID for r in rules}
def process_comment(self, comment):
comment = str(comment)
if DISABLE_RULE_PATTERN.match(comment):
items = comment[18:].rstrip().split(' ')
rules = [item[5:] for item in items][1:]
if len(rules) == 0:
self.rules = self.all_rules.copy()
else:
for id in rules:
if id in self.all_rules:
self.rules.add(id)
elif ENABLE_RULE_PATTERN.match(comment):
items = comment[17:].rstrip().split(' ')
rules = [item[5:] for item in items][1:]
if len(rules) == 0:
self.rules.clear()
else:
for id in rules:
self.rules.discard(id)
def is_disabled_by_directive(self, problem):
return problem.rule in self.rules
class DisableLineDirective(DisableDirective):
def process_comment(self, comment):
comment = str(comment)
if re.match(r'^# yamllint disable-line( rule:\S+)*\s*$', comment):
items = comment[23:].rstrip().split(' ')
rules = [item[5:] for item in items][1:]
if len(rules) == 0:
self.rules = self.all_rules.copy()
else:
for id in rules:
if id in self.all_rules:
self.rules.add(id)
# Use a cache to store problems and flush it only when an end of line is
# found. This allows the use of yamllint directive to disable some rules on
# some lines.
cache = []
disabled = DisableDirective()
disabled_for_line = DisableLineDirective()
disabled_for_next_line = DisableLineDirective()
for elem in parser.token_or_comment_or_line_generator(buffer):
if isinstance(elem, parser.Token):
for rule in token_rules:
rule_conf = conf.rules[rule.ID]
@ -72,15 +140,39 @@ def get_costemic_problems(buffer, conf):
context[rule.ID]):
problem.rule = rule.ID
problem.level = rule_conf['level']
yield problem
cache.append(problem)
elif isinstance(elem, parser.Comment):
for rule in comment_rules:
rule_conf = conf.rules[rule.ID]
for problem in rule.check(rule_conf, elem):
problem.rule = rule.ID
problem.level = rule_conf['level']
cache.append(problem)
disabled.process_comment(elem)
if elem.is_inline():
disabled_for_line.process_comment(elem)
else:
disabled_for_next_line.process_comment(elem)
elif isinstance(elem, parser.Line):
for rule in line_rules:
rule_conf = conf.rules[rule.ID]
for problem in rule.check(rule_conf, elem):
problem.rule = rule.ID
problem.level = rule_conf['level']
cache.append(problem)
# This is the last token/comment/line of this line, let's flush the
# problems found (but filter them according to the directives)
for problem in cache:
if not (disabled_for_line.is_disabled_by_directive(problem) or
disabled.is_disabled_by_directive(problem)):
yield problem
disabled_for_line = disabled_for_next_line
disabled_for_next_line = DisableLineDirective()
cache = []
def get_syntax_error(buffer):
try:
@ -88,31 +180,34 @@ def get_syntax_error(buffer):
except yaml.error.MarkedYAMLError as e:
problem = LintProblem(e.problem_mark.line + 1,
e.problem_mark.column + 1,
'syntax error: ' + e.problem)
'syntax error: ' + e.problem + ' (syntax)')
problem.level = 'error'
return problem
def _run(buffer, conf):
def _run(buffer, conf, filepath):
assert hasattr(buffer, '__getitem__'), \
'_run() argument must be a buffer, not a stream'
first_line = next(parser.line_generator(buffer)).content
if re.match(r'^#\s*yamllint disable-file\s*$', first_line):
return
# If the document contains a syntax error, save it and yield it at the
# 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):
yield syntax_error
# If there is already a yamllint error at the same place, discard
# it as it is probably redundant (and maybe it's just a 'warning',
# Discard the problem since it is at the same place as the syntax
# error and is probably redundant (and maybe it's just a 'warning',
# in which case the script won't even exit with a failure status).
if (syntax_error.line == problem.line and
syntax_error.column == problem.column):
syntax_error = None
continue
syntax_error = None
continue
yield problem
@ -120,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.
@ -128,11 +223,14 @@ def run(input, conf):
:param input: buffer, string or stream to read from
:param conf: yamllint configuration object
"""
if type(input) in (type(b''), type(u'')): # compat with Python 2 & 3
return _run(input, conf)
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase
if filepath is not None and conf.is_file_ignored(filepath):
return ()
if isinstance(input, (bytes, str)):
return _run(input, conf, filepath)
elif isinstance(input, 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')

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -17,7 +16,7 @@
import yaml
class Line(object):
class Line:
def __init__(self, line_no, buffer, start, end):
self.line_no = line_no
self.start = start
@ -29,7 +28,7 @@ class Line(object):
return self.buffer[self.start:self.end]
class Token(object):
class Token:
def __init__(self, line_no, curr, prev, next, nextnext):
self.line_no = line_no
self.curr = curr
@ -38,12 +37,49 @@ class Token(object):
self.nextnext = nextnext
class Comment:
def __init__(self, line_no, column_no, buffer, pointer,
token_before=None, token_after=None, comment_before=None):
self.line_no = line_no
self.column_no = column_no
self.buffer = buffer
self.pointer = pointer
self.token_before = token_before
self.token_after = token_after
self.comment_before = comment_before
def __str__(self):
end = self.buffer.find('\n', self.pointer)
if end == -1:
end = self.buffer.find('\0', self.pointer)
if end != -1:
return self.buffer[self.pointer:end]
return self.buffer[self.pointer:]
def __eq__(self, other):
return (isinstance(other, Comment) and
self.line_no == other.line_no and
self.column_no == other.column_no and
str(self) == str(other))
def is_inline(self):
return (
not isinstance(self.token_before, yaml.StreamStartToken) and
self.line_no == self.token_before.end_mark.line + 1 and
# sometimes token end marks are on the next line
self.buffer[self.token_before.end_mark.pointer - 1] != '\n'
)
def line_generator(buffer):
line_no = 1
cur = 0
next = buffer.find('\n')
while next != -1:
yield Line(line_no, buffer, start=cur, end=next)
if next > 0 and buffer[next - 1] == '\r':
yield Line(line_no, buffer, start=cur, end=next - 1)
else:
yield Line(line_no, buffer, start=cur, end=next)
cur = next + 1
next = buffer.find('\n', cur)
line_no += 1
@ -51,7 +87,39 @@ def line_generator(buffer):
yield Line(line_no, buffer, start=cur, end=len(buffer))
def token_generator(buffer):
def comments_between_tokens(token1, token2):
"""Find all comments between two tokens"""
if token2 is None:
buf = token1.end_mark.buffer[token1.end_mark.pointer:]
elif (token1.end_mark.line == token2.start_mark.line and
not isinstance(token1, yaml.StreamStartToken) and
not isinstance(token2, yaml.StreamEndToken)):
return
else:
buf = token1.end_mark.buffer[token1.end_mark.pointer:
token2.start_mark.pointer]
line_no = token1.end_mark.line + 1
column_no = token1.end_mark.column + 1
pointer = token1.end_mark.pointer
comment_before = None
for line in buf.split('\n'):
pos = line.find('#')
if pos != -1:
comment = Comment(line_no, column_no + pos,
token1.end_mark.buffer, pointer + pos,
token1, token2, comment_before)
yield comment
comment_before = comment
pointer += len(line) + 1
line_no += 1
column_no = 1
def token_or_comment_generator(buffer):
yaml_loader = yaml.BaseLoader(buffer)
try:
@ -59,10 +127,13 @@ def token_generator(buffer):
curr = yaml_loader.get_token()
while curr is not None:
next = yaml_loader.get_token()
nextnext = yaml_loader.peek_token()
nextnext = (yaml_loader.peek_token()
if yaml_loader.check_token() else None)
yield Token(curr.start_mark.line + 1, curr, prev, next, nextnext)
yield from comments_between_tokens(curr, next)
prev = curr
curr = next
@ -70,19 +141,19 @@ def token_generator(buffer):
pass
def token_or_line_generator(buffer):
def token_or_comment_or_line_generator(buffer):
"""Generator that mixes tokens and lines, ordering them by line number"""
token_gen = token_generator(buffer)
tok_or_com_gen = token_or_comment_generator(buffer)
line_gen = line_generator(buffer)
token = next(token_gen, None)
tok_or_com = next(tok_or_com_gen, None)
line = next(line_gen, None)
while token is not None or line is not None:
if token is None or (line is not None and
token.line_no > line.line_no):
while tok_or_com is not None or line is not None:
if tok_or_com is None or (line is not None and
tok_or_com.line_no > line.line_no):
yield line
line = next(line_gen, None)
else:
yield token
token = next(token_gen, None)
yield tok_or_com
tok_or_com = next(tok_or_com_gen, None)

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -15,6 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from yamllint.rules import (
anchors,
braces,
brackets,
colons,
@ -24,16 +24,23 @@ 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,
float_values,
quoted_strings,
trailing_spaces,
truthy,
)
_RULES = {
anchors.ID: anchors,
braces.ID: braces,
brackets.ID: brackets,
colons.ID: colons,
@ -43,13 +50,19 @@ _RULES = {
document_end.ID: document_end,
document_start.ID: document_start,
empty_lines.ID: empty_lines,
empty_values.ID: empty_values,
float_values.ID: float_values,
hyphens.ID: hyphens,
indentation.ID: indentation,
key_duplicates.ID: key_duplicates,
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,
quoted_strings.ID: quoted_strings,
trailing_spaces.ID: trailing_spaces,
truthy.ID: truthy,
}

@ -0,0 +1,174 @@
# Copyright (C) 2023 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to report duplicated anchors and aliases referencing undeclared
anchors.
.. rubric:: Options
* Set ``forbid-undeclared-aliases`` to ``true`` to avoid aliases that reference
an anchor that hasn't been declared (either not declared at all, or declared
later in the document).
* Set ``forbid-duplicated-anchors`` to ``true`` to avoid duplications of a same
anchor.
* Set ``forbid-unused-anchors`` to ``true`` to avoid anchors being declared but
not used anywhere in the YAML document via alias.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
anchors:
forbid-undeclared-aliases: true
forbid-duplicated-anchors: false
forbid-unused-anchors: false
.. rubric:: Examples
#. With ``anchors: {forbid-undeclared-aliases: true}``
the following code snippet would **PASS**:
::
---
- &anchor
foo: bar
- *anchor
the following code snippet would **FAIL**:
::
---
- &anchor
foo: bar
- *unknown
the following code snippet would **FAIL**:
::
---
- &anchor
foo: bar
- <<: *unknown
extra: value
#. With ``anchors: {forbid-duplicated-anchors: true}``
the following code snippet would **PASS**:
::
---
- &anchor1 Foo Bar
- &anchor2 [item 1, item 2]
the following code snippet would **FAIL**:
::
---
- &anchor Foo Bar
- &anchor [item 1, item 2]
#. With ``anchors: {forbid-unused-anchors: true}``
the following code snippet would **PASS**:
::
---
- &anchor
foo: bar
- *anchor
the following code snippet would **FAIL**:
::
---
- &anchor
foo: bar
- items:
- item1
- item2
"""
import yaml
from yamllint.linter import LintProblem
ID = 'anchors'
TYPE = 'token'
CONF = {'forbid-undeclared-aliases': bool,
'forbid-duplicated-anchors': bool,
'forbid-unused-anchors': bool}
DEFAULT = {'forbid-undeclared-aliases': True,
'forbid-duplicated-anchors': False,
'forbid-unused-anchors': False}
def check(conf, token, prev, next, nextnext, context):
if (conf['forbid-undeclared-aliases'] or
conf['forbid-duplicated-anchors'] or
conf['forbid-unused-anchors']):
if isinstance(token, (
yaml.StreamStartToken,
yaml.DocumentStartToken,
yaml.DocumentEndToken)):
context['anchors'] = {}
if (conf['forbid-undeclared-aliases'] and
isinstance(token, yaml.AliasToken) and
token.value not in context['anchors']):
yield LintProblem(
token.start_mark.line + 1, token.start_mark.column + 1,
f'found undeclared alias "{token.value}"')
if (conf['forbid-duplicated-anchors'] and
isinstance(token, yaml.AnchorToken) and
token.value in context['anchors']):
yield LintProblem(
token.start_mark.line + 1, token.start_mark.column + 1,
f'found duplicated anchor "{token.value}"')
if conf['forbid-unused-anchors']:
# Unused anchors can only be detected at the end of Document.
# End of document can be either
# - end of stream
# - end of document sign '...'
# - start of a new document sign '---'
# If next token indicates end of document,
# check if the anchors have been used or not.
# If they haven't been used, report problem on those anchors.
if isinstance(next, (yaml.StreamEndToken,
yaml.DocumentStartToken,
yaml.DocumentEndToken)):
for anchor, info in context['anchors'].items():
if not info['used']:
yield LintProblem(info['line'] + 1,
info['column'] + 1,
f'found unused anchor "{anchor}"')
elif isinstance(token, yaml.AliasToken):
context['anchors'].get(token.value, {})['used'] = True
if (conf['forbid-undeclared-aliases'] or
conf['forbid-duplicated-anchors'] or
conf['forbid-unused-anchors']):
if isinstance(token, yaml.AnchorToken):
context['anchors'][token.value] = {
'line': token.start_mark.line,
'column': token.start_mark.column,
'used': False
}

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -15,17 +14,64 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to control the number of spaces inside braces (``{`` and ``}``).
Use this rule to control the use of flow mappings or number of spaces inside
braces (``{`` and ``}``).
.. rubric:: Options
* ``forbid`` is used to forbid the use of flow mappings which are denoted by
surrounding braces (``{`` and ``}``). Use ``true`` to forbid the use of flow
mappings completely. Use ``non-empty`` to forbid the use of all flow
mappings except for empty ones.
* ``min-spaces-inside`` defines the minimal number of spaces required inside
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:: Default values (when enabled)
.. code-block:: yaml
rules:
braces:
forbid: false
min-spaces-inside: 0
max-spaces-inside: 0
min-spaces-inside-empty: -1
max-spaces-inside-empty: -1
.. rubric:: Examples
#. With ``braces: {forbid: true}``
the following code snippet would **PASS**:
::
object:
key1: 4
key2: 8
the following code snippet would **FAIL**:
::
object: { key1: 4, key2: 8 }
#. With ``braces: {forbid: non-empty}``
the following code snippet would **PASS**:
::
object: {}
the following code snippet would **FAIL**:
::
object: { key1: 4, key2: 8 }
#. With ``braces: {min-spaces-inside: 0, max-spaces-inside: 0}``
the following code snippet would **PASS**:
@ -59,22 +105,82 @@ 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: {}
"""
import yaml
from yamllint.linter import LintProblem
from yamllint.rules.common import spaces_after, spaces_before
ID = 'braces'
TYPE = 'token'
CONF = {'min-spaces-inside': int,
'max-spaces-inside': int}
CONF = {'forbid': (bool, 'non-empty'),
'min-spaces-inside': int,
'max-spaces-inside': int,
'min-spaces-inside-empty': int,
'max-spaces-inside-empty': int}
DEFAULT = {'forbid': False,
'min-spaces-inside': 0,
'max-spaces-inside': 0,
'min-spaces-inside-empty': -1,
'max-spaces-inside-empty': -1}
def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.FlowMappingStartToken):
if (conf['forbid'] is True and
isinstance(token, yaml.FlowMappingStartToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'forbidden flow mapping')
elif (conf['forbid'] == 'non-empty' and
isinstance(token, yaml.FlowMappingStartToken) and
not isinstance(next, yaml.FlowMappingEndToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'forbidden flow mapping')
elif (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'],

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -15,18 +14,65 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to control the number of spaces inside brackets (``[`` and
``]``).
Use this rule to control the use of flow sequences or the number of spaces
inside brackets (``[`` and ``]``).
.. rubric:: Options
* ``forbid`` is used to forbid the use of flow sequences which are denoted by
surrounding brackets (``[`` and ``]``). Use ``true`` to forbid the use of
flow sequences completely. Use ``non-empty`` to forbid the use of all flow
sequences except for empty ones.
* ``min-spaces-inside`` defines the minimal number of spaces required inside
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:: Default values (when enabled)
.. code-block:: yaml
rules:
brackets:
forbid: false
min-spaces-inside: 0
max-spaces-inside: 0
min-spaces-inside-empty: -1
max-spaces-inside-empty: -1
.. rubric:: Examples
#. With ``brackets: {forbid: true}``
the following code snippet would **PASS**:
::
object:
- 1
- 2
- abc
the following code snippet would **FAIL**:
::
object: [ 1, 2, abc ]
#. With ``brackets: {forbid: non-empty}``
the following code snippet would **PASS**:
::
object: []
the following code snippet would **FAIL**:
::
object: [ 1, 2, abc ]
#. With ``brackets: {min-spaces-inside: 0, max-spaces-inside: 0}``
the following code snippet would **PASS**:
@ -60,22 +106,83 @@ 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: []
"""
import yaml
from yamllint.linter import LintProblem
from yamllint.rules.common import spaces_after, spaces_before
ID = 'brackets'
TYPE = 'token'
CONF = {'min-spaces-inside': int,
'max-spaces-inside': int}
CONF = {'forbid': (bool, 'non-empty'),
'min-spaces-inside': int,
'max-spaces-inside': int,
'min-spaces-inside-empty': int,
'max-spaces-inside-empty': int}
DEFAULT = {'forbid': False,
'min-spaces-inside': 0,
'max-spaces-inside': 0,
'min-spaces-inside-empty': -1,
'max-spaces-inside-empty': -1}
def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.FlowSequenceStartToken):
if (conf['forbid'] is True and
isinstance(token, yaml.FlowSequenceStartToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'forbidden flow sequence')
elif (conf['forbid'] == 'non-empty' and
isinstance(token, yaml.FlowSequenceStartToken) and
not isinstance(next, yaml.FlowSequenceEndToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'forbidden flow sequence')
elif (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'],

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -24,6 +23,15 @@ Use this rule to control the number of spaces before and after colons (``:``).
* ``max-spaces-after`` defines the maximal number of spaces allowed after
colons (use ``-1`` to disable).
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
colons:
max-spaces-before: 0
max-spaces-after: 1
.. rubric:: Examples
#. With ``colons: {max-spaces-before: 0, max-spaces-after: 1}``
@ -72,17 +80,21 @@ Use this rule to control the number of spaces before and after colons (``:``).
import yaml
from yamllint.rules.common import spaces_after, spaces_before, is_explicit_key
from yamllint.rules.common import is_explicit_key, spaces_after, spaces_before
ID = 'colons'
TYPE = 'token'
CONF = {'max-spaces-before': int,
'max-spaces-after': int}
DEFAULT = {'max-spaces-before': 0,
'max-spaces-after': 1}
def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.ValueToken):
if isinstance(token, yaml.ValueToken) and not (
isinstance(prev, yaml.AliasToken) and
token.start_mark.pointer - prev.end_mark.pointer == 1):
problem = spaces_before(token, prev, next,
max=conf['max-spaces-before'],
max_desc='too many spaces before colon')

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -26,6 +25,16 @@ Use this rule to control the number of spaces before and after commas (``,``).
* ``max-spaces-after`` defines the maximal number of spaces allowed after
commas (use ``-1`` to disable).
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
commas:
max-spaces-before: 0
min-spaces-after: 1
max-spaces-after: 1
.. rubric:: Examples
#. With ``commas: {max-spaces-before: 0}``
@ -66,7 +75,7 @@ Use this rule to control the number of spaces before and after commas (``,``).
::
strange var:
[10, 20,30, {x: 1, y: 2}]
[10, 20, 30, {x: 1, y: 2}]
the following code snippet would **FAIL**:
::
@ -103,6 +112,9 @@ TYPE = 'token'
CONF = {'max-spaces-before': int,
'min-spaces-after': int,
'max-spaces-after': int}
DEFAULT = {'max-spaces-before': 0,
'min-spaces-after': 1,
'max-spaces-after': 1}
def check(conf, token, prev, next, nextnext, context):

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -20,14 +19,27 @@ 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.
* Use ``ignore-shebangs`` to ignore a
`shebang <https://en.wikipedia.org/wiki/Shebang_(Unix)>`_ at the beginning of
the file when ``require-starting-space`` is set.
* ``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:: Default values (when enabled)
.. code-block:: yaml
rules:
comments:
require-starting-space: true
ignore-shebangs: true
min-spaces-from-content: 2
.. rubric:: Examples
#. With ``comments: {require-starting-space: yes}``
#. With ``comments: {require-starting-space: true}``
the following code snippet would **PASS**:
::
@ -35,6 +47,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**:
::
@ -55,33 +73,41 @@ Use this rule to control the position and formatting of comments.
"""
import yaml
from yamllint.linter import LintProblem
from yamllint.rules.common import get_comments_between_tokens
ID = 'comments'
TYPE = 'token'
TYPE = 'comment'
CONF = {'require-starting-space': bool,
'ignore-shebangs': bool,
'min-spaces-from-content': int}
def check(conf, token, prev, next, nextnext, context):
for comment in get_comments_between_tokens(token, next):
if (conf['min-spaces-from-content'] != -1 and
not isinstance(token, yaml.StreamStartToken) and
comment.line == token.end_mark.line + 1):
# Sometimes token end marks are on the next line
if token.end_mark.buffer[token.end_mark.pointer - 1] != '\n':
if (comment.pointer - token.end_mark.pointer <
conf['min-spaces-from-content']):
yield LintProblem(comment.line, comment.column,
'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, comment.column + 1,
'missing starting space in comment')
DEFAULT = {'require-starting-space': True,
'ignore-shebangs': True,
'min-spaces-from-content': 2}
def check(conf, comment):
if (conf['min-spaces-from-content'] != -1 and comment.is_inline() and
comment.pointer - comment.token_before.end_mark.pointer <
conf['min-spaces-from-content']):
yield LintProblem(comment.line_no, comment.column_no,
'too few spaces before 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):
if (conf['ignore-shebangs'] and
comment.line_no == 1 and
comment.column_no == 1 and
comment.buffer[text_start] == '!'):
return
# We can test for both \r and \r\n just by checking first char
# \r itself is a valid newline on some older OS.
elif comment.buffer[text_start] not in {' ', '\n', '\r', '\x00'}:
column = comment.column_no + text_start - comment.pointer
yield LintProblem(comment.line_no,
column,
'missing starting space in comment')

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -78,11 +77,11 @@ Use this rule to force comments to be indented like content.
import yaml
from yamllint.linter import LintProblem
from yamllint.rules.common import get_line_indent, get_comments_between_tokens
from yamllint.rules.common import get_line_indent
ID = 'comments-indentation'
TYPE = 'token'
TYPE = 'comment'
# Case A:
@ -98,28 +97,41 @@ TYPE = 'token'
# # commented line 2
# current: line
def check(conf, token, prev, next, nextnext, context):
if prev is None:
def check(conf, comment):
# Only check block comments
if (not isinstance(comment.token_before, yaml.StreamStartToken) and
comment.token_before.end_mark.line + 1 == comment.line_no):
return
curr_line_indent = token.start_mark.column
if isinstance(token, yaml.StreamEndToken):
curr_line_indent = 0
next_line_indent = comment.token_after.start_mark.column
if isinstance(comment.token_after, yaml.StreamEndToken):
next_line_indent = 0
skip_first_line = True
if isinstance(prev, yaml.StreamStartToken):
skip_first_line = False
if isinstance(comment.token_before, yaml.StreamStartToken):
prev_line_indent = 0
else:
prev_line_indent = get_line_indent(prev)
if prev_line_indent <= curr_line_indent:
prev_line_indent = -1 # disable it
for comment in get_comments_between_tokens(
prev, token, skip_first_line=skip_first_line):
if comment.column - 1 == curr_line_indent:
prev_line_indent = -1 # disable it
elif comment.column - 1 != prev_line_indent:
yield LintProblem(comment.line, comment.column,
'comment not indented like content')
prev_line_indent = get_line_indent(comment.token_before)
# In the following case only the next line indent is valid:
# list:
# # comment
# - 1
# - 2
prev_line_indent = max(prev_line_indent, next_line_indent)
# If two indents are valid but a previous comment went back to normal
# indent, for the next ones to do the same. In other words, avoid this:
# list:
# - 1
# # comment on valid indent (0)
# # comment on valid indent (4)
# other-list:
# - 2
if (comment.comment_before is not None and
not comment.comment_before.is_inline()):
prev_line_indent = comment.comment_before.column_no - 1
if (comment.column_no - 1 != prev_line_indent and
comment.column_no - 1 != next_line_indent):
yield LintProblem(comment.line_no, comment.column_no,
'comment not indented like content')

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -48,27 +47,6 @@ def spaces_before(token, prev, next, min=-1, max=-1,
token.start_mark.column + 1, min_desc)
class Comment(object):
def __init__(self, line, column, buffer, pointer):
self.line = line
self.column = column
self.buffer = buffer
self.pointer = pointer
def __repr__(self):
end = self.buffer.find('\n', self.pointer)
if end == -1:
end = self.buffer.find('\0', self.pointer)
if end != -1:
return self.buffer[self.pointer:end]
return self.buffer[self.pointer:]
def __eq__(self, other):
return (self.line == other.line and
self.column == other.column and
str(self) == str(other))
def get_line_indent(token):
"""Finds the indent of the line the token starts in."""
start = token.start_mark.buffer.rfind('\n', 0,
@ -98,35 +76,6 @@ def get_real_end_line(token):
return end_line
def get_comments_between_tokens(token1, token2, skip_first_line=False):
if token2 is None:
buf = token1.end_mark.buffer[token1.end_mark.pointer:]
elif (token1.end_mark.line == token2.start_mark.line and
not isinstance(token1, yaml.StreamStartToken) and
not isinstance(token2, yaml.StreamEndToken)):
return
else:
buf = token1.end_mark.buffer[token1.end_mark.pointer:
token2.start_mark.pointer]
line_no = token1.end_mark.line + 1
column_no = token1.end_mark.column + 1
pointer = token1.end_mark.pointer
for line in buf.split('\n'):
if skip_first_line:
skip_first_line = False
else:
pos = line.find('#')
if pos != -1:
yield Comment(line_no, column_no + pos,
token1.end_mark.buffer, pointer + pos)
pointer += len(line) + 1
line_no += 1
column_no = 1
def is_explicit_key(token):
# explicit key:
# ? key

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -19,12 +18,20 @@ 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:: Default values (when enabled)
.. code-block:: yaml
rules:
document-end:
present: true
.. rubric:: Examples
#. With ``document-end: {present: yes}``
#. With ``document-end: {present: true}``
the following code snippet would **PASS**:
::
@ -49,7 +56,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**:
::
@ -82,18 +89,23 @@ from yamllint.linter import LintProblem
ID = 'document-end'
TYPE = 'token'
CONF = {'present': bool}
DEFAULT = {'present': True}
def check(conf, token, prev, next, nextnext, context):
if conf['present']:
if (isinstance(token, yaml.StreamEndToken) and
not (isinstance(prev, yaml.DocumentEndToken) or
isinstance(prev, yaml.StreamStartToken))):
is_stream_end = isinstance(token, yaml.StreamEndToken)
is_start = isinstance(token, yaml.DocumentStartToken)
prev_is_end_or_stream_start = isinstance(
prev, (yaml.DocumentEndToken, yaml.StreamStartToken)
)
prev_is_directive = isinstance(prev, yaml.DirectiveToken)
if is_stream_end and not prev_is_end_or_stream_start:
yield LintProblem(token.start_mark.line, 1,
'missing document end "..."')
elif (isinstance(token, yaml.DocumentStartToken) and
not (isinstance(prev, yaml.DocumentEndToken) or
isinstance(prev, yaml.StreamStartToken))):
elif is_start and not (prev_is_end_or_stream_start
or prev_is_directive):
yield LintProblem(token.start_mark.line + 1, 1,
'missing document end "..."')

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -19,12 +18,20 @@ 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:: Default values (when enabled)
.. code-block:: yaml
rules:
document-start:
present: true
.. rubric:: Examples
#. With ``document-start: {present: yes}``
#. With ``document-start: {present: true}``
the following code snippet would **PASS**:
::
@ -45,7 +52,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**:
::
@ -72,6 +79,7 @@ from yamllint.linter import LintProblem
ID = 'document-start'
TYPE = 'token'
CONF = {'present': bool}
DEFAULT = {'present': True}
def check(conf, token, prev, next, nextnext, context):

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -25,6 +24,16 @@ Use this rule to set a maximal number of allowed consecutive blank lines.
* ``max-end`` defines the maximal number of empty lines allowed at the end of
the file. This option takes precedence over ``max``.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
empty-lines:
max: 2
max-start: 0
max-end: 0
.. rubric:: Examples
#. With ``empty-lines: {max: 1}``
@ -58,32 +67,45 @@ TYPE = 'line'
CONF = {'max': int,
'max-start': int,
'max-end': int}
DEFAULT = {'max': 2,
'max-start': 0,
'max-end': 0}
def check(conf, line):
if line.start == line.end and line.end < len(line.buffer):
# Only alert on the last blank line of a serie
if (line.end < len(line.buffer) - 1 and
line.buffer[line.end + 1] == '\n'):
# Only alert on the last blank line of a series
if (line.end + 2 <= len(line.buffer) and
line.buffer[line.end:line.end + 2] == '\n\n'):
return
elif (line.end + 4 <= len(line.buffer) and
line.buffer[line.end:line.end + 4] == '\r\n\r\n'):
return
blank_lines = 0
while (line.start > blank_lines and
line.buffer[line.start - blank_lines - 1] == '\n'):
start = line.start
while start >= 2 and line.buffer[start - 2:start] == '\r\n':
blank_lines += 1
start -= 2
while start >= 1 and line.buffer[start - 1] == '\n':
blank_lines += 1
start -= 1
max = conf['max']
# Special case: start of document
if line.start - blank_lines == 0:
if start == 0:
blank_lines += 1 # first line doesn't have a preceding \n
max = conf['max-start']
# Special case: end of document
# NOTE: The last line of a file is always supposed to end with a new
# line. See POSIX definition of a line at:
if line.end == len(line.buffer) - 1 and line.buffer[line.end] == '\n':
if ((line.end == len(line.buffer) - 1 and
line.buffer[line.end] == '\n') or
(line.end == len(line.buffer) - 2 and
line.buffer[line.end:line.end + 2] == '\r\n')):
# Allow the exception of the one-byte file containing '\n'
if line.end == 0:
return

@ -0,0 +1,104 @@
# 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:: Default values (when enabled)
.. code-block:: yaml
rules:
empty-values:
forbid-in-block-mappings: true
forbid-in-flow-mappings: true
.. 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}
DEFAULT = {'forbid-in-block-mappings': True,
'forbid-in-flow-mappings': True}
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')

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

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -22,6 +21,14 @@ Use this rule to control the number of spaces after hyphens (``-``).
* ``max-spaces-after`` defines the maximal number of spaces allowed after
hyphens.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
hyphens:
max-spaces-after: 1
.. rubric:: Examples
#. With ``hyphens: {max-spaces-after: 1}``
@ -76,6 +83,7 @@ from yamllint.rules.common import spaces_after
ID = 'hyphens'
TYPE = 'token'
CONF = {'max-spaces-after': int}
DEFAULT = {'max-spaces-after': 1}
def check(conf, token, prev, next, nextnext, context):

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -25,12 +24,22 @@ 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:: Default values (when enabled)
.. code-block:: yaml
rules:
indentation:
spaces: consistent
indent-sequences: true
check-multi-line-strings: false
.. rubric:: Examples
@ -99,7 +108,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 +161,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**:
::
@ -193,7 +202,7 @@ Use this rule to control the indentation.
import yaml
from yamllint.linter import LintProblem
from yamllint.rules.common import is_explicit_key, get_real_end_line
from yamllint.rules.common import get_real_end_line, is_explicit_key
ID = 'indentation'
@ -201,12 +210,15 @@ TYPE = 'token'
CONF = {'spaces': (int, 'consistent'),
'indent-sequences': (bool, 'whatever', 'consistent'),
'check-multi-line-strings': bool}
DEFAULT = {'spaces': 'consistent',
'indent-sequences': True,
'check-multi-line-strings': False}
ROOT, B_MAP, F_MAP, B_SEQ, F_SEQ, B_ENT, KEY, VAL = range(8)
labels = ('ROOT', 'B_MAP', 'F_MAP', 'B_SEQ', 'F_SEQ', 'B_ENT', 'KEY', 'VAL')
class Parent(object):
class Parent:
def __init__(self, type, indent, line_indent=None):
self.type = type
self.indent = indent
@ -224,7 +236,7 @@ def check_scalar_indentation(conf, token, context):
def compute_expected_indent(found_indent):
def detect_indent(base_indent):
if type(context['spaces']) is not int:
if not isinstance(context['spaces'], int):
context['spaces'] = found_indent - base_indent
return base_indent + context['spaces']
@ -312,7 +324,7 @@ def _check(conf, token, prev, next, nextnext, context):
token.start_mark.line + 1 > context['cur_line'])
def detect_indent(base_indent, next):
if type(context['spaces']) is not int:
if not isinstance(context['spaces'], int):
context['spaces'] = next.start_mark.column - base_indent
return base_indent + context['spaces']
@ -329,14 +341,18 @@ def _check(conf, token, prev, next, nextnext, context):
expected = detect_indent(expected, token)
if found_indentation != expected:
yield LintProblem(token.start_mark.line + 1, found_indentation + 1,
'wrong indentation: expected %d but found %d' %
(expected, found_indentation))
if expected < 0:
message = 'wrong indentation: expected at least %d' % \
(found_indentation + 1)
else:
message = 'wrong indentation: expected %d but found %d' % \
(expected, found_indentation)
yield LintProblem(token.start_mark.line + 1,
found_indentation + 1, message)
if (isinstance(token, yaml.ScalarToken) and
conf['check-multi-line-strings']):
for problem in check_scalar_indentation(conf, token, context):
yield problem
yield from check_scalar_indentation(conf, token, context)
# Step 2.a:
@ -399,6 +415,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 +489,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 unknown value (-1).
indent = -1
else:
indent = detect_indent(context['stack'][-1].indent,
next)
else: # 'whatever' or 'consistent'
if next.start_mark.column == context['stack'][-1].indent:
# key:
@ -548,8 +580,7 @@ def _check(conf, token, prev, next, nextnext, context):
def check(conf, token, prev, next, nextnext, context):
try:
for problem in _check(conf, token, prev, next, nextnext, context):
yield problem
yield from _check(conf, token, prev, next, nextnext, context)
except AssertionError:
yield LintProblem(token.start_mark.line + 1,
token.start_mark.column + 1,

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -61,12 +60,11 @@ from yamllint.linter import LintProblem
ID = 'key-duplicates'
TYPE = 'token'
CONF = {}
MAP, SEQ = range(2)
class Parent(object):
class Parent:
def __init__(self, type):
self.type = type
self.keys = []
@ -85,13 +83,16 @@ def check(conf, token, prev, next, nextnext, context):
elif isinstance(token, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken)):
context['stack'].pop()
if len(context['stack']) > 0:
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 next.value in context['stack'][-1].keys:
if (next.value in context['stack'][-1].keys and
# `<<` is "merge key", see http://yaml.org/type/merge.html
next.value != '<<'):
yield LintProblem(
next.start_mark.line + 1, next.start_mark.column + 1,
'duplication of key "%s" in mapping' % next.value)

@ -0,0 +1,127 @@
# 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 default. As a result, the
ordering is case-sensitive and not accent-friendly (see examples below).
This can be changed by setting the global ``locale`` option. This allows one
to sort case and accents properly.
.. 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
#. With global option ``locale: "en_US.UTF-8"`` and rule ``key-ordering: {}``
as opposed to before, the following code snippet would now **PASS**:
::
- t-shirt: 1
T-shirt: 2
t-shirts: 3
T-shirts: 4
- hair: true
haïr: true
hais: true
haïssable: true
"""
from locale import strcoll
import yaml
from yamllint.linter import LintProblem
ID = 'key-ordering'
TYPE = 'token'
MAP, SEQ = range(2)
class Parent:
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(strcoll(next.value, key) < 0
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)

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@ -22,7 +21,19 @@ 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:: Default values (when enabled)
.. code-block:: yaml
rules:
line-length:
max: 80
allow-non-breakable-words: true
allow-non-breakable-inline-mappings: false
.. rubric:: Examples
@ -42,7 +53,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**:
::
@ -59,9 +70,22 @@ Use this rule to set a limit to lines length.
the following code snippet would **FAIL**:
::
- this line is waaaaaaaaaaaaaay too long but could be easily splitted...
- this line is waaaaaaaaaaaaaay too long but could be easily split...
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: no}``
#. With ``line-length: {max: 60, allow-non-breakable-words: false}``
the following code snippet would **FAIL**:
::
@ -73,17 +97,42 @@ 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}
DEFAULT = {'max': 80,
'allow-non-breakable-words': True,
'allow-non-breakable-inline-mappings': False}
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] == ' ':
@ -91,11 +140,19 @@ def check(conf, line):
if start != line.end:
if line.buffer[start] == '#':
while line.buffer[start] == '#':
start += 1
start += 1
elif line.buffer[start] == '-':
start += 2
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']))

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

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

@ -0,0 +1,114 @@
# 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:: Options
* Use ``forbid-implicit-octal`` to prevent numbers starting with ``0``.
* Use ``forbid-explicit-octal`` to prevent numbers starting with ``0o``.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
octal-values:
forbid-implicit-octal: true
forbid-explicit-octal: true
.. 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 re
import yaml
from yamllint.linter import LintProblem
ID = 'octal-values'
TYPE = 'token'
CONF = {'forbid-implicit-octal': bool,
'forbid-explicit-octal': bool}
DEFAULT = {'forbid-implicit-octal': True,
'forbid-explicit-octal': True}
IS_OCTAL_NUMBER_PATTERN = re.compile(r'^[0-7]+$')
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' and
IS_OCTAL_NUMBER_PATTERN.match(val[1:])):
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
IS_OCTAL_NUMBER_PATTERN.match(val[2:])):
yield LintProblem(
token.start_mark.line + 1, token.end_mark.column + 1,
'forbidden explicit octal value "%s"' %
token.value)

@ -0,0 +1,289 @@
# Copyright (C) 2018 ClearScore
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to forbid any string values that are not quoted, or to prevent
quoted strings without needing it. You can also enforce the type of the quote
used.
.. rubric:: Options
* ``quote-type`` defines allowed quotes: ``single``, ``double`` or ``any``
(default).
* ``required`` defines whether using quotes in string values is required
(``true``, default) or not (``false``), or only allowed when really needed
(``only-when-needed``).
* ``extra-required`` is a list of PCRE regexes to force string values to be
quoted, if they match any regex. This option can only be used with
``required: false`` and ``required: only-when-needed``.
* ``extra-allowed`` is a list of PCRE regexes to allow quoted string values,
even if ``required: only-when-needed`` is set.
* ``allow-quoted-quotes`` allows (``true``) using disallowed quotes for strings
with allowed quotes inside. Default ``false``.
**Note**: Multi-line strings (with ``|`` or ``>``) will not be checked.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
quoted-strings:
quote-type: any
required: true
extra-required: []
extra-allowed: []
allow-quoted-quotes: false
.. rubric:: Examples
#. With ``quoted-strings: {quote-type: any, required: true}``
the following code snippet would **PASS**:
::
foo: "bar"
bar: 'foo'
number: 123
boolean: true
the following code snippet would **FAIL**:
::
foo: bar
#. With ``quoted-strings: {quote-type: single, required: only-when-needed}``
the following code snippet would **PASS**:
::
foo: bar
bar: foo
not_number: '123'
not_boolean: 'true'
not_comment: '# comment'
not_list: '[1, 2, 3]'
not_map: '{a: 1, b: 2}'
the following code snippet would **FAIL**:
::
foo: 'bar'
#. With ``quoted-strings: {required: false, extra-required: [^http://,
^ftp://]}``
the following code snippet would **PASS**:
::
- localhost
- "localhost"
- "http://localhost"
- "ftp://localhost"
the following code snippet would **FAIL**:
::
- http://localhost
- ftp://localhost
#. With ``quoted-strings: {required: only-when-needed, extra-allowed:
[^http://, ^ftp://], extra-required: [QUOTED]}``
the following code snippet would **PASS**:
::
- localhost
- "http://localhost"
- "ftp://localhost"
- "this is a string that needs to be QUOTED"
the following code snippet would **FAIL**:
::
- "localhost"
- this is a string that needs to be QUOTED
#. With ``quoted-strings: {quote-type: double, allow-quoted-quotes: false}``
the following code snippet would **PASS**:
::
foo: "bar\\"baz"
the following code snippet would **FAIL**:
::
foo: 'bar"baz'
#. With ``quoted-strings: {quote-type: double, allow-quoted-quotes: true}``
the following code snippet would **PASS**:
::
foo: 'bar"baz'
"""
import re
import yaml
from yamllint.linter import LintProblem
ID = 'quoted-strings'
TYPE = 'token'
CONF = {'quote-type': ('any', 'single', 'double'),
'required': (True, False, 'only-when-needed'),
'extra-required': [str],
'extra-allowed': [str],
'allow-quoted-quotes': bool}
DEFAULT = {'quote-type': 'any',
'required': True,
'extra-required': [],
'extra-allowed': [],
'allow-quoted-quotes': False}
def VALIDATE(conf):
if conf['required'] is True and len(conf['extra-allowed']) > 0:
return 'cannot use both "required: true" and "extra-allowed"'
if conf['required'] is True and len(conf['extra-required']) > 0:
return 'cannot use both "required: true" and "extra-required"'
if conf['required'] is False and len(conf['extra-allowed']) > 0:
return 'cannot use both "required: false" and "extra-allowed"'
DEFAULT_SCALAR_TAG = 'tag:yaml.org,2002:str'
# https://stackoverflow.com/a/36514274
yaml.resolver.Resolver.add_implicit_resolver(
'tag:yaml.org,2002:int',
re.compile(r'''^(?:[-+]?0b[0-1_]+
|[-+]?0o?[0-7_]+
|[-+]?0[0-7_]+
|[-+]?(?:0|[1-9][0-9_]*)
|[-+]?0x[0-9a-fA-F_]+
|[-+]?[1-9][0-9_]*(?::[0-5]?[0-9])+)$''', re.X),
list('-+0123456789'))
def _quote_match(quote_type, token_style):
return ((quote_type == 'any') or
(quote_type == 'single' and token_style == "'") or
(quote_type == 'double' and token_style == '"'))
def _quotes_are_needed(string):
loader = yaml.BaseLoader('key: ' + string)
# Remove the 5 first tokens corresponding to 'key: ' (StreamStartToken,
# BlockMappingStartToken, KeyToken, ScalarToken(value=key), ValueToken)
for _ in range(5):
loader.get_token()
try:
a, b = loader.get_token(), loader.get_token()
if (isinstance(a, yaml.ScalarToken) and a.style is None and
isinstance(b, yaml.BlockEndToken) and a.value == string):
return False
return True
except yaml.scanner.ScannerError:
return True
def _has_quoted_quotes(token):
return ((not token.plain) and
((token.style == "'" and '"' in token.value) or
(token.style == '"' and "'" in token.value)))
def check(conf, token, prev, next, nextnext, context):
if not (isinstance(token, yaml.tokens.ScalarToken) and
isinstance(prev, (yaml.BlockEntryToken, yaml.FlowEntryToken,
yaml.FlowSequenceStartToken, yaml.TagToken,
yaml.ValueToken))):
return
# Ignore explicit types, e.g. !!str testtest or !!int 42
if (prev and isinstance(prev, yaml.tokens.TagToken) and
prev.value[0] == '!!'):
return
# Ignore numbers, booleans, etc.
resolver = yaml.resolver.Resolver()
tag = resolver.resolve(yaml.nodes.ScalarNode, token.value, (True, False))
if token.plain and tag != DEFAULT_SCALAR_TAG:
return
# Ignore multi-line strings
if not token.plain and token.style in ("|", ">"):
return
quote_type = conf['quote-type']
msg = None
if conf['required'] is True:
# Quotes are mandatory and need to match config
if (token.style is None or
not (_quote_match(quote_type, token.style) or
(conf['allow-quoted-quotes'] and _has_quoted_quotes(token)))):
msg = "string value is not quoted with %s quotes" % quote_type
elif conf['required'] is False:
# Quotes are not mandatory but when used need to match config
if (token.style and
not _quote_match(quote_type, token.style) and
not (conf['allow-quoted-quotes'] and
_has_quoted_quotes(token))):
msg = "string value is not quoted with %s quotes" % quote_type
elif not token.style:
is_extra_required = any(re.search(r, token.value)
for r in conf['extra-required'])
if is_extra_required:
msg = "string value is not quoted"
elif conf['required'] == 'only-when-needed':
# Quotes are not strictly needed here
if (token.style and tag == DEFAULT_SCALAR_TAG and token.value and
not _quotes_are_needed(token.value)):
is_extra_required = any(re.search(r, token.value)
for r in conf['extra-required'])
is_extra_allowed = any(re.search(r, token.value)
for r in conf['extra-allowed'])
if not (is_extra_required or is_extra_allowed):
msg = "string value is redundantly quoted with %s quotes" % (
quote_type)
# But when used need to match config
elif (token.style and
not _quote_match(quote_type, token.style) and
not (conf['allow-quoted-quotes'] and _has_quoted_quotes(token))):
msg = "string value is not quoted with %s quotes" % quote_type
elif not token.style:
is_extra_required = len(conf['extra-required']) and any(
re.search(r, token.value) for r in conf['extra-required'])
if is_extra_required:
msg = "string value is not quoted"
if msg is not None:
yield LintProblem(
token.start_mark.line + 1,
token.start_mark.column + 1,
msg)

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

@ -0,0 +1,157 @@
# 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 non-explicitly typed truthy values other than allowed
ones (by default: ``true`` and ``false``), for example ``YES`` or ``off``.
This can be useful to prevent surprises from YAML parsers transforming
``[yes, FALSE, Off]`` into ``[true, false, false]`` or
``{y: 1, yes: 2, on: 3, true: 4, True: 5}`` into ``{y: 1, true: 5}``.
.. rubric:: Options
* ``allowed-values`` defines the list of truthy values which will be ignored
during linting. The default is ``['true', 'false']``, but can be changed to
any list containing: ``'TRUE'``, ``'True'``, ``'true'``, ``'FALSE'``,
``'False'``, ``'false'``, ``'YES'``, ``'Yes'``, ``'yes'``, ``'NO'``,
``'No'``, ``'no'``, ``'ON'``, ``'On'``, ``'on'``, ``'OFF'``, ``'Off'``,
``'off'``.
* ``check-keys`` disables verification for keys in mappings. By default,
``truthy`` rule applies to both keys and values. Set this option to ``false``
to prevent this.
.. rubric:: Default values (when enabled)
.. code-block:: yaml
rules:
truthy:
allowed-values: ['true', 'false']
check-keys: true
.. rubric:: Examples
#. With ``truthy: {}``
the following code snippet would **PASS**:
::
boolean: true
object: {"True": 1, 1: "True"}
"yes": 1
"on": 2
"True": 3
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
#. With ``truthy: {allowed-values: ["yes", "no"]}``
the following code snippet would **PASS**:
::
- yes
- no
- "true"
- 'false'
- foo
- bar
the following code snippet would **FAIL**:
::
- true
- false
- on
- off
#. With ``truthy: {check-keys: false}``
the following code snippet would **PASS**:
::
yes: 1
on: 2
true: 3
the following code snippet would **FAIL**:
::
yes: Yes
on: On
true: True
"""
import yaml
from yamllint.linter import LintProblem
TRUTHY = ['YES', 'Yes', 'yes',
'NO', 'No', 'no',
'TRUE', 'True', 'true',
'FALSE', 'False', 'false',
'ON', 'On', 'on',
'OFF', 'Off', 'off']
ID = 'truthy'
TYPE = 'token'
CONF = {'allowed-values': list(TRUTHY), 'check-keys': bool}
DEFAULT = {'allowed-values': ['true', 'false'], 'check-keys': True}
def check(conf, token, prev, next, nextnext, context):
if prev and isinstance(prev, yaml.tokens.TagToken):
return
if (not conf['check-keys'] and isinstance(prev, yaml.tokens.KeyToken) and
isinstance(token, yaml.tokens.ScalarToken)):
return
if isinstance(token, yaml.tokens.ScalarToken):
if (token.value in (set(TRUTHY) - set(conf['allowed-values'])) and
token.style is None):
yield LintProblem(token.start_mark.line + 1,
token.start_mark.column + 1,
"truthy value should be one of [" +
", ".join(sorted(conf['allowed-values'])) + "]")
Loading…
Cancel
Save