Skip to content

Commit aee620a

Browse files
committed
Throw an error if block sequence/mapping indent contains a tab
fix #80
1 parent f0f205b commit aee620a

File tree

4 files changed

+100
-1
lines changed

4 files changed

+100
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4949
(previously usage of custom non-ascii tags may have led to invalid YAML that can't be parsed).
5050
- Anchors now work correctly with empty nodes, #301.
5151
- Fix incorrect parsing of invalid block mapping syntax, #418.
52+
- Throw an error if block sequence/mapping indent contains a tab, #80.
5253

5354

5455
## [3.14.1] - 2020-12-07

lib/loader.js

+25
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ function State(input, options) {
150150
this.lineStart = 0;
151151
this.lineIndent = 0;
152152

153+
// position of first leading tab in the current line,
154+
// used to make sure there are no tabs in the indentation
155+
this.firstTabInLine = -1;
156+
153157
this.documents = [];
154158

155159
/*
@@ -389,6 +393,7 @@ function readLineBreak(state) {
389393

390394
state.line += 1;
391395
state.lineStart = state.position;
396+
state.firstTabInLine = -1;
392397
}
393398

394399
function skipSeparationSpace(state, allowComments, checkIndent) {
@@ -397,6 +402,9 @@ function skipSeparationSpace(state, allowComments, checkIndent) {
397402

398403
while (ch !== 0) {
399404
while (is_WHITE_SPACE(ch)) {
405+
if (ch === 0x09/* Tab */ && state.firstTabInLine === -1) {
406+
state.firstTabInLine = state.position;
407+
}
400408
ch = state.input.charCodeAt(++state.position);
401409
}
402410

@@ -959,13 +967,21 @@ function readBlockSequence(state, nodeIndent) {
959967
detected = false,
960968
ch;
961969

970+
// there is a leading tab before this token, so it can't be a block sequence/mapping;
971+
// it can still be flow sequence/mapping or a scalar
972+
if (state.firstTabInLine !== -1) return false;
973+
962974
if (state.anchor !== null) {
963975
state.anchorMap[state.anchor] = _result;
964976
}
965977

966978
ch = state.input.charCodeAt(state.position);
967979

968980
while (ch !== 0) {
981+
if (state.firstTabInLine !== -1) {
982+
state.position = state.firstTabInLine;
983+
throwError(state, 'tab characters must not be used in indentation');
984+
}
969985

970986
if (ch !== 0x2D/* - */) {
971987
break;
@@ -1030,13 +1046,22 @@ function readBlockMapping(state, nodeIndent, flowIndent) {
10301046
detected = false,
10311047
ch;
10321048

1049+
// there is a leading tab before this token, so it can't be a block sequence/mapping;
1050+
// it can still be flow sequence/mapping or a scalar
1051+
if (state.firstTabInLine !== -1) return false;
1052+
10331053
if (state.anchor !== null) {
10341054
state.anchorMap[state.anchor] = _result;
10351055
}
10361056

10371057
ch = state.input.charCodeAt(state.position);
10381058

10391059
while (ch !== 0) {
1060+
if (!atExplicitKey && state.firstTabInLine !== -1) {
1061+
state.position = state.firstTabInLine;
1062+
throwError(state, 'tab characters must not be used in indentation');
1063+
}
1064+
10401065
following = state.input.charCodeAt(state.position + 1);
10411066
_line = state.line; // Save the current line.
10421067

lib/snippet.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function getLine(buffer, lineStart, lineEnd, position, maxLineLength) {
2121
}
2222

2323
return {
24-
str: head + buffer.slice(lineStart, lineEnd) + tail,
24+
str: head + buffer.slice(lineStart, lineEnd).replace(/\t/g, '→') + tail,
2525
pos: position - lineStart + head.length // relative position
2626
};
2727
}

test/issues/0080.js

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
'use strict';
2+
3+
4+
const assert = require('assert');
5+
const yaml = require('../../');
6+
7+
8+
it('should throw when tabs are used as indentation', function () {
9+
assert.throws(() => yaml.load(`
10+
\tfoo: 1
11+
bar: 2
12+
`), /end of the stream or a document separator is expected/);
13+
14+
assert.throws(() => yaml.load(`
15+
foo: 1
16+
\tbar: 2
17+
`), /tab characters must not be used/);
18+
19+
assert.throws(() => yaml.load(`
20+
\t- foo
21+
- bar
22+
`), /end of the stream or a document separator is expected/);
23+
24+
assert.throws(() => yaml.load(`
25+
- foo
26+
\t- bar
27+
`), /tab characters must not be used/);
28+
});
29+
30+
31+
it('should allow tabs inside separation spaces', function () {
32+
assert.deepStrictEqual(yaml.load(`
33+
foo\t \t:\t \t1\t \t
34+
\t \t \t
35+
bar \t : \t 2 \t
36+
`), { foo: 1, bar: 2 });
37+
38+
assert.deepStrictEqual(yaml.load(`
39+
-\t \tfoo\t \t
40+
\t \t \t
41+
- \t bar \t
42+
`), [ 'foo', 'bar' ]);
43+
44+
assert.deepStrictEqual(yaml.load(`
45+
\t{\tfoo\t:\t1\t,\tbar\t:\t2\t}\t
46+
`), { foo: 1, bar: 2 });
47+
48+
assert.deepStrictEqual(yaml.load(`
49+
\t[\tfoo\t,\tbar\t]\t
50+
`), [ 'foo', 'bar' ]);
51+
52+
assert.deepStrictEqual(yaml.load(`
53+
foo: # string indent = 1
54+
\t \t1
55+
\t 2
56+
\t \t3
57+
`), { foo: '1 2 3' });
58+
});
59+
60+
61+
it('should throw when tabs are used as indentation in strings', function () {
62+
assert.throws(() => yaml.load(`
63+
foo:
64+
bar: |
65+
\tbaz
66+
`), /tab characters must not be used/);
67+
68+
assert.deepStrictEqual(yaml.load(`
69+
foo:
70+
bar: |
71+
\tbaz
72+
`), { foo: { bar: '\tbaz\n' } });
73+
});

0 commit comments

Comments
 (0)