Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60b72daad4 | ||
|
|
773bb8a648 | ||
|
|
d3cd8ba332 | ||
|
|
e56a7c788c | ||
|
|
d017631aff | ||
|
|
5b98cd2053 | ||
|
|
82dd7dbf16 | ||
|
|
4533b8ae49 | ||
|
|
a2c68fdf9b | ||
|
|
82ed191bc9 | ||
|
|
92ff315fb4 | ||
|
|
f4cebdc054 | ||
|
|
d174f9e3e3 | ||
|
|
c8ba8f7e99 | ||
|
|
63dd8313f8 | ||
|
|
7be5867675 | ||
|
|
6061a2c4cc | ||
|
|
09118e417c | ||
|
|
71b90ae208 | ||
|
|
8844855353 | ||
|
|
0eb310e102 | ||
|
|
cdd094220c | ||
|
|
7a7d98c96a | ||
|
|
9f99f25db5 | ||
|
|
8c839a20c2 | ||
|
|
8e6e851c5b | ||
|
|
edd4cca02f | ||
|
|
867970258e | ||
|
|
d0cb5998c4 | ||
|
|
a5c97220e7 | ||
|
|
598e5e4370 | ||
|
|
03076ee214 | ||
|
|
eabd349902 |
47
README.rst
47
README.rst
@@ -24,10 +24,10 @@ Written in Python (compatible with Python 2 & 3).
|
|||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
http://yamllint.readthedocs.org/
|
http://yamllint.readthedocs.io/
|
||||||
|
|
||||||
Short overview
|
Overview
|
||||||
--------------
|
--------
|
||||||
|
|
||||||
Screenshot
|
Screenshot
|
||||||
^^^^^^^^^^
|
^^^^^^^^^^
|
||||||
@@ -50,13 +50,6 @@ On Debian 9+ / Ubuntu 16.04+:
|
|||||||
|
|
||||||
sudo apt-get install yamllint
|
sudo apt-get install yamllint
|
||||||
|
|
||||||
On older Debian / Ubuntu versions:
|
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
sudo add-apt-repository -y ppa:adrienverge/ppa && sudo apt-get update
|
|
||||||
sudo apt-get install yamllint
|
|
||||||
|
|
||||||
Alternatively using pip, the Python package manager:
|
Alternatively using pip, the Python package manager:
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
@@ -82,15 +75,19 @@ Usage
|
|||||||
yamllint -d relaxed file.yaml
|
yamllint -d relaxed file.yaml
|
||||||
|
|
||||||
# Use a custom lint configuration
|
# Use a custom lint configuration
|
||||||
yamllint -c ~/myconfig file.yml
|
yamllint -c /path/to/myconfig file-to-lint.yaml
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
# Output a parsable format (for syntax checking in editors like Vim, emacs...)
|
# Output a parsable format (for syntax checking in editors like Vim, emacs...)
|
||||||
yamllint -f parsable file.yaml
|
yamllint -f parsable file.yaml
|
||||||
|
|
||||||
Configuration example
|
`Read more in the complete documentation! <http://yamllint.readthedocs.io/>`_
|
||||||
^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
Features
|
||||||
|
^^^^^^^^
|
||||||
|
|
||||||
|
Here is a yamllint configuration file example:
|
||||||
|
|
||||||
.. code:: yaml
|
.. code:: yaml
|
||||||
|
|
||||||
@@ -104,3 +101,27 @@ Configuration example
|
|||||||
|
|
||||||
# don't bother me with this rule
|
# don't bother me with this rule
|
||||||
indentation: disable
|
indentation: disable
|
||||||
|
|
||||||
|
Within a YAML file, special comments can be used to disable checks for a single
|
||||||
|
line:
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
This line is waaaaaaaaaay too long # yamllint disable-line
|
||||||
|
|
||||||
|
or for a whole block:
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
# yamllint disable rule:colons
|
||||||
|
- Lorem : ipsum
|
||||||
|
dolor : sit amet,
|
||||||
|
consectetur : adipiscing elit
|
||||||
|
# yamllint enable
|
||||||
|
|
||||||
|
`Read more in the complete documentation! <http://yamllint.readthedocs.io/>`_
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
`GPL version 3 <LICENSE>`_
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
Configuration
|
Configuration
|
||||||
=============
|
=============
|
||||||
|
|
||||||
yamllint uses a set of *rules* to check sources files for problems. Each rule is
|
yamllint uses a set of :doc:`rules <rules>` to check source files for problems.
|
||||||
independent from the others, and can be enabled, disabled or tweaked. All these
|
Each rule is independent from the others, and can be enabled, disabled or
|
||||||
settings can be gathered in a configuration file.
|
tweaked. All these settings can be gathered in a configuration file.
|
||||||
|
|
||||||
To use a custom configuration file, either name it ``.yamllint`` in your working
|
To use a custom configuration file, use the ``-c`` option:
|
||||||
directory, or use the ``-c`` option:
|
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
yamllint -c ~/myconfig file.yaml
|
yamllint -c /path/to/myconfig file-to-lint.yaml
|
||||||
|
|
||||||
|
If ``-c`` is not provided, yamllint will look for a configuration file in the
|
||||||
|
following locations (by order of preference):
|
||||||
|
|
||||||
|
- ``.yamllint`` in the current working directory
|
||||||
|
- ``$XDG_CONFIG_HOME/yamllint/config``
|
||||||
|
- ``~/.config/yamllint/config``
|
||||||
|
|
||||||
|
Finally if no config file is found, the default configuration is applied.
|
||||||
|
|
||||||
Default configuration
|
Default configuration
|
||||||
---------------------
|
---------------------
|
||||||
@@ -23,7 +31,10 @@ Unless told otherwise, yamllint uses its ``default`` configuration:
|
|||||||
Details on rules can be found on :doc:`the rules page <rules>`.
|
Details on rules can be found on :doc:`the rules page <rules>`.
|
||||||
|
|
||||||
There is another pre-defined configuration named ``relaxed``. As its name
|
There is another pre-defined configuration named ``relaxed``. As its name
|
||||||
suggests, it is more tolerant.
|
suggests, it is more tolerant:
|
||||||
|
|
||||||
|
.. literalinclude:: ../yamllint/conf/relaxed.yaml
|
||||||
|
:language: yaml
|
||||||
|
|
||||||
It can be chosen using:
|
It can be chosen using:
|
||||||
|
|
||||||
|
|||||||
75
docs/disable_with_comments.rst
Normal file
75
docs/disable_with_comments.rst
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
Disable with comments
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Disabling checks for a specific line
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
To prevent yamllint from reporting problems for a specific line, add a directive
|
||||||
|
comment (``# yamllint disable-line ...``) on that line, or on the line above.
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
# The following mapping contains the same key twice,
|
||||||
|
# but I know what I'm doing:
|
||||||
|
key: value 1
|
||||||
|
key: value 2 # yamllint disable-line rule:key-duplicates
|
||||||
|
|
||||||
|
- This line is waaaaaaaaaay too long but yamllint will not report anything about it. # yamllint disable-line rule:line-length
|
||||||
|
This line will be checked by yamllint.
|
||||||
|
|
||||||
|
or:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
# The following mapping contains the same key twice,
|
||||||
|
# but I know what I'm doing:
|
||||||
|
key: value 1
|
||||||
|
# yamllint disable-line rule:key-duplicates
|
||||||
|
key: value 2
|
||||||
|
|
||||||
|
# yamllint disable-line rule:line-length
|
||||||
|
- This line is waaaaaaaaaay too long but yamllint will not report anything about it.
|
||||||
|
This line will be checked by yamllint.
|
||||||
|
|
||||||
|
It it possible, although not recommend, to disabled **all** rules for a
|
||||||
|
specific line:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
# yamllint disable-line
|
||||||
|
- { all : rules ,are disabled for this line}
|
||||||
|
|
||||||
|
If you need to disable multiple rules, it is allowed to chain rules like this:
|
||||||
|
``# yamllint disable-line rule:hyphens rule:commas rule:indentation``.
|
||||||
|
|
||||||
|
Disabling checks for all (or part of) the file
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
To prevent yamllint from reporting problems for the whoe file, or for a block of
|
||||||
|
lines within the file, use ``# yamllint disable ...`` and ``# yamllint enable
|
||||||
|
...`` directive comments. For instance:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
# yamllint disable rule:colons
|
||||||
|
- Lorem : ipsum
|
||||||
|
dolor : sit amet,
|
||||||
|
consectetur : adipiscing elit
|
||||||
|
# yamllint enable rule:colons
|
||||||
|
|
||||||
|
- rest of the document...
|
||||||
|
|
||||||
|
It it possible, although not recommend, to disabled **all** rules:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
# yamllint disable
|
||||||
|
- Lorem :
|
||||||
|
ipsum:
|
||||||
|
dolor : [ sit,amet]
|
||||||
|
- consectetur : adipiscing elit
|
||||||
|
# yamllint enable
|
||||||
|
|
||||||
|
If you need to disable multiple rules, it is allowed to chain rules like this:
|
||||||
|
``# yamllint disable rule:hyphens rule:commas rule:indentation``.
|
||||||
@@ -23,5 +23,6 @@ Table of contents
|
|||||||
quickstart
|
quickstart
|
||||||
configuration
|
configuration
|
||||||
rules
|
rules
|
||||||
|
disable_with_comments
|
||||||
development
|
development
|
||||||
text_editors
|
text_editors
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (C) 2016 Adrien Vergé
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import locale
|
||||||
|
|
||||||
|
|
||||||
|
locale.setlocale(locale.LC_ALL, 'C')
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ class CommentsTestCase(RuleTestCase):
|
|||||||
' #comment 3 bis\n'
|
' #comment 3 bis\n'
|
||||||
' # comment 3 ter\n'
|
' # comment 3 ter\n'
|
||||||
'\n'
|
'\n'
|
||||||
|
'################################\n'
|
||||||
|
'## comment 4\n'
|
||||||
|
'##comment 5\n'
|
||||||
|
'\n'
|
||||||
'string: "Une longue phrase." # this is French\n', conf)
|
'string: "Une longue phrase." # this is French\n', conf)
|
||||||
|
|
||||||
def test_starting_space(self):
|
def test_starting_space(self):
|
||||||
@@ -52,7 +56,11 @@ class CommentsTestCase(RuleTestCase):
|
|||||||
'# comment 2\n'
|
'# comment 2\n'
|
||||||
'# comment 3\n'
|
'# comment 3\n'
|
||||||
' # comment 3 bis\n'
|
' # comment 3 bis\n'
|
||||||
' # comment 3 ter\n', conf)
|
' # comment 3 ter\n'
|
||||||
|
'\n'
|
||||||
|
'################################\n'
|
||||||
|
'## comment 4\n'
|
||||||
|
'## comment 5\n', conf)
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'#comment\n'
|
'#comment\n'
|
||||||
'\n'
|
'\n'
|
||||||
@@ -63,9 +71,14 @@ class CommentsTestCase(RuleTestCase):
|
|||||||
'# comment 2\n'
|
'# comment 2\n'
|
||||||
'#comment 3\n'
|
'#comment 3\n'
|
||||||
' #comment 3 bis\n'
|
' #comment 3 bis\n'
|
||||||
' # comment 3 ter\n', conf,
|
' # comment 3 ter\n'
|
||||||
|
'\n'
|
||||||
|
'################################\n'
|
||||||
|
'## comment 4\n'
|
||||||
|
'##comment 5\n', conf,
|
||||||
problem1=(2, 2), problem2=(6, 13),
|
problem1=(2, 2), problem2=(6, 13),
|
||||||
problem4=(9, 2), problem5=(10, 4))
|
problem3=(9, 2), problem4=(10, 4),
|
||||||
|
problem5=(15, 3))
|
||||||
|
|
||||||
def test_spaces_from_content(self):
|
def test_spaces_from_content(self):
|
||||||
conf = ('comments:\n'
|
conf = ('comments:\n'
|
||||||
@@ -106,13 +119,18 @@ class CommentsTestCase(RuleTestCase):
|
|||||||
' #comment 3 bis\n'
|
' #comment 3 bis\n'
|
||||||
' # comment 3 ter\n'
|
' # comment 3 ter\n'
|
||||||
'\n'
|
'\n'
|
||||||
|
'################################\n'
|
||||||
|
'## comment 4\n'
|
||||||
|
'##comment 5\n'
|
||||||
|
'\n'
|
||||||
'string: "Une longue phrase." # this is French\n', conf,
|
'string: "Une longue phrase." # this is French\n', conf,
|
||||||
problem1=(2, 2),
|
problem1=(2, 2),
|
||||||
problem2=(4, 7),
|
problem2=(4, 7),
|
||||||
problem3=(6, 11), problem4=(6, 12),
|
problem3=(6, 11), problem4=(6, 12),
|
||||||
problem5=(9, 2),
|
problem5=(9, 2),
|
||||||
problem6=(10, 4),
|
problem6=(10, 4),
|
||||||
problem7=(13, 30))
|
problem7=(15, 3),
|
||||||
|
problem8=(17, 30))
|
||||||
|
|
||||||
def test_empty_comment(self):
|
def test_empty_comment(self):
|
||||||
conf = ('comments:\n'
|
conf = ('comments:\n'
|
||||||
@@ -132,6 +150,14 @@ class CommentsTestCase(RuleTestCase):
|
|||||||
' min-spaces-from-content: 2\n')
|
' min-spaces-from-content: 2\n')
|
||||||
self.check('# comment\n', conf)
|
self.check('# comment\n', conf)
|
||||||
|
|
||||||
|
def test_last_line(self):
|
||||||
|
conf = ('comments:\n'
|
||||||
|
' require-starting-space: yes\n'
|
||||||
|
' min-spaces-from-content: 2\n'
|
||||||
|
'new-line-at-end-of-file: disable\n')
|
||||||
|
self.check('# comment with no newline char:\n'
|
||||||
|
'#', conf)
|
||||||
|
|
||||||
def test_multi_line_scalar(self):
|
def test_multi_line_scalar(self):
|
||||||
conf = ('comments:\n'
|
conf = ('comments:\n'
|
||||||
' require-starting-space: yes\n'
|
' require-starting-space: yes\n'
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class CommentsIndentationTestCase(RuleTestCase):
|
|||||||
'# line 2\n', conf, problem=(2, 2))
|
'# line 2\n', conf, problem=(2, 2))
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
' # line 1\n'
|
' # line 1\n'
|
||||||
' # line 2\n', conf, problem1=(2, 3), problem2=(3, 3))
|
' # line 2\n', conf, problem1=(2, 3))
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'obj:\n'
|
'obj:\n'
|
||||||
' # normal\n'
|
' # normal\n'
|
||||||
@@ -143,3 +143,15 @@ class CommentsIndentationTestCase(RuleTestCase):
|
|||||||
'# hey\n'
|
'# hey\n'
|
||||||
'# normal\n'
|
'# normal\n'
|
||||||
' #\n', conf, problem=(4, 2))
|
' #\n', conf, problem=(4, 2))
|
||||||
|
|
||||||
|
def test_inline_comment(self):
|
||||||
|
conf = 'comments-indentation: enable'
|
||||||
|
self.check('---\n'
|
||||||
|
'- a # inline\n'
|
||||||
|
'# ok\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'- a # inline\n'
|
||||||
|
' # not ok\n', conf, problem=(3, 2))
|
||||||
|
self.check('---\n'
|
||||||
|
' # not ok\n'
|
||||||
|
'- a # inline\n', conf, problem=(2, 2))
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ import unittest
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from yamllint.rules.common import (Comment, get_line_indent,
|
from yamllint.rules.common import get_line_indent
|
||||||
get_comments_between_tokens)
|
|
||||||
|
|
||||||
|
|
||||||
class CommonTestCase(unittest.TestCase):
|
class CommonTestCase(unittest.TestCase):
|
||||||
@@ -43,54 +42,3 @@ class CommonTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(get_line_indent(tokens[i]), 0)
|
self.assertEqual(get_line_indent(tokens[i]), 0)
|
||||||
for i in (13, 16, 18, 22, 24):
|
for i in (13, 16, 18, 22, 24):
|
||||||
self.assertEqual(get_line_indent(tokens[i]), 2)
|
self.assertEqual(get_line_indent(tokens[i]), 2)
|
||||||
|
|
||||||
def check_comments(self, buffer, *expected):
|
|
||||||
yaml_loader = yaml.BaseLoader(buffer)
|
|
||||||
|
|
||||||
comments = []
|
|
||||||
|
|
||||||
next = yaml_loader.peek_token()
|
|
||||||
while next is not None:
|
|
||||||
curr = yaml_loader.get_token()
|
|
||||||
next = yaml_loader.peek_token()
|
|
||||||
for comment in get_comments_between_tokens(curr, next):
|
|
||||||
comments.append(comment)
|
|
||||||
|
|
||||||
self.assertEqual(comments, list(expected))
|
|
||||||
|
|
||||||
def test_get_comments_between_tokens(self):
|
|
||||||
self.check_comments('# comment\n',
|
|
||||||
Comment(1, 1, '# comment', 0))
|
|
||||||
self.check_comments('---\n'
|
|
||||||
'# comment\n'
|
|
||||||
'...\n',
|
|
||||||
Comment(2, 1, '# comment', 0))
|
|
||||||
self.check_comments('---\n'
|
|
||||||
'# no newline char',
|
|
||||||
Comment(2, 1, '# no newline char', 0))
|
|
||||||
self.check_comments('# just comment',
|
|
||||||
Comment(1, 1, '# just comment', 0))
|
|
||||||
self.check_comments('\n'
|
|
||||||
' # indented comment\n',
|
|
||||||
Comment(2, 4, '# indented comment', 0))
|
|
||||||
self.check_comments('\n'
|
|
||||||
'# trailing spaces \n',
|
|
||||||
Comment(2, 1, '# trailing spaces ', 0))
|
|
||||||
self.check_comments('# comment one\n'
|
|
||||||
'\n'
|
|
||||||
'key: val # key=val\n'
|
|
||||||
'\n'
|
|
||||||
'# this is\n'
|
|
||||||
'# a block \n'
|
|
||||||
'# comment\n'
|
|
||||||
'\n'
|
|
||||||
'other:\n'
|
|
||||||
' - foo # equals\n'
|
|
||||||
' # bar\n',
|
|
||||||
Comment(1, 1, '# comment one', 0),
|
|
||||||
Comment(3, 11, '# key=val', 0),
|
|
||||||
Comment(5, 1, '# this is', 0),
|
|
||||||
Comment(6, 1, '# a block ', 0),
|
|
||||||
Comment(7, 1, '# comment', 0),
|
|
||||||
Comment(10, 10, '# equals', 0),
|
|
||||||
Comment(11, 10, '# bar', 0))
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from tests.common import RuleTestCase
|
from tests.common import RuleTestCase
|
||||||
from yamllint.parser import token_generator
|
from yamllint.parser import token_or_comment_generator, Comment
|
||||||
from yamllint.rules.indentation import check
|
from yamllint.rules.indentation import check
|
||||||
|
|
||||||
|
|
||||||
@@ -38,7 +38,8 @@ class IndentationStackTestCase(RuleTestCase):
|
|||||||
'check-multi-line-strings': False}
|
'check-multi-line-strings': False}
|
||||||
context = {}
|
context = {}
|
||||||
output = ''
|
output = ''
|
||||||
for elem in token_generator(source):
|
for elem in [t for t in token_or_comment_generator(source)
|
||||||
|
if not isinstance(t, Comment)]:
|
||||||
list(check(conf, elem.curr, elem.prev, elem.next, elem.nextnext,
|
list(check(conf, elem.curr, elem.prev, elem.next, elem.nextnext,
|
||||||
context))
|
context))
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,15 @@ class LineLengthTestCase(RuleTestCase):
|
|||||||
' # http://localhost/very/long/url\n'
|
' # http://localhost/very/long/url\n'
|
||||||
' comment\n'
|
' comment\n'
|
||||||
'...\n', conf)
|
'...\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'this:\n'
|
||||||
|
'is:\n'
|
||||||
|
'another:\n'
|
||||||
|
' - https://localhost/very/very/long/url\n'
|
||||||
|
'...\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'long_line: http://localhost/very/very/long/url\n', conf,
|
||||||
|
problem=(2, 21))
|
||||||
|
|
||||||
conf = 'line-length: {max: 20, allow-non-breakable-words: no}'
|
conf = 'line-length: {max: 20, allow-non-breakable-words: no}'
|
||||||
self.check('---\n' + 30 * 'A' + '\n', conf, problem=(2, 21))
|
self.check('---\n' + 30 * 'A' + '\n', conf, problem=(2, 21))
|
||||||
@@ -94,3 +103,45 @@ class LineLengthTestCase(RuleTestCase):
|
|||||||
' # http://localhost/very/long/url\n'
|
' # http://localhost/very/long/url\n'
|
||||||
' comment\n'
|
' comment\n'
|
||||||
'...\n', conf, problem=(5, 21))
|
'...\n', conf, problem=(5, 21))
|
||||||
|
self.check('---\n'
|
||||||
|
'this:\n'
|
||||||
|
'is:\n'
|
||||||
|
'another:\n'
|
||||||
|
' - https://localhost/very/very/long/url\n'
|
||||||
|
'...\n', conf, problem=(5, 21))
|
||||||
|
self.check('---\n'
|
||||||
|
'long_line: http://localhost/very/very/long/url\n'
|
||||||
|
'...\n', conf, problem=(2, 21))
|
||||||
|
|
||||||
|
conf = ('line-length: {max: 20, allow-non-breakable-words: yes}\n'
|
||||||
|
'trailing-spaces: disable')
|
||||||
|
self.check('---\n'
|
||||||
|
'loooooooooong+word+and+some+space+at+the+end \n',
|
||||||
|
conf, problem=(2, 21))
|
||||||
|
|
||||||
|
def test_non_breakable_inline_mappings(self):
|
||||||
|
conf = 'line-length: {max: 20, ' \
|
||||||
|
'allow-non-breakable-inline-mappings: yes}'
|
||||||
|
self.check('---\n'
|
||||||
|
'long_line: http://localhost/very/very/long/url\n'
|
||||||
|
'long line: http://localhost/very/very/long/url\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'- long line: http://localhost/very/very/long/url\n', conf)
|
||||||
|
|
||||||
|
self.check('---\n'
|
||||||
|
'long_line: http://localhost/short/url + word\n'
|
||||||
|
'long line: http://localhost/short/url + word\n',
|
||||||
|
conf, problem1=(2, 21), problem2=(3, 21))
|
||||||
|
|
||||||
|
conf = ('line-length: {max: 20,'
|
||||||
|
' allow-non-breakable-inline-mappings: yes}\n'
|
||||||
|
'trailing-spaces: disable')
|
||||||
|
self.check('---\n'
|
||||||
|
'long_line: and+some+space+at+the+end \n',
|
||||||
|
conf, problem=(2, 21))
|
||||||
|
self.check('---\n'
|
||||||
|
'long line: and+some+space+at+the+end \n',
|
||||||
|
conf, problem=(2, 21))
|
||||||
|
self.check('---\n'
|
||||||
|
'- long line: and+some+space+at+the+end \n',
|
||||||
|
conf, problem=(2, 21))
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ try:
|
|||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
import fcntl
|
||||||
|
import locale
|
||||||
import os
|
import os
|
||||||
|
import pty
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
@@ -64,6 +67,15 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
f.write('---\n'
|
f.write('---\n'
|
||||||
'key: value\n')
|
'key: value\n')
|
||||||
|
|
||||||
|
# non-ASCII chars
|
||||||
|
os.mkdir(os.path.join(self.wd, 'non-ascii'))
|
||||||
|
with open(os.path.join(self.wd, 'non-ascii', 'utf-8'), 'wb') as f:
|
||||||
|
f.write((u'---\n'
|
||||||
|
u'- hétérogénéité\n'
|
||||||
|
u'# 19.99 €\n'
|
||||||
|
u'- お早う御座います。\n'
|
||||||
|
u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'))
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
shutil.rmtree(self.wd)
|
shutil.rmtree(self.wd)
|
||||||
|
|
||||||
@@ -153,6 +165,49 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(out, '')
|
self.assertEqual(out, '')
|
||||||
self.assertRegexpMatches(err, r'^invalid config: not a dict')
|
self.assertRegexpMatches(err, r'^invalid config: not a dict')
|
||||||
|
|
||||||
|
def test_run_with_config_file(self):
|
||||||
|
with open(os.path.join(self.wd, 'config'), 'w') as f:
|
||||||
|
f.write('rules: {trailing-spaces: disable}')
|
||||||
|
|
||||||
|
with self.assertRaises(SystemExit) as ctx:
|
||||||
|
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
|
||||||
|
self.assertEqual(ctx.exception.code, 0)
|
||||||
|
|
||||||
|
with open(os.path.join(self.wd, 'config'), 'w') as f:
|
||||||
|
f.write('rules: {trailing-spaces: enable}')
|
||||||
|
|
||||||
|
with self.assertRaises(SystemExit) as ctx:
|
||||||
|
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
|
||||||
|
self.assertEqual(ctx.exception.code, 1)
|
||||||
|
|
||||||
|
def test_run_with_user_global_config_file(self):
|
||||||
|
home = os.path.join(self.wd, 'fake-home')
|
||||||
|
os.mkdir(home)
|
||||||
|
dir = os.path.join(home, '.config')
|
||||||
|
os.mkdir(dir)
|
||||||
|
dir = os.path.join(dir, 'yamllint')
|
||||||
|
os.mkdir(dir)
|
||||||
|
config = os.path.join(dir, 'config')
|
||||||
|
|
||||||
|
temp = os.environ['HOME']
|
||||||
|
os.environ['HOME'] = home
|
||||||
|
|
||||||
|
with open(config, 'w') as f:
|
||||||
|
f.write('rules: {trailing-spaces: disable}')
|
||||||
|
|
||||||
|
with self.assertRaises(SystemExit) as ctx:
|
||||||
|
cli.run((os.path.join(self.wd, 'a.yaml'), ))
|
||||||
|
self.assertEqual(ctx.exception.code, 0)
|
||||||
|
|
||||||
|
with open(config, 'w') as f:
|
||||||
|
f.write('rules: {trailing-spaces: enable}')
|
||||||
|
|
||||||
|
with self.assertRaises(SystemExit) as ctx:
|
||||||
|
cli.run((os.path.join(self.wd, 'a.yaml'), ))
|
||||||
|
self.assertEqual(ctx.exception.code, 1)
|
||||||
|
|
||||||
|
os.environ['HOME'] = temp
|
||||||
|
|
||||||
def test_run_version(self):
|
def test_run_version(self):
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
sys.stdout, sys.stderr = StringIO(), StringIO()
|
||||||
with self.assertRaises(SystemExit) as ctx:
|
with self.assertRaises(SystemExit) as ctx:
|
||||||
@@ -218,6 +273,26 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(out, '')
|
self.assertEqual(out, '')
|
||||||
self.assertEqual(err, '')
|
self.assertEqual(err, '')
|
||||||
|
|
||||||
|
def test_run_non_ascii_file(self):
|
||||||
|
file = os.path.join(self.wd, 'non-ascii', 'utf-8')
|
||||||
|
|
||||||
|
# Make sure the default localization conditions on this "system"
|
||||||
|
# support UTF-8 encoding.
|
||||||
|
loc = locale.getlocale()
|
||||||
|
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
|
||||||
|
|
||||||
|
sys.stdout, sys.stderr = StringIO(), StringIO()
|
||||||
|
with self.assertRaises(SystemExit) as ctx:
|
||||||
|
cli.run(('-f', 'parsable', file))
|
||||||
|
|
||||||
|
locale.setlocale(locale.LC_ALL, loc)
|
||||||
|
|
||||||
|
self.assertEqual(ctx.exception.code, 0)
|
||||||
|
|
||||||
|
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
||||||
|
self.assertEqual(out, '')
|
||||||
|
self.assertEqual(err, '')
|
||||||
|
|
||||||
def test_run_multiple_files(self):
|
def test_run_multiple_files(self):
|
||||||
items = [os.path.join(self.wd, 'empty.yml'),
|
items = [os.path.join(self.wd, 'empty.yml'),
|
||||||
os.path.join(self.wd, 's')]
|
os.path.join(self.wd, 's')]
|
||||||
@@ -235,7 +310,7 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
'(key-duplicates)\n') % file)
|
'(key-duplicates)\n') % file)
|
||||||
self.assertEqual(err, '')
|
self.assertEqual(err, '')
|
||||||
|
|
||||||
def test_run_colored_output(self):
|
def test_run_piped_output_nocolor(self):
|
||||||
file = os.path.join(self.wd, 'a.yaml')
|
file = os.path.join(self.wd, 'a.yaml')
|
||||||
|
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
sys.stdout, sys.stderr = StringIO(), StringIO()
|
||||||
@@ -245,6 +320,38 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(ctx.exception.code, 1)
|
self.assertEqual(ctx.exception.code, 1)
|
||||||
|
|
||||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
||||||
|
self.assertEqual(out, (
|
||||||
|
'%s\n'
|
||||||
|
' 2:4 error trailing spaces (trailing-spaces)\n'
|
||||||
|
' 3:4 error no new line character at the end of file '
|
||||||
|
'(new-line-at-end-of-file)\n'
|
||||||
|
'\n' % file))
|
||||||
|
self.assertEqual(err, '')
|
||||||
|
|
||||||
|
def test_run_colored_output(self):
|
||||||
|
file = os.path.join(self.wd, 'a.yaml')
|
||||||
|
|
||||||
|
# Create a pseudo-TTY and redirect stdout to it
|
||||||
|
master, slave = pty.openpty()
|
||||||
|
sys.stdout = sys.stderr = os.fdopen(slave, 'w')
|
||||||
|
|
||||||
|
with self.assertRaises(SystemExit) as ctx:
|
||||||
|
cli.run((file, ))
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
self.assertEqual(ctx.exception.code, 1)
|
||||||
|
|
||||||
|
# Read output from TTY
|
||||||
|
output = os.fdopen(master, 'r')
|
||||||
|
flag = fcntl.fcntl(master, fcntl.F_GETFD)
|
||||||
|
fcntl.fcntl(master, fcntl.F_SETFL, flag | os.O_NONBLOCK)
|
||||||
|
|
||||||
|
out = output.read().replace('\r\n', '\n')
|
||||||
|
|
||||||
|
sys.stdout.close()
|
||||||
|
sys.stderr.close()
|
||||||
|
output.close()
|
||||||
|
|
||||||
self.assertEqual(out, (
|
self.assertEqual(out, (
|
||||||
'\033[4m%s\033[0m\n'
|
'\033[4m%s\033[0m\n'
|
||||||
' \033[2m2:4\033[0m \033[31merror\033[0m '
|
' \033[2m2:4\033[0m \033[31merror\033[0m '
|
||||||
@@ -253,4 +360,3 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
'no new line character at the end of file '
|
'no new line character at the end of file '
|
||||||
'\033[2m(new-line-at-end-of-file)\033[0m\n'
|
'\033[2m(new-line-at-end-of-file)\033[0m\n'
|
||||||
'\n' % file))
|
'\n' % file))
|
||||||
self.assertEqual(err, '')
|
|
||||||
|
|||||||
@@ -44,3 +44,15 @@ class LinterTestCase(unittest.TestCase):
|
|||||||
def test_run_on_list(self):
|
def test_run_on_list(self):
|
||||||
self.assertRaises(TypeError, linter.run,
|
self.assertRaises(TypeError, linter.run,
|
||||||
['h', 'e', 'l', 'l', 'o'], self.fake_config())
|
['h', 'e', 'l', 'l', 'o'], self.fake_config())
|
||||||
|
|
||||||
|
def test_run_on_non_ascii_chars(self):
|
||||||
|
s = (u'- hétérogénéité\n'
|
||||||
|
u'# 19.99 €\n')
|
||||||
|
linter.run(s, self.fake_config())
|
||||||
|
linter.run(s.encode('utf-8'), self.fake_config())
|
||||||
|
linter.run(s.encode('iso-8859-15'), self.fake_config())
|
||||||
|
|
||||||
|
s = (u'- お早う御座います。\n'
|
||||||
|
u'# الأَبْجَدِيَّة العَرَبِيَّة\n')
|
||||||
|
linter.run(s, self.fake_config())
|
||||||
|
linter.run(s.encode('utf-8'), self.fake_config())
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ import unittest
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from yamllint.parser import (line_generator, token_generator,
|
from yamllint.parser import (line_generator, token_or_comment_generator,
|
||||||
token_or_line_generator, Line, Token)
|
token_or_comment_or_line_generator,
|
||||||
|
Line, Token, Comment)
|
||||||
|
|
||||||
|
|
||||||
class ParserTestCase(unittest.TestCase):
|
class ParserTestCase(unittest.TestCase):
|
||||||
@@ -61,8 +62,8 @@ class ParserTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(e[2].line_no, 3)
|
self.assertEqual(e[2].line_no, 3)
|
||||||
self.assertEqual(e[2].content, 'at the end')
|
self.assertEqual(e[2].content, 'at the end')
|
||||||
|
|
||||||
def test_token_generator(self):
|
def test_token_or_comment_generator(self):
|
||||||
e = list(token_generator(''))
|
e = list(token_or_comment_generator(''))
|
||||||
self.assertEqual(len(e), 2)
|
self.assertEqual(len(e), 2)
|
||||||
self.assertEqual(e[0].prev, None)
|
self.assertEqual(e[0].prev, None)
|
||||||
self.assertIsInstance(e[0].curr, yaml.Token)
|
self.assertIsInstance(e[0].curr, yaml.Token)
|
||||||
@@ -71,16 +72,74 @@ class ParserTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(e[1].curr, e[0].next)
|
self.assertEqual(e[1].curr, e[0].next)
|
||||||
self.assertEqual(e[1].next, None)
|
self.assertEqual(e[1].next, None)
|
||||||
|
|
||||||
e = list(token_generator('---\n'
|
e = list(token_or_comment_generator('---\n'
|
||||||
'k: v\n'))
|
'k: v\n'))
|
||||||
self.assertEqual(len(e), 9)
|
self.assertEqual(len(e), 9)
|
||||||
self.assertIsInstance(e[3].curr, yaml.KeyToken)
|
self.assertIsInstance(e[3].curr, yaml.KeyToken)
|
||||||
self.assertIsInstance(e[5].curr, yaml.ValueToken)
|
self.assertIsInstance(e[5].curr, yaml.ValueToken)
|
||||||
|
|
||||||
def test_token_or_line_generator(self):
|
e = list(token_or_comment_generator('# start comment\n'
|
||||||
e = list(token_or_line_generator('---\n'
|
'- a\n'
|
||||||
'k: v\n'))
|
'- key: val # key=val\n'
|
||||||
self.assertEqual(len(e), 12)
|
'# this is\n'
|
||||||
|
'# a block \n'
|
||||||
|
'# comment\n'
|
||||||
|
'- c\n'
|
||||||
|
'# end comment\n'))
|
||||||
|
self.assertEqual(len(e), 21)
|
||||||
|
self.assertIsInstance(e[1], Comment)
|
||||||
|
self.assertEqual(e[1], Comment(1, 1, '# start comment', 0))
|
||||||
|
self.assertEqual(e[11], Comment(3, 13, '# key=val', 0))
|
||||||
|
self.assertEqual(e[12], Comment(4, 1, '# this is', 0))
|
||||||
|
self.assertEqual(e[13], Comment(5, 1, '# a block ', 0))
|
||||||
|
self.assertEqual(e[14], Comment(6, 1, '# comment', 0))
|
||||||
|
self.assertEqual(e[18], Comment(8, 1, '# end comment', 0))
|
||||||
|
|
||||||
|
e = list(token_or_comment_generator('---\n'
|
||||||
|
'# no newline char'))
|
||||||
|
self.assertEqual(e[2], Comment(2, 1, '# no newline char', 0))
|
||||||
|
|
||||||
|
e = list(token_or_comment_generator('# just comment'))
|
||||||
|
self.assertEqual(e[1], Comment(1, 1, '# just comment', 0))
|
||||||
|
|
||||||
|
e = list(token_or_comment_generator('\n'
|
||||||
|
' # indented comment\n'))
|
||||||
|
self.assertEqual(e[1], Comment(2, 4, '# indented comment', 0))
|
||||||
|
|
||||||
|
e = list(token_or_comment_generator('\n'
|
||||||
|
'# trailing spaces \n'))
|
||||||
|
self.assertEqual(e[1], Comment(2, 1, '# trailing spaces ', 0))
|
||||||
|
|
||||||
|
e = [c for c in
|
||||||
|
token_or_comment_generator('# block\n'
|
||||||
|
'# comment\n'
|
||||||
|
'- data # inline comment\n'
|
||||||
|
'# block\n'
|
||||||
|
'# comment\n'
|
||||||
|
'- k: v # inline comment\n'
|
||||||
|
'- [ l, ist\n'
|
||||||
|
'] # inline comment\n'
|
||||||
|
'- { m: ap\n'
|
||||||
|
'} # inline comment\n'
|
||||||
|
'# block comment\n'
|
||||||
|
'- data # inline comment\n')
|
||||||
|
if isinstance(c, Comment)]
|
||||||
|
self.assertEqual(len(e), 10)
|
||||||
|
self.assertFalse(e[0].is_inline())
|
||||||
|
self.assertFalse(e[1].is_inline())
|
||||||
|
self.assertTrue(e[2].is_inline())
|
||||||
|
self.assertFalse(e[3].is_inline())
|
||||||
|
self.assertFalse(e[4].is_inline())
|
||||||
|
self.assertTrue(e[5].is_inline())
|
||||||
|
self.assertTrue(e[6].is_inline())
|
||||||
|
self.assertTrue(e[7].is_inline())
|
||||||
|
self.assertFalse(e[8].is_inline())
|
||||||
|
self.assertTrue(e[9].is_inline())
|
||||||
|
|
||||||
|
def test_token_or_comment_or_line_generator(self):
|
||||||
|
e = list(token_or_comment_or_line_generator('---\n'
|
||||||
|
'k: v # k=v\n'))
|
||||||
|
self.assertEqual(len(e), 13)
|
||||||
self.assertIsInstance(e[0], Token)
|
self.assertIsInstance(e[0], Token)
|
||||||
self.assertIsInstance(e[0].curr, yaml.StreamStartToken)
|
self.assertIsInstance(e[0].curr, yaml.StreamStartToken)
|
||||||
self.assertIsInstance(e[1], Token)
|
self.assertIsInstance(e[1], Token)
|
||||||
@@ -89,5 +148,6 @@ class ParserTestCase(unittest.TestCase):
|
|||||||
self.assertIsInstance(e[3].curr, yaml.BlockMappingStartToken)
|
self.assertIsInstance(e[3].curr, yaml.BlockMappingStartToken)
|
||||||
self.assertIsInstance(e[4].curr, yaml.KeyToken)
|
self.assertIsInstance(e[4].curr, yaml.KeyToken)
|
||||||
self.assertIsInstance(e[6].curr, yaml.ValueToken)
|
self.assertIsInstance(e[6].curr, yaml.ValueToken)
|
||||||
self.assertIsInstance(e[8], Line)
|
self.assertIsInstance(e[8], Comment)
|
||||||
self.assertIsInstance(e[11], Line)
|
self.assertIsInstance(e[9], Line)
|
||||||
|
self.assertIsInstance(e[12], Line)
|
||||||
|
|||||||
304
tests/test_yamllint_directives.py
Normal file
304
tests/test_yamllint_directives.py
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (C) 2016 Adrien Vergé
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from tests.common import RuleTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class YamllintDirectivesTestCase(RuleTestCase):
|
||||||
|
conf = ('commas: disable\n'
|
||||||
|
'trailing-spaces: {}\n'
|
||||||
|
'colons: {max-spaces-before: 1}\n')
|
||||||
|
|
||||||
|
def test_disable_directive(self):
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'- bad : colon\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(3, 18, 'trailing-spaces'),
|
||||||
|
problem2=(4, 8, 'colons'),
|
||||||
|
problem3=(6, 7, 'colons'),
|
||||||
|
problem4=(6, 26, 'trailing-spaces'))
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'# yamllint disable\n'
|
||||||
|
'- bad : colon\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem=(3, 18, 'trailing-spaces'))
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'# yamllint disable\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'- bad : colon\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'# yamllint enable\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(8, 7, 'colons'),
|
||||||
|
problem2=(8, 26, 'trailing-spaces'))
|
||||||
|
|
||||||
|
def test_disable_directive_with_rules(self):
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'# yamllint disable rule:trailing-spaces\n'
|
||||||
|
'- bad : colon\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(3, 18, 'trailing-spaces'),
|
||||||
|
problem2=(5, 8, 'colons'),
|
||||||
|
problem3=(7, 7, 'colons'))
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'# yamllint disable rule:trailing-spaces\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'- bad : colon\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'# yamllint enable rule:trailing-spaces\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(5, 8, 'colons'),
|
||||||
|
problem2=(8, 7, 'colons'),
|
||||||
|
problem3=(8, 26, 'trailing-spaces'))
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'# yamllint disable rule:trailing-spaces\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'- bad : colon\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'# yamllint enable\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(5, 8, 'colons'),
|
||||||
|
problem2=(8, 7, 'colons'),
|
||||||
|
problem3=(8, 26, 'trailing-spaces'))
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'# yamllint disable\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'- bad : colon\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'# yamllint enable rule:trailing-spaces\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem=(8, 26, 'trailing-spaces'))
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'# yamllint disable rule:colons\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'# yamllint disable rule:trailing-spaces\n'
|
||||||
|
'- bad : colon\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'# yamllint enable rule:colons\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(4, 18, 'trailing-spaces'),
|
||||||
|
problem2=(9, 7, 'colons'))
|
||||||
|
|
||||||
|
def test_disable_line_directive(self):
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'# yamllint disable-line\n'
|
||||||
|
'- bad : colon\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(3, 18, 'trailing-spaces'),
|
||||||
|
problem2=(7, 7, 'colons'),
|
||||||
|
problem3=(7, 26, 'trailing-spaces'))
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'- bad : colon # yamllint disable-line\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(3, 18, 'trailing-spaces'),
|
||||||
|
problem2=(6, 7, 'colons'),
|
||||||
|
problem3=(6, 26, 'trailing-spaces'))
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'- bad : colon\n'
|
||||||
|
'- [valid , YAML] # yamllint disable-line\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(3, 18, 'trailing-spaces'),
|
||||||
|
problem2=(4, 8, 'colons'),
|
||||||
|
problem3=(6, 7, 'colons'),
|
||||||
|
problem4=(6, 26, 'trailing-spaces'))
|
||||||
|
|
||||||
|
def test_disable_line_directive_with_rules(self):
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'# yamllint disable-line rule:colons\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'- bad : colon\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(4, 18, 'trailing-spaces'),
|
||||||
|
problem2=(5, 8, 'colons'),
|
||||||
|
problem3=(7, 7, 'colons'),
|
||||||
|
problem4=(7, 26, 'trailing-spaces'))
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- trailing spaces # yamllint disable-line rule:colons \n'
|
||||||
|
'- bad : colon\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(3, 55, 'trailing-spaces'),
|
||||||
|
problem2=(4, 8, 'colons'),
|
||||||
|
problem3=(6, 7, 'colons'),
|
||||||
|
problem4=(6, 26, 'trailing-spaces'))
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'# yamllint disable-line rule:colons\n'
|
||||||
|
'- bad : colon\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(3, 18, 'trailing-spaces'),
|
||||||
|
problem2=(7, 7, 'colons'),
|
||||||
|
problem3=(7, 26, 'trailing-spaces'))
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'- bad : colon # yamllint disable-line rule:colons\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(3, 18, 'trailing-spaces'),
|
||||||
|
problem2=(6, 7, 'colons'),
|
||||||
|
problem3=(6, 26, 'trailing-spaces'))
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'- bad : colon\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'# yamllint disable-line rule:colons\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(3, 18, 'trailing-spaces'),
|
||||||
|
problem2=(4, 8, 'colons'),
|
||||||
|
problem3=(7, 26, 'trailing-spaces'))
|
||||||
|
self.check('---\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'- trailing spaces \n'
|
||||||
|
'- bad : colon\n'
|
||||||
|
'- [valid , YAML]\n'
|
||||||
|
'# yamllint disable-line rule:colons rule:trailing-spaces\n'
|
||||||
|
'- bad : colon and spaces \n'
|
||||||
|
'- [valid , YAML]\n',
|
||||||
|
self.conf,
|
||||||
|
problem1=(3, 18, 'trailing-spaces'),
|
||||||
|
problem2=(4, 8, 'colons'))
|
||||||
|
|
||||||
|
def test_directive_on_last_line(self):
|
||||||
|
conf = 'new-line-at-end-of-file: {}'
|
||||||
|
self.check('---\n'
|
||||||
|
'no new line',
|
||||||
|
conf,
|
||||||
|
problem=(2, 12, 'new-line-at-end-of-file'))
|
||||||
|
self.check('---\n'
|
||||||
|
'# yamllint disable\n'
|
||||||
|
'no new line',
|
||||||
|
conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'no new line # yamllint disable',
|
||||||
|
conf)
|
||||||
|
|
||||||
|
def test_indented_directive(self):
|
||||||
|
conf = 'brackets: {min-spaces-inside: 0, max-spaces-inside: 0}'
|
||||||
|
self.check('---\n'
|
||||||
|
'- a: 1\n'
|
||||||
|
' b:\n'
|
||||||
|
' c: [ x]\n',
|
||||||
|
conf,
|
||||||
|
problem=(4, 12, 'brackets'))
|
||||||
|
self.check('---\n'
|
||||||
|
'- a: 1\n'
|
||||||
|
' b:\n'
|
||||||
|
' # yamllint disable-line rule:brackets\n'
|
||||||
|
' c: [ x]\n',
|
||||||
|
conf)
|
||||||
|
|
||||||
|
def test_directive_on_itself(self):
|
||||||
|
conf = ('comments: {min-spaces-from-content: 2}\n'
|
||||||
|
'comments-indentation: {}\n')
|
||||||
|
self.check('---\n'
|
||||||
|
'- a: 1 # comment too close\n'
|
||||||
|
' b:\n'
|
||||||
|
' # wrong indentation\n'
|
||||||
|
' c: [x]\n',
|
||||||
|
conf,
|
||||||
|
problem1=(2, 8, 'comments'),
|
||||||
|
problem2=(4, 2, 'comments-indentation'))
|
||||||
|
self.check('---\n'
|
||||||
|
'# yamllint disable\n'
|
||||||
|
'- a: 1 # comment too close\n'
|
||||||
|
' b:\n'
|
||||||
|
' # wrong indentation\n'
|
||||||
|
' c: [x]\n',
|
||||||
|
conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'- a: 1 # yamllint disable-line\n'
|
||||||
|
' b:\n'
|
||||||
|
' # yamllint disable-line\n'
|
||||||
|
' # wrong indentation\n'
|
||||||
|
' c: [x]\n',
|
||||||
|
conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'- a: 1 # yamllint disable-line rule:comments\n'
|
||||||
|
' b:\n'
|
||||||
|
' # yamllint disable-line rule:comments-indentation\n'
|
||||||
|
' # wrong indentation\n'
|
||||||
|
' c: [x]\n',
|
||||||
|
conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'# yamllint disable\n'
|
||||||
|
'- a: 1 # comment too close\n'
|
||||||
|
' # yamllint enable rule:comments-indentation\n'
|
||||||
|
' b:\n'
|
||||||
|
' # wrong indentation\n'
|
||||||
|
' c: [x]\n',
|
||||||
|
conf,
|
||||||
|
problem=(6, 2, 'comments-indentation'))
|
||||||
@@ -22,7 +22,7 @@ indentation, etc."""
|
|||||||
|
|
||||||
|
|
||||||
APP_NAME = 'yamllint'
|
APP_NAME = 'yamllint'
|
||||||
APP_VERSION = '1.2.1'
|
APP_VERSION = '1.4.0'
|
||||||
APP_DESCRIPTION = __doc__
|
APP_DESCRIPTION = __doc__
|
||||||
|
|
||||||
__author__ = u'Adrien Vergé'
|
__author__ = u'Adrien Vergé'
|
||||||
|
|||||||
@@ -48,6 +48,17 @@ class Format(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def standard(problem, filename):
|
def standard(problem, filename):
|
||||||
|
line = ' %d:%d' % (problem.line, problem.column)
|
||||||
|
line += max(12 - len(line), 0) * ' '
|
||||||
|
line += problem.level
|
||||||
|
line += max(21 - len(line), 0) * ' '
|
||||||
|
line += problem.desc
|
||||||
|
if problem.rule:
|
||||||
|
line += ' (%s)' % problem.rule
|
||||||
|
return line
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def standard_color(problem, filename):
|
||||||
line = ' \033[2m%d:%d\033[0m' % (problem.line, problem.column)
|
line = ' \033[2m%d:%d\033[0m' % (problem.line, problem.column)
|
||||||
line += max(20 - len(line), 0) * ' '
|
line += max(20 - len(line), 0) * ' '
|
||||||
if problem.level == 'warning':
|
if problem.level == 'warning':
|
||||||
@@ -86,6 +97,13 @@ def run(argv=None):
|
|||||||
'simultaneously.', file=sys.stderr)
|
'simultaneously.', file=sys.stderr)
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
|
||||||
|
# User-global config is supposed to be in ~/.config/yamllint/config
|
||||||
|
if 'XDG_CONFIG_HOME' in os.environ:
|
||||||
|
user_global_config = os.path.join(
|
||||||
|
os.environ['XDG_CONFIG_HOME'], 'yamllint', 'config')
|
||||||
|
else:
|
||||||
|
user_global_config = os.path.expanduser('~/.config/yamllint/config')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if args.config_data is not None:
|
if args.config_data is not None:
|
||||||
if args.config_data != '' and ':' not in args.config_data:
|
if args.config_data != '' and ':' not in args.config_data:
|
||||||
@@ -95,6 +113,8 @@ def run(argv=None):
|
|||||||
conf = YamlLintConfig(file=args.config_file)
|
conf = YamlLintConfig(file=args.config_file)
|
||||||
elif os.path.isfile('.yamllint'):
|
elif os.path.isfile('.yamllint'):
|
||||||
conf = YamlLintConfig(file='.yamllint')
|
conf = YamlLintConfig(file='.yamllint')
|
||||||
|
elif os.path.isfile(user_global_config):
|
||||||
|
conf = YamlLintConfig(file=user_global_config)
|
||||||
else:
|
else:
|
||||||
conf = YamlLintConfig('extends: default')
|
conf = YamlLintConfig('extends: default')
|
||||||
except YamlLintConfigError as e:
|
except YamlLintConfigError as e:
|
||||||
@@ -110,11 +130,17 @@ def run(argv=None):
|
|||||||
for problem in linter.run(f, conf):
|
for problem in linter.run(f, conf):
|
||||||
if args.format == 'parsable':
|
if args.format == 'parsable':
|
||||||
print(Format.parsable(problem, file))
|
print(Format.parsable(problem, file))
|
||||||
else:
|
elif sys.stdout.isatty():
|
||||||
if first:
|
if first:
|
||||||
print('\033[4m%s\033[0m' % file)
|
print('\033[4m%s\033[0m' % file)
|
||||||
first = False
|
first = False
|
||||||
|
|
||||||
|
print(Format.standard_color(problem, file))
|
||||||
|
else:
|
||||||
|
if first:
|
||||||
|
print(file)
|
||||||
|
first = False
|
||||||
|
|
||||||
print(Format.standard(problem, file))
|
print(Format.standard(problem, file))
|
||||||
|
|
||||||
if return_code == 0 and problem.level == 'error':
|
if return_code == 0 and problem.level == 'error':
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ rules:
|
|||||||
line-length:
|
line-length:
|
||||||
max: 80
|
max: 80
|
||||||
allow-non-breakable-words: yes
|
allow-non-breakable-words: yes
|
||||||
|
allow-non-breakable-inline-mappings: no
|
||||||
new-line-at-end-of-file: enable
|
new-line-at-end-of-file: enable
|
||||||
new-lines:
|
new-lines:
|
||||||
type: unix
|
type: unix
|
||||||
|
|||||||
@@ -25,3 +25,4 @@ rules:
|
|||||||
indent-sequences: consistent
|
indent-sequences: consistent
|
||||||
line-length:
|
line-length:
|
||||||
level: warning
|
level: warning
|
||||||
|
allow-non-breakable-inline-mappings: yes
|
||||||
|
|||||||
@@ -14,6 +14,8 @@
|
|||||||
# 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 re
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from yamllint import parser
|
from yamllint import parser
|
||||||
@@ -56,13 +58,69 @@ def get_costemic_problems(buffer, conf):
|
|||||||
|
|
||||||
# Split token rules from line rules
|
# Split token rules from line rules
|
||||||
token_rules = [r for r in rules if r.TYPE == 'token']
|
token_rules = [r for r in rules if r.TYPE == 'token']
|
||||||
|
comment_rules = [r for r in rules if r.TYPE == 'comment']
|
||||||
line_rules = [r for r in rules if r.TYPE == 'line']
|
line_rules = [r for r in rules if r.TYPE == 'line']
|
||||||
|
|
||||||
context = {}
|
context = {}
|
||||||
for rule in token_rules:
|
for rule in token_rules:
|
||||||
context[rule.ID] = {}
|
context[rule.ID] = {}
|
||||||
|
|
||||||
for elem in parser.token_or_line_generator(buffer):
|
class DisableDirective():
|
||||||
|
def __init__(self):
|
||||||
|
self.rules = set()
|
||||||
|
self.all_rules = set([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
|
||||||
|
|
||||||
|
if re.match(r'^# yamllint disable( rule:\S+)*\s*$', comment):
|
||||||
|
rules = [item[5:] for item in comment[18:].split(' ')][1:]
|
||||||
|
if len(rules) == 0:
|
||||||
|
self.rules = self.all_rules.copy()
|
||||||
|
else:
|
||||||
|
for id in rules:
|
||||||
|
if id in self.all_rules:
|
||||||
|
self.rules.add(id)
|
||||||
|
|
||||||
|
elif re.match(r'^# yamllint enable( rule:\S+)*\s*$', comment):
|
||||||
|
rules = [item[5:] for item in comment[17:].split(' ')][1:]
|
||||||
|
if len(rules) == 0:
|
||||||
|
self.rules.clear()
|
||||||
|
else:
|
||||||
|
for id in rules:
|
||||||
|
self.rules.discard(id)
|
||||||
|
|
||||||
|
def is_disabled_by_directive(self, problem):
|
||||||
|
return problem.rule in self.rules
|
||||||
|
|
||||||
|
class DisableLineDirective(DisableDirective):
|
||||||
|
def process_comment(self, comment):
|
||||||
|
try:
|
||||||
|
comment = str(comment)
|
||||||
|
except UnicodeError:
|
||||||
|
return # this certainly wasn't a yamllint directive comment
|
||||||
|
|
||||||
|
if re.match(r'^# yamllint disable-line( rule:\S+)*\s*$', comment):
|
||||||
|
rules = [item[5:] for item in comment[23:].split(' ')][1:]
|
||||||
|
if len(rules) == 0:
|
||||||
|
self.rules = self.all_rules.copy()
|
||||||
|
else:
|
||||||
|
for id in rules:
|
||||||
|
if id in self.all_rules:
|
||||||
|
self.rules.add(id)
|
||||||
|
|
||||||
|
# Use a cache to store problems and flush it only when a end of line is
|
||||||
|
# found. This allows the use of yamllint directive to disable some rules on
|
||||||
|
# some lines.
|
||||||
|
cache = []
|
||||||
|
disabled = DisableDirective()
|
||||||
|
disabled_for_line = DisableLineDirective()
|
||||||
|
disabled_for_next_line = DisableLineDirective()
|
||||||
|
|
||||||
|
for elem in parser.token_or_comment_or_line_generator(buffer):
|
||||||
if isinstance(elem, parser.Token):
|
if isinstance(elem, parser.Token):
|
||||||
for rule in token_rules:
|
for rule in token_rules:
|
||||||
rule_conf = conf.rules[rule.ID]
|
rule_conf = conf.rules[rule.ID]
|
||||||
@@ -72,15 +130,39 @@ def get_costemic_problems(buffer, conf):
|
|||||||
context[rule.ID]):
|
context[rule.ID]):
|
||||||
problem.rule = rule.ID
|
problem.rule = rule.ID
|
||||||
problem.level = rule_conf['level']
|
problem.level = rule_conf['level']
|
||||||
yield problem
|
cache.append(problem)
|
||||||
|
elif isinstance(elem, parser.Comment):
|
||||||
|
for rule in comment_rules:
|
||||||
|
rule_conf = conf.rules[rule.ID]
|
||||||
|
for problem in rule.check(rule_conf, elem):
|
||||||
|
problem.rule = rule.ID
|
||||||
|
problem.level = rule_conf['level']
|
||||||
|
cache.append(problem)
|
||||||
|
|
||||||
|
disabled.process_comment(elem)
|
||||||
|
if elem.is_inline():
|
||||||
|
disabled_for_line.process_comment(elem)
|
||||||
|
else:
|
||||||
|
disabled_for_next_line.process_comment(elem)
|
||||||
elif isinstance(elem, parser.Line):
|
elif isinstance(elem, parser.Line):
|
||||||
for rule in line_rules:
|
for rule in line_rules:
|
||||||
rule_conf = conf.rules[rule.ID]
|
rule_conf = conf.rules[rule.ID]
|
||||||
for problem in rule.check(rule_conf, elem):
|
for problem in rule.check(rule_conf, elem):
|
||||||
problem.rule = rule.ID
|
problem.rule = rule.ID
|
||||||
problem.level = rule_conf['level']
|
problem.level = rule_conf['level']
|
||||||
|
cache.append(problem)
|
||||||
|
|
||||||
|
# This is the last token/comment/line of this line, let's flush the
|
||||||
|
# problems found (but filter them according to the directives)
|
||||||
|
for problem in cache:
|
||||||
|
if not (disabled_for_line.is_disabled_by_directive(problem) or
|
||||||
|
disabled.is_disabled_by_directive(problem)):
|
||||||
yield problem
|
yield problem
|
||||||
|
|
||||||
|
disabled_for_line = disabled_for_next_line
|
||||||
|
disabled_for_next_line = DisableLineDirective()
|
||||||
|
cache = []
|
||||||
|
|
||||||
|
|
||||||
def get_syntax_error(buffer):
|
def get_syntax_error(buffer):
|
||||||
try:
|
try:
|
||||||
@@ -94,6 +176,9 @@ def get_syntax_error(buffer):
|
|||||||
|
|
||||||
|
|
||||||
def _run(buffer, conf):
|
def _run(buffer, conf):
|
||||||
|
assert hasattr(buffer, '__getitem__'), \
|
||||||
|
'_run() argument must be a buffer, not a stream'
|
||||||
|
|
||||||
# If the document contains a syntax error, save it and yield it at the
|
# If the document contains a syntax error, save it and yield it at the
|
||||||
# right line
|
# right line
|
||||||
syntax_error = get_syntax_error(buffer)
|
syntax_error = get_syntax_error(buffer)
|
||||||
|
|||||||
@@ -38,6 +38,40 @@ class Token(object):
|
|||||||
self.nextnext = nextnext
|
self.nextnext = nextnext
|
||||||
|
|
||||||
|
|
||||||
|
class Comment(object):
|
||||||
|
def __init__(self, line_no, column_no, buffer, pointer,
|
||||||
|
token_before=None, token_after=None, comment_before=None):
|
||||||
|
self.line_no = line_no
|
||||||
|
self.column_no = column_no
|
||||||
|
self.buffer = buffer
|
||||||
|
self.pointer = pointer
|
||||||
|
self.token_before = token_before
|
||||||
|
self.token_after = token_after
|
||||||
|
self.comment_before = comment_before
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
end = self.buffer.find('\n', self.pointer)
|
||||||
|
if end == -1:
|
||||||
|
end = self.buffer.find('\0', self.pointer)
|
||||||
|
if end != -1:
|
||||||
|
return self.buffer[self.pointer:end]
|
||||||
|
return self.buffer[self.pointer:]
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return (isinstance(other, Comment) and
|
||||||
|
self.line_no == other.line_no and
|
||||||
|
self.column_no == other.column_no and
|
||||||
|
str(self) == str(other))
|
||||||
|
|
||||||
|
def is_inline(self):
|
||||||
|
return (
|
||||||
|
not isinstance(self.token_before, yaml.StreamStartToken) and
|
||||||
|
self.line_no == self.token_before.end_mark.line + 1 and
|
||||||
|
# sometimes token end marks are on the next line
|
||||||
|
self.buffer[self.token_before.end_mark.pointer - 1] != '\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def line_generator(buffer):
|
def line_generator(buffer):
|
||||||
line_no = 1
|
line_no = 1
|
||||||
cur = 0
|
cur = 0
|
||||||
@@ -51,7 +85,39 @@ def line_generator(buffer):
|
|||||||
yield Line(line_no, buffer, start=cur, end=len(buffer))
|
yield Line(line_no, buffer, start=cur, end=len(buffer))
|
||||||
|
|
||||||
|
|
||||||
def token_generator(buffer):
|
def comments_between_tokens(token1, token2):
|
||||||
|
"""Find all comments between two tokens"""
|
||||||
|
if token2 is None:
|
||||||
|
buf = token1.end_mark.buffer[token1.end_mark.pointer:]
|
||||||
|
elif (token1.end_mark.line == token2.start_mark.line and
|
||||||
|
not isinstance(token1, yaml.StreamStartToken) and
|
||||||
|
not isinstance(token2, yaml.StreamEndToken)):
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
buf = token1.end_mark.buffer[token1.end_mark.pointer:
|
||||||
|
token2.start_mark.pointer]
|
||||||
|
|
||||||
|
line_no = token1.end_mark.line + 1
|
||||||
|
column_no = token1.end_mark.column + 1
|
||||||
|
pointer = token1.end_mark.pointer
|
||||||
|
|
||||||
|
comment_before = None
|
||||||
|
for line in buf.split('\n'):
|
||||||
|
pos = line.find('#')
|
||||||
|
if pos != -1:
|
||||||
|
comment = Comment(line_no, column_no + pos,
|
||||||
|
token1.end_mark.buffer, pointer + pos,
|
||||||
|
token1, token2, comment_before)
|
||||||
|
yield comment
|
||||||
|
|
||||||
|
comment_before = comment
|
||||||
|
|
||||||
|
pointer += len(line) + 1
|
||||||
|
line_no += 1
|
||||||
|
column_no = 1
|
||||||
|
|
||||||
|
|
||||||
|
def token_or_comment_generator(buffer):
|
||||||
yaml_loader = yaml.BaseLoader(buffer)
|
yaml_loader = yaml.BaseLoader(buffer)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -63,6 +129,9 @@ def token_generator(buffer):
|
|||||||
|
|
||||||
yield Token(curr.start_mark.line + 1, curr, prev, next, nextnext)
|
yield Token(curr.start_mark.line + 1, curr, prev, next, nextnext)
|
||||||
|
|
||||||
|
for comment in comments_between_tokens(curr, next):
|
||||||
|
yield comment
|
||||||
|
|
||||||
prev = curr
|
prev = curr
|
||||||
curr = next
|
curr = next
|
||||||
|
|
||||||
@@ -70,19 +139,19 @@ def token_generator(buffer):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def token_or_line_generator(buffer):
|
def token_or_comment_or_line_generator(buffer):
|
||||||
"""Generator that mixes tokens and lines, ordering them by line number"""
|
"""Generator that mixes tokens and lines, ordering them by line number"""
|
||||||
token_gen = token_generator(buffer)
|
tok_or_com_gen = token_or_comment_generator(buffer)
|
||||||
line_gen = line_generator(buffer)
|
line_gen = line_generator(buffer)
|
||||||
|
|
||||||
token = next(token_gen, None)
|
tok_or_com = next(tok_or_com_gen, None)
|
||||||
line = next(line_gen, None)
|
line = next(line_gen, None)
|
||||||
|
|
||||||
while token is not None or line is not None:
|
while tok_or_com is not None or line is not None:
|
||||||
if token is None or (line is not None and
|
if tok_or_com is None or (line is not None and
|
||||||
token.line_no > line.line_no):
|
tok_or_com.line_no > line.line_no):
|
||||||
yield line
|
yield line
|
||||||
line = next(line_gen, None)
|
line = next(line_gen, None)
|
||||||
else:
|
else:
|
||||||
yield token
|
yield tok_or_com
|
||||||
token = next(token_gen, None)
|
tok_or_com = next(tok_or_com_gen, None)
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ Use this rule to control the position and formatting of comments.
|
|||||||
# This sentence
|
# This sentence
|
||||||
# is a block comment
|
# is a block comment
|
||||||
|
|
||||||
|
the following code snippet would **PASS**:
|
||||||
|
::
|
||||||
|
|
||||||
|
##############################
|
||||||
|
## This is some documentation
|
||||||
|
|
||||||
the following code snippet would **FAIL**:
|
the following code snippet would **FAIL**:
|
||||||
::
|
::
|
||||||
|
|
||||||
@@ -55,33 +61,29 @@ Use this rule to control the position and formatting of comments.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
from yamllint.linter import LintProblem
|
from yamllint.linter import LintProblem
|
||||||
from yamllint.rules.common import get_comments_between_tokens
|
|
||||||
|
|
||||||
|
|
||||||
ID = 'comments'
|
ID = 'comments'
|
||||||
TYPE = 'token'
|
TYPE = 'comment'
|
||||||
CONF = {'require-starting-space': bool,
|
CONF = {'require-starting-space': bool,
|
||||||
'min-spaces-from-content': int}
|
'min-spaces-from-content': int}
|
||||||
|
|
||||||
|
|
||||||
def check(conf, token, prev, next, nextnext, context):
|
def check(conf, comment):
|
||||||
for comment in get_comments_between_tokens(token, next):
|
if (conf['min-spaces-from-content'] != -1 and comment.is_inline() and
|
||||||
if (conf['min-spaces-from-content'] != -1 and
|
comment.pointer - comment.token_before.end_mark.pointer <
|
||||||
not isinstance(token, yaml.StreamStartToken) and
|
conf['min-spaces-from-content']):
|
||||||
comment.line == token.end_mark.line + 1):
|
yield LintProblem(comment.line_no, comment.column_no,
|
||||||
# Sometimes token end marks are on the next line
|
'too few spaces before comment')
|
||||||
if token.end_mark.buffer[token.end_mark.pointer - 1] != '\n':
|
|
||||||
if (comment.pointer - token.end_mark.pointer <
|
|
||||||
conf['min-spaces-from-content']):
|
|
||||||
yield LintProblem(comment.line, comment.column,
|
|
||||||
'too few spaces before comment')
|
|
||||||
|
|
||||||
if (conf['require-starting-space'] and
|
if conf['require-starting-space']:
|
||||||
comment.pointer + 1 < len(comment.buffer) and
|
text_start = comment.pointer + 1
|
||||||
comment.buffer[comment.pointer + 1] != ' ' and
|
while (comment.buffer[text_start] == '#' and
|
||||||
comment.buffer[comment.pointer + 1] != '\n'):
|
text_start < len(comment.buffer)):
|
||||||
yield LintProblem(comment.line, comment.column + 1,
|
text_start += 1
|
||||||
|
if (text_start < len(comment.buffer) and
|
||||||
|
comment.buffer[text_start] not in (' ', '\n', '\0')):
|
||||||
|
yield LintProblem(comment.line_no,
|
||||||
|
comment.column_no + text_start - comment.pointer,
|
||||||
'missing starting space in comment')
|
'missing starting space in comment')
|
||||||
|
|||||||
@@ -78,11 +78,11 @@ Use this rule to force comments to be indented like content.
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from yamllint.linter import LintProblem
|
from yamllint.linter import LintProblem
|
||||||
from yamllint.rules.common import get_line_indent, get_comments_between_tokens
|
from yamllint.rules.common import get_line_indent
|
||||||
|
|
||||||
|
|
||||||
ID = 'comments-indentation'
|
ID = 'comments-indentation'
|
||||||
TYPE = 'token'
|
TYPE = 'comment'
|
||||||
|
|
||||||
|
|
||||||
# Case A:
|
# Case A:
|
||||||
@@ -98,28 +98,42 @@ TYPE = 'token'
|
|||||||
# # commented line 2
|
# # commented line 2
|
||||||
# current: line
|
# current: line
|
||||||
|
|
||||||
def check(conf, token, prev, next, nextnext, context):
|
def check(conf, comment):
|
||||||
if prev is None:
|
# Only check block comments
|
||||||
|
if (not isinstance(comment.token_before, yaml.StreamStartToken) and
|
||||||
|
comment.token_before.end_mark.line + 1 == comment.line_no):
|
||||||
return
|
return
|
||||||
|
|
||||||
curr_line_indent = token.start_mark.column
|
next_line_indent = comment.token_after.start_mark.column
|
||||||
if isinstance(token, yaml.StreamEndToken):
|
if isinstance(comment.token_after, yaml.StreamEndToken):
|
||||||
curr_line_indent = 0
|
next_line_indent = 0
|
||||||
|
|
||||||
skip_first_line = True
|
if isinstance(comment.token_before, yaml.StreamStartToken):
|
||||||
if isinstance(prev, yaml.StreamStartToken):
|
|
||||||
skip_first_line = False
|
|
||||||
prev_line_indent = 0
|
prev_line_indent = 0
|
||||||
else:
|
else:
|
||||||
prev_line_indent = get_line_indent(prev)
|
prev_line_indent = get_line_indent(comment.token_before)
|
||||||
|
|
||||||
if prev_line_indent <= curr_line_indent:
|
# In the following case only the next line indent is valid:
|
||||||
prev_line_indent = -1 # disable it
|
# list:
|
||||||
|
# # comment
|
||||||
|
# - 1
|
||||||
|
# - 2
|
||||||
|
if prev_line_indent <= next_line_indent:
|
||||||
|
prev_line_indent = next_line_indent
|
||||||
|
|
||||||
for comment in get_comments_between_tokens(
|
# If two indents are valid but a previous comment went back to normal
|
||||||
prev, token, skip_first_line=skip_first_line):
|
# indent, for the next ones to do the same. In other words, avoid this:
|
||||||
if comment.column - 1 == curr_line_indent:
|
# list:
|
||||||
prev_line_indent = -1 # disable it
|
# - 1
|
||||||
elif comment.column - 1 != prev_line_indent:
|
# # comment on valid indent (0)
|
||||||
yield LintProblem(comment.line, comment.column,
|
# # comment on valid indent (4)
|
||||||
'comment not indented like content')
|
# other-list:
|
||||||
|
# - 2
|
||||||
|
if (comment.comment_before is not None and
|
||||||
|
not comment.comment_before.is_inline()):
|
||||||
|
prev_line_indent = comment.comment_before.column_no - 1
|
||||||
|
|
||||||
|
if (comment.column_no - 1 != prev_line_indent and
|
||||||
|
comment.column_no - 1 != next_line_indent):
|
||||||
|
yield LintProblem(comment.line_no, comment.column_no,
|
||||||
|
'comment not indented like content')
|
||||||
|
|||||||
@@ -48,27 +48,6 @@ def spaces_before(token, prev, next, min=-1, max=-1,
|
|||||||
token.start_mark.column + 1, min_desc)
|
token.start_mark.column + 1, min_desc)
|
||||||
|
|
||||||
|
|
||||||
class Comment(object):
|
|
||||||
def __init__(self, line, column, buffer, pointer):
|
|
||||||
self.line = line
|
|
||||||
self.column = column
|
|
||||||
self.buffer = buffer
|
|
||||||
self.pointer = pointer
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
end = self.buffer.find('\n', self.pointer)
|
|
||||||
if end == -1:
|
|
||||||
end = self.buffer.find('\0', self.pointer)
|
|
||||||
if end != -1:
|
|
||||||
return self.buffer[self.pointer:end]
|
|
||||||
return self.buffer[self.pointer:]
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return (self.line == other.line and
|
|
||||||
self.column == other.column and
|
|
||||||
str(self) == str(other))
|
|
||||||
|
|
||||||
|
|
||||||
def get_line_indent(token):
|
def get_line_indent(token):
|
||||||
"""Finds the indent of the line the token starts in."""
|
"""Finds the indent of the line the token starts in."""
|
||||||
start = token.start_mark.buffer.rfind('\n', 0,
|
start = token.start_mark.buffer.rfind('\n', 0,
|
||||||
@@ -98,35 +77,6 @@ def get_real_end_line(token):
|
|||||||
return end_line
|
return end_line
|
||||||
|
|
||||||
|
|
||||||
def get_comments_between_tokens(token1, token2, skip_first_line=False):
|
|
||||||
if token2 is None:
|
|
||||||
buf = token1.end_mark.buffer[token1.end_mark.pointer:]
|
|
||||||
elif (token1.end_mark.line == token2.start_mark.line and
|
|
||||||
not isinstance(token1, yaml.StreamStartToken) and
|
|
||||||
not isinstance(token2, yaml.StreamEndToken)):
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
buf = token1.end_mark.buffer[token1.end_mark.pointer:
|
|
||||||
token2.start_mark.pointer]
|
|
||||||
|
|
||||||
line_no = token1.end_mark.line + 1
|
|
||||||
column_no = token1.end_mark.column + 1
|
|
||||||
pointer = token1.end_mark.pointer
|
|
||||||
|
|
||||||
for line in buf.split('\n'):
|
|
||||||
if skip_first_line:
|
|
||||||
skip_first_line = False
|
|
||||||
else:
|
|
||||||
pos = line.find('#')
|
|
||||||
if pos != -1:
|
|
||||||
yield Comment(line_no, column_no + pos,
|
|
||||||
token1.end_mark.buffer, pointer + pos)
|
|
||||||
|
|
||||||
pointer += len(line) + 1
|
|
||||||
line_no += 1
|
|
||||||
column_no = 1
|
|
||||||
|
|
||||||
|
|
||||||
def is_explicit_key(token):
|
def is_explicit_key(token):
|
||||||
# explicit key:
|
# explicit key:
|
||||||
# ? key
|
# ? key
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ CONF = {'max': int,
|
|||||||
|
|
||||||
def check(conf, line):
|
def check(conf, line):
|
||||||
if line.start == line.end and line.end < len(line.buffer):
|
if line.start == line.end and line.end < len(line.buffer):
|
||||||
# Only alert on the last blank line of a serie
|
# Only alert on the last blank line of a series
|
||||||
if (line.end < len(line.buffer) - 1 and
|
if (line.end < len(line.buffer) - 1 and
|
||||||
line.buffer[line.end + 1] == '\n'):
|
line.buffer[line.end + 1] == '\n'):
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ Use this rule to set a limit to lines length.
|
|||||||
* ``allow-non-breakable-words`` is used to allow non breakable words (without
|
* ``allow-non-breakable-words`` is used to allow non breakable words (without
|
||||||
spaces inside) to overflow the limit. This is useful for long URLs, for
|
spaces inside) to overflow the limit. This is useful for long URLs, for
|
||||||
instance. Use ``yes`` to allow, ``no`` to forbid.
|
instance. Use ``yes`` to allow, ``no`` to forbid.
|
||||||
|
* ``allow-non-breakable-inline-mappings`` implies ``allow-non-breakable-words``
|
||||||
|
and extends it to also allow non-breakable words in inline mappings.
|
||||||
|
|
||||||
.. rubric:: Examples
|
.. rubric:: Examples
|
||||||
|
|
||||||
@@ -59,7 +61,20 @@ Use this rule to set a limit to lines length.
|
|||||||
the following code snippet would **FAIL**:
|
the following code snippet would **FAIL**:
|
||||||
::
|
::
|
||||||
|
|
||||||
- this line is waaaaaaaaaaaaaay too long but could be easily splitted...
|
- this line is waaaaaaaaaaaaaay too long but could be easily split...
|
||||||
|
|
||||||
|
and the following code snippet would also **FAIL**:
|
||||||
|
::
|
||||||
|
|
||||||
|
- foobar: http://localhost/very/very/very/very/very/very/very/very/long/url
|
||||||
|
|
||||||
|
#. With ``line-length: {max: 60, allow-non-breakable-words: yes,
|
||||||
|
allow-non-breakable-inline-mappings: yes}``
|
||||||
|
|
||||||
|
the following code snippet would **PASS**:
|
||||||
|
::
|
||||||
|
|
||||||
|
- foobar: http://localhost/very/very/very/very/very/very/very/very/long/url
|
||||||
|
|
||||||
#. With ``line-length: {max: 60, allow-non-breakable-words: no}``
|
#. With ``line-length: {max: 60, allow-non-breakable-words: no}``
|
||||||
|
|
||||||
@@ -73,29 +88,50 @@ Use this rule to set a limit to lines length.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
from yamllint.linter import LintProblem
|
from yamllint.linter import LintProblem
|
||||||
|
|
||||||
|
|
||||||
ID = 'line-length'
|
ID = 'line-length'
|
||||||
TYPE = 'line'
|
TYPE = 'line'
|
||||||
CONF = {'max': int,
|
CONF = {'max': int,
|
||||||
'allow-non-breakable-words': bool}
|
'allow-non-breakable-words': bool,
|
||||||
|
'allow-non-breakable-inline-mappings': bool}
|
||||||
|
|
||||||
|
|
||||||
|
def check_inline_mapping(line):
|
||||||
|
loader = yaml.SafeLoader(line.content)
|
||||||
|
while loader.peek_token():
|
||||||
|
if isinstance(loader.get_token(), yaml.BlockMappingStartToken):
|
||||||
|
while loader.peek_token():
|
||||||
|
if isinstance(loader.get_token(), yaml.ValueToken):
|
||||||
|
t = loader.get_token()
|
||||||
|
if isinstance(t, yaml.ScalarToken):
|
||||||
|
return ' ' not in line.content[t.start_mark.column:]
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def check(conf, line):
|
def check(conf, line):
|
||||||
if line.end - line.start > conf['max']:
|
if line.end - line.start > conf['max']:
|
||||||
|
conf['allow-non-breakable-words'] |= \
|
||||||
|
conf['allow-non-breakable-inline-mappings']
|
||||||
if conf['allow-non-breakable-words']:
|
if conf['allow-non-breakable-words']:
|
||||||
start = line.start
|
start = line.start
|
||||||
while start < line.end and line.buffer[start] == ' ':
|
while start < line.end and line.buffer[start] == ' ':
|
||||||
start += 1
|
start += 1
|
||||||
|
|
||||||
if start != line.end:
|
if start != line.end:
|
||||||
if line.buffer[start] == '#':
|
if line.buffer[start] in ('#', '-'):
|
||||||
start += 2
|
start += 2
|
||||||
|
|
||||||
if line.buffer.find(' ', start, line.end) == -1:
|
if line.buffer.find(' ', start, line.end) == -1:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if (conf['allow-non-breakable-inline-mappings'] and
|
||||||
|
check_inline_mapping(line)):
|
||||||
|
return
|
||||||
|
|
||||||
yield LintProblem(line.line_no, conf['max'] + 1,
|
yield LintProblem(line.line_no, conf['max'] + 1,
|
||||||
'line too long (%d > %d characters)' %
|
'line too long (%d > %d characters)' %
|
||||||
(line.end - line.start, conf['max']))
|
(line.end - line.start, conf['max']))
|
||||||
|
|||||||
Reference in New Issue
Block a user