diff --git a/docs/disable_with_comments.rst b/docs/disable_with_comments.rst new file mode 100644 index 0000000..10477fb --- /dev/null +++ b/docs/disable_with_comments.rst @@ -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``. diff --git a/docs/index.rst b/docs/index.rst index 773b143..97e1fc7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,5 +23,6 @@ Table of contents quickstart configuration rules + disable_with_comments development text_editors diff --git a/tests/test_yamllint_directives.py b/tests/test_yamllint_directives.py new file mode 100644 index 0000000..8c6e865 --- /dev/null +++ b/tests/test_yamllint_directives.py @@ -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 . + +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')) diff --git a/yamllint/linter.py b/yamllint/linter.py index 98919d2..9b55b29 100644 --- a/yamllint/linter.py +++ b/yamllint/linter.py @@ -14,6 +14,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import re + import yaml from yamllint import parser @@ -63,6 +65,55 @@ def get_costemic_problems(buffer, conf): for rule in token_rules: context[rule.ID] = {} + class DisableDirective(): + def __init__(self): + self.rules = set() + self.all_rules = set([r.ID for r in rules]) + + def process_comment(self, comment): + comment = repr(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): + comment = repr(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): for rule in token_rules: @@ -73,22 +124,45 @@ def get_costemic_problems(buffer, conf): context[rule.ID]): problem.rule = rule.ID problem.level = rule_conf['level'] - yield problem + cache.append(problem) elif isinstance(elem, parser.Comment): for rule in comment_rules: rule_conf = conf.rules[rule.ID] for problem in rule.check(rule_conf, elem): problem.rule = rule.ID problem.level = rule_conf['level'] - yield problem + cache.append(problem) + + disabled.process_comment(elem) + if elem.is_inline(): + disabled_for_line.process_comment(elem) + else: + disabled_for_next_line.process_comment(elem) elif isinstance(elem, parser.Line): for rule in line_rules: rule_conf = conf.rules[rule.ID] for problem in rule.check(rule_conf, elem): problem.rule = rule.ID problem.level = rule_conf['level'] + cache.append(problem) + + # This is the last token/comment/line of this line, let's flush the + # problems found (but filter them according to the directives) + for problem in cache: + if not (disabled_for_line.is_disabled_by_directive(problem) or + disabled.is_disabled_by_directive(problem)): yield problem + disabled_for_line = disabled_for_next_line + disabled_for_next_line = DisableLineDirective() + cache = [] + + # If no new line at the end of file, the cache is not empty + for problem in cache: + if not (disabled_for_line.is_disabled_by_directive(problem) or + disabled.is_disabled_by_directive(problem)): + yield problem + def get_syntax_error(buffer): try: