Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33224a04e4 | ||
|
|
fd9d2a00ff | ||
|
|
0b0251bacc | ||
|
|
ad5cec9c6c | ||
|
|
fb14cbdbd9 | ||
|
|
8288a6f331 | ||
|
|
9d8b0d4d2c | ||
|
|
39c878c819 | ||
|
|
222f7a27c1 | ||
|
|
effb4db3b4 | ||
|
|
d617eb70ae | ||
|
|
f09aef4f89 | ||
|
|
01c12f2462 | ||
|
|
918f15b68d | ||
|
|
97e2210ec9 | ||
|
|
1934206cef |
60
README.md
60
README.md
@@ -4,3 +4,63 @@ A linter for YAML files.
|
|||||||
|
|
||||||
[](https://travis-ci.org/adrienverge/yamllint)
|
[](https://travis-ci.org/adrienverge/yamllint)
|
||||||
[](https://coveralls.io/github/adrienverge/yamllint?branch=master)
|
[](https://coveralls.io/github/adrienverge/yamllint?branch=master)
|
||||||
|
|
||||||
|
Compatible with Python 2 & 3.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yamllint my_file.yml my_other_file.yaml ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yamllint -c ~/myconfig my_file.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# To output a format parsable (by editors like Vim, emacs, etc.)
|
||||||
|
yamllint -f parsable my_file.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pip install yamllint
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
There is no documentation yet, so here is what you need to know: you can
|
||||||
|
override some rules, disable them or pass them in *warning* (instead of
|
||||||
|
*error*). Have a look at the content of `yamllint/conf/default.yml` and create
|
||||||
|
your own configuration file.
|
||||||
|
|
||||||
|
It could look like this:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# This is my first, very own configuration file for yamllint!
|
||||||
|
# It extends the default conf by adjusting some options.
|
||||||
|
|
||||||
|
extends: default
|
||||||
|
|
||||||
|
rules:
|
||||||
|
# 80 should be enough, but don't fail if a line is longer
|
||||||
|
line-length:
|
||||||
|
max: 80
|
||||||
|
level: warning
|
||||||
|
|
||||||
|
# accept both
|
||||||
|
# key:
|
||||||
|
# - item
|
||||||
|
# and
|
||||||
|
# key:
|
||||||
|
# - item
|
||||||
|
indentation:
|
||||||
|
indent-sequences: whatever
|
||||||
|
|
||||||
|
# don't bother me with this rule
|
||||||
|
comments-indentation: disable
|
||||||
|
```
|
||||||
|
|
||||||
|
Tip: if you have a `.yamllint` file in your working directory, it will be
|
||||||
|
automatically loaded as configuration by yamllint.
|
||||||
|
|||||||
@@ -33,12 +33,19 @@ class RuleTestCase(unittest.TestCase):
|
|||||||
'rules': conf}
|
'rules': conf}
|
||||||
return parse_config(yaml.safe_dump(conf))
|
return parse_config(yaml.safe_dump(conf))
|
||||||
|
|
||||||
def check(self, source, conf, line=None, column=None, **kwargs):
|
def check(self, source, conf, **kwargs):
|
||||||
expected_problems = []
|
expected_problems = []
|
||||||
for key in kwargs:
|
for key in kwargs:
|
||||||
assert key.startswith('problem')
|
assert key.startswith('problem')
|
||||||
|
if len(kwargs[key]) > 2:
|
||||||
|
if kwargs[key][2] == 'syntax':
|
||||||
|
rule_id = None
|
||||||
|
else:
|
||||||
|
rule_id = kwargs[key][2]
|
||||||
|
else:
|
||||||
|
rule_id = self.rule_id
|
||||||
expected_problems.append(
|
expected_problems.append(
|
||||||
LintProblem(kwargs[key][0], kwargs[key][1], rule=self.rule_id))
|
LintProblem(kwargs[key][0], kwargs[key][1], rule=rule_id))
|
||||||
expected_problems.sort()
|
expected_problems.sort()
|
||||||
|
|
||||||
real_problems = list(lint(source, self.build_fake_config(conf)))
|
real_problems = list(lint(source, self.build_fake_config(conf)))
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class ColonTestCase(RuleTestCase):
|
|||||||
' - p: kdjf\n'
|
' - p: kdjf\n'
|
||||||
' - q: val0\n'
|
' - q: val0\n'
|
||||||
' - q2:\n'
|
' - q2:\n'
|
||||||
' - val1\n'
|
' - val1\n'
|
||||||
'...\n', conf)
|
'...\n', conf)
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'object:\n'
|
'object:\n'
|
||||||
@@ -67,7 +67,7 @@ class ColonTestCase(RuleTestCase):
|
|||||||
' - o: {k1: v1}\n'
|
' - o: {k1: v1}\n'
|
||||||
' - o: {k1: v1}\n'
|
' - o: {k1: v1}\n'
|
||||||
' - q2:\n'
|
' - q2:\n'
|
||||||
' - val1\n'
|
' - val1\n'
|
||||||
'...\n', conf)
|
'...\n', conf)
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'a: {b: {c: d, e : f}}\n', conf)
|
'a: {b: {c: d, e : f}}\n', conf)
|
||||||
@@ -94,7 +94,7 @@ class ColonTestCase(RuleTestCase):
|
|||||||
'...\n', conf, problem=(2, 4))
|
'...\n', conf, problem=(2, 4))
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'- lib :\n'
|
'- lib :\n'
|
||||||
' - var\n'
|
' - var\n'
|
||||||
'...\n', conf, problem=(2, 6))
|
'...\n', conf, problem=(2, 6))
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'a: {b: {c : d, e : f}}\n', conf,
|
'a: {b: {c : d, e : f}}\n', conf,
|
||||||
@@ -152,6 +152,21 @@ class ColonTestCase(RuleTestCase):
|
|||||||
'a: {b: {c: d, e : f}}\n', conf,
|
'a: {b: {c: d, e : f}}\n', conf,
|
||||||
problem1=(2, 12), problem2=(2, 20))
|
problem1=(2, 12), problem2=(2, 20))
|
||||||
|
|
||||||
|
def test_after_enabled_question_mark(self):
|
||||||
|
conf = 'colons: {max-spaces-before: -1, max-spaces-after: 1}'
|
||||||
|
self.check('---\n'
|
||||||
|
'? key\n'
|
||||||
|
': value\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'? key\n'
|
||||||
|
': value\n', conf, problem=(2, 3))
|
||||||
|
self.check('---\n'
|
||||||
|
'? key\n'
|
||||||
|
': value\n', conf, problem1=(2, 3), problem2=(3, 3))
|
||||||
|
self.check('---\n'
|
||||||
|
'- ? key\n'
|
||||||
|
' : value\n', conf, problem1=(2, 5), problem2=(3, 5))
|
||||||
|
|
||||||
def test_after_max(self):
|
def test_after_max(self):
|
||||||
conf = 'colons: {max-spaces-before: -1, max-spaces-after: 3}'
|
conf = 'colons: {max-spaces-before: -1, max-spaces-after: 3}'
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
|
|||||||
@@ -152,3 +152,36 @@ class CommaTestCase(RuleTestCase):
|
|||||||
problem1=(2, 12), problem2=(2, 16), problem3=(2, 31),
|
problem1=(2, 12), problem2=(2, 16), problem3=(2, 31),
|
||||||
problem4=(2, 36), problem5=(2, 50), problem6=(4, 8),
|
problem4=(2, 36), problem5=(2, 50), problem6=(4, 8),
|
||||||
problem7=(5, 11), problem8=(8, 13))
|
problem7=(5, 11), problem8=(8, 13))
|
||||||
|
|
||||||
|
def test_comma_on_new_line(self):
|
||||||
|
conf = 'commas: {max-spaces-before: 0, max-spaces-after: 1}'
|
||||||
|
self.check('---\n'
|
||||||
|
'flow-seq: [1, 2, 3\n'
|
||||||
|
' , 4, 5, 6]\n'
|
||||||
|
'...\n', conf, problem=(3, 11))
|
||||||
|
self.check('---\n'
|
||||||
|
'flow-map: {a: 1, b: 2\n'
|
||||||
|
' , c: 3}\n'
|
||||||
|
'...\n', conf, problem=(3, 11))
|
||||||
|
conf = ('commas: {max-spaces-before: 0, max-spaces-after: 1}\n'
|
||||||
|
'indentation: disable\n')
|
||||||
|
self.check('---\n'
|
||||||
|
'flow-seq: [1, 2, 3\n'
|
||||||
|
' , 4, 5, 6]\n'
|
||||||
|
'...\n', conf, problem=(3, 9))
|
||||||
|
self.check('---\n'
|
||||||
|
'flow-map: {a: 1, b: 2\n'
|
||||||
|
' , c: 3}\n'
|
||||||
|
'...\n', conf, problem=(3, 9))
|
||||||
|
self.check('---\n'
|
||||||
|
'[\n'
|
||||||
|
'1,\n'
|
||||||
|
'2\n'
|
||||||
|
', 3\n'
|
||||||
|
']\n', conf, problem=(5, 1))
|
||||||
|
self.check('---\n'
|
||||||
|
'{\n'
|
||||||
|
'a: 1,\n'
|
||||||
|
'b: 2\n'
|
||||||
|
', c: 3\n'
|
||||||
|
'}\n', conf, problem=(5, 1))
|
||||||
|
|||||||
@@ -88,15 +88,39 @@ class CommentsIndentationTestCase(RuleTestCase):
|
|||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'obj1:\n'
|
'obj1:\n'
|
||||||
' a: 1\n'
|
' a: 1\n'
|
||||||
' # comments\n'
|
' # the following line is disabled\n'
|
||||||
|
' # b: 2\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'obj1:\n'
|
||||||
|
' a: 1\n'
|
||||||
|
' # b: 2\n'
|
||||||
'\n'
|
'\n'
|
||||||
'obj2:\n'
|
'obj2:\n'
|
||||||
' b: 2\n', conf, problem=(4, 3))
|
' b: 2\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'obj1:\n'
|
||||||
|
' a: 1\n'
|
||||||
|
' # b: 2\n'
|
||||||
|
'# this object is useless\n'
|
||||||
|
'obj2: no\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'obj1:\n'
|
||||||
|
' a: 1\n'
|
||||||
|
'# this object is useless\n'
|
||||||
|
' # b: 2\n'
|
||||||
|
'obj2: no\n', conf, problem=(5, 3))
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'obj1:\n'
|
'obj1:\n'
|
||||||
' a: 1\n'
|
' a: 1\n'
|
||||||
' # comments\n'
|
' # comments\n'
|
||||||
' b: 2\n', conf)
|
' b: 2\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'my list for today:\n'
|
||||||
|
' - todo 1\n'
|
||||||
|
' - todo 2\n'
|
||||||
|
' # commented for now\n'
|
||||||
|
' # - todo 3\n'
|
||||||
|
'...\n', conf)
|
||||||
|
|
||||||
def test_first_line(self):
|
def test_first_line(self):
|
||||||
conf = 'comments-indentation: {}'
|
conf = 'comments-indentation: {}'
|
||||||
|
|||||||
@@ -18,10 +18,32 @@ import unittest
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from yamllint.rules.common import Comment, get_comments_between_tokens
|
from yamllint.rules.common import (Comment, get_line_indent,
|
||||||
|
get_comments_between_tokens)
|
||||||
|
|
||||||
|
|
||||||
class CommonTestCase(unittest.TestCase):
|
class CommonTestCase(unittest.TestCase):
|
||||||
|
def test_get_line_indent(self):
|
||||||
|
tokens = list(yaml.scan('a: 1\n'
|
||||||
|
'b:\n'
|
||||||
|
' - c: [2, 3, {d: 4}]\n'))
|
||||||
|
|
||||||
|
self.assertEqual(tokens[3].value, 'a')
|
||||||
|
self.assertEqual(tokens[5].value, '1')
|
||||||
|
self.assertEqual(tokens[7].value, 'b')
|
||||||
|
self.assertEqual(tokens[13].value, 'c')
|
||||||
|
self.assertEqual(tokens[16].value, '2')
|
||||||
|
self.assertEqual(tokens[18].value, '3')
|
||||||
|
self.assertEqual(tokens[22].value, 'd')
|
||||||
|
self.assertEqual(tokens[24].value, '4')
|
||||||
|
|
||||||
|
for i in (3, 5):
|
||||||
|
self.assertEqual(get_line_indent(tokens[i]), 0)
|
||||||
|
for i in (7,):
|
||||||
|
self.assertEqual(get_line_indent(tokens[i]), 0)
|
||||||
|
for i in (13, 16, 18, 22, 24):
|
||||||
|
self.assertEqual(get_line_indent(tokens[i]), 2)
|
||||||
|
|
||||||
def check_comments(self, buffer, *expected):
|
def check_comments(self, buffer, *expected):
|
||||||
yaml_loader = yaml.BaseLoader(buffer)
|
yaml_loader = yaml.BaseLoader(buffer)
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class DocumentStartTestCase(RuleTestCase):
|
|||||||
'...\n'
|
'...\n'
|
||||||
'second: document\n'
|
'second: document\n'
|
||||||
'---\n'
|
'---\n'
|
||||||
'third: document\n', conf, problem=(4, 1))
|
'third: document\n', conf, problem=(4, 1, 'syntax'))
|
||||||
|
|
||||||
def test_directives(self):
|
def test_directives(self):
|
||||||
conf = 'document-start: {present: yes}'
|
conf = 'document-start: {present: yes}'
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ class HyphenTestCase(RuleTestCase):
|
|||||||
'- elem2\n', conf)
|
'- elem2\n', conf)
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'object:\n'
|
'object:\n'
|
||||||
'- elem1\n'
|
' - elem1\n'
|
||||||
'- elem2\n', conf)
|
' - elem2\n', conf)
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'object:\n'
|
'object:\n'
|
||||||
'- elem1\n'
|
' - elem1\n'
|
||||||
'- elem2\n', conf)
|
' - elem2\n', conf)
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'object:\n'
|
'object:\n'
|
||||||
' subobject:\n'
|
' subobject:\n'
|
||||||
@@ -69,12 +69,12 @@ class HyphenTestCase(RuleTestCase):
|
|||||||
'- elem2\n', conf, problem=(2, 3))
|
'- elem2\n', conf, problem=(2, 3))
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'object:\n'
|
'object:\n'
|
||||||
'- elem1\n'
|
' - elem1\n'
|
||||||
'- elem2\n', conf, problem=(4, 3))
|
' - elem2\n', conf, problem=(4, 5))
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'object:\n'
|
'object:\n'
|
||||||
'- elem1\n'
|
' - elem1\n'
|
||||||
'- elem2\n', conf, problem1=(3, 3), problem2=(4, 3))
|
' - elem2\n', conf, problem1=(3, 5), problem2=(4, 5))
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'object:\n'
|
'object:\n'
|
||||||
' subobject:\n'
|
' subobject:\n'
|
||||||
|
|||||||
@@ -48,37 +48,205 @@ class IndentationTestCase(RuleTestCase):
|
|||||||
'...\n', conf)
|
'...\n', conf)
|
||||||
|
|
||||||
def test_one_space(self):
|
def test_one_space(self):
|
||||||
conf = 'indentation: {spaces: 1}'
|
conf = 'indentation: {spaces: 1, indent-sequences: no}'
|
||||||
|
self.check('---\n'
|
||||||
|
'object:\n'
|
||||||
|
' k1:\n'
|
||||||
|
' - a\n'
|
||||||
|
' - b\n'
|
||||||
|
' k2: v2\n'
|
||||||
|
' k3:\n'
|
||||||
|
' - name: Unix\n'
|
||||||
|
' date: 1969\n'
|
||||||
|
' - name: Linux\n'
|
||||||
|
' date: 1991\n'
|
||||||
|
'...\n', conf)
|
||||||
|
conf = 'indentation: {spaces: 1, indent-sequences: yes}'
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'object:\n'
|
'object:\n'
|
||||||
' k1:\n'
|
' k1:\n'
|
||||||
' - a\n'
|
' - a\n'
|
||||||
' - b\n'
|
' - b\n'
|
||||||
' k2: v2\n'
|
' k2: v2\n'
|
||||||
|
' k3:\n'
|
||||||
|
' - name: Unix\n'
|
||||||
|
' date: 1969\n'
|
||||||
|
' - name: Linux\n'
|
||||||
|
' date: 1991\n'
|
||||||
'...\n', conf)
|
'...\n', conf)
|
||||||
|
|
||||||
def test_two_spaces(self):
|
def test_two_spaces(self):
|
||||||
conf = 'indentation: {spaces: 2}'
|
conf = 'indentation: {spaces: 2, indent-sequences: no}'
|
||||||
|
self.check('---\n'
|
||||||
|
'object:\n'
|
||||||
|
' k1:\n'
|
||||||
|
' - a\n'
|
||||||
|
' - b\n'
|
||||||
|
' k2: v2\n'
|
||||||
|
' k3:\n'
|
||||||
|
' - name: Unix\n'
|
||||||
|
' date: 1969\n'
|
||||||
|
' - name: Linux\n'
|
||||||
|
' date: 1991\n'
|
||||||
|
'...\n', conf)
|
||||||
|
conf = 'indentation: {spaces: 2, indent-sequences: yes}'
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'object:\n'
|
'object:\n'
|
||||||
' k1:\n'
|
' k1:\n'
|
||||||
' - a\n'
|
' - a\n'
|
||||||
' - b\n'
|
' - b\n'
|
||||||
' k2: v2\n'
|
' k2: v2\n'
|
||||||
|
' k3:\n'
|
||||||
|
' - name: Unix\n'
|
||||||
|
' date: 1969\n'
|
||||||
|
' - name: Linux\n'
|
||||||
|
' date: 1991\n'
|
||||||
'...\n', conf)
|
'...\n', conf)
|
||||||
|
|
||||||
def test_three_spaces(self):
|
def test_three_spaces(self):
|
||||||
conf = 'indentation: {spaces: 3}'
|
conf = 'indentation: {spaces: 3, indent-sequences: no}'
|
||||||
|
self.check('---\n'
|
||||||
|
'object:\n'
|
||||||
|
' k1:\n'
|
||||||
|
' - a\n'
|
||||||
|
' - b\n'
|
||||||
|
' k2: v2\n'
|
||||||
|
' k3:\n'
|
||||||
|
' - name: Unix\n'
|
||||||
|
' date: 1969\n'
|
||||||
|
' - name: Linux\n'
|
||||||
|
' date: 1991\n'
|
||||||
|
'...\n', conf)
|
||||||
|
conf = 'indentation: {spaces: 3, indent-sequences: yes}'
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'object:\n'
|
'object:\n'
|
||||||
' k1:\n'
|
' k1:\n'
|
||||||
' - a\n'
|
' - a\n'
|
||||||
' - b\n'
|
' - b\n'
|
||||||
' k2: v2\n'
|
' k2: v2\n'
|
||||||
|
' k3:\n'
|
||||||
|
' - name: Unix\n'
|
||||||
|
' date: 1969\n'
|
||||||
|
' - name: Linux\n'
|
||||||
|
' date: 1991\n'
|
||||||
'...\n', conf)
|
'...\n', conf)
|
||||||
|
|
||||||
def test_under_indented(self):
|
def test_indent_sequences_whatever(self):
|
||||||
|
conf = 'indentation: {spaces: 4, indent-sequences: whatever}'
|
||||||
|
self.check('---\n'
|
||||||
|
'list one:\n'
|
||||||
|
'- 1\n'
|
||||||
|
'- 2\n'
|
||||||
|
'- 3\n'
|
||||||
|
'list two:\n'
|
||||||
|
' - a\n'
|
||||||
|
' - b\n'
|
||||||
|
' - c\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'list one:\n'
|
||||||
|
' - 1\n'
|
||||||
|
' - 2\n'
|
||||||
|
' - 3\n'
|
||||||
|
'list two:\n'
|
||||||
|
' - a\n'
|
||||||
|
' - b\n'
|
||||||
|
' - c\n', conf, problem=(3, 3))
|
||||||
|
self.check('---\n'
|
||||||
|
'list one:\n'
|
||||||
|
'- 1\n'
|
||||||
|
'- 2\n'
|
||||||
|
'- 3\n'
|
||||||
|
'list two:\n'
|
||||||
|
' - a\n'
|
||||||
|
' - b\n'
|
||||||
|
' - c\n', conf, problem=(7, 3))
|
||||||
|
self.check('---\n'
|
||||||
|
'list:\n'
|
||||||
|
' - 1\n'
|
||||||
|
' - 2\n'
|
||||||
|
' - 3\n'
|
||||||
|
'- a\n'
|
||||||
|
'- b\n'
|
||||||
|
'- c\n', conf, problem=(6, 1, 'syntax'))
|
||||||
|
|
||||||
|
def test_flow_mappings(self):
|
||||||
conf = 'indentation: {spaces: 2}'
|
conf = 'indentation: {spaces: 2}'
|
||||||
|
self.check('---\n'
|
||||||
|
'a: {x: 1,\n'
|
||||||
|
' y,\n'
|
||||||
|
' z: 1}\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'a: {x: 1,\n'
|
||||||
|
' y,\n'
|
||||||
|
' z: 1}\n', conf, problem=(3, 4))
|
||||||
|
self.check('---\n'
|
||||||
|
'a: {x: 1,\n'
|
||||||
|
' y,\n'
|
||||||
|
' z: 1}\n', conf, problem=(3, 6))
|
||||||
|
self.check('---\n'
|
||||||
|
'a: {x: 1,\n'
|
||||||
|
' y, z: 1\n'
|
||||||
|
'}\n', conf, problem=(3, 3))
|
||||||
|
self.check('---\n'
|
||||||
|
'a: {\n'
|
||||||
|
' x: 1,\n'
|
||||||
|
' y, z: 1\n'
|
||||||
|
'}\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'a: {\n'
|
||||||
|
' x: 1,\n'
|
||||||
|
' y, z: 1}\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'a: {\n'
|
||||||
|
' x: 1,\n'
|
||||||
|
' y, z: 1\n'
|
||||||
|
'}\n', conf, problem=(3, 4))
|
||||||
|
self.check('---\n'
|
||||||
|
'a: {\n'
|
||||||
|
' x: 1,\n'
|
||||||
|
' y, z: 1\n'
|
||||||
|
' }\n', conf, problem=(5, 3))
|
||||||
|
|
||||||
|
def test_flow_sequences(self):
|
||||||
|
conf = 'indentation: {spaces: 2}'
|
||||||
|
self.check('---\n'
|
||||||
|
'a: [x,\n'
|
||||||
|
' y,\n'
|
||||||
|
' z]\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'a: [x,\n'
|
||||||
|
' y,\n'
|
||||||
|
' z]\n', conf, problem=(3, 4))
|
||||||
|
self.check('---\n'
|
||||||
|
'a: [x,\n'
|
||||||
|
' y,\n'
|
||||||
|
' z]\n', conf, problem=(3, 6))
|
||||||
|
self.check('---\n'
|
||||||
|
'a: [x,\n'
|
||||||
|
' y, z\n'
|
||||||
|
']\n', conf, problem=(3, 3))
|
||||||
|
self.check('---\n'
|
||||||
|
'a: [\n'
|
||||||
|
' x,\n'
|
||||||
|
' y, z\n'
|
||||||
|
']\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'a: [\n'
|
||||||
|
' x,\n'
|
||||||
|
' y, z]\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'a: [\n'
|
||||||
|
' x,\n'
|
||||||
|
' y, z\n'
|
||||||
|
']\n', conf, problem=(3, 4))
|
||||||
|
self.check('---\n'
|
||||||
|
'a: [\n'
|
||||||
|
' x,\n'
|
||||||
|
' y, z\n'
|
||||||
|
' ]\n', conf, problem=(5, 3))
|
||||||
|
|
||||||
|
def test_under_indented(self):
|
||||||
|
conf = 'indentation: {spaces: 2, indent-sequences: yes}'
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'object:\n'
|
'object:\n'
|
||||||
' val: 1\n'
|
' val: 1\n'
|
||||||
@@ -88,7 +256,13 @@ class IndentationTestCase(RuleTestCase):
|
|||||||
' k1:\n'
|
' k1:\n'
|
||||||
' - a\n'
|
' - a\n'
|
||||||
'...\n', conf, problem=(4, 4))
|
'...\n', conf, problem=(4, 4))
|
||||||
conf = 'indentation: {spaces: 4}'
|
self.check('---\n'
|
||||||
|
'object:\n'
|
||||||
|
' k3:\n'
|
||||||
|
' - name: Unix\n'
|
||||||
|
' date: 1969\n'
|
||||||
|
'...\n', conf, problem=(5, 6, 'syntax'))
|
||||||
|
conf = 'indentation: {spaces: 4, indent-sequences: yes}'
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'object:\n'
|
'object:\n'
|
||||||
' val: 1\n'
|
' val: 1\n'
|
||||||
@@ -98,9 +272,15 @@ class IndentationTestCase(RuleTestCase):
|
|||||||
'- el2:\n'
|
'- el2:\n'
|
||||||
' - subel\n'
|
' - subel\n'
|
||||||
'...\n', conf, problem=(4, 4))
|
'...\n', conf, problem=(4, 4))
|
||||||
|
self.check('---\n'
|
||||||
|
'object:\n'
|
||||||
|
' k3:\n'
|
||||||
|
' - name: Linux\n'
|
||||||
|
' date: 1991\n'
|
||||||
|
'...\n', conf, problem=(5, 10, 'syntax'))
|
||||||
|
|
||||||
def test_over_indented(self):
|
def test_over_indented(self):
|
||||||
conf = 'indentation: {spaces: 2}'
|
conf = 'indentation: {spaces: 2, indent-sequences: yes}'
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'object:\n'
|
'object:\n'
|
||||||
' val: 1\n'
|
' val: 1\n'
|
||||||
@@ -110,7 +290,13 @@ class IndentationTestCase(RuleTestCase):
|
|||||||
' k1:\n'
|
' k1:\n'
|
||||||
' - a\n'
|
' - a\n'
|
||||||
'...\n', conf, problem=(4, 6))
|
'...\n', conf, problem=(4, 6))
|
||||||
conf = 'indentation: {spaces: 4}'
|
self.check('---\n'
|
||||||
|
'object:\n'
|
||||||
|
' k3:\n'
|
||||||
|
' - name: Unix\n'
|
||||||
|
' date: 1969\n'
|
||||||
|
'...\n', conf, problem=(5, 12, 'syntax'))
|
||||||
|
conf = 'indentation: {spaces: 4, indent-sequences: yes}'
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'object:\n'
|
'object:\n'
|
||||||
' val: 1\n'
|
' val: 1\n'
|
||||||
@@ -118,7 +304,7 @@ class IndentationTestCase(RuleTestCase):
|
|||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
' object:\n'
|
' object:\n'
|
||||||
' val: 1\n'
|
' val: 1\n'
|
||||||
'...\n', conf, problem1=(2, 2), problem2=(3, 6))
|
'...\n', conf, problem=(2, 2))
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'- el1\n'
|
'- el1\n'
|
||||||
'- el2:\n'
|
'- el2:\n'
|
||||||
@@ -129,30 +315,57 @@ class IndentationTestCase(RuleTestCase):
|
|||||||
'- el2:\n'
|
'- el2:\n'
|
||||||
' - subel\n'
|
' - subel\n'
|
||||||
'...\n', conf, problem=(4, 15))
|
'...\n', conf, problem=(4, 15))
|
||||||
|
self.check('---\n'
|
||||||
|
' - el1\n'
|
||||||
|
' - el2:\n'
|
||||||
|
' - subel\n'
|
||||||
|
'...\n', conf,
|
||||||
|
problem=(2, 3))
|
||||||
|
self.check('---\n'
|
||||||
|
'object:\n'
|
||||||
|
' k3:\n'
|
||||||
|
' - name: Linux\n'
|
||||||
|
' date: 1991\n'
|
||||||
|
'...\n', conf, problem=(5, 16, 'syntax'))
|
||||||
|
conf = 'indentation: {spaces: 4, indent-sequences: whatever}'
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
' - el1\n'
|
' - el1\n'
|
||||||
' - el2:\n'
|
' - el2:\n'
|
||||||
' - subel\n'
|
' - subel\n'
|
||||||
'...\n', conf,
|
'...\n', conf,
|
||||||
problem1=(2, 3), problem2=(3, 3), problem3=(4, 5))
|
problem=(2, 3))
|
||||||
|
|
||||||
def test_multi_lines(self):
|
def test_multi_lines(self):
|
||||||
|
conf = 'indentation: {spaces: 2, indent-sequences: yes}'
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'long_string: >\n'
|
'long_string: >\n'
|
||||||
' bla bla blah\n'
|
' bla bla blah\n'
|
||||||
' blah bla bla\n'
|
' blah bla bla\n'
|
||||||
'...\n', None)
|
'...\n', conf)
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'- long_string: >\n'
|
'- long_string: >\n'
|
||||||
' bla bla blah\n'
|
' bla bla blah\n'
|
||||||
' blah bla bla\n'
|
' blah bla bla\n'
|
||||||
'...\n', None)
|
'...\n', conf)
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'obj:\n'
|
'obj:\n'
|
||||||
' - long_string: >\n'
|
' - long_string: >\n'
|
||||||
' bla bla blah\n'
|
' bla bla blah\n'
|
||||||
' blah bla bla\n'
|
' blah bla bla\n'
|
||||||
'...\n', None)
|
'...\n', conf)
|
||||||
|
|
||||||
|
def test_empty_value(self):
|
||||||
|
conf = 'indentation: {spaces: 2}'
|
||||||
|
self.check('---\n'
|
||||||
|
'key1:\n'
|
||||||
|
'key2: not empty\n'
|
||||||
|
'key3:\n'
|
||||||
|
'...\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
'-\n'
|
||||||
|
'- item 2\n'
|
||||||
|
'-\n'
|
||||||
|
'...\n', conf)
|
||||||
|
|
||||||
def test_nested_collections(self):
|
def test_nested_collections(self):
|
||||||
conf = 'indentation: {spaces: 2}'
|
conf = 'indentation: {spaces: 2}'
|
||||||
@@ -163,7 +376,7 @@ class IndentationTestCase(RuleTestCase):
|
|||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'- o:\n'
|
'- o:\n'
|
||||||
' k1: v1\n'
|
' k1: v1\n'
|
||||||
'...\n', conf, problem=(3, 2))
|
'...\n', conf, problem=(3, 2, 'syntax'))
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'- o:\n'
|
'- o:\n'
|
||||||
' k1: v1\n'
|
' k1: v1\n'
|
||||||
@@ -171,18 +384,33 @@ class IndentationTestCase(RuleTestCase):
|
|||||||
conf = 'indentation: {spaces: 4}'
|
conf = 'indentation: {spaces: 4}'
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'- o:\n'
|
'- o:\n'
|
||||||
' k1: v1\n'
|
' k1: v1\n'
|
||||||
'...\n', conf)
|
'...\n', conf)
|
||||||
self.check('---\n'
|
|
||||||
'- o:\n'
|
|
||||||
' k1: v1\n'
|
|
||||||
'...\n', conf, problem=(3, 4))
|
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'- o:\n'
|
'- o:\n'
|
||||||
' k1: v1\n'
|
' k1: v1\n'
|
||||||
'...\n', conf, problem=(3, 6))
|
'...\n', conf, problem=(3, 6))
|
||||||
|
self.check('---\n'
|
||||||
|
'- o:\n'
|
||||||
|
' k1: v1\n'
|
||||||
|
'...\n', conf, problem=(3, 8))
|
||||||
|
self.check('---\n'
|
||||||
|
'- - - - item\n'
|
||||||
|
' - elem 1\n'
|
||||||
|
' - elem 2\n'
|
||||||
|
' - - - - - very nested: a\n'
|
||||||
|
' key: value\n'
|
||||||
|
'...\n', conf)
|
||||||
|
self.check('---\n'
|
||||||
|
' - - - - item\n'
|
||||||
|
' - elem 1\n'
|
||||||
|
' - elem 2\n'
|
||||||
|
' - - - - - very nested: a\n'
|
||||||
|
' key: value\n'
|
||||||
|
'...\n', conf, problem=(2, 2))
|
||||||
|
|
||||||
def test_return(self):
|
def test_return(self):
|
||||||
|
conf = 'indentation: {spaces: 2}'
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'a:\n'
|
'a:\n'
|
||||||
' b:\n'
|
' b:\n'
|
||||||
@@ -191,19 +419,19 @@ class IndentationTestCase(RuleTestCase):
|
|||||||
' e:\n'
|
' e:\n'
|
||||||
' f:\n'
|
' f:\n'
|
||||||
'g:\n'
|
'g:\n'
|
||||||
'...\n', None)
|
'...\n', conf)
|
||||||
# self.check('---\n'
|
self.check('---\n'
|
||||||
# 'a:\n'
|
'a:\n'
|
||||||
# ' b:\n'
|
' b:\n'
|
||||||
# ' c:\n'
|
' c:\n'
|
||||||
# ' d:\n'
|
' d:\n'
|
||||||
# '...\n', None, problem=(5, 5))
|
'...\n', conf, problem=(5, 4, 'syntax'))
|
||||||
# self.check('---\n'
|
self.check('---\n'
|
||||||
# 'a:\n'
|
'a:\n'
|
||||||
# ' b:\n'
|
' b:\n'
|
||||||
# ' c:\n'
|
' c:\n'
|
||||||
# ' d:\n'
|
' d:\n'
|
||||||
# '...\n', None, problem=(5, 2))
|
'...\n', conf, problem=(5, 2, 'syntax'))
|
||||||
|
|
||||||
def test_first_line(self):
|
def test_first_line(self):
|
||||||
conf = ('indentation: {spaces: 2}\n'
|
conf = ('indentation: {spaces: 2}\n'
|
||||||
|
|||||||
@@ -35,3 +35,35 @@ class YamlLintTestCase(RuleTestCase):
|
|||||||
'%TAG ! tag:clarkevans.com,2002:\n'
|
'%TAG ! tag:clarkevans.com,2002:\n'
|
||||||
'doc: ument\n'
|
'doc: ument\n'
|
||||||
'...\n', None, problem=(3, 1))
|
'...\n', None, problem=(3, 1))
|
||||||
|
|
||||||
|
def test_explicit_mapping(self):
|
||||||
|
self.check('---\n'
|
||||||
|
'? key\n'
|
||||||
|
': - value 1\n'
|
||||||
|
' - value 2\n'
|
||||||
|
'...\n', None)
|
||||||
|
self.check('---\n'
|
||||||
|
'?\n'
|
||||||
|
' key\n'
|
||||||
|
': {a: 1}\n'
|
||||||
|
'...\n', None)
|
||||||
|
self.check('---\n'
|
||||||
|
'?\n'
|
||||||
|
' key\n'
|
||||||
|
':\n'
|
||||||
|
' val\n'
|
||||||
|
'...\n', None)
|
||||||
|
|
||||||
|
def test_mapping_between_sequences(self):
|
||||||
|
# This is valid YAML. See http://www.yaml.org/spec/1.2/spec.html,
|
||||||
|
# example 2.11
|
||||||
|
self.check('---\n'
|
||||||
|
'? - Detroit Tigers\n'
|
||||||
|
' - Chicago cubs\n'
|
||||||
|
':\n'
|
||||||
|
' - 2001-07-23\n'
|
||||||
|
'\n'
|
||||||
|
'? [New York Yankees,\n'
|
||||||
|
' Atlanta Braves]\n'
|
||||||
|
': [2001-07-02, 2001-08-12,\n'
|
||||||
|
' 2001-08-14]\n', None)
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ class TrailingSpacesTestCase(RuleTestCase):
|
|||||||
self.check('', conf)
|
self.check('', conf)
|
||||||
self.check('\n', conf)
|
self.check('\n', conf)
|
||||||
self.check(' \n', conf, problem=(1, 1))
|
self.check(' \n', conf, problem=(1, 1))
|
||||||
self.check('\t\t\t\n', conf, problem=(1, 1))
|
self.check('\t\t\t\n', conf, problem=(1, 1, 'syntax'))
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'some: text \n', conf, problem=(2, 11))
|
'some: text \n', conf, problem=(2, 11))
|
||||||
self.check('---\n'
|
self.check('---\n'
|
||||||
'some: text\t\n', conf, problem=(2, 11))
|
'some: text\t\n', conf, problem=(2, 11, 'syntax'))
|
||||||
|
|
||||||
def test_with_dos_new_lines(self):
|
def test_with_dos_new_lines(self):
|
||||||
conf = ('trailing-spaces: {}\n'
|
conf = ('trailing-spaces: {}\n'
|
||||||
|
|||||||
69
tests/test_config.py
Normal file
69
tests/test_config.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# -*- 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 os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from yamllint import config
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigTestCase(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.base = config.parse_config_from_file(os.path.join(
|
||||||
|
os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
|
||||||
|
'yamllint', 'conf', 'default.yml'))
|
||||||
|
|
||||||
|
def test_extend_config_disable_rule(self):
|
||||||
|
new = config.parse_config('extends: default\n'
|
||||||
|
'rules:\n'
|
||||||
|
' trailing-spaces: disable\n')
|
||||||
|
|
||||||
|
base = self.base.copy()
|
||||||
|
del base['trailing-spaces']
|
||||||
|
|
||||||
|
self.assertEqual(sorted(new.keys()), sorted(base.keys()))
|
||||||
|
for rule in new:
|
||||||
|
self.assertEqual(new[rule], base[rule])
|
||||||
|
|
||||||
|
def test_extend_config_override_whole_rule(self):
|
||||||
|
new = config.parse_config('extends: default\n'
|
||||||
|
'rules:\n'
|
||||||
|
' empty-lines:\n'
|
||||||
|
' max: 42\n'
|
||||||
|
' max-start: 43\n'
|
||||||
|
' max-end: 44\n')
|
||||||
|
|
||||||
|
base = self.base.copy()
|
||||||
|
base['empty-lines']['max'] = 42
|
||||||
|
base['empty-lines']['max-start'] = 43
|
||||||
|
base['empty-lines']['max-end'] = 44
|
||||||
|
|
||||||
|
self.assertEqual(sorted(new.keys()), sorted(base.keys()))
|
||||||
|
for rule in new:
|
||||||
|
self.assertEqual(new[rule], base[rule])
|
||||||
|
|
||||||
|
def test_extend_config_override_rule_partly(self):
|
||||||
|
new = config.parse_config('extends: default\n'
|
||||||
|
'rules:\n'
|
||||||
|
' empty-lines:\n'
|
||||||
|
' max-start: 42\n')
|
||||||
|
|
||||||
|
base = self.base.copy()
|
||||||
|
base['empty-lines']['max-start'] = 42
|
||||||
|
|
||||||
|
self.assertEqual(sorted(new.keys()), sorted(base.keys()))
|
||||||
|
for rule in new:
|
||||||
|
self.assertEqual(new[rule], base[rule])
|
||||||
@@ -22,7 +22,7 @@ from yamllint import parser
|
|||||||
|
|
||||||
|
|
||||||
APP_NAME = 'yamllint'
|
APP_NAME = 'yamllint'
|
||||||
APP_VERSION = '0.2.0'
|
APP_VERSION = '0.3.0'
|
||||||
APP_DESCRIPTION = 'A linter for YAML files.'
|
APP_DESCRIPTION = 'A linter for YAML files.'
|
||||||
|
|
||||||
__author__ = 'Adrien Vergé'
|
__author__ = 'Adrien Vergé'
|
||||||
@@ -38,12 +38,17 @@ def get_costemic_problems(buffer, conf):
|
|||||||
token_rules = [r for r in rules if r.TYPE == 'token']
|
token_rules = [r for r in rules if r.TYPE == 'token']
|
||||||
line_rules = [r for r in rules if r.TYPE == 'line']
|
line_rules = [r for r in rules if r.TYPE == 'line']
|
||||||
|
|
||||||
|
context = {}
|
||||||
|
for rule in token_rules:
|
||||||
|
context[rule.ID] = {}
|
||||||
|
|
||||||
for elem in parser.token_or_line_generator(buffer):
|
for elem in parser.token_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[rule.ID]
|
rule_conf = conf[rule.ID]
|
||||||
for problem in rule.check(rule_conf,
|
for problem in rule.check(rule_conf,
|
||||||
elem.curr, elem.prev, elem.next):
|
elem.curr, elem.prev, elem.next,
|
||||||
|
context[rule.ID]):
|
||||||
problem.rule = rule.ID
|
problem.rule = rule.ID
|
||||||
problem.level = rule_conf['level']
|
problem.level = rule_conf['level']
|
||||||
yield problem
|
yield problem
|
||||||
@@ -58,7 +63,7 @@ def get_costemic_problems(buffer, conf):
|
|||||||
|
|
||||||
def get_syntax_error(buffer):
|
def get_syntax_error(buffer):
|
||||||
try:
|
try:
|
||||||
list(yaml.safe_load_all(buffer))
|
list(yaml.parse(buffer, Loader=yaml.BaseLoader))
|
||||||
except yaml.error.MarkedYAMLError as e:
|
except yaml.error.MarkedYAMLError as e:
|
||||||
problem = LintProblem(e.problem_mark.line + 1,
|
problem = LintProblem(e.problem_mark.line + 1,
|
||||||
e.problem_mark.column + 1,
|
e.problem_mark.column + 1,
|
||||||
@@ -76,11 +81,16 @@ def _lint(buffer, conf):
|
|||||||
# Insert the syntax error (if any) at the right place...
|
# Insert the syntax error (if any) at the right place...
|
||||||
if (syntax_error and syntax_error.line <= problem.line and
|
if (syntax_error and syntax_error.line <= problem.line and
|
||||||
syntax_error.column <= problem.column):
|
syntax_error.column <= problem.column):
|
||||||
# ... unless there is already a yamllint error, in which case the
|
yield syntax_error
|
||||||
# syntax error is probably redundant.
|
|
||||||
if (syntax_error.line != problem.line or
|
# If there is already a yamllint error at the same place, discard
|
||||||
syntax_error.column != problem.column):
|
# it as it is probably redundant (and maybe it's just a 'warning',
|
||||||
yield syntax_error
|
# in which case the script won't even exit with a failure status).
|
||||||
|
if (syntax_error.line == problem.line and
|
||||||
|
syntax_error.column == problem.column):
|
||||||
|
syntax_error = None
|
||||||
|
continue
|
||||||
|
|
||||||
syntax_error = None
|
syntax_error = None
|
||||||
|
|
||||||
yield problem
|
yield problem
|
||||||
|
|||||||
@@ -31,12 +31,10 @@ rules:
|
|||||||
max-spaces-after: 1
|
max-spaces-after: 1
|
||||||
indentation:
|
indentation:
|
||||||
spaces: 2
|
spaces: 2
|
||||||
|
indent-sequences: yes
|
||||||
line-length:
|
line-length:
|
||||||
max: 80
|
max: 80
|
||||||
new-line-at-end-of-file: {level: error}
|
new-line-at-end-of-file: {level: error}
|
||||||
new-lines:
|
new-lines:
|
||||||
type: unix
|
type: unix
|
||||||
#sequences-indentation:
|
|
||||||
# level: warning
|
|
||||||
# present: yes
|
|
||||||
trailing-spaces: {}
|
trailing-spaces: {}
|
||||||
|
|||||||
@@ -46,7 +46,11 @@ def extend_config(content):
|
|||||||
if 'extends' in conf:
|
if 'extends' in conf:
|
||||||
base = parse_config_from_file(get_extended_conf(conf['extends']))
|
base = parse_config_from_file(get_extended_conf(conf['extends']))
|
||||||
|
|
||||||
base.update(conf['rules'])
|
for rule in conf['rules']:
|
||||||
|
if type(conf['rules'][rule]) == dict and rule in base:
|
||||||
|
base[rule].update(conf['rules'][rule])
|
||||||
|
else:
|
||||||
|
base[rule] = conf['rules'][rule]
|
||||||
conf['rules'] = base
|
conf['rules'] = base
|
||||||
|
|
||||||
return conf
|
return conf
|
||||||
@@ -83,10 +87,16 @@ def parse_config(content):
|
|||||||
raise YamlLintConfigError(
|
raise YamlLintConfigError(
|
||||||
'invalid config: unknown option "%s" for rule "%s"' %
|
'invalid config: unknown option "%s" for rule "%s"' %
|
||||||
(optkey, id))
|
(optkey, id))
|
||||||
if type(conf['rules'][id][optkey]) != options[optkey]:
|
if type(options[optkey]) == tuple:
|
||||||
raise YamlLintConfigError(
|
if conf['rules'][id][optkey] not in options[optkey]:
|
||||||
'invalid config: option "%s" of "%s" should be %s' %
|
raise YamlLintConfigError(
|
||||||
(optkey, id, options[optkey].__name__))
|
('invalid config: option "%s" of "%s" should be '
|
||||||
|
'in %s') % (optkey, id, options[optkey]))
|
||||||
|
else:
|
||||||
|
if type(conf['rules'][id][optkey]) != options[optkey]:
|
||||||
|
raise YamlLintConfigError(
|
||||||
|
('invalid config: option "%s" of "%s" should be '
|
||||||
|
'%s' % (optkey, id, options[optkey].__name__)))
|
||||||
rules[id][optkey] = conf['rules'][id][optkey]
|
rules[id][optkey] = conf['rules'][id][optkey]
|
||||||
else:
|
else:
|
||||||
raise YamlLintConfigError(('invalid config: rule "%s": should be '
|
raise YamlLintConfigError(('invalid config: rule "%s": should be '
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ CONF = {'min-spaces-inside': int,
|
|||||||
'max-spaces-inside': int}
|
'max-spaces-inside': int}
|
||||||
|
|
||||||
|
|
||||||
def check(conf, token, prev, next):
|
def check(conf, token, prev, next, context):
|
||||||
if isinstance(token, yaml.FlowMappingStartToken):
|
if isinstance(token, yaml.FlowMappingStartToken):
|
||||||
problem = spaces_after(token, prev, next,
|
problem = spaces_after(token, prev, next,
|
||||||
min=conf['min-spaces-inside'],
|
min=conf['min-spaces-inside'],
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ CONF = {'min-spaces-inside': int,
|
|||||||
'max-spaces-inside': int}
|
'max-spaces-inside': int}
|
||||||
|
|
||||||
|
|
||||||
def check(conf, token, prev, next):
|
def check(conf, token, prev, next, context):
|
||||||
if isinstance(token, yaml.FlowSequenceStartToken):
|
if isinstance(token, yaml.FlowSequenceStartToken):
|
||||||
problem = spaces_after(token, prev, next,
|
problem = spaces_after(token, prev, next,
|
||||||
min=conf['min-spaces-inside'],
|
min=conf['min-spaces-inside'],
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from yamllint.rules.common import spaces_after, spaces_before
|
from yamllint.rules.common import spaces_after, spaces_before, is_explicit_key
|
||||||
|
|
||||||
|
|
||||||
ID = 'colons'
|
ID = 'colons'
|
||||||
@@ -25,7 +25,7 @@ CONF = {'max-spaces-before': int,
|
|||||||
'max-spaces-after': int}
|
'max-spaces-after': int}
|
||||||
|
|
||||||
|
|
||||||
def check(conf, token, prev, next):
|
def check(conf, token, prev, next, context):
|
||||||
if isinstance(token, yaml.ValueToken):
|
if isinstance(token, yaml.ValueToken):
|
||||||
problem = spaces_before(token, prev, next,
|
problem = spaces_before(token, prev, next,
|
||||||
max=conf['max-spaces-before'],
|
max=conf['max-spaces-before'],
|
||||||
@@ -38,3 +38,10 @@ def check(conf, token, prev, next):
|
|||||||
max_desc='too many spaces after colon')
|
max_desc='too many spaces after colon')
|
||||||
if problem is not None:
|
if problem is not None:
|
||||||
yield problem
|
yield problem
|
||||||
|
|
||||||
|
if isinstance(token, yaml.KeyToken) and is_explicit_key(token):
|
||||||
|
problem = spaces_after(token, prev, next,
|
||||||
|
max=conf['max-spaces-after'],
|
||||||
|
max_desc='too many spaces after question mark')
|
||||||
|
if problem is not None:
|
||||||
|
yield problem
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
from yamllint.errors import LintProblem
|
||||||
from yamllint.rules.common import spaces_after, spaces_before
|
from yamllint.rules.common import spaces_after, spaces_before
|
||||||
|
|
||||||
|
|
||||||
@@ -25,13 +26,18 @@ CONF = {'max-spaces-before': int,
|
|||||||
'max-spaces-after': int}
|
'max-spaces-after': int}
|
||||||
|
|
||||||
|
|
||||||
def check(conf, token, prev, next):
|
def check(conf, token, prev, next, context):
|
||||||
if isinstance(token, yaml.FlowEntryToken):
|
if isinstance(token, yaml.FlowEntryToken):
|
||||||
problem = spaces_before(token, prev, next,
|
if prev is not None and prev.end_mark.line < token.start_mark.line:
|
||||||
max=conf['max-spaces-before'],
|
yield LintProblem(token.start_mark.line + 1,
|
||||||
max_desc='too many spaces before comma')
|
max(1, token.start_mark.column),
|
||||||
if problem is not None:
|
'too many spaces before comma')
|
||||||
yield problem
|
else:
|
||||||
|
problem = spaces_before(token, prev, next,
|
||||||
|
max=conf['max-spaces-before'],
|
||||||
|
max_desc='too many spaces before comma')
|
||||||
|
if problem is not None:
|
||||||
|
yield problem
|
||||||
|
|
||||||
problem = spaces_after(token, prev, next,
|
problem = spaces_after(token, prev, next,
|
||||||
max=conf['max-spaces-after'],
|
max=conf['max-spaces-after'],
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ CONF = {'require-starting-space': bool,
|
|||||||
'min-spaces-from-content': int}
|
'min-spaces-from-content': int}
|
||||||
|
|
||||||
|
|
||||||
def check(conf, token, prev, next):
|
def check(conf, token, prev, next, context):
|
||||||
for comment in get_comments_between_tokens(token, next):
|
for comment in get_comments_between_tokens(token, next):
|
||||||
if (conf['min-spaces-from-content'] != -1 and
|
if (conf['min-spaces-from-content'] != -1 and
|
||||||
not isinstance(token, yaml.StreamStartToken) and
|
not isinstance(token, yaml.StreamStartToken) and
|
||||||
|
|||||||
@@ -17,27 +17,48 @@
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from yamllint.errors import LintProblem
|
from yamllint.errors import LintProblem
|
||||||
from yamllint.rules.common import get_comments_between_tokens
|
from yamllint.rules.common import get_line_indent, get_comments_between_tokens
|
||||||
|
|
||||||
|
|
||||||
ID = 'comments-indentation'
|
ID = 'comments-indentation'
|
||||||
TYPE = 'token'
|
TYPE = 'token'
|
||||||
|
|
||||||
|
|
||||||
def check(conf, token, prev, next):
|
# Case A:
|
||||||
|
#
|
||||||
|
# prev: line:
|
||||||
|
# # commented line
|
||||||
|
# current: line
|
||||||
|
#
|
||||||
|
# Case B:
|
||||||
|
#
|
||||||
|
# prev: line
|
||||||
|
# # commented line 1
|
||||||
|
# # commented line 2
|
||||||
|
# current: line
|
||||||
|
|
||||||
|
def check(conf, token, prev, next, context):
|
||||||
if prev is None:
|
if prev is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
token_indent = token.start_mark.column
|
curr_line_indent = token.start_mark.column
|
||||||
if isinstance(token, yaml.StreamEndToken):
|
if isinstance(token, yaml.StreamEndToken):
|
||||||
token_indent = 0
|
curr_line_indent = 0
|
||||||
|
|
||||||
skip_first = True
|
skip_first_line = True
|
||||||
if isinstance(prev, yaml.StreamStartToken):
|
if isinstance(prev, yaml.StreamStartToken):
|
||||||
skip_first = False
|
skip_first_line = False
|
||||||
|
prev_line_indent = 0
|
||||||
|
else:
|
||||||
|
prev_line_indent = get_line_indent(prev)
|
||||||
|
|
||||||
for comment in get_comments_between_tokens(prev, token,
|
if prev_line_indent <= curr_line_indent:
|
||||||
skip_first_line=skip_first):
|
prev_line_indent = -1 # disable it
|
||||||
if comment.column != token_indent + 1:
|
|
||||||
|
for comment in get_comments_between_tokens(
|
||||||
|
prev, token, skip_first_line=skip_first_line):
|
||||||
|
if comment.column - 1 == curr_line_indent:
|
||||||
|
prev_line_indent = -1 # disable it
|
||||||
|
elif comment.column - 1 != prev_line_indent:
|
||||||
yield LintProblem(comment.line, comment.column,
|
yield LintProblem(comment.line, comment.column,
|
||||||
'comment not intended like content')
|
'comment not indented like content')
|
||||||
|
|||||||
@@ -64,6 +64,16 @@ class Comment(object):
|
|||||||
str(self) == str(other))
|
str(self) == str(other))
|
||||||
|
|
||||||
|
|
||||||
|
def get_line_indent(token):
|
||||||
|
"""Finds the indent of the line the token starts in."""
|
||||||
|
start = token.start_mark.buffer.rfind('\n', 0,
|
||||||
|
token.start_mark.pointer) + 1
|
||||||
|
content = start
|
||||||
|
while token.start_mark.buffer[content] == ' ':
|
||||||
|
content += 1
|
||||||
|
return content - start
|
||||||
|
|
||||||
|
|
||||||
def get_comments_between_tokens(token1, token2, skip_first_line=False):
|
def get_comments_between_tokens(token1, token2, skip_first_line=False):
|
||||||
if token2 is None:
|
if token2 is None:
|
||||||
buf = token1.end_mark.buffer[token1.end_mark.pointer:]
|
buf = token1.end_mark.buffer[token1.end_mark.pointer:]
|
||||||
@@ -91,3 +101,15 @@ def get_comments_between_tokens(token1, token2, skip_first_line=False):
|
|||||||
pointer += len(line) + 1
|
pointer += len(line) + 1
|
||||||
line_no += 1
|
line_no += 1
|
||||||
column_no = 1
|
column_no = 1
|
||||||
|
|
||||||
|
|
||||||
|
def is_explicit_key(token):
|
||||||
|
# explicit key:
|
||||||
|
# ? key
|
||||||
|
# : v
|
||||||
|
# or
|
||||||
|
# ?
|
||||||
|
# key
|
||||||
|
# : v
|
||||||
|
return (token.start_mark.pointer < token.end_mark.pointer and
|
||||||
|
token.start_mark.buffer[token.start_mark.pointer] == '?')
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ TYPE = 'token'
|
|||||||
CONF = {'present': bool}
|
CONF = {'present': bool}
|
||||||
|
|
||||||
|
|
||||||
def check(conf, token, prev, next):
|
def check(conf, token, prev, next, context):
|
||||||
if conf['present']:
|
if conf['present']:
|
||||||
if (isinstance(token, yaml.StreamEndToken) and
|
if (isinstance(token, yaml.StreamEndToken) and
|
||||||
not (isinstance(prev, yaml.DocumentEndToken) or
|
not (isinstance(prev, yaml.DocumentEndToken) or
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ TYPE = 'token'
|
|||||||
CONF = {'present': bool}
|
CONF = {'present': bool}
|
||||||
|
|
||||||
|
|
||||||
def check(conf, token, prev, next):
|
def check(conf, token, prev, next, context):
|
||||||
if conf['present']:
|
if conf['present']:
|
||||||
if (isinstance(prev, (yaml.StreamStartToken,
|
if (isinstance(prev, (yaml.StreamStartToken,
|
||||||
yaml.DocumentEndToken,
|
yaml.DocumentEndToken,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ TYPE = 'token'
|
|||||||
CONF = {'max-spaces-after': int}
|
CONF = {'max-spaces-after': int}
|
||||||
|
|
||||||
|
|
||||||
def check(conf, token, prev, next):
|
def check(conf, token, prev, next, context):
|
||||||
if isinstance(token, yaml.BlockEntryToken):
|
if isinstance(token, yaml.BlockEntryToken):
|
||||||
problem = spaces_after(token, prev, next,
|
problem = spaces_after(token, prev, next,
|
||||||
max=conf['max-spaces-after'],
|
max=conf['max-spaces-after'],
|
||||||
|
|||||||
@@ -17,54 +17,172 @@
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from yamllint.errors import LintProblem
|
from yamllint.errors import LintProblem
|
||||||
|
from yamllint.rules.common import is_explicit_key
|
||||||
|
|
||||||
|
|
||||||
ID = 'indentation'
|
ID = 'indentation'
|
||||||
TYPE = 'token'
|
TYPE = 'token'
|
||||||
CONF = {'spaces': int}
|
CONF = {'spaces': int,
|
||||||
|
'indent-sequences': (True, False, 'whatever')}
|
||||||
|
|
||||||
|
ROOT, MAP, B_SEQ, F_SEQ, KEY, VAL = range(6)
|
||||||
|
|
||||||
|
|
||||||
def check(conf, token, prev, next):
|
class Parent(object):
|
||||||
if isinstance(token, (yaml.StreamStartToken, yaml.StreamEndToken)):
|
def __init__(self, type, indent):
|
||||||
return
|
self.type = type
|
||||||
|
self.indent = indent
|
||||||
|
self.explicit_key = False
|
||||||
|
|
||||||
# Check if first token in line
|
|
||||||
if (not isinstance(prev, (yaml.StreamStartToken, yaml.DirectiveToken)) and
|
|
||||||
token.start_mark.line == prev.end_mark.line):
|
|
||||||
return
|
|
||||||
|
|
||||||
if token.start_mark.column % conf['spaces'] != 0:
|
def check(conf, token, prev, next, context):
|
||||||
yield LintProblem(
|
if 'stack' not in context:
|
||||||
token.end_mark.line + 1, token.start_mark.column + 1,
|
context['stack'] = [Parent(ROOT, 0)]
|
||||||
'indentation is not a multiple of %d' % conf['spaces'])
|
context['cur_line'] = -1
|
||||||
return
|
|
||||||
|
|
||||||
if isinstance(prev, (yaml.StreamStartToken,
|
# Step 1: Lint
|
||||||
yaml.DirectiveToken,
|
|
||||||
yaml.DocumentStartToken,
|
|
||||||
yaml.DocumentEndToken)):
|
|
||||||
indent = 0
|
|
||||||
else:
|
|
||||||
buffer = prev.end_mark.buffer
|
|
||||||
start = buffer.rfind('\n', 0, prev.end_mark.pointer) + 1
|
|
||||||
|
|
||||||
indent = 0
|
if (not isinstance(token, (yaml.StreamStartToken, yaml.StreamEndToken)) and
|
||||||
while buffer[start + indent] == ' ':
|
not isinstance(token, yaml.BlockEndToken) and
|
||||||
indent += 1
|
not (isinstance(token, yaml.ScalarToken) and token.value == '') and
|
||||||
|
token.start_mark.line + 1 > context['cur_line']):
|
||||||
|
|
||||||
if token.start_mark.column > indent:
|
found_indentation = token.start_mark.column
|
||||||
if not isinstance(prev, (yaml.BlockSequenceStartToken,
|
expected = context['stack'][-1].indent
|
||||||
yaml.BlockMappingStartToken,
|
|
||||||
yaml.FlowSequenceStartToken,
|
|
||||||
yaml.FlowMappingStartToken,
|
|
||||||
yaml.KeyToken,
|
|
||||||
yaml.ValueToken)):
|
|
||||||
yield LintProblem(
|
|
||||||
token.end_mark.line + 1, token.start_mark.column + 1,
|
|
||||||
'unexpected indentation')
|
|
||||||
|
|
||||||
elif token.start_mark.column != indent + conf['spaces']:
|
if isinstance(token, (yaml.FlowMappingEndToken,
|
||||||
yield LintProblem(
|
yaml.FlowSequenceEndToken)):
|
||||||
token.end_mark.line + 1, token.start_mark.column + 1,
|
expected = 0
|
||||||
'found indentation of %d instead of %d' %
|
elif (context['stack'][-1].type == KEY and
|
||||||
(token.start_mark.column, indent + conf['spaces']))
|
context['stack'][-1].explicit_key and
|
||||||
|
not isinstance(token, yaml.ValueToken)):
|
||||||
|
expected += conf['spaces']
|
||||||
|
|
||||||
|
if found_indentation != expected:
|
||||||
|
yield LintProblem(token.start_mark.line + 1, found_indentation + 1,
|
||||||
|
'wrong indentation: expected %d but found %d' %
|
||||||
|
(expected, found_indentation))
|
||||||
|
|
||||||
|
context['cur_line_indent'] = found_indentation
|
||||||
|
context['cur_line'] = token.end_mark.line + 1
|
||||||
|
|
||||||
|
# Step 2: Update state
|
||||||
|
|
||||||
|
if isinstance(token, yaml.BlockMappingStartToken):
|
||||||
|
assert isinstance(next, yaml.KeyToken)
|
||||||
|
if next.start_mark.line == token.start_mark.line:
|
||||||
|
# - a: 1
|
||||||
|
# b: 2
|
||||||
|
# or
|
||||||
|
# - ? a
|
||||||
|
# : 1
|
||||||
|
indent = token.start_mark.column
|
||||||
|
else:
|
||||||
|
# - ?
|
||||||
|
# a
|
||||||
|
# : 1
|
||||||
|
indent = token.start_mark.column + conf['spaces']
|
||||||
|
|
||||||
|
context['stack'].append(Parent(MAP, indent))
|
||||||
|
|
||||||
|
elif isinstance(token, yaml.FlowMappingStartToken):
|
||||||
|
if next.start_mark.line == token.start_mark.line:
|
||||||
|
# - {a: 1, b: 2}
|
||||||
|
indent = next.start_mark.column
|
||||||
|
else:
|
||||||
|
# - {
|
||||||
|
# a: 1, b: 2
|
||||||
|
# }
|
||||||
|
indent = context['cur_line_indent'] + conf['spaces']
|
||||||
|
|
||||||
|
context['stack'].append(Parent(MAP, indent))
|
||||||
|
|
||||||
|
elif isinstance(token, yaml.BlockSequenceStartToken):
|
||||||
|
# - - a
|
||||||
|
# - b
|
||||||
|
assert next.start_mark.line == token.start_mark.line
|
||||||
|
assert isinstance(next, yaml.BlockEntryToken)
|
||||||
|
|
||||||
|
indent = token.start_mark.column
|
||||||
|
|
||||||
|
context['stack'].append(Parent(B_SEQ, indent))
|
||||||
|
|
||||||
|
elif isinstance(token, yaml.FlowSequenceStartToken):
|
||||||
|
if next.start_mark.line == token.start_mark.line:
|
||||||
|
# - [a, b]
|
||||||
|
indent = next.start_mark.column
|
||||||
|
else:
|
||||||
|
# - [
|
||||||
|
# a, b
|
||||||
|
# ]
|
||||||
|
indent = context['cur_line_indent'] + conf['spaces']
|
||||||
|
|
||||||
|
context['stack'].append(Parent(F_SEQ, indent))
|
||||||
|
|
||||||
|
elif isinstance(token, (yaml.BlockEndToken,
|
||||||
|
yaml.FlowMappingEndToken,
|
||||||
|
yaml.FlowSequenceEndToken)):
|
||||||
|
assert context['stack'][-1].type in (MAP, B_SEQ, F_SEQ)
|
||||||
|
context['stack'].pop()
|
||||||
|
|
||||||
|
elif isinstance(token, yaml.KeyToken):
|
||||||
|
indent = context['stack'][-1].indent
|
||||||
|
|
||||||
|
context['stack'].append(Parent(KEY, indent))
|
||||||
|
|
||||||
|
context['stack'][-1].explicit_key = is_explicit_key(token)
|
||||||
|
|
||||||
|
if context['stack'][-1].type == VAL:
|
||||||
|
context['stack'].pop()
|
||||||
|
assert context['stack'][-1].type == KEY
|
||||||
|
context['stack'].pop()
|
||||||
|
|
||||||
|
elif isinstance(token, yaml.ValueToken):
|
||||||
|
assert context['stack'][-1].type == KEY
|
||||||
|
|
||||||
|
# Discard empty values
|
||||||
|
if isinstance(next, (yaml.BlockEndToken,
|
||||||
|
yaml.FlowMappingEndToken,
|
||||||
|
yaml.FlowSequenceEndToken,
|
||||||
|
yaml.KeyToken)):
|
||||||
|
context['stack'].pop()
|
||||||
|
else:
|
||||||
|
if context['stack'][-1].explicit_key:
|
||||||
|
# ? k
|
||||||
|
# : value
|
||||||
|
# or
|
||||||
|
# ? k
|
||||||
|
# :
|
||||||
|
# value
|
||||||
|
indent = context['stack'][-1].indent + conf['spaces']
|
||||||
|
elif next.start_mark.line == prev.start_mark.line:
|
||||||
|
# k: value
|
||||||
|
indent = next.start_mark.column
|
||||||
|
elif isinstance(next, (yaml.BlockSequenceStartToken,
|
||||||
|
yaml.BlockEntryToken)):
|
||||||
|
# NOTE: We add BlockEntryToken in the test above because
|
||||||
|
# sometimes BlockSequenceStartToken are not issued. Try
|
||||||
|
# yaml.scan()ning this:
|
||||||
|
# '- lib:\n'
|
||||||
|
# ' - var\n'
|
||||||
|
if conf['indent-sequences'] is False:
|
||||||
|
indent = context['stack'][-1].indent
|
||||||
|
elif conf['indent-sequences'] is True:
|
||||||
|
indent = context['stack'][-1].indent + conf['spaces']
|
||||||
|
else: # 'whatever'
|
||||||
|
if next.start_mark.column == context['stack'][-1].indent:
|
||||||
|
# key:
|
||||||
|
# - e1
|
||||||
|
# - e2
|
||||||
|
indent = context['stack'][-1].indent
|
||||||
|
else:
|
||||||
|
# key:
|
||||||
|
# - e1
|
||||||
|
# - e2
|
||||||
|
indent = context['stack'][-1].indent + conf['spaces']
|
||||||
|
else:
|
||||||
|
# k:
|
||||||
|
# value
|
||||||
|
indent = context['stack'][-1].indent + conf['spaces']
|
||||||
|
|
||||||
|
context['stack'].append(Parent(VAL, indent))
|
||||||
|
|||||||
Reference in New Issue
Block a user