Skip to content

Commit 4e66d72

Browse files
committed
fix: Improve tab handling (tests DK95 & Y79Y, #553)
1 parent d06f386 commit 4e66d72

10 files changed

Lines changed: 53 additions & 21 deletions

src/compose/compose-doc.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export function composeDoc<
3838
next: value ?? end?.[0],
3939
offset,
4040
onError,
41+
parentIndent: 0,
4142
startOnNewline: true
4243
})
4344
if (props.found) {

src/compose/compose-scalar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function composeScalar(
1616
) {
1717
const { value, type, comment, range } =
1818
token.type === 'block-scalar'
19-
? resolveBlockScalar(token, ctx.options.strict, onError)
19+
? resolveBlockScalar(ctx, token, onError)
2020
: resolveFlowScalar(token, ctx.options.strict, onError)
2121

2222
const tagName = tagToken

src/compose/resolve-block-map.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export function resolveBlockMap(
3434
next: key ?? sep?.[0],
3535
offset,
3636
onError,
37+
parentIndent: bm.indent,
3738
startOnNewline: true
3839
})
3940
const implicitKey = !keyProps.found
@@ -83,6 +84,7 @@ export function resolveBlockMap(
8384
next: value,
8485
offset: keyNode.range[2],
8586
onError,
87+
parentIndent: bm.indent,
8688
startOnNewline: !key || key.type === 'block-scalar'
8789
})
8890
offset = valueProps.end

src/compose/resolve-block-scalar.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { Range } from '../nodes/Node.js'
22
import { Scalar } from '../nodes/Scalar.js'
33
import type { BlockScalar } from '../parse/cst.js'
4+
import type { ComposeContext } from './compose-node.js'
45
import type { ComposeErrorHandler } from './composer.js'
56

67
export function resolveBlockScalar(
8+
ctx: ComposeContext,
79
scalar: BlockScalar,
8-
strict: boolean,
910
onError: ComposeErrorHandler
1011
): {
1112
value: string
@@ -14,7 +15,7 @@ export function resolveBlockScalar(
1415
range: Range
1516
} {
1617
const start = scalar.offset
17-
const header = parseBlockScalarHeader(scalar, strict, onError)
18+
const header = parseBlockScalarHeader(scalar, ctx.options.strict, onError)
1819
if (!header)
1920
return { value: '', type: null, comment: '', range: [start, start, start] }
2021
const type = header.mode === '>' ? Scalar.BLOCK_FOLDED : Scalar.BLOCK_LITERAL
@@ -56,6 +57,10 @@ export function resolveBlockScalar(
5657
}
5758
if (header.indent === 0) trimIndent = indent.length
5859
contentStart = i
60+
if (trimIndent === 0 && !ctx.atRoot) {
61+
const message = 'Block scalar values in collections must be indented'
62+
onError(offset, 'BAD_INDENT', message)
63+
}
5964
break
6065
}
6166
offset += indent.length + content.length + 1

src/compose/resolve-block-seq.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export function resolveBlockSeq(
2525
next: value,
2626
offset,
2727
onError,
28+
parentIndent: bs.indent,
2829
startOnNewline: true
2930
})
3031
if (!props.found) {

src/compose/resolve-flow-collection.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export function resolveFlowCollection(
4444
next: key ?? sep?.[0],
4545
offset,
4646
onError,
47+
parentIndent: fc.indent,
4748
startOnNewline: false
4849
})
4950
if (!props.found) {
@@ -130,6 +131,7 @@ export function resolveFlowCollection(
130131
next: value,
131132
offset: keyNode.range[2],
132133
onError,
134+
parentIndent: fc.indent,
133135
startOnNewline: false
134136
})
135137

src/compose/resolve-props.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,21 @@ export interface ResolvePropsArg {
77
next: Token | null | undefined
88
offset: number
99
onError: ComposeErrorHandler
10+
parentIndent: number
1011
startOnNewline: boolean
1112
}
1213

1314
export function resolveProps(
1415
tokens: SourceToken[],
15-
{ flow, indicator, next, offset, onError, startOnNewline }: ResolvePropsArg
16+
{
17+
flow,
18+
indicator,
19+
next,
20+
offset,
21+
onError,
22+
parentIndent,
23+
startOnNewline
24+
}: ResolvePropsArg
1625
) {
1726
let spaceBefore = false
1827
let atNewline = startOnNewline
@@ -43,7 +52,7 @@ export function resolveProps(
4352
reqSpace = false
4453
}
4554
if (tab) {
46-
if (token.type !== 'comment') {
55+
if (atNewline && token.type !== 'comment' && token.type !== 'newline') {
4756
onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation')
4857
}
4958
tab = null
@@ -55,9 +64,8 @@ export function resolveProps(
5564
// In a flow collection, only the parser handles indent.
5665
if (
5766
!flow &&
58-
atNewline &&
5967
(indicator !== 'doc-start' || next?.type !== 'flow-collection') &&
60-
token.source[0] === '\t'
68+
token.source.includes('\t')
6169
) {
6270
tab = token
6371
}
@@ -132,7 +140,8 @@ export function resolveProps(
132140
`Unexpected ${token.source} in ${flow ?? 'collection'}`
133141
)
134142
found = token
135-
atNewline = false
143+
atNewline =
144+
indicator === 'seq-item-ind' || indicator === 'explicit-key-ind'
136145
hasSpace = false
137146
break
138147
case 'comma':
@@ -167,7 +176,13 @@ export function resolveProps(
167176
'Tags and anchors must be separated from the next token by white space'
168177
)
169178
}
170-
if (tab) onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation')
179+
if (
180+
tab &&
181+
((atNewline && tab.indent <= parentIndent) ||
182+
next?.type === 'block-map' ||
183+
next?.type === 'block-seq')
184+
)
185+
onError(tab, 'TAB_AS_INDENT', 'Tabs are not allowed as indentation')
171186
return {
172187
comma,
173188
found,

src/parse/cst-scalar.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ComposeContext } from '../compose/compose-node.js'
12
import type { ComposeErrorHandler } from '../compose/composer.js'
23
import { resolveBlockScalar } from '../compose/resolve-block-scalar.js'
34
import { resolveFlowScalar } from '../compose/resolve-flow-scalar.js'
@@ -55,7 +56,11 @@ export function resolveAsScalar(
5556
case 'double-quoted-scalar':
5657
return resolveFlowScalar(token, strict, _onError)
5758
case 'block-scalar':
58-
return resolveBlockScalar(token, strict, _onError)
59+
return resolveBlockScalar(
60+
{ options: { strict } } as ComposeContext,
61+
token,
62+
_onError
63+
)
5964
}
6065
}
6166
return null

src/parse/lexer.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -551,13 +551,23 @@ export class Lexer {
551551
nl = this.buffer.length
552552
}
553553
}
554-
if (!this.blockScalarKeep) {
554+
555+
// Trailing insufficiently indented tabs are invalid.
556+
// To catch that during parsing, we include them in the block scalar value.
557+
let i = nl + 1
558+
ch = this.buffer[i]
559+
while (ch === ' ') ch = this.buffer[++i]
560+
if (ch === '\t') {
561+
while (ch === '\t' || ch === ' ' || ch === '\r' || ch === '\n')
562+
ch = this.buffer[++i]
563+
nl = i - 1
564+
} else if (!this.blockScalarKeep) {
555565
do {
556566
let i = nl - 1
557567
let ch = this.buffer[i]
558568
if (ch === '\r') ch = this.buffer[--i]
559569
const lastChar = i // Drop the line if last char not more indented
560-
while (ch === ' ' || ch === '\t') ch = this.buffer[--i]
570+
while (ch === ' ') ch = this.buffer[--i]
561571
if (ch === '\n' && i >= this.pos && i + 1 + indent > lastChar) nl = i
562572
else break
563573
} while (true)

tests/yaml-test-suite.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,6 @@ const skip: Record<string, boolean | string[]> = {
2121
'SF5V/0': ['errors'], // allow duplicate %YAML directives
2222

2323
// FIXME recent upstream additions
24-
'DK95/0': true,
25-
'DK95/4': true,
26-
'DK95/5': true,
27-
'Y79Y/4': ['errors'],
28-
'Y79Y/5': ['errors'],
29-
'Y79Y/6': ['errors'],
30-
'Y79Y/7': ['errors'],
31-
'Y79Y/8': ['errors'],
32-
'Y79Y/9': ['errors'],
3324
'ZYU8/2': ['errors']
3425
}
3526

0 commit comments

Comments
 (0)