Compare commits

..

126 Commits

Author SHA1 Message Date
Adrien Vergé
b9e1fd18c1 yamllint version 1.29.0 2023-01-10 18:57:21 +01:00
Peter Leitzen
fa0bb03f9a cli: Add --list-files command line option
This option lists the files to lint by yamllint, taking into account `ignore`
and `yaml-files` configuration options.
2023-01-10 18:48:38 +01:00
Matthew Gamble
2a904f8fc1 configuration: Allow using a list of strings in ignore configuration
This may feel more natural for some users, rather than embedding
multiple entries in a multi-line string.
2023-01-10 18:45:09 +01:00
Ville Skyttä
6194a282fc docs: Spelling and grammar fixes 2022-12-12 19:08:31 +01:00
Dimitri Papadopoulos
5ac3ed4490 Apply some pyupgrade suggestions
io.open() is an alias for for the builtin open() function:
	https://docs.python.org/3/library/io.html#io.open

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Fixes https://github.com/adrienverge/yamllint/issues/495
2022-10-04 08:59:51 +02:00
Dimitri Papadopoulos Orfanos
e8391de711 Drop support for Python 3.6
Python 3.6 reached end-of-life on 2021-12-23.
https://peps.python.org/pep-0494/
2022-09-13 07:59:17 +02:00
Dimitri Papadopoulos
a5adec1570 ci: Update GitHub Actions
https://github.com/actions/checkout
https://github.com/actions/setup-python
2022-09-13 07:58:03 +02:00
Adrien Vergé
9cce294041 yamllint version 1.28.0 2022-09-12 14:37:48 +02:00
andrewnaguib
2f8ad7003a config: Implement for ignore-from-file option
Closes https://github.com/adrienverge/yamllint/issues/360
Co-authored-by: Adrien Vergé <@adrienverge>
2022-08-10 08:35:40 +02:00
Roman Geraskin
fb0c0a5247 quoted-strings: fix docs example 2022-08-07 20:13:48 +02:00
Roman Geraskin
352e1a975e quoted-strings: Add allow-quoted-quotes option
Allows strings like `'foo"bar'` on `quote-type: double` and vice versa.
2022-08-07 13:47:35 +02:00
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)
2022-08-06 15:23:23 +02:00
Dimitri Papadopoulos
6b6fdba3bf linter: pre-compile disable/enable rules regexes
Not only this should improve performance, but I find the code more
readable.
2022-08-06 15:23:23 +02:00
Dimitri Papadopoulos Orfanos
868350681a License: Update to latest version of GPLv3
http:// → https://
2022-08-06 15:14:57 +02:00
Andrew Imeson
94c1c2bcf2 docs: Update ALE vim plugin link 2022-08-05 18:57:21 +02:00
Andrew Imeson
0130e15c8c docs: Simplify GitHub Actions example 2022-08-05 18:57:21 +02:00
Dimitri Papadopoulos
ae3158cd1f No need to inherit from object in Python 3 2022-08-05 08:49:56 +02:00
Dimitri Papadopoulos
4c7b47daf3 Most __future__ imports are specific to Python 2 2022-08-05 08:49:56 +02:00
Dimitri Papadopoulos Orfanos
3346843edc docs: Better compress PNG image 2022-08-05 08:43:55 +02:00
Dimitri Papadopoulos Orfanos
ea70520216 Changelog: Fix typo found by codespell 2022-08-05 08:42:10 +02:00
Adrien Vergé
a09ad89268 yamllint version 1.27.1 2022-07-08 18:06:54 +02:00
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
2022-07-08 18:05:21 +02:00
Adrien Vergé
8a364e2fde yamllint version 1.27.0 2022-07-08 17:32:19 +02:00
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.
2022-07-08 17:21:22 +02:00
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"
2022-07-08 17:21:22 +02:00
Derek Brown
8ac7d58693 float-values: Add a new rule to check floating-point numbers 2022-07-08 17:21:22 +02:00
Andrew Imeson
40cab7f999 docs: Bump application copyright year 2022-06-24 17:15:21 +02:00
Andrew Imeson
34a4f76e8b docs: Remove repeated word 'copyright'
Fixes #476 - credit to @chrillep and @adrienverge
2022-06-24 17:15:21 +02:00
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.
2022-06-23 08:19:30 +02:00
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
2022-06-23 08:19:30 +02:00
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
2022-06-23 08:19:30 +02:00
Christian Widlund
695fc5f1f1 docs: Add plugin section for Visual Studio Code 2022-06-22 18:12:56 +02:00
Christian Widlund
632665c3e6 docs: Add plugin section for IntelliJ 2022-06-20 14:47:43 +02:00
Derek Brown
5658cf7f42 octal-values: Pre-compile regex for performance 2022-06-11 12:03:44 +02:00
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.
2022-06-10 11:19:59 +02:00
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.
2022-06-10 08:39:50 +02:00
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.
2022-06-09 10:03:08 +02:00
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.
2022-06-09 09:40:06 +02:00
Andrew Imeson
e49a101160 Add rstcheck to CI to lint docs 2022-06-08 09:21:44 +02:00
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} }
2022-04-20 11:00:08 +02:00
Andrew Imeson
2f423117c1 docs: Attempt to clarify configuration file location
Closes #96, Closes #212
2022-03-24 10:23:39 +01:00
Andrew Imeson
f58448cb21 Fix spelling of "across" in test output 2022-03-21 13:48:34 +01:00
Andrew Imeson
7974d518cd Fix grammar in key_ordering docs to make Lintian happy
Fixes part of #76
2022-03-21 13:48:34 +01:00
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
2022-03-21 13:48:34 +01:00
Andrew Imeson
c34c962691 Remove the repeated word "automatically" in GHA doc 2022-03-21 13:48:34 +01:00
Madison Swain-Bowden
4f1bbc33dc docs: Fix link syntax on integration.rst 2021-12-24 09:34:15 +01:00
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
2021-12-19 22:02:56 +01:00
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.
2021-12-01 08:52:59 +01:00
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.
2021-11-24 14:38:26 +01:00
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
2021-11-17 16:43:46 +01:00
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
2021-11-17 16:43:46 +01:00
Adrien Vergé
f2e2e0c366 docs: Update CONTRIBUTING.rst
Be more precise in contributing instructions.
2021-10-20 09:40:59 +02:00
Adrien Vergé
058fef7559 yamllint version 1.26.3 2021-08-21 19:26:15 +02:00
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.
2021-08-21 19:25:27 +02:00
Adrien Vergé
33ce0fa960 yamllint version 1.26.2 2021-08-03 12:57:43 +02:00
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.
2021-06-10 16:16:22 +02:00
Adrien Vergé
85ccd625a3 yamllint version 1.26.1 2021-04-06 16:02:55 +02:00
Patryk Małek
e53ea093e2 line_length: skip all hash signs starting comment 2021-03-24 18:04:48 +01:00
Adrien Vergé
5d8ef2ea23 CI: Simplify 'pip' commands 2021-03-24 17:16:57 +01:00
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
2021-03-24 17:16:57 +01:00
Adrien Vergé
66bf76a362 CI: Switch to GitHub Actions
Because Travis CI is dead.
2021-03-16 16:13:01 +01:00
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
2021-02-11 08:55:08 +01:00
Adrien Vergé
0fff4e29e4 yamllint version 1.26.0 2021-01-29 18:10:35 +01:00
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.
2021-01-11 16:38:29 +01:00
Adrien Vergé
a3fc64d134 End support for Python 2
As planned and advertized, yamllint drops support for Python 2 on 2021.
2021-01-06 07:55:10 +01:00
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.
2020-12-15 11:52:21 +01:00
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).
2020-12-04 10:10:10 +01:00
Rex Ledesma
0f9dffde23 docs: Add configuration for integration with Arcanist 2020-10-24 13:24:55 +02:00
Mathieu Couette
cef0b48993 tests: Add unittest aliases to Python 2.7 2020-10-12 11:24:01 +02:00
Mathieu Couette
11b1f1c14e tests: Fix indentation issues 2020-10-12 11:24:01 +02:00
Mathieu Couette
9ee8c27ac9 tests: Replace deprecated aliases
https://docs.python.org/3/library/unittest.html#deprecated-aliases
2020-10-12 11:24:01 +02:00
Florian Bruhin
8eebab68ab Fix typo in changelog 2020-10-05 09:42:42 +02:00
Per Lundberg
2103bd73de README.rst: fix typo 2020-10-02 12:51:01 +02:00
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
2020-09-30 09:43:31 +02:00
Adrien Vergé
16e0f9d7b2 yamllint version 1.25.0 2020-09-29 08:52:34 +02:00
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.
2020-09-27 10:06:33 +02:00
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.
2020-09-26 11:12:26 +02:00
Andrew Imeson
67cb4eb24d Auto-change output format if GitHub Actions detected 2020-09-23 15:41:25 +02:00
Andrew Imeson
50c7453824 Add support for GitHub Annotations output format
Support the format used by GitHub Actions to annotate pull
requests with linter failures
2020-09-23 15:41:25 +02:00
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.
2020-09-14 10:14:10 +02:00
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.
2020-09-09 20:01:25 +02:00
Julien Falque
0a88c55194 quoted-strings: Fix detecting strings with hashtag as requiring quotes 2020-09-08 11:53:06 +02:00
Julien Falque
ac19d1e427 octal-values: Prevent detection of 8 and 9 as octal values 2020-09-08 09:53:54 +02:00
Adrien Vergé
597e88bb7b docs: Make 'yaml-file' config documentation clearer
Related to https://github.com/adrienverge/yamllint/issues/311.
2020-09-02 18:23:51 +02:00
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>
2020-09-02 18:02:27 +02:00
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>
2020-09-02 18:02:27 +02:00
Sorin Sbarnea
d274543b72 docs: Add Python API usage example
Fixes: #297
2020-08-25 20:10:32 +02:00
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)
```
2020-08-18 20:07:54 +02:00
Benjamin Wuethrich
b65769c9d2 docs: Add default values to rules with options 2020-07-22 11:54:05 +02:00
Wolfgang Walther
b80997eba6 CI: Add build environment without UTF-8 locales to travis-ci
Preventing regressions like #285
2020-07-20 13:57:06 +02:00
Adrien Vergé
8b758d4e7e yamllint version 1.24.2 2020-07-16 09:35:08 +02:00
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
2020-07-16 09:34:13 +02:00
Adrien Vergé
0fceca2354 yamllint version 1.24.1 2020-07-15 14:49:51 +02:00
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
2020-07-15 14:48:48 +02:00
Adrien Vergé
0016390e78 yamllint version 1.24.0 2020-07-15 11:50:36 +02:00
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.
2020-07-15 11:46:05 +02:00
Jonathan Sokolowski
a2218988ee config: Do no match directories that look like YAML files
Fixes #279
2020-07-10 09:27:34 +02:00
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
2020-07-08 16:27:08 +02:00
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.
2020-05-03 16:55:57 +02:00
Adrien Vergé
30c90dbf70 Add contribution instructions in CONTRIBUTING.rst
Closes https://github.com/adrienverge/yamllint/issues/263.
2020-05-03 16:51:22 +02:00
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.
2020-04-30 16:38:19 +02:00
Will Badart
278a79f093 Mention YAMLLINT_CONFIG_FILE in the documentation 2020-04-29 09:43:16 +02:00
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.
2020-04-29 09:39:50 +02:00
Will Badart
94c0416f6b Specify config with environment variable YAMLLINT_CONFIG_FILE
Add option to specify config file with environment variable.
Add test case.
2020-04-28 11:13:32 +02:00
Adrien Vergé
a54cbce1b6 yamllint version 1.23.0 2020-04-17 10:31:52 +02:00
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)
2020-04-17 10:29:55 +02:00
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'],
2020-04-17 10:29:55 +02:00
Adrien Vergé
851d34b9fd config: Allow rules to validate their configuration 2020-04-17 10:29:55 +02:00
Adrien Vergé
483a8d89a5 yamllint version 1.22.1 2020-04-15 07:55:57 +02:00
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.
2020-04-15 07:48:59 +02:00
80 changed files with 2556 additions and 361 deletions

58
.github/workflows/ci.yaml vendored Normal file
View File

@@ -0,0 +1,58 @@
---
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:
python -m pip install flake8 flake8-import-order sphinx
rstcheck[sphinx] doc8
- run: python -m pip install .
- run: flake8 .
- run: doc8 $(git ls-files '*.rst')
- run: rstcheck --ignore-directives automodule $(git ls-files '*.rst')
- run: yamllint --strict $(git ls-files '*.yaml' '*.yml')
- run: python setup.py build_sphinx
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 coveralls
- run: pip install .
- run: coverage run --source=yamllint -m unittest discover
- name: Coveralls
uses: AndreMiras/coveralls-python-action@develop

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ __pycache__
/dist
/yamllint.egg-info
/build
/.eggs

View File

@@ -1,24 +0,0 @@
---
dist: xenial # required for Python >= 3.7 (travis-ci/travis-ci#9069)
language: python
python:
- 2.7
- 3.5
- 3.6
- 3.7
- 3.8
- nightly
install:
- pip install pyyaml coveralls flake8 flake8-import-order doc8
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then pip install sphinx; fi
- pip install .
script:
- if [[ $TRAVIS_PYTHON_VERSION != nightly ]]; then flake8 .; fi
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then doc8 $(git ls-files '*.rst'); fi
- yamllint --strict $(git ls-files '*.yaml' '*.yml')
- coverage run --source=yamllint setup.py test
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then
python setup.py build_sphinx;
fi
after_success:
coveralls

View File

@@ -1,6 +1,129 @@
Changelog
=========
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)
-------------------

48
CONTRIBUTING.rst Normal file
View File

@@ -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.

View File

@@ -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>.

View File

@@ -19,11 +19,7 @@ indentation, etc.
:target: https://yamllint.readthedocs.io/en/latest/?badge=latest
:alt: Documentation status
Written in Python (compatible with Python 2 & 3).
⚠ Python 2 upstream support stopped on January 1, 2020. yamllint will keep
best-effort support for Python 2.7 until January 1, 2021. Passed that date,
yamllint will drop all Python 2-related code.
Written in Python (compatible with Python 3 only).
Documentation
-------------
@@ -78,7 +74,7 @@ Usage
# Output a parsable format (for syntax checking in editors like Vim, emacs...)
yamllint -f parsable file.yaml
`Read more in the complete documentation! <https://yamllint.readthedocs.io/>`_
`Read more in the complete documentation! <https://yamllint.readthedocs.io/>`__
Features
^^^^^^^^
@@ -136,7 +132,7 @@ Specific files can be ignored (totally or for some rules only) using a
*.ignore-trailing-spaces.yaml
/ascii-art/*
`Read more in the complete documentation! <https://yamllint.readthedocs.io/>`_
`Read more in the complete documentation! <https://yamllint.readthedocs.io/>`__
License
-------

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# yamllint documentation build configuration file, created by
# sphinx-quickstart on Thu Jan 21 21:18:52 2016.
@@ -6,9 +5,9 @@ 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 ------------------------------------------------
@@ -21,7 +20,7 @@ source_suffix = '.rst'
master_doc = 'index'
project = APP_NAME
copyright = __copyright__
copyright = __copyright__.lstrip('Copyright ')
version = APP_VERSION
release = APP_VERSION
@@ -39,7 +38,7 @@ htmlhelp_basename = 'yamllintdoc'
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'yamllint', '', [u'Adrien Vergé'], 1)
('index', 'yamllint', 'Linter for YAML files', ['Adrien Vergé'], 1)
]
# -- Build with sphinx automodule without needing to install third-party libs

View File

@@ -14,10 +14,11 @@ To use a custom configuration file, use the ``-c`` option:
If ``-c`` is not provided, yamllint will look for a configuration file in the
following locations (by order of preference):
- ``.yamllint``, ``.yamllint.yaml`` or ``.yamllint.yml`` in the current working
directory
- ``$XDG_CONFIG_HOME/yamllint/config``
- ``~/.config/yamllint/config``
- a file named ``.yamllint``, ``.yamllint.yaml``, or ``.yamllint.yml`` in the
current working directory
- 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.
@@ -122,8 +123,8 @@ warning level problems, only error level ones.
YAML files extensions
---------------------
To configure what yamllint should consider as YAML files, set ``yaml-files``
configuration option. The default is:
To configure what yamllint should consider as YAML files when listing
directories, set ``yaml-files`` configuration option. The default is:
.. code-block:: yaml
@@ -135,11 +136,19 @@ configuration option. The default is:
The same rules as for ignoring paths apply (``.gitignore``-style path pattern,
see below).
If you need to know the exact list of files that yamllint would process,
without really linting them, you can use ``--list-files``:
.. code:: bash
yamllint --list-files .
Ignoring paths
--------------
It is possible to exclude specific files or directories, so that the linter
doesn't process them.
doesn't process them. They can be provided either as a list of paths, or as a
bulk string.
You can either totally ignore files (they won't be looked at):
@@ -152,6 +161,13 @@ You can either totally ignore files (they won't be looked at):
all/this/directory/
*.template.yaml
# or:
ignore:
- /this/specific/file.yaml
- all/this/directory/
- '*.template.yaml'
or ignore paths only for specific rules:
.. code-block:: yaml
@@ -164,6 +180,14 @@ or ignore paths only for specific rules:
/this-file-has-trailing-spaces-but-it-is-OK.yaml
/generated/*.yaml
# or:
rules:
trailing-spaces:
ignore:
- /this-file-has-trailing-spaces-but-it-is-OK.yaml
- /generated/*.yaml
Note that this ``.gitignore``-style path pattern allows complex path
exclusion/inclusion, see the `pathspec README file
<https://pypi.python.org/pypi/pathspec>`_ for more details.
@@ -188,3 +212,43 @@ Here is a more complex example:
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
locale: en_US.UTF-8

View File

@@ -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("example.yaml", yaml_config):
print(p.desc, p.line, p.rule)
.. automodule:: yamllint.linter
:members:

View File

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

View File

@@ -17,3 +17,49 @@ Here is an example, to add in your .pre-commit-config.yaml
hooks:
- id: yamllint
args: [-c=/path/to/.yamllint]
Integration with GitHub Actions
-------------------------------
yamllint auto-detects when it's running inside of `GitHub
Actions <https://github.com/features/actions>`_ and automatically uses the
suited output format to decorate code with linting errors. You can also force
the GitHub Actions output with ``yamllint --format github``.
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)$)"
}
}
}

View File

@@ -64,6 +64,12 @@ empty-values
.. automodule:: yamllint.rules.empty_values
float-values
------------
.. automodule:: yamllint.rules.float_values
hyphens
-------

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -9,7 +9,7 @@ text editor.
Vim
---
Assuming that the `ALE <https://github.com/w0rp/ale>`_ plugin is
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.
@@ -33,6 +33,16 @@ 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
------------------

View File

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

View File

@@ -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,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from setuptools import find_packages, setup
from setuptools import setup
from yamllint import (__author__, __license__,
APP_NAME, APP_VERSION, APP_DESCRIPTION)
@@ -27,30 +26,4 @@ setup(
description=APP_DESCRIPTION.split('\n')[0],
long_description=APP_DESCRIPTION,
license=__license__,
keywords=['yaml', 'lint', 'linter', 'syntax', 'checker'],
url='https://github.com/adrienverge/yamllint',
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Software Development',
'Topic :: Software Development :: Debuggers',
'Topic :: Software Development :: Quality Assurance',
'Topic :: Software Development :: Testing',
],
packages=find_packages(exclude=['tests', 'tests.*']),
entry_points={'console_scripts': ['yamllint=yamllint.cli:run']},
package_data={'yamllint': ['conf/*.yaml']},
install_requires=['pathspec >=0.5.3', 'pyyaml'],
test_suite='tests',
)

View File

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

View File

@@ -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,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import contextlib
import os
import shutil
import tempfile
import unittest
@@ -57,7 +58,7 @@ def build_temp_workspace(files):
tempdir = tempfile.mkdtemp(prefix='yamllint-tests-')
for path, content in files.items():
path = os.path.join(tempdir, path)
path = os.path.join(tempdir, path).encode('utf-8')
if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))
@@ -69,3 +70,17 @@ def build_temp_workspace(files):
f.write(content)
return tempdir
@contextlib.contextmanager
def temp_workspace(files):
"""Provide a temporary workspace that is automatically cleaned up."""
backup_wd = os.getcwd()
wd = build_temp_workspace(files)
try:
os.chdir(wd)
yield
finally:
os.chdir(backup_wd)
shutil.rmtree(wd)

View File

@@ -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,6 +30,60 @@ 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:\n'
' max-spaces-inside: -1\n'

View File

@@ -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,6 +30,58 @@ 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:\n'
' max-spaces-inside: -1\n'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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))

View File

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

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@@ -1371,6 +1370,45 @@ class IndentationTestCase(RuleTestCase):
' key: value\n'
'...\n', conf, problem=(2, 2))
def test_nested_collections_with_spaces_consistent(self):
"""Tests behavior of {spaces: consistent} in nested collections to
ensure wrong-indentation is properly caught--especially when the
expected indent value is initially unknown. For details, see
https://github.com/adrienverge/yamllint/issues/485.
"""
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: true}')
self.check('---\n'
'- item:\n'
' - elem\n'
'- item:\n'
' - elem\n'
'...\n', conf, problem=(3, 3))
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: false}')
self.check('---\n'
'- item:\n'
' - elem\n'
'- item:\n'
' - elem\n'
'...\n', conf, problem=(5, 5))
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: consistent}')
self.check('---\n'
'- item:\n'
' - elem\n'
'- item:\n'
' - elem\n'
'...\n', conf, problem=(5, 5))
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: whatever}')
self.check('---\n'
'- item:\n'
' - elem\n'
'- item:\n'
' - elem\n'
'...\n', conf)
def test_return(self):
conf = 'indentation: {spaces: consistent}'
self.check('---\n'

View File

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

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Johannes F. Knauf
#
# 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/>.
import locale
from tests.common import RuleTestCase
@@ -103,10 +104,6 @@ class KeyOrderingTestCase(RuleTestCase):
'haïr: true\n'
'hais: true\n', conf,
problem=(3, 1))
self.check('---\n'
'haïr: true\n'
'hais: true\n', conf,
problem=(3, 1))
def test_key_tokens_in_flow_sequences(self):
conf = 'key-ordering: enable'
@@ -114,3 +111,39 @@ class KeyOrderingTestCase(RuleTestCase):
'[\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))

View File

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

View File

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

View File

@@ -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
@@ -40,6 +41,16 @@ class NewLinesTestCase(RuleTestCase):
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-line-at-end-of-file: disable\n'
'new-lines: {type: dos}\n')
@@ -49,3 +60,37 @@ class NewLinesTestCase(RuleTestCase):
self.check('\r\n', conf)
self.check('---\ntext\n', conf, problem=(1, 4))
self.check('---\r\ntext\r\n', conf)
def test_platform_type(self):
conf = ('new-line-at-end-of-file: disable\n'
'new-lines: {type: platform}\n')
self.check('', conf)
# mock the Linux new-line-character
with mock.patch('yamllint.rules.new_lines.linesep', '\n'):
self.check('\n', conf)
self.check('\r\n', conf, problem=(1, 1))
self.check('---\ntext\n', conf)
self.check('---\r\ntext\r\n', conf, problem=(1, 4))
self.check('---\r\ntext\n', conf, problem=(1, 4))
# FIXME: the following tests currently don't work
# because only the first line is checked for line-endings
# see: issue #475
# ---
# self.check('---\ntext\r\nfoo\n', conf, problem=(2, 4))
# self.check('---\ntext\r\n', conf, problem=(2, 4))
# mock the Windows new-line-character
with mock.patch('yamllint.rules.new_lines.linesep', '\r\n'):
self.check('\r\n', conf)
self.check('\n', conf, problem=(1, 1))
self.check('---\r\ntext\r\n', conf)
self.check('---\ntext\n', conf, problem=(1, 4))
self.check('---\ntext\r\n', conf, problem=(1, 4))
# FIXME: the following tests currently don't work
# because only the first line is checked for line-endings
# see: issue #475
# ---
# self.check('---\r\ntext\nfoo\r\n', conf, problem=(2, 4))
# self.check('---\r\ntext\n', conf, problem=(2, 4))

View File

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

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 ClearScore
#
# This program is free software: you can redistribute it and/or modify
@@ -16,6 +15,8 @@
from tests.common import RuleTestCase
from yamllint import config
class QuotedTestCase(RuleTestCase):
rule_id = 'quoted-strings'
@@ -316,3 +317,242 @@ class QuotedTestCase(RuleTestCase):
' "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)

View File

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

View File

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

View File

@@ -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,25 +13,23 @@
# 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
from tests.common import build_temp_workspace
from tests.common import build_temp_workspace, temp_workspace
from yamllint import cli
from yamllint import config
class RunContext(object):
class RunContext:
"""Context manager for ``cli.run()`` to capture exit code and streams."""
def __init__(self, case):
@@ -55,6 +52,16 @@ class RunContext(object):
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):
@@ -72,6 +79,9 @@ class CommandLineTestCase(unittest.TestCase):
# 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'
@@ -83,14 +93,21 @@ class CommandLineTestCase(unittest.TestCase):
'key: value\n',
# non-ASCII chars
'non-ascii/éçäγλνπ¥/utf-8': (
u'---\n'
u'- hétérogénéité\n'
u'# 19.99 €\n'
u'- お早う御座います。\n'
u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'),
'---\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
@@ -99,14 +116,19 @@ class CommandLineTestCase(unittest.TestCase):
shutil.rmtree(cls.wd)
@unittest.skipIf(not utf8_available() and sys.version_info < (3, 7),
'UTF-8 paths not supported')
def test_find_files_recursively(self):
conf = config.YamlLintConfig('extends: default')
self.assertEqual(
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/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')],
)
@@ -131,6 +153,7 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual(
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')],
)
@@ -140,6 +163,8 @@ class CommandLineTestCase(unittest.TestCase):
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')]
@@ -151,7 +176,8 @@ class CommandLineTestCase(unittest.TestCase):
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, 'empty.yml'),
os.path.join(self.wd, 'sub/directory.yaml/empty.yml')]
)
conf = config.YamlLintConfig('extends: default\n'
@@ -168,11 +194,15 @@ class CommandLineTestCase(unittest.TestCase):
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')]
)
@@ -185,11 +215,15 @@ class CommandLineTestCase(unittest.TestCase):
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')]
)
@@ -208,19 +242,19 @@ class CommandLineTestCase(unittest.TestCase):
cli.run(())
self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches(ctx.stderr, r'^usage')
self.assertRegex(ctx.stderr, r'^usage')
with RunContext(self) as ctx:
cli.run(('--unknown-arg', ))
self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches(ctx.stderr, r'^usage')
self.assertRegex(ctx.stderr, r'^usage')
with RunContext(self) as ctx:
cli.run(('-c', './conf.yaml', '-d', 'relaxed', 'file'))
self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches(
self.assertRegex(
ctx.stderr.splitlines()[-1],
r'^yamllint: error: argument -d\/--config-data: '
r'not allowed with argument -c\/--config-file$'
@@ -231,21 +265,31 @@ class CommandLineTestCase(unittest.TestCase):
cli.run(('-', 'file'))
self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches(ctx.stderr, r'^usage')
self.assertRegex(ctx.stderr, r'^usage')
def test_run_with_bad_config(self):
with RunContext(self) as ctx:
cli.run(('-d', 'rules: {a: b}', 'file'))
self.assertEqual(ctx.returncode, -1)
self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches(ctx.stderr, r'^invalid config: no such rule')
self.assertRegex(ctx.stderr, r'^invalid config: no such rule')
def test_run_with_empty_config(self):
with RunContext(self) as ctx:
cli.run(('-d', '', 'file'))
self.assertEqual(ctx.returncode, -1)
self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches(ctx.stderr, r'^invalid config: not a dict')
self.assertRegex(ctx.stderr, r'^invalid config: not a dict')
def test_run_with_implicit_extends_config(self):
path = os.path.join(self.wd, 'warn.yaml')
with RunContext(self) as ctx:
cli.run(('-d', 'default', '-f', 'parsable', path))
expected_out = ('%s:1:1: [warning] missing document start "---" '
'(document-start)\n' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (0, expected_out, ''))
def test_run_with_config_file(self):
with open(os.path.join(self.wd, 'config'), 'w') as f:
@@ -262,6 +306,7 @@ class CommandLineTestCase(unittest.TestCase):
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
self.assertEqual(ctx.returncode, 1)
@unittest.skipIf(os.environ.get('GITHUB_RUN_ID'), '$HOME not overridable')
def test_run_with_user_global_config_file(self):
home = os.path.join(self.wd, 'fake-home')
dir = os.path.join(home, '.config', 'yamllint')
@@ -285,11 +330,83 @@ class CommandLineTestCase(unittest.TestCase):
cli.run((os.path.join(self.wd, 'a.yaml'), ))
self.assertEqual(ctx.returncode, 1)
def test_run_with_user_xdg_config_home_in_env(self):
self.addCleanup(os.environ.__delitem__, 'XDG_CONFIG_HOME')
with tempfile.TemporaryDirectory('w') as d:
os.environ['XDG_CONFIG_HOME'] = d
os.makedirs(os.path.join(d, 'yamllint'))
with open(os.path.join(d, 'yamllint', 'config'), 'w') as f:
f.write('extends: relaxed')
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', os.path.join(self.wd, 'warn.yaml')))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
def test_run_with_user_yamllint_config_file_in_env(self):
self.addCleanup(os.environ.__delitem__, 'YAMLLINT_CONFIG_FILE')
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):
with RunContext(self) as ctx:
cli.run(('--version', ))
self.assertEqual(ctx.returncode, 0)
self.assertRegexpMatches(ctx.stdout + ctx.stderr, r'yamllint \d+\.\d+')
self.assertRegex(ctx.stdout + ctx.stderr, r'yamllint \d+\.\d+')
def test_run_non_existing_file(self):
path = os.path.join(self.wd, 'i-do-not-exist.yaml')
@@ -298,7 +415,7 @@ class CommandLineTestCase(unittest.TestCase):
cli.run(('-f', 'parsable', path))
self.assertEqual(ctx.returncode, -1)
self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches(ctx.stderr, r'No such file or directory')
self.assertRegex(ctx.stderr, r'No such file or directory')
def test_run_one_problem_file(self):
path = os.path.join(self.wd, 'a.yaml')
@@ -340,18 +457,12 @@ class CommandLineTestCase(unittest.TestCase):
cli.run(('-f', 'parsable', path))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (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))
path = os.path.join(self.wd, 'non-ascii', 'éçäγλνπ¥', 'utf-8')
# Make sure the default localization conditions on this "system"
# support UTF-8 encoding.
loc = locale.getlocale()
try:
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
except locale.Error:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
self.addCleanup(locale.setlocale, locale.LC_ALL, loc)
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', path))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
@@ -458,6 +569,55 @@ class CommandLineTestCase(unittest.TestCase):
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_format_colored_warning(self):
path = os.path.join(self.wd, 'warn.yaml')
with RunContext(self) as ctx:
cli.run((path, '--format', 'colored'))
expected_out = (
'\033[4m%s\033[0m\n'
' \033[2m1:1\033[0m \033[33mwarning\033[0m '
'missing document start "---" \033[2m(document-start)\033[0m\n'
'\n' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (0, expected_out, ''))
def test_run_format_github(self):
path = os.path.join(self.wd, 'a.yaml')
with RunContext(self) as ctx:
cli.run((path, '--format', 'github'))
expected_out = (
'::group::%s\n'
'::error file=%s,line=2,col=4::2:4 [trailing-spaces] trailing'
' 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
@@ -517,3 +677,60 @@ class CommandLineTestCase(unittest.TestCase):
'\n' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_list_files(self):
with RunContext(self) as ctx:
cli.run(('--list-files', self.wd))
self.assertEqual(ctx.returncode, 0)
self.assertEqual(
sorted(ctx.stdout.splitlines()),
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 'en.yaml'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/directory.yaml/empty.yml'),
os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
config = '{ignore: "*.yml", yaml-files: ["*.*"]}'
with RunContext(self) as ctx:
cli.run(('--list-files', '-d', config, self.wd))
self.assertEqual(ctx.returncode, 0)
self.assertEqual(
sorted(ctx.stdout.splitlines()),
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'c.yaml'),
os.path.join(self.wd, 'en.yaml'),
os.path.join(self.wd, 'no-yaml.json'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/directory.yaml/not-yaml.txt'),
os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
class CommandLineConfigTestCase(unittest.TestCase):
def test_config_file(self):
workspace = {'a.yml': 'hello: world\n'}
conf = ('---\n'
'extends: relaxed\n')
for conf_file in ('.yamllint', '.yamllint.yml', '.yamllint.yaml'):
with self.subTest(conf_file):
with temp_workspace(workspace):
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', '.'))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr),
(0, './a.yml:1:1: [warning] missing document '
'start "---" (document-start)\n', ''))
with temp_workspace({**workspace, **{conf_file: conf}}):
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', '.'))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr),
(0, '', ''))

View File

@@ -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,10 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
from io import StringIO
import os
import shutil
import sys
@@ -26,6 +22,7 @@ import unittest
from tests.common import build_temp_workspace
from yamllint.config import YamlLintConfigError
from yamllint import cli
from yamllint import config
@@ -48,7 +45,7 @@ class SimpleConfigTestCase(unittest.TestCase):
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'
@@ -67,7 +64,7 @@ class SimpleConfigTestCase(unittest.TestCase):
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'
@@ -105,7 +102,7 @@ class SimpleConfigTestCase(unittest.TestCase):
self.assertEqual(c.rules['indentation']['check-multi-line-strings'],
False)
with self.assertRaisesRegexp(
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: option "indent-sequences" of "indentation" '
'should be in '):
@@ -125,7 +122,7 @@ class SimpleConfigTestCase(unittest.TestCase):
self.assertEqual(c.rules['hyphens'], False)
def test_validate_rule_conf(self):
class Rule(object):
class Rule:
ID = 'fake'
self.assertFalse(config.validate_rule_conf(Rule, False))
@@ -193,6 +190,41 @@ class SimpleConfigTestCase(unittest.TestCase):
config.validate_rule_conf, Rule,
{'multiple': ['item4']})
def test_invalid_rule(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: rule "colons": should be either '
'"enable", "disable" or a dict'):
config.YamlLintConfig('rules:\n'
' colons: invalid\n')
def test_invalid_ignore(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: ignore should contain file patterns'):
config.YamlLintConfig('ignore: yes\n')
def test_invalid_rule_ignore(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: ignore should contain file patterns'):
config.YamlLintConfig('rules:\n'
' colons:\n'
' ignore: yes\n')
def test_invalid_locale(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: locale should be a string'):
config.YamlLintConfig('locale: yes\n')
def test_invalid_yaml_files(self):
with self.assertRaisesRegex(
config.YamlLintConfigError,
'invalid config: yaml-files should be a list of file '
'patterns'):
config.YamlLintConfig('yaml-files: yes\n')
class ExtendedConfigTestCase(unittest.TestCase):
def test_extend_on_object(self):
@@ -337,6 +369,26 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(c.rules['colons']['max-spaces-before'], 0)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
def test_extended_ignore_str(self):
with tempfile.NamedTemporaryFile('w') as f:
f.write('ignore: |\n'
' *.template.yaml\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n')
self.assertEqual(c.ignore.match_file('test.template.yaml'), True)
self.assertEqual(c.ignore.match_file('test.yaml'), False)
def test_extended_ignore_list(self):
with tempfile.NamedTemporaryFile('w') as f:
f.write('ignore:\n'
' - "*.template.yaml"\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n')
self.assertEqual(c.ignore.match_file('test.template.yaml'), True)
self.assertEqual(c.ignore.match_file('test.yaml'), False)
class ExtendedLibraryConfigTestCase(unittest.TestCase):
def test_extend_config_disable_rule(self):
@@ -388,10 +440,10 @@ class ExtendedLibraryConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['empty-lines']['max-end'], 0)
class IgnorePathConfigTestCase(unittest.TestCase):
class IgnoreConfigTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
super(IgnorePathConfigTestCase, cls).setUpClass()
super().setUpClass()
bad_yaml = ('---\n'
'- key: val1\n'
@@ -411,22 +463,6 @@ class IgnorePathConfigTestCase(unittest.TestCase):
's/s/ign-trail/file.yaml': bad_yaml,
's/s/ign-trail/s/s/file.yaml': bad_yaml,
's/s/ign-trail/s/s/file2.lint-me-anyway.yaml': bad_yaml,
'.yamllint': 'ignore: |\n'
' *.dont-lint-me.yaml\n'
' /bin/\n'
' !/bin/*.lint-me-anyway.yaml\n'
'\n'
'extends: default\n'
'\n'
'rules:\n'
' key-duplicates:\n'
' ignore: |\n'
' /ign-dup\n'
' trailing-spaces:\n'
' ignore: |\n'
' ign-trail\n'
' !*.lint-me-anyway.yaml\n',
})
cls.backup_wd = os.getcwd()
@@ -434,13 +470,101 @@ class IgnorePathConfigTestCase(unittest.TestCase):
@classmethod
def tearDownClass(cls):
super(IgnorePathConfigTestCase, cls).tearDownClass()
super().tearDownClass()
os.chdir(cls.backup_wd)
shutil.rmtree(cls.wd)
def test_run_with_ignored_path(self):
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', '.'))
@@ -478,3 +602,162 @@ class IgnorePathConfigTestCase(unittest.TestCase):
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
)))
def test_run_with_ignore_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,
)))

View File

@@ -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,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())
@@ -45,13 +44,23 @@ class LinterTestCase(unittest.TestCase):
['h', 'e', 'l', 'l', 'o'], self.fake_config())
def test_run_on_non_ascii_chars(self):
s = (u'- hétérogénéité\n'
u'# 19.99 €\n')
s = ('- hétérogénéité\n'
'# 19.99 €\n')
linter.run(s, self.fake_config())
linter.run(s.encode('utf-8'), self.fake_config())
linter.run(s.encode('iso-8859-15'), self.fake_config())
s = (u'- お早う御座います。\n'
u'# الأَبْجَدِيَّة العَرَبِيَّة\n')
s = ('- お早う御座います。\n'
'# الأَبْجَدِيَّة العَرَبِيَّة\n')
linter.run(s, self.fake_config())
linter.run(s.encode('utf-8'), self.fake_config())
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)')

View File

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

View File

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

View File

@@ -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

View File

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

View File

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

View File

@@ -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.22.0'
APP_VERSION = '1.29.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

View File

@@ -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,10 +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 __future__ import print_function
import argparse
import io
import locale
import os
import platform
import sys
@@ -49,7 +46,7 @@ def supports_color():
hasattr(sys.stdout, 'isatty') and sys.stdout.isatty())
class Format(object):
class Format:
@staticmethod
def parsable(problem, filename):
return ('%(file)s:%(line)s:%(column)s: [%(level)s] %(message)s' %
@@ -84,19 +81,47 @@ 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 == 'colored' or \
(args_format == 'auto' and supports_color()):
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
@@ -107,6 +132,9 @@ def show_problems(problems, file, args_format, no_warn):
first = False
print(Format.standard(problem, file))
if not first and args_format == 'github':
print('::endgroup::')
if not first and args_format != 'parsable':
print('')
@@ -129,8 +157,11 @@ def run(argv=None):
config_group.add_argument('-d', '--config-data', dest='config_data',
action='store',
help='custom configuration (as YAML source)')
parser.add_argument('--list-files', action='store_true', dest='list_files',
help='list files to lint and exit')
parser.add_argument('-f', '--format',
choices=('parsable', 'standard', 'colored', 'auto'),
choices=('parsable', 'standard', 'colored', 'github',
'auto'),
default='auto', help='format for parsing output')
parser.add_argument('-s', '--strict',
action='store_true',
@@ -144,8 +175,11 @@ def run(argv=None):
args = parser.parse_args(argv)
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
if 'XDG_CONFIG_HOME' in os.environ:
elif 'XDG_CONFIG_HOME' in os.environ:
user_global_config = os.path.join(
os.environ['XDG_CONFIG_HOME'], 'yamllint', 'config')
else:
@@ -172,14 +206,23 @@ def run(argv=None):
print(e, file=sys.stderr)
sys.exit(-1)
if conf.locale is not None:
locale.setlocale(locale.LC_ALL, conf.locale)
if args.list_files:
for file in find_files_recursively(args.files, conf):
if not conf.is_file_ignored(file):
print(file)
sys.exit(0)
max_level = 0
for file in find_files_recursively(args.files, conf):
filepath = file[2:] if file.startswith('./') else file
try:
with io.open(file, newline='') as f:
with open(file, newline='') as f:
problems = linter.run(f, conf, filepath)
except EnvironmentError as e:
except OSError as e:
print(e, file=sys.stderr)
sys.exit(-1)
prob_level = show_problems(problems, file, args_format=args.format,
@@ -190,7 +233,7 @@ def run(argv=None):
if args.stdin:
try:
problems = linter.run(sys.stdin, conf, '')
except EnvironmentError as e:
except OSError as e:
print(e, file=sys.stderr)
sys.exit(-1)
prob_level = show_problems(problems, 'stdin', args_format=args.format,

View File

@@ -19,6 +19,7 @@ rules:
level: warning
empty-lines: enable
empty-values: disable
float-values: disable
hyphens: enable
indentation: enable
key-duplicates: enable

View File

@@ -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,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import fileinput
import os.path
import pathspec
@@ -26,7 +26,7 @@ class YamlLintConfigError(Exception):
pass
class YamlLintConfig(object):
class YamlLintConfig:
def __init__(self, content=None, file=None):
assert (content is None) ^ (file is None)
@@ -35,6 +35,8 @@ class YamlLintConfig(object):
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()
@@ -46,7 +48,7 @@ class YamlLintConfig(object):
return self.ignore and self.ignore.match_file(filepath)
def is_yaml_file(self, filepath):
return self.yaml_files.match_file(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()
@@ -95,12 +97,31 @@ class YamlLintConfig(object):
except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e)
if 'ignore' in conf:
if not isinstance(conf['ignore'], str):
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')
self.ignore = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
if 'yaml-files' in conf:
if not (isinstance(conf['yaml-files'], list)
@@ -111,6 +132,12 @@ class YamlLintConfig(object):
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:
@@ -128,11 +155,16 @@ def validate_rule_conf(rule, conf):
if isinstance(conf, dict):
if ('ignore' in conf and
not isinstance(conf['ignore'], pathspec.pathspec.PathSpec)):
if not isinstance(conf['ignore'], str):
if isinstance(conf['ignore'], str):
conf['ignore'] = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
elif (isinstance(conf['ignore'], list) and
all(isinstance(line, str) for line in conf['ignore'])):
conf['ignore'] = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'])
else:
raise YamlLintConfigError(
'invalid config: ignore should contain file patterns')
conf['ignore'] = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
if 'level' not in conf:
conf['level'] = 'error'
@@ -143,7 +175,7 @@ def validate_rule_conf(rule, conf):
options = getattr(rule, 'CONF', {})
options_default = getattr(rule, 'DEFAULT', {})
for optkey in conf:
if optkey in ('ignore', 'level'):
if optkey in ('ignore', 'ignore-from-file', 'level'):
continue
if optkey not in options:
raise YamlLintConfigError(
@@ -157,11 +189,12 @@ def validate_rule_conf(rule, conf):
raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be in %s'
% (optkey, rule.ID, options[optkey]))
# Example: CONF = {option: ['flag1', 'flag2']}
# → {option: [flag1]} → {option: [flag1, flag2]}
# 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]
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 '
@@ -177,6 +210,12 @@ def validate_rule_conf(rule, conf):
for optkey in options:
if optkey not in conf:
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')

View File

@@ -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/>.
import re
import io
import yaml
@@ -30,8 +30,11 @@ PROBLEM_LEVELS = {
'error': 2,
}
DISABLE_RULE_PATTERN = re.compile(r'^# yamllint disable( rule:\S+)*\s*$')
ENABLE_RULE_PATTERN = re.compile(r'^# yamllint enable( rule:\S+)*\s*$')
class LintProblem(object):
class LintProblem:
"""Represents a linting problem found by yamllint."""
def __init__(self, line, column, desc='<no description>', rule=None):
#: Line on which the problem was found (starting at 1)
@@ -81,13 +84,11 @@ def get_cosmetic_problems(buffer, conf, filepath):
self.all_rules = {r.ID for r in rules}
def process_comment(self, comment):
try:
comment = str(comment)
except UnicodeError:
return # this certainly wasn't a yamllint directive comment
comment = str(comment)
if re.match(r'^# yamllint disable( rule:\S+)*\s*$', comment):
rules = [item[5:] for item in comment[18:].split(' ')][1:]
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:
@@ -95,8 +96,9 @@ def get_cosmetic_problems(buffer, conf, filepath):
if id in self.all_rules:
self.rules.add(id)
elif re.match(r'^# yamllint enable( rule:\S+)*\s*$', comment):
rules = [item[5:] for item in comment[17:].split(' ')][1:]
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:
@@ -108,13 +110,11 @@ def get_cosmetic_problems(buffer, conf, filepath):
class DisableLineDirective(DisableDirective):
def process_comment(self, comment):
try:
comment = str(comment)
except UnicodeError:
return # this certainly wasn't a yamllint directive comment
comment = str(comment)
if re.match(r'^# yamllint disable-line( rule:\S+)*\s*$', comment):
rules = [item[5:] for item in comment[23:].split(' ')][1:]
items = comment[23:].rstrip().split(' ')
rules = [item[5:] for item in items][1:]
if len(rules) == 0:
self.rules = self.all_rules.copy()
else:
@@ -122,7 +122,7 @@ def get_cosmetic_problems(buffer, conf, filepath):
if id in self.all_rules:
self.rules.add(id)
# Use a cache to store problems and flush it only when a end of line is
# Use a cache to store problems and flush it only when an end of line is
# found. This allows the use of yamllint directive to disable some rules on
# some lines.
cache = []
@@ -203,15 +203,11 @@ def _run(buffer, conf, filepath):
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
@@ -230,9 +226,9 @@ def run(input, conf, filepath=None):
if conf.is_file_ignored(filepath):
return ()
if isinstance(input, (type(b''), type(u''))): # compat with Python 2 & 3
if isinstance(input, (bytes, str)):
return _run(input, conf, filepath)
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase
elif isinstance(input, io.IOBase):
# We need to have everything in memory to parse correctly
content = input.read()
return _run(content, conf, filepath)

View File

@@ -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,7 +37,7 @@ class Token(object):
self.nextnext = nextnext
class Comment(object):
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
@@ -133,8 +132,7 @@ def token_or_comment_generator(buffer):
yield Token(curr.start_mark.line + 1, curr, prev, next, nextnext)
for comment in comments_between_tokens(curr, next):
yield comment
yield from comments_between_tokens(curr, next)
prev = curr
curr = next

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@@ -33,6 +32,7 @@ from yamllint.rules import (
new_line_at_end_of_file,
new_lines,
octal_values,
float_values,
quoted_strings,
trailing_spaces,
truthy,
@@ -49,6 +49,7 @@ _RULES = {
document_start.ID: document_start,
empty_lines.ID: empty_lines,
empty_values.ID: empty_values,
float_values.ID: float_values,
hyphens.ID: hyphens,
indentation.ID: indentation,
key_duplicates.ID: key_duplicates,

View File

@@ -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,10 +14,15 @@
# 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
@@ -28,8 +32,46 @@ Use this rule to control the number of spaces inside braces (``{`` and ``}``).
* ``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**:
@@ -92,23 +134,39 @@ Use this rule to control the number of spaces inside braces (``{`` and ``}``).
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,
CONF = {'forbid': (bool, 'non-empty'),
'min-spaces-inside': int,
'max-spaces-inside': int,
'min-spaces-inside-empty': int,
'max-spaces-inside-empty': int}
DEFAULT = {'min-spaces-inside': 0,
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) and
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']

View File

@@ -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,11 +14,15 @@
# 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
@@ -29,8 +32,47 @@ Use this rule to control the number of spaces inside brackets (``[`` and
* ``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**:
@@ -93,23 +135,39 @@ Use this rule to control the number of spaces inside brackets (``[`` and
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,
CONF = {'forbid': (bool, 'non-empty'),
'min-spaces-inside': int,
'max-spaces-inside': int,
'min-spaces-inside-empty': int,
'max-spaces-inside-empty': int}
DEFAULT = {'min-spaces-inside': 0,
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) and
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']

View File

@@ -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}``

View File

@@ -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**:
::

View File

@@ -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,6 +27,16 @@ Use this rule to control the position and formatting of comments.
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: true}``
@@ -64,8 +73,6 @@ Use this rule to control the position and formatting of comments.
"""
import re
from yamllint.linter import LintProblem
@@ -95,9 +102,11 @@ def check(conf, comment):
if (conf['ignore-shebangs'] and
comment.line_no == 1 and
comment.column_no == 1 and
re.match(r'^!\S', comment.buffer[text_start:])):
comment.buffer[text_start] == '!'):
return
elif comment.buffer[text_start] not in (' ', '\n', '\0'):
# 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,

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@@ -118,8 +117,7 @@ def check(conf, comment):
# # comment
# - 1
# - 2
if prev_line_indent <= next_line_indent:
prev_line_indent = next_line_indent
prev_line_indent = max(prev_line_indent, next_line_indent)
# If two indents are valid but a previous comment went back to normal
# indent, for the next ones to do the same. In other words, avoid this:

View File

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

View File

@@ -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 require or forbid the use of document end marker (``...``).
* 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: true}``

View File

@@ -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 require or forbid the use of document start marker (``---``).
* 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: true}``

View File

@@ -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}``

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Greg Dubicki
#
# This program is free software: you can redistribute it and/or modify
@@ -23,6 +22,15 @@ Use this rule to prevent nodes with empty content, that implicitly result in
* 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}``

View File

@@ -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}"',
)

View File

@@ -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}``

View File

@@ -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,16 @@ Use this rule to control the indentation.
* ``check-multi-line-strings`` defines whether to lint indentation in
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
#. With ``indentation: {spaces: 1}``
@@ -209,7 +218,7 @@ ROOT, B_MAP, F_MAP, B_SEQ, F_SEQ, B_ENT, KEY, VAL = range(8)
labels = ('ROOT', 'B_MAP', 'F_MAP', 'B_SEQ', 'F_SEQ', 'B_ENT', 'KEY', 'VAL')
class Parent(object):
class Parent:
def __init__(self, type, indent, line_indent=None):
self.type = type
self.indent = indent
@@ -332,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:
@@ -484,8 +497,8 @@ def _check(conf, token, prev, next, nextnext, context):
# indentation it should have (because `spaces` is
# `consistent` and its value has not been computed yet
# -- this is probably the beginning of the document).
# So we choose an arbitrary value (2).
indent = 2
# So we choose an unknown value (-1).
indent = -1
else:
indent = detect_indent(context['stack'][-1].indent,
next)
@@ -567,8 +580,7 @@ def _check(conf, token, prev, next, nextnext, context):
def check(conf, token, prev, next, nextnext, context):
try:
for problem in _check(conf, token, prev, next, nextnext, context):
yield problem
yield from _check(conf, token, prev, next, nextnext, context)
except AssertionError:
yield LintProblem(token.start_mark.line + 1,
token.start_mark.column + 1,

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé
#
# This program is free software: you can redistribute it and/or modify
@@ -65,7 +64,7 @@ TYPE = 'token'
MAP, SEQ = range(2)
class Parent(object):
class Parent:
def __init__(self, type):
self.type = type
self.keys = []
@@ -84,7 +83,8 @@ 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

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Johannes F. Knauf
#
# This program is free software: you can redistribute it and/or modify
@@ -16,8 +15,10 @@
"""
Use this rule to enforce alphabetical ordering of keys in mappings. The sorting
order uses the Unicode code point number. As a result, the ordering is
case-sensitive and not accent-friendly (see examples below).
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
@@ -63,8 +64,24 @@ case-sensitive and not accent-friendly (see examples below).
- 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
@@ -76,7 +93,7 @@ TYPE = 'token'
MAP, SEQ = range(2)
class Parent(object):
class Parent:
def __init__(self, type):
self.type = type
self.keys = []
@@ -101,7 +118,8 @@ def check(conf, token, prev, next, nextnext, context):
# This check is done because KeyTokens can be found inside flow
# sequences... strange, but allowed.
if len(context['stack']) > 0 and context['stack'][-1].type == MAP:
if any(next.value < key for key in context['stack'][-1].keys):
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)

View File

@@ -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,10 +16,6 @@
"""
Use this rule to set a limit to lines length.
Note: with Python 2, the ``line-length`` rule may not work properly with
unicode characters because of the way strings are represented in bytes. We
recommend running yamllint with Python 3.
.. rubric:: Options
* ``max`` defines the maximal (inclusive) length of lines.
@@ -30,6 +25,16 @@ recommend running yamllint with Python 3.
* ``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
#. With ``line-length: {max: 70}``
@@ -134,7 +139,11 @@ def check(conf, line):
start += 1
if start != line.end:
if line.buffer[start] in ('#', '-'):
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:

View File

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

View File

@@ -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,28 +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.end + 2 > len(line.buffer) or
line.buffer[line.end:line.end + 2] != '\r\n'):
yield LintProblem(1, line.end - line.start + 1,
'wrong new line character: expected \\r\\n')
else:
if line.buffer[line.end] == '\r':
yield LintProblem(1, line.end - line.start + 1,
'wrong new line character: expected \\n')
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('\'')))

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 ScienJus
#
# This program is free software: you can redistribute it and/or modify
@@ -20,6 +19,20 @@ 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}``
@@ -57,6 +70,8 @@ converted to ``8``.
city-code: 0o10
"""
import re
import yaml
from yamllint.linter import LintProblem
@@ -69,6 +84,8 @@ CONF = {'forbid-implicit-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):
@@ -78,7 +95,8 @@ def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.tokens.ScalarToken):
if not token.style:
val = token.value
if val.isdigit() and len(val) > 1 and val[0] == '0':
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"' %
@@ -88,7 +106,8 @@ def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.tokens.ScalarToken):
if not token.style:
val = token.value
if len(val) > 2 and val[:2] == '0o' and val[2:].isdigit():
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"' %

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 ClearScore
#
# This program is free software: you can redistribute it and/or modify
@@ -26,9 +25,28 @@ used.
* ``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}``
@@ -63,8 +81,64 @@ used.
::
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
@@ -72,21 +146,68 @@ from yamllint.linter import LintProblem
ID = 'quoted-strings'
TYPE = 'token'
CONF = {'quote-type': ('any', 'single', 'double'),
'required': (True, False, 'only-when-needed')}
'required': (True, False, 'only-when-needed'),
'extra-required': [str],
'extra-allowed': [str],
'allow-quoted-quotes': bool}
DEFAULT = {'quote-type': 'any',
'required': True}
DEFAULT_SCALAR_TAG = u'tag:yaml.org,2002:str'
START_TOKENS = {'#', '*', '!', '?', '@', '`', '&',
',', '-', '{', '}', '[', ']', ':'}
'required': True,
'extra-required': [],
'extra-allowed': [],
'allow-quoted-quotes': False}
def quote_match(quote_type, token_style):
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,
@@ -107,40 +228,59 @@ def check(conf, token, prev, next, nextnext, context):
return
# Ignore multi-line strings
if (not token.plain) and (token.style == "|" or token.style == ">"):
if not token.plain and token.style in ("|", ">"):
return
quote_type = conf['quote-type']
required = conf['required']
# Completely relaxed about quotes (same as the rule being disabled)
if required is False and quote_type == 'any':
return
msg = None
if required is True:
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):
msg = "string value is not quoted with %s quotes" % (quote_type)
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 required is False:
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):
msg = "string value is not quoted with %s quotes" % (quote_type)
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.plain:
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"
# Quotes are disallowed when not needed
if (tag == DEFAULT_SCALAR_TAG and token.value
and token.value[0] not in START_TOKENS):
msg = "string value is redundantly quoted with %s quotes" % (
quote_type)
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):
msg = "string value is not quoted with %s quotes" % (quote_type)
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(

View File

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

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Peter Ericson
#
# This program is free software: you can redistribute it and/or modify
@@ -15,7 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Use this rule to forbid non-explictly typed truthy values other than allowed
Use this rule to forbid non-explicitly typed truthy values other than allowed
ones (by default: ``true`` and ``false``), for example ``YES`` or ``off``.
This can be useful to prevent surprises from YAML parsers transforming
@@ -34,6 +33,15 @@ This can be useful to prevent surprises from YAML parsers transforming
``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: {}``