From 851b9ac42c9e69411a6294159eeb0f0c66710089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Wed, 13 Jan 2016 21:36:35 +0100 Subject: [PATCH] Rules: Add the 'comments' rule --- tests/rules/test_comments.py | 165 +++++++++++++++++++++++++++++++++++ yamllint/conf/default.yml | 14 +-- yamllint/rules/__init__.py | 2 + yamllint/rules/comments.py | 84 ++++++++++++++++++ 4 files changed, 259 insertions(+), 6 deletions(-) create mode 100644 tests/rules/test_comments.py create mode 100644 yamllint/rules/comments.py diff --git a/tests/rules/test_comments.py b/tests/rules/test_comments.py new file mode 100644 index 0000000..9d247bc --- /dev/null +++ b/tests/rules/test_comments.py @@ -0,0 +1,165 @@ +# -*- 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 . + +import yaml + +from tests.rules.common import RuleTestCase +from yamllint.rules.inline_comments import (Comment, + get_comments_until_next_token) + + +class CommentsTestCase(RuleTestCase): + rule_id = 'comments' + + 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_until_next_token(curr, next): + comments.append(comment) + + self.assertEqual(comments, list(expected)) + + def test_get_comments_until_next_token(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('\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)) + + def test_disabled(self): + conf = 'comments: disable' + self.check('---\n' + '#comment\n' + '\n' + 'test: # description\n' + ' - foo # bar\n' + ' - hello #world\n' + '\n' + '# comment 2\n' + '#comment 3\n' + ' #comment 3 bis\n' + ' # comment 3 ter\n' + '\n' + 'string: "Une longue phrase." # this is French\n', conf) + + def test_starting_space(self): + conf = ('comments:\n' + ' require-starting-space: yes\n' + ' min-spaces-from-content: -1\n') + self.check('---\n' + '# comment\n' + '\n' + 'test: # description\n' + ' - foo # bar\n' + ' - hello # world\n' + '\n' + '# comment 2\n' + '# comment 3\n' + ' # comment 3 bis\n' + ' # comment 3 ter\n', conf) + self.check('---\n' + '#comment\n' + '\n' + 'test: # description\n' + ' - foo # bar\n' + ' - hello #world\n' + '\n' + '# comment 2\n' + '#comment 3\n' + ' #comment 3 bis\n' + ' # comment 3 ter\n', conf, + problem1=(2, 2), problem2=(6, 13), + problem4=(9, 2), problem5=(10, 4)) + + def test_spaces_from_content(self): + conf = ('comments:\n' + ' require-starting-space: no\n' + ' min-spaces-from-content: 2\n') + self.check('---\n' + '# comment\n' + '\n' + 'test: # description\n' + ' - foo # bar\n' + ' - hello #world\n' + '\n' + 'string: "Une longue phrase." # this is French\n', conf) + self.check('---\n' + '# comment\n' + '\n' + 'test: # description\n' + ' - foo # bar\n' + ' - hello #world\n' + '\n' + 'string: "Une longue phrase." # this is French\n', conf, + problem1=(4, 7), problem2=(6, 11), problem3=(8, 30)) + + def test_both(self): + conf = ('comments:\n' + ' require-starting-space: yes\n' + ' min-spaces-from-content: 2\n') + self.check('---\n' + '#comment\n' + '\n' + 'test: # description\n' + ' - foo # bar\n' + ' - hello #world\n' + '\n' + '# comment 2\n' + '#comment 3\n' + ' #comment 3 bis\n' + ' # comment 3 ter\n' + '\n' + 'string: "Une longue phrase." # this is French\n', conf, + problem1=(2, 2), + problem2=(4, 7), + problem3=(6, 11), problem4=(6, 12), + problem5=(9, 2), + problem6=(10, 4), + problem7=(13, 30)) diff --git a/yamllint/conf/default.yml b/yamllint/conf/default.yml index 5b1a86d..8ee5f10 100644 --- a/yamllint/conf/default.yml +++ b/yamllint/conf/default.yml @@ -1,8 +1,8 @@ --- rules: - #block-sequence-indentation: - # present: yes + # block-sequence-indentation: + # present: yes braces: min-spaces-inside: 0 max-spaces-inside: 0 @@ -15,9 +15,10 @@ rules: commas: max-spaces-before: 0 max-spaces-after: 1 - #comment: - # min-spaces-after: 1 - # min-spaces-before: 2 + comments: + level: warning + require-starting-space: yes + min-spaces-from-content: 2 document-end: disable document-start: level: warning @@ -30,7 +31,8 @@ rules: max-spaces-after: 1 indentation: spaces: 2 - #comments: yes + # indent-lists: yes + # check-comments: yes line-length: max: 80 new-line-at-end-of-file: {level: error} diff --git a/yamllint/rules/__init__.py b/yamllint/rules/__init__.py index 854056b..5994604 100644 --- a/yamllint/rules/__init__.py +++ b/yamllint/rules/__init__.py @@ -19,6 +19,7 @@ from yamllint.rules import ( brackets, colons, commas, + comments, document_end, document_start, empty_lines, @@ -35,6 +36,7 @@ _RULES = { brackets.ID: brackets, colons.ID: colons, commas.ID: commas, + comments.ID: comments, document_end.ID: document_end, document_start.ID: document_start, empty_lines.ID: empty_lines, diff --git a/yamllint/rules/comments.py b/yamllint/rules/comments.py new file mode 100644 index 0000000..120fd13 --- /dev/null +++ b/yamllint/rules/comments.py @@ -0,0 +1,84 @@ +# -*- 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 yamllint.errors import LintProblem + + +ID = 'comments' +TYPE = 'token' +CONF = {'require-starting-space': bool, + 'min-spaces-from-content': int} + + +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_comments_until_next_token(token, next): + if next is None: + buf = token.end_mark.buffer[token.end_mark.pointer:] + elif token.end_mark.line == next.start_mark.line: + return + else: + buf = token.end_mark.buffer[token.end_mark.pointer: + next.start_mark.pointer] + + line_no = token.end_mark.line + 1 + column_no = token.end_mark.column + 1 + pointer = token.end_mark.pointer + + for line in buf.split('\n'): + pos = line.find('#') + if pos != -1: + yield Comment(line_no, column_no + pos, + token.end_mark.buffer, pointer + pos) + + pointer += len(line) + 1 + line_no += 1 + column_no = 1 + + +def check(conf, token, prev, next): + for comment in get_comments_until_next_token(token, next): + if (conf['min-spaces-from-content'] != -1 and + comment.line == token.end_mark.line + 1 and + 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 + comment.pointer + 1 < len(comment.buffer) and + comment.buffer[comment.pointer + 1] != ' '): + yield LintProblem(comment.line, comment.column + 1, + 'missing starting space in comment')