diff --git a/tests/rules/test_indentation.py b/tests/rules/test_indentation.py index 40b38aa..af8e5da 100644 --- a/tests/rules/test_indentation.py +++ b/tests/rules/test_indentation.py @@ -15,6 +15,206 @@ # along with this program. If not, see . from tests.common import RuleTestCase +from yamllint.parser import token_generator +from yamllint.rules.indentation import check + + +class IndentationStackTestCase(RuleTestCase): + # This test suite checks that the "indentation stack" built by the + # indentation rule is valid. It is important, since everything else in the + # rule relies on this stack. + + maxDiff = None + + def format_stack(self, stack): + """Transform the stack at a given moment into a printable string like: + + B_MAP:0 KEY:0 VAL:5 + """ + return ' '.join(map(str, stack[1:])) + + def full_stack(self, source): + conf = {'spaces': 2, 'indent-sequences': True, + 'check-multi-line-strings': False} + context = {} + output = '' + for elem in token_generator(source): + list(check(conf, elem.curr, elem.prev, elem.next, context)) + + token_type = (elem.curr.__class__.__name__ + .replace('Token', '') + .replace('Block', 'B').replace('Flow', 'F') + .replace('Sequence', 'Seq') + .replace('Mapping', 'Map')) + if token_type in ('StreamStart', 'StreamEnd'): + continue + output += '%9s %s\n' % (token_type, + self.format_stack(context['stack'])) + return output + + def test_simple_mapping(self): + self.assertMultiLineEqual( + self.full_stack('key: val\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:5\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack(' key: val\n'), + 'BMapStart B_MAP:5\n' + ' Key B_MAP:5 KEY:5\n' + ' Scalar B_MAP:5 KEY:5\n' + ' Value B_MAP:5 KEY:5 VAL:10\n' + ' Scalar B_MAP:5\n' + ' BEnd \n') + + def test_simple_sequence(self): + self.assertMultiLineEqual( + self.full_stack('- 1\n' + '- 2\n' + '- 3\n'), + 'BSeqStart B_SEQ:0\n' + ' BEntry B_SEQ:0 B_ENT:2\n' + ' Scalar B_SEQ:0\n' + ' BEntry B_SEQ:0 B_ENT:2\n' + ' Scalar B_SEQ:0\n' + ' BEntry B_SEQ:0 B_ENT:2\n' + ' Scalar B_SEQ:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('key:\n' + ' - 1\n' + ' - 2\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + 'BSeqStart B_MAP:0 KEY:0 VAL:2 B_SEQ:2\n' + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:2 B_ENT:4\n' + ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:2\n' + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:2 B_ENT:4\n' + ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:2\n' + ' BEnd B_MAP:0\n' + ' BEnd \n') + + def test_non_indented_sequences(self): + # There seems to be a bug in pyyaml: depending on the indentation, a + # sequence does not produce the same tokens. More precisely, the + # following YAML: + # usr: + # - lib + # produces a BlockSequenceStartToken and a BlockEndToken around the + # "lib" sequence, whereas the following: + # usr: + # - lib + # does not (both two tokens are omitted). + # So, yamllint must create fake 'B_SEQ'. This test makes sure it does. + + self.assertMultiLineEqual( + self.full_stack('usr:\n' + ' - lib\n' + 'var: cache\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + 'BSeqStart B_MAP:0 KEY:0 VAL:2 B_SEQ:2\n' + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:2 B_ENT:4\n' + ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:2\n' + ' BEnd B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:5\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('usr:\n' + '- lib\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + # missing BSeqStart here + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n' + ' Scalar B_MAP:0\n' + # missing BEnd here + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('usr:\n' + '- lib\n' + 'var: cache\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + # missing BSeqStart here + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n' + ' Scalar B_MAP:0\n' + # missing BEnd here + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:5\n' + ' Scalar B_MAP:0\n' + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('usr:\n' + '- []\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + # missing BSeqStart here + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n' + 'FSeqStart B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 F_SEQ:3\n' + ' FSeqEnd B_MAP:0\n' + # missing BEnd here + ' BEnd \n') + + self.assertMultiLineEqual( + self.full_stack('usr:\n' + '- k:\n' + ' v\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:2\n' + # missing BSeqStart here + ' BEntry B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2\n' + 'BMapStart B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2\n' + ' Key B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2\n' + ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2\n' + ' Value B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2 KEY:2 VAL:4\n' # noqa + ' Scalar B_MAP:0 KEY:0 VAL:2 B_SEQ:0 B_ENT:2 B_MAP:2\n' + ' BEnd B_MAP:0\n' + # missing BEnd here + ' BEnd \n') + + def test_flows(self): + self.assertMultiLineEqual( + self.full_stack('usr: [\n' + ' {k:\n' + ' v}\n' + ' ]\n'), + 'BMapStart B_MAP:0\n' + ' Key B_MAP:0 KEY:0\n' + ' Scalar B_MAP:0 KEY:0\n' + ' Value B_MAP:0 KEY:0 VAL:5\n' + 'FSeqStart B_MAP:0 KEY:0 VAL:5 F_SEQ:2\n' + 'FMapStart B_MAP:0 KEY:0 VAL:5 F_SEQ:2 F_MAP:3\n' + ' Key B_MAP:0 KEY:0 VAL:5 F_SEQ:2 F_MAP:3 KEY:3\n' + ' Scalar B_MAP:0 KEY:0 VAL:5 F_SEQ:2 F_MAP:3 KEY:3\n' + ' Value B_MAP:0 KEY:0 VAL:5 F_SEQ:2 F_MAP:3 KEY:3 VAL:5\n' + ' Scalar B_MAP:0 KEY:0 VAL:5 F_SEQ:2 F_MAP:3\n' + ' FMapEnd B_MAP:0 KEY:0 VAL:5 F_SEQ:2\n' + ' FSeqEnd B_MAP:0\n' + ' BEnd \n') class IndentationTestCase(RuleTestCase): diff --git a/yamllint/rules/indentation.py b/yamllint/rules/indentation.py index 1a7410e..35c9679 100644 --- a/yamllint/rules/indentation.py +++ b/yamllint/rules/indentation.py @@ -155,6 +155,7 @@ CONF = {'spaces': int, 'check-multi-line-strings': bool} ROOT, B_MAP, F_MAP, B_SEQ, F_SEQ, B_ENT, KEY, VAL = range(8) +labels = ('ROOT', 'B_MAP', 'F_MAP', 'B_SEQ', 'F_SEQ', 'B_ENT', 'KEY', 'VAL') class Parent(object): @@ -165,6 +166,9 @@ class Parent(object): self.explicit_key = False self.implicit_block_seq = False + def __repr__(self): + return '%s:%d' % (labels[self.type], self.indent) + def check_scalar_indentation(conf, token, context): if token.start_mark.line == token.end_mark.line: