diff --git a/tests/rules/test_comments.py b/tests/rules/test_comments.py index 42ac733..4f64a97 100644 --- a/tests/rules/test_comments.py +++ b/tests/rules/test_comments.py @@ -14,67 +14,15 @@ # 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' + conf = ('comments: disable\n' + 'comments-indentation: disable\n') self.check('---\n' '#comment\n' '\n' @@ -92,7 +40,8 @@ class CommentsTestCase(RuleTestCase): def test_starting_space(self): conf = ('comments:\n' ' require-starting-space: yes\n' - ' min-spaces-from-content: -1\n') + ' min-spaces-from-content: -1\n' + 'comments-indentation: disable\n') self.check('---\n' '# comment\n' '\n' @@ -143,7 +92,8 @@ class CommentsTestCase(RuleTestCase): def test_both(self): conf = ('comments:\n' ' require-starting-space: yes\n' - ' min-spaces-from-content: 2\n') + ' min-spaces-from-content: 2\n' + 'comments-indentation: disable\n') self.check('---\n' '#comment\n' '\n' @@ -174,4 +124,10 @@ class CommentsTestCase(RuleTestCase): '# This is paragraph 2.\n', conf) self.check('---\n' 'inline: comment #\n' - '\n', conf) + 'foo: bar\n', conf) + + def test_first_line(self): + conf = ('comments:\n' + ' require-starting-space: yes\n' + ' min-spaces-from-content: 2\n') + self.check('# comment\n', conf) diff --git a/tests/rules/test_comments_indentation.py b/tests/rules/test_comments_indentation.py new file mode 100644 index 0000000..47b1ff9 --- /dev/null +++ b/tests/rules/test_comments_indentation.py @@ -0,0 +1,121 @@ +# -*- 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.rules.common import RuleTestCase + + +class CommentsIndentationTestCase(RuleTestCase): + rule_id = 'comments-indentation' + + def test_disable(self): + conf = 'comments-indentation: disable' + self.check('---\n' + ' # line 1\n' + '# line 2\n' + ' # line 3\n' + ' # line 4\n' + '\n' + 'obj:\n' + ' # these\n' + ' # are\n' + ' # [good]\n' + '# bad\n' + ' # comments\n' + ' a: b\n' + '\n' + 'obj1:\n' + ' a: 1\n' + ' # comments\n' + '\n' + 'obj2:\n' + ' b: 2\n' + '\n' + '# empty\n' + '#\n' + '# comment\n' + '...\n', conf) + + def test_enabled(self): + conf = 'comments-indentation: {}' + self.check('---\n' + '# line 1\n' + '# line 2\n', conf) + self.check('---\n' + ' # line 1\n' + '# line 2\n', conf, problem=(2, 2)) + self.check('---\n' + ' # line 1\n' + ' # line 2\n', conf, problem1=(2, 3), problem2=(3, 3)) + self.check('---\n' + 'obj:\n' + ' # normal\n' + ' a: b\n', conf) + self.check('---\n' + 'obj:\n' + ' # bad\n' + ' a: b\n', conf, problem=(3, 2)) + self.check('---\n' + 'obj:\n' + '# bad\n' + ' a: b\n', conf, problem=(3, 1)) + self.check('---\n' + 'obj:\n' + ' # bad\n' + ' a: b\n', conf, problem=(3, 4)) + self.check('---\n' + 'obj:\n' + ' # these\n' + ' # are\n' + ' # [good]\n' + '# bad\n' + ' # comments\n' + ' a: b\n', conf, + problem1=(3, 2), problem2=(4, 4), + problem3=(6, 1), problem4=(7, 7)) + self.check('---\n' + 'obj1:\n' + ' a: 1\n' + ' # comments\n' + '\n' + 'obj2:\n' + ' b: 2\n', conf, problem=(4, 3)) + self.check('---\n' + 'obj1:\n' + ' a: 1\n' + ' # comments\n' + ' b: 2\n', conf) + + def test_first_line(self): + conf = 'comments-indentation: {}' + self.check('# comment\n', conf) + self.check(' # comment\n', conf, problem=(1, 3)) + + def test_no_newline_at_end(self): + conf = ('comments-indentation: {}\n' + 'new-line-at-end-of-file: disable\n') + self.check('# comment', conf) + self.check(' # comment', conf, problem=(1, 3)) + + def test_empty_comment(self): + conf = 'comments-indentation: {}' + self.check('---\n' + '# hey\n' + '# normal\n' + '#\n', conf) + self.check('---\n' + '# hey\n' + '# normal\n' + ' #\n', conf, problem=(4, 2)) diff --git a/tests/rules/test_common.py b/tests/rules/test_common.py new file mode 100644 index 0000000..ca3320f --- /dev/null +++ b/tests/rules/test_common.py @@ -0,0 +1,74 @@ +# -*- 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 unittest + +import yaml + +from yamllint.rules.common import Comment, get_comments_between_tokens + + +class CommonTestCase(unittest.TestCase): + 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)) diff --git a/yamllint/conf/default.yml b/yamllint/conf/default.yml index 8ee5f10..5a30434 100644 --- a/yamllint/conf/default.yml +++ b/yamllint/conf/default.yml @@ -1,8 +1,6 @@ --- rules: - # block-sequence-indentation: - # present: yes braces: min-spaces-inside: 0 max-spaces-inside: 0 @@ -19,6 +17,8 @@ rules: level: warning require-starting-space: yes min-spaces-from-content: 2 + comments-indentation: + level: warning document-end: disable document-start: level: warning @@ -31,11 +31,12 @@ rules: max-spaces-after: 1 indentation: spaces: 2 - # indent-lists: yes - # check-comments: yes line-length: max: 80 new-line-at-end-of-file: {level: error} new-lines: type: unix + #sequences-indentation: + # level: warning + # present: yes trailing-spaces: {} diff --git a/yamllint/rules/__init__.py b/yamllint/rules/__init__.py index 5994604..8a7dd23 100644 --- a/yamllint/rules/__init__.py +++ b/yamllint/rules/__init__.py @@ -20,6 +20,7 @@ from yamllint.rules import ( colons, commas, comments, + comments_indentation, document_end, document_start, empty_lines, @@ -37,6 +38,7 @@ _RULES = { colons.ID: colons, commas.ID: commas, comments.ID: comments, + comments_indentation.ID: comments_indentation, 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 index 4a8d83c..2b88d1a 100644 --- a/yamllint/rules/comments.py +++ b/yamllint/rules/comments.py @@ -14,7 +14,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import yaml + from yamllint.errors import LintProblem +from yamllint.rules.common import get_comments_between_tokens ID = 'comments' @@ -23,54 +26,10 @@ 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): + for comment in get_comments_between_tokens(token, next): if (conf['min-spaces-from-content'] != -1 and + not isinstance(token, yaml.StreamStartToken) and comment.line == token.end_mark.line + 1 and comment.pointer - token.end_mark.pointer < conf['min-spaces-from-content']): diff --git a/yamllint/rules/comments_indentation.py b/yamllint/rules/comments_indentation.py new file mode 100644 index 0000000..d8e76ab --- /dev/null +++ b/yamllint/rules/comments_indentation.py @@ -0,0 +1,43 @@ +# -*- 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 yamllint.errors import LintProblem +from yamllint.rules.common import get_comments_between_tokens + + +ID = 'comments-indentation' +TYPE = 'token' + + +def check(conf, token, prev, next): + if prev is None: + return + + token_indent = token.start_mark.column + if isinstance(token, yaml.StreamEndToken): + token_indent = 0 + + skip_first = True + if isinstance(prev, yaml.StreamStartToken): + skip_first = False + + for comment in get_comments_between_tokens(prev, token, + skip_first_line=skip_first): + if comment.column != token_indent + 1: + yield LintProblem(comment.line, comment.column, + 'comment not intended like content') diff --git a/yamllint/rules/common.py b/yamllint/rules/common.py index 955d91a..4047c28 100644 --- a/yamllint/rules/common.py +++ b/yamllint/rules/common.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 yaml + from yamllint.errors import LintProblem @@ -39,3 +41,53 @@ def spaces_before(token, prev, next, min=-1, max=-1, elif min != - 1 and spaces < min: return LintProblem(token.start_mark.line + 1, 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_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