Merge branch 'master' into master

pull/262/head
Sorin Sbarnea 3 years ago committed by GitHub
commit dd09ea250d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,52 @@
---
name: CI
on: # yamllint disable-line rule:truthy
push:
pull_request:
branches:
- master
jobs:
lint:
name: Linters
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
- run: python -m pip install flake8 flake8-import-order doc8 sphinx
- run: python -m pip install .
- run: flake8 .
- run: doc8 $(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.6'
- '3.7'
- '3.8'
- '3.9'
- '3.10'
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
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,28 +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
env:
- REMOVE_LOCALES=false
- REMOVE_LOCALES=true
install:
- pip install pyyaml coveralls flake8 flake8-import-order doc8
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then pip install sphinx; fi
- pip install .
- if [[ $REMOVE_LOCALES = "true" ]]; then sudo rm -rf /usr/lib/locale/*; fi
script:
- if [[ $TRAVIS_PYTHON_VERSION != nightly ]]; then flake8 .; fi
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then doc8 $(git ls-files '*.rst'); fi
- yamllint --strict $(git ls-files '*.yaml' '*.yml')
- coverage run --source=yamllint -m unittest discover
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then
python setup.py build_sphinx;
fi
after_success:
coveralls

@ -1,6 +1,32 @@
Changelog Changelog
========= =========
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) 1.25.0 (2020-09-29)
------------------- -------------------

@ -42,5 +42,7 @@ Pull Request Process
6. Write a `good commit message 6. Write a `good commit message
<http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html>`_. <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. 7. Then, open a pull request.

@ -19,11 +19,7 @@ indentation, etc.
:target: https://yamllint.readthedocs.io/en/latest/?badge=latest :target: https://yamllint.readthedocs.io/en/latest/?badge=latest
:alt: Documentation status :alt: Documentation status
Written in Python (compatible with Python 2 & 3). Written in Python (compatible with Python 3 only).
⚠ Python 2 upstream support stopped on January 1, 2020. yamllint will keep
best-effort support for Python 2.7 until January 1, 2021. Past that date,
yamllint will drop all Python 2-related code.
Documentation Documentation
------------- -------------

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

@ -14,11 +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 If ``-c`` is not provided, yamllint will look for a configuration file in the
following locations (by order of preference): following locations (by order of preference):
- ``.yamllint``, ``.yamllint.yaml`` or ``.yamllint.yml`` in the current working - a file named ``.yamllint``, ``.yamllint.yaml``, or ``.yamllint.yml`` in the
directory current working directory
- the file referenced by ``$YAMLLINT_CONFIG_FILE``, if set - a filename referenced by ``$YAMLLINT_CONFIG_FILE``, if set
- ``$XDG_CONFIG_HOME/yamllint/config`` - a file named ``$XDG_CONFIG_HOME/yamllint/config`` or
- ``~/.config/yamllint/config`` ``~/.config/yamllint/config``, if present
Finally if no config file is found, the default configuration is applied. Finally if no config file is found, the default configuration is applied.

@ -22,9 +22,9 @@ Integration with GitHub Actions
------------------------------- -------------------------------
yamllint auto-detects when it's running inside of `GitHub yamllint auto-detects when it's running inside of `GitHub
Actions<https://github.com/features/actions>` and automatically uses the suited Actions <https://github.com/features/actions>`_ and automatically uses the suited
output format to decorate code with linting errors automatically. You can also output format to decorate code with linting errors. You can also force the
force the GitHub Actions output with ``yamllint --format github``. GitHub Actions output with ``yamllint --format github``.
An example workflow using GitHub Actions: An example workflow using GitHub Actions:
@ -51,3 +51,22 @@ An example workflow using GitHub Actions:
- name: Lint YAML files - name: Lint YAML files
run: yamllint . 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)$)"
}
}
}

@ -26,14 +26,12 @@ classifiers =
Environment :: Console Environment :: Console
Intended Audience :: Developers Intended Audience :: Developers
License :: OSI Approved :: GNU General Public License v3 (GPLv3) 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
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Topic :: Software Development Topic :: Software Development
Topic :: Software Development :: Debuggers Topic :: Software Development :: Debuggers
Topic :: Software Development :: Quality Assurance Topic :: Software Development :: Quality Assurance
@ -48,7 +46,7 @@ project_urls =
[options] [options]
packages = find: packages = find:
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.* python_requires = >=3.6
include_package_data = True include_package_data = True
install_requires = install_requires =
@ -69,3 +67,6 @@ yamllint = conf/*.yaml
[options.entry_points] [options.entry_points]
console_scripts = console_scripts =
yamllint = yamllint.cli:run yamllint = yamllint.cli:run
[coverage:run]
relative_files = True

@ -61,6 +61,30 @@ class ColonTestCase(RuleTestCase):
' a: 1\n' ' a: 1\n'
'}\n', conf, problem=(2, 8)) '}\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): def test_min_spaces(self):
conf = ('braces:\n' conf = ('braces:\n'
' max-spaces-inside: -1\n' ' max-spaces-inside: -1\n'

@ -60,6 +60,29 @@ class ColonTestCase(RuleTestCase):
' b\n' ' b\n'
']\n', conf, problem=(2, 9)) ']\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): def test_min_spaces(self):
conf = ('brackets:\n' conf = ('brackets:\n'
' max-spaces-inside: -1\n' ' max-spaces-inside: -1\n'

@ -97,7 +97,7 @@ class CommentsTestCase(RuleTestCase):
'#!/bin/env my-interpreter\n' '#!/bin/env my-interpreter\n'
'', conf, '', conf,
problem1=(1, 2), problem2=(3, 2), problem3=(4, 2)) 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)) conf, problem1=(1, 2))
self.check('key: #!/not/a/shebang\n', self.check('key: #!/not/a/shebang\n',
conf, problem1=(1, 8)) conf, problem1=(1, 8))
@ -117,8 +117,7 @@ class CommentsTestCase(RuleTestCase):
'#comment\n' '#comment\n'
'#!/bin/env my-interpreter\n', conf, '#!/bin/env my-interpreter\n', conf,
problem2=(3, 2), problem3=(4, 2)) problem2=(3, 2), problem3=(4, 2))
self.check('#! not a shebang\n', self.check('#! is a valid shebang too\n', conf)
conf, problem1=(1, 2))
self.check('key: #!/not/a/shebang\n', self.check('key: #!/not/a/shebang\n',
conf, problem1=(1, 8)) conf, problem1=(1, 8))

@ -14,9 +14,6 @@
# You should have received a copy of the GNU General Public License # 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 <http://www.gnu.org/licenses/>.
import sys
import unittest
from tests.common import RuleTestCase from tests.common import RuleTestCase
@ -119,6 +116,27 @@ class LineLengthTestCase(RuleTestCase):
'long_line: http://localhost/very/very/long/url\n' 'long_line: http://localhost/very/very/long/url\n'
'...\n', conf, problem=(2, 21)) '...\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' conf = ('line-length: {max: 20, allow-non-breakable-words: true}\n'
'trailing-spaces: disable') 'trailing-spaces: disable')
self.check('---\n' self.check('---\n'
@ -159,18 +177,17 @@ class LineLengthTestCase(RuleTestCase):
' {% this line is' + 99 * ' really' + ' long %}\n', ' {% this line is' + 99 * ' really' + ' long %}\n',
conf, problem=(3, 81)) conf, problem=(3, 81))
@unittest.skipIf(sys.version_info < (3, 0), 'Python 2 not supported')
def test_unicode(self): def test_unicode(self):
conf = 'line-length: {max: 53}' conf = 'line-length: {max: 53}'
self.check('---\n' self.check('---\n'
'# This is a test to check if “line-length” works nice\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)
conf = 'line-length: {max: 52}' conf = 'line-length: {max: 51}'
self.check('---\n' self.check('---\n'
'# This is a test to check if “line-length” works nice\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, problem1=(2, 53), problem2=(3, 53)) conf, problem1=(2, 52), problem2=(3, 52))
def test_with_dos_newlines(self): def test_with_dos_newlines(self):
conf = ('line-length: {max: 10}\n' conf = ('line-length: {max: 10}\n'

@ -436,3 +436,21 @@ class QuotedTestCase(RuleTestCase):
'- foo bar\n' '- foo bar\n'
'- "foo bar"\n', '- "foo bar"\n',
conf, problem1=(3, 3), problem2=(7, 3), problem3=(11, 3)) 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))

@ -14,10 +14,7 @@
# You should have received a copy of the GNU General Public License # 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 <http://www.gnu.org/licenses/>.
try: from io import StringIO
from cStringIO import StringIO
except ImportError:
from io import StringIO
import fcntl import fcntl
import locale import locale
import os import os
@ -246,19 +243,19 @@ class CommandLineTestCase(unittest.TestCase):
cli.run(()) cli.run(())
self.assertNotEqual(ctx.returncode, 0) self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '') self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches(ctx.stderr, r'^usage') self.assertRegex(ctx.stderr, r'^usage')
with RunContext(self) as ctx: with RunContext(self) as ctx:
cli.run(('--unknown-arg', )) cli.run(('--unknown-arg', ))
self.assertNotEqual(ctx.returncode, 0) self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '') self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches(ctx.stderr, r'^usage') self.assertRegex(ctx.stderr, r'^usage')
with RunContext(self) as ctx: with RunContext(self) as ctx:
cli.run(('-c', './conf.yaml', '-d', 'relaxed', 'file')) cli.run(('-c', './conf.yaml', '-d', 'relaxed', 'file'))
self.assertNotEqual(ctx.returncode, 0) self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '') self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches( self.assertRegex(
ctx.stderr.splitlines()[-1], ctx.stderr.splitlines()[-1],
r'^yamllint: error: argument -d\/--config-data: ' r'^yamllint: error: argument -d\/--config-data: '
r'not allowed with argument -c\/--config-file$' r'not allowed with argument -c\/--config-file$'
@ -269,21 +266,21 @@ class CommandLineTestCase(unittest.TestCase):
cli.run(('-', 'file')) cli.run(('-', 'file'))
self.assertNotEqual(ctx.returncode, 0) self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '') self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches(ctx.stderr, r'^usage') self.assertRegex(ctx.stderr, r'^usage')
def test_run_with_bad_config(self): def test_run_with_bad_config(self):
with RunContext(self) as ctx: with RunContext(self) as ctx:
cli.run(('-d', 'rules: {a: b}', 'file')) cli.run(('-d', 'rules: {a: b}', 'file'))
self.assertEqual(ctx.returncode, -1) self.assertEqual(ctx.returncode, -1)
self.assertEqual(ctx.stdout, '') 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): def test_run_with_empty_config(self):
with RunContext(self) as ctx: with RunContext(self) as ctx:
cli.run(('-d', '', 'file')) cli.run(('-d', '', 'file'))
self.assertEqual(ctx.returncode, -1) self.assertEqual(ctx.returncode, -1)
self.assertEqual(ctx.stdout, '') 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_config_file(self): def test_run_with_config_file(self):
with open(os.path.join(self.wd, 'config'), 'w') as f: with open(os.path.join(self.wd, 'config'), 'w') as f:
@ -300,6 +297,7 @@ class CommandLineTestCase(unittest.TestCase):
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml'))) cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
self.assertEqual(ctx.returncode, 1) 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): def test_run_with_user_global_config_file(self):
home = os.path.join(self.wd, 'fake-home') home = os.path.join(self.wd, 'fake-home')
dir = os.path.join(home, '.config', 'yamllint') dir = os.path.join(home, '.config', 'yamllint')
@ -386,7 +384,7 @@ class CommandLineTestCase(unittest.TestCase):
with RunContext(self) as ctx: with RunContext(self) as ctx:
cli.run(('--version', )) cli.run(('--version', ))
self.assertEqual(ctx.returncode, 0) 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): def test_run_non_existing_file(self):
path = os.path.join(self.wd, 'i-do-not-exist.yaml') path = os.path.join(self.wd, 'i-do-not-exist.yaml')
@ -395,7 +393,7 @@ class CommandLineTestCase(unittest.TestCase):
cli.run(('-f', 'parsable', path)) cli.run(('-f', 'parsable', path))
self.assertEqual(ctx.returncode, -1) self.assertEqual(ctx.returncode, -1)
self.assertEqual(ctx.stdout, '') 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): def test_run_one_problem_file(self):
path = os.path.join(self.wd, 'a.yaml') path = os.path.join(self.wd, 'a.yaml')
@ -555,11 +553,13 @@ class CommandLineTestCase(unittest.TestCase):
with RunContext(self) as ctx: with RunContext(self) as ctx:
cli.run((path, '--format', 'github')) cli.run((path, '--format', 'github'))
expected_out = ( expected_out = (
'::error file=%s,line=2,col=4::[trailing-spaces] trailing' '::group::%s\n'
'::error file=%s,line=2,col=4::2:4 [trailing-spaces] trailing'
' spaces\n' ' spaces\n'
'::error file=%s,line=3,col=4::[new-line-at-end-of-file] no' '::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' ' new line character at the end of file\n'
% (path, path)) '::endgroup::\n\n'
% (path, path, path))
self.assertEqual( self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, '')) (ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
@ -573,11 +573,13 @@ class CommandLineTestCase(unittest.TestCase):
os.environ['GITHUB_WORKFLOW'] = 'something' os.environ['GITHUB_WORKFLOW'] = 'something'
cli.run((path, )) cli.run((path, ))
expected_out = ( expected_out = (
'::error file=%s,line=2,col=4::[trailing-spaces] trailing' '::group::%s\n'
'::error file=%s,line=2,col=4::2:4 [trailing-spaces] trailing'
' spaces\n' ' spaces\n'
'::error file=%s,line=3,col=4::[new-line-at-end-of-file] no' '::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' ' new line character at the end of file\n'
% (path, path)) '::endgroup::\n\n'
% (path, path, path))
self.assertEqual( self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, '')) (ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))

@ -14,10 +14,7 @@
# You should have received a copy of the GNU General Public License # 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 <http://www.gnu.org/licenses/>.
try: from io import StringIO
from cStringIO import StringIO
except ImportError:
from io import StringIO
import os import os
import shutil import shutil
import sys import sys
@ -48,7 +45,7 @@ class SimpleConfigTestCase(unittest.TestCase):
config.YamlLintConfig('not: valid: yaml') config.YamlLintConfig('not: valid: yaml')
def test_unknown_rule(self): def test_unknown_rule(self):
with self.assertRaisesRegexp( with self.assertRaisesRegex(
config.YamlLintConfigError, config.YamlLintConfigError,
'invalid config: no such rule: "this-one-does-not-exist"'): 'invalid config: no such rule: "this-one-does-not-exist"'):
config.YamlLintConfig('rules:\n' config.YamlLintConfig('rules:\n'
@ -67,7 +64,7 @@ class SimpleConfigTestCase(unittest.TestCase):
self.assertEqual(c.rules['colons']['max-spaces-after'], 1) self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
def test_unknown_option(self): def test_unknown_option(self):
with self.assertRaisesRegexp( with self.assertRaisesRegex(
config.YamlLintConfigError, config.YamlLintConfigError,
'invalid config: unknown option "abcdef" for rule "colons"'): 'invalid config: unknown option "abcdef" for rule "colons"'):
config.YamlLintConfig('rules:\n' config.YamlLintConfig('rules:\n'
@ -105,7 +102,7 @@ class SimpleConfigTestCase(unittest.TestCase):
self.assertEqual(c.rules['indentation']['check-multi-line-strings'], self.assertEqual(c.rules['indentation']['check-multi-line-strings'],
False) False)
with self.assertRaisesRegexp( with self.assertRaisesRegex(
config.YamlLintConfigError, config.YamlLintConfigError,
'invalid config: option "indent-sequences" of "indentation" ' 'invalid config: option "indent-sequences" of "indentation" '
'should be in '): 'should be in '):

@ -47,15 +47,14 @@ class ModuleTestCase(unittest.TestCase):
subprocess.check_output([PYTHON, '-m', 'yamllint'], subprocess.check_output([PYTHON, '-m', 'yamllint'],
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
self.assertEqual(ctx.exception.returncode, 2) self.assertEqual(ctx.exception.returncode, 2)
self.assertRegexpMatches(ctx.exception.output.decode(), self.assertRegex(ctx.exception.output.decode(), r'^usage: yamllint')
r'^usage: yamllint')
def test_run_module_on_bad_dir(self): def test_run_module_on_bad_dir(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx: with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output([PYTHON, '-m', 'yamllint', subprocess.check_output([PYTHON, '-m', 'yamllint',
'/does/not/exist'], '/does/not/exist'],
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT)
self.assertRegexpMatches(ctx.exception.output.decode(), self.assertRegex(ctx.exception.output.decode(),
r'No such file or directory') r'No such file or directory')
def test_run_module_on_file(self): def test_run_module_on_file(self):

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

@ -93,6 +93,10 @@ class Format(object):
line += 'line=' + format(problem.line) + ',' line += 'line=' + format(problem.line) + ','
line += 'col=' + format(problem.column) line += 'col=' + format(problem.column)
line += '::' line += '::'
line += format(problem.line)
line += ':'
line += format(problem.column)
line += ' '
if problem.rule: if problem.rule:
line += '[' + problem.rule + '] ' line += '[' + problem.rule + '] '
line += problem.desc line += problem.desc
@ -103,18 +107,25 @@ def show_problems(problems, file, args_format, no_warn):
max_level = 0 max_level = 0
first = True 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: for problem in problems:
max_level = max(max_level, PROBLEM_LEVELS[problem.level]) max_level = max(max_level, PROBLEM_LEVELS[problem.level])
if no_warn and (problem.level != 'error'): if no_warn and (problem.level != 'error'):
continue continue
if args_format == 'parsable': if args_format == 'parsable':
print(Format.parsable(problem, file)) print(Format.parsable(problem, file))
elif args_format == 'github' or (args_format == 'auto' and elif args_format == 'github':
'GITHUB_ACTIONS' in os.environ and if first:
'GITHUB_WORKFLOW' in os.environ): print('::group::%s' % file)
first = False
print(Format.github(problem, file)) print(Format.github(problem, file))
elif args_format == 'colored' or \ elif args_format == 'colored':
(args_format == 'auto' and supports_color()):
if first: if first:
print('\033[4m%s\033[0m' % file) print('\033[4m%s\033[0m' % file)
first = False first = False
@ -125,6 +136,9 @@ def show_problems(problems, file, args_format, no_warn):
first = False first = False
print(Format.standard(problem, file)) print(Format.standard(problem, file))
if not first and args_format == 'github':
print('::endgroup::')
if not first and args_format != 'parsable': if not first and args_format != 'parsable':
print('') print('')

@ -242,7 +242,7 @@ def run(input, conf, filepath=None):
if conf.is_file_ignored(filepath): if conf.is_file_ignored(filepath):
return () return ()
if isinstance(input, (type(b''), type(u''))): # compat with Python 2 & 3 if isinstance(input, (bytes, str)):
return _run(input, conf, filepath) return _run(input, conf, filepath)
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase
# We need to have everything in memory to parse correctly # We need to have everything in memory to parse correctly

@ -22,7 +22,8 @@ braces (``{`` and ``}``).
* ``forbid`` is used to forbid the use of flow mappings which are denoted by * ``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 surrounding braces (``{`` and ``}``). Use ``true`` to forbid the use of flow
mappings completely. 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 * ``min-spaces-inside`` defines the minimal number of spaces required inside
braces. braces.
* ``max-spaces-inside`` defines the maximal number of spaces allowed inside * ``max-spaces-inside`` defines the maximal number of spaces allowed inside
@ -60,6 +61,18 @@ braces (``{`` and ``}``).
object: { key1: 4, key2: 8 } 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}`` #. With ``braces: {min-spaces-inside: 0, max-spaces-inside: 0}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
@ -128,7 +141,7 @@ from yamllint.rules.common import spaces_after, spaces_before
ID = 'braces' ID = 'braces'
TYPE = 'token' TYPE = 'token'
CONF = {'forbid': bool, CONF = {'forbid': (bool, 'non-empty'),
'min-spaces-inside': int, 'min-spaces-inside': int,
'max-spaces-inside': int, 'max-spaces-inside': int,
'min-spaces-inside-empty': int, 'min-spaces-inside-empty': int,
@ -141,7 +154,15 @@ DEFAULT = {'forbid': False,
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):
if conf['forbid'] and isinstance(token, yaml.FlowMappingStartToken): if (conf['forbid'] is True and
isinstance(token, yaml.FlowMappingStartToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'forbidden flow mapping')
elif (conf['forbid'] == 'non-empty' and
isinstance(token, yaml.FlowMappingStartToken) and
not isinstance(next, yaml.FlowMappingEndToken)):
yield LintProblem(token.start_mark.line + 1, yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1, token.end_mark.column + 1,
'forbidden flow mapping') 'forbidden flow mapping')

@ -22,7 +22,8 @@ inside brackets (``[`` and ``]``).
* ``forbid`` is used to forbid the use of flow sequences which are denoted by * ``forbid`` is used to forbid the use of flow sequences which are denoted by
surrounding brackets (``[`` and ``]``). Use ``true`` to forbid the use of surrounding brackets (``[`` and ``]``). Use ``true`` to forbid the use of
flow sequences completely. 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 * ``min-spaces-inside`` defines the minimal number of spaces required inside
brackets. brackets.
* ``max-spaces-inside`` defines the maximal number of spaces allowed inside * ``max-spaces-inside`` defines the maximal number of spaces allowed inside
@ -61,6 +62,18 @@ inside brackets (``[`` and ``]``).
object: [ 1, 2, abc ] 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}`` #. With ``brackets: {min-spaces-inside: 0, max-spaces-inside: 0}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
@ -129,7 +142,7 @@ from yamllint.rules.common import spaces_after, spaces_before
ID = 'brackets' ID = 'brackets'
TYPE = 'token' TYPE = 'token'
CONF = {'forbid': bool, CONF = {'forbid': (bool, 'non-empty'),
'min-spaces-inside': int, 'min-spaces-inside': int,
'max-spaces-inside': int, 'max-spaces-inside': int,
'min-spaces-inside-empty': int, 'min-spaces-inside-empty': int,
@ -142,7 +155,15 @@ DEFAULT = {'forbid': False,
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):
if conf['forbid'] and isinstance(token, yaml.FlowSequenceStartToken): if (conf['forbid'] is True and
isinstance(token, yaml.FlowSequenceStartToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'forbidden flow sequence')
elif (conf['forbid'] == 'non-empty' and
isinstance(token, yaml.FlowSequenceStartToken) and
not isinstance(next, yaml.FlowSequenceEndToken)):
yield LintProblem(token.start_mark.line + 1, yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1, token.end_mark.column + 1,
'forbidden flow sequence') 'forbidden flow sequence')

@ -74,8 +74,6 @@ Use this rule to control the position and formatting of comments.
""" """
import re
from yamllint.linter import LintProblem from yamllint.linter import LintProblem
@ -105,7 +103,7 @@ def check(conf, comment):
if (conf['ignore-shebangs'] and if (conf['ignore-shebangs'] and
comment.line_no == 1 and comment.line_no == 1 and
comment.column_no == 1 and comment.column_no == 1 and
re.match(r'^!\S', comment.buffer[text_start:])): comment.buffer[text_start] == '!'):
return return
# We can test for both \r and \r\n just by checking first char # We can test for both \r and \r\n just by checking first char
# \r itself is a valid newline on some older OS. # \r itself is a valid newline on some older OS.

@ -18,8 +18,8 @@
Use this rule to enforce alphabetical ordering of keys in mappings. The sorting Use this rule to enforce alphabetical ordering of keys in mappings. The sorting
order uses the Unicode code point number as a default. As a result, the 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). ordering is case-sensitive and not accent-friendly (see examples below).
This can be changed by setting the global ``locale`` option. This allows to This can be changed by setting the global ``locale`` option. This allows one
sort case and accents properly. to sort case and accents properly.
.. rubric:: Examples .. rubric:: Examples

@ -17,10 +17,6 @@
""" """
Use this rule to set a limit to lines length. 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 .. rubric:: Options
* ``max`` defines the maximal (inclusive) length of lines. * ``max`` defines the maximal (inclusive) length of lines.
@ -144,7 +140,11 @@ def check(conf, line):
start += 1 start += 1
if start != line.end: 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 start += 2
if line.buffer.find(' ', start, line.end) == -1: if line.buffer.find(' ', start, line.end) == -1:

@ -144,6 +144,17 @@ def VALIDATE(conf):
DEFAULT_SCALAR_TAG = u'tag:yaml.org,2002:str' DEFAULT_SCALAR_TAG = u'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): def _quote_match(quote_type, token_style):
return ((quote_type == 'any') or return ((quote_type == 'any') or

Loading…
Cancel
Save