Skip to content

Commit aea700d

Browse files
committed
fix: Require newline in all cases for props on block sequence (fixes #557)
1 parent 1b8fde6 commit aea700d

File tree

5 files changed

+54
-8
lines changed

5 files changed

+54
-8
lines changed

src/compose/compose-collection.ts

+23-3
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,40 @@ function resolveCollection(
4343
return coll
4444
}
4545

46+
interface Props {
47+
anchor: SourceToken | null
48+
tag: SourceToken | null
49+
newlineAfterProp: SourceToken | null
50+
}
51+
4652
export function composeCollection(
4753
CN: ComposeNode,
4854
ctx: ComposeContext,
4955
token: BlockMap | BlockSequence | FlowCollection,
50-
tagToken: SourceToken | null,
56+
props: Props,
5157
onError: ComposeErrorHandler
5258
) {
59+
const tagToken = props.tag
5360
const tagName: string | null = !tagToken
5461
? null
5562
: ctx.directives.tagName(tagToken.source, msg =>
5663
onError(tagToken, 'TAG_RESOLVE_FAILED', msg)
5764
)
5865

66+
if (token.type === 'block-seq') {
67+
const { anchor, newlineAfterProp: nl } = props
68+
const lastProp =
69+
anchor && tagToken
70+
? anchor.offset > tagToken.offset
71+
? anchor
72+
: tagToken
73+
: anchor ?? tagToken
74+
if (lastProp && (!nl || nl.offset < lastProp.offset)) {
75+
const message = 'Missing newline after block sequence props'
76+
onError(lastProp, 'MISSING_CHAR', message)
77+
}
78+
}
79+
5980
const expType: 'map' | 'seq' =
6081
token.type === 'block-map'
6182
? 'map'
@@ -72,8 +93,7 @@ export function composeCollection(
7293
!tagName ||
7394
tagName === '!' ||
7495
(tagName === YAMLMap.tagName && expType === 'map') ||
75-
(tagName === YAMLSeq.tagName && expType === 'seq') ||
76-
!expType
96+
(tagName === YAMLSeq.tagName && expType === 'seq')
7797
) {
7898
return resolveCollection(CN, ctx, token, onError, tagName)
7999
}

src/compose/compose-node.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ interface Props {
2222
comment: string
2323
anchor: SourceToken | null
2424
tag: SourceToken | null
25+
newlineAfterProp: SourceToken | null
2526
end: number
2627
}
2728

@@ -57,7 +58,7 @@ export function composeNode(
5758
case 'block-map':
5859
case 'block-seq':
5960
case 'flow-collection':
60-
node = composeCollection(CN, ctx, token, tag, onError)
61+
node = composeCollection(CN, ctx, token, props, onError)
6162
if (anchor) node.anchor = anchor.source.substring(1)
6263
break
6364
default: {

src/compose/resolve-block-map.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export function resolveBlockMap(
5757
}
5858
continue
5959
}
60-
if (keyProps.hasNewlineAfterProp || containsNewline(key)) {
60+
if (keyProps.newlineAfterProp || containsNewline(key)) {
6161
onError(
6262
key ?? start[start.length - 1],
6363
'MULTILINE_IMPLICIT_KEY',

src/compose/resolve-props.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ export function resolveProps(
2929
let comment = ''
3030
let commentSep = ''
3131
let hasNewline = false
32-
let hasNewlineAfterProp = false
3332
let reqSpace = false
3433
let tab: SourceToken | null = null
3534
let anchor: SourceToken | null = null
3635
let tag: SourceToken | null = null
36+
let newlineAfterProp: SourceToken | null = null
3737
let comma: SourceToken | null = null
3838
let found: SourceToken | null = null
3939
let start: number | null = null
@@ -92,7 +92,7 @@ export function resolveProps(
9292
} else commentSep += token.source
9393
atNewline = true
9494
hasNewline = true
95-
if (anchor || tag) hasNewlineAfterProp = true
95+
if (anchor || tag) newlineAfterProp = token
9696
hasSpace = true
9797
break
9898
case 'anchor':
@@ -189,9 +189,9 @@ export function resolveProps(
189189
spaceBefore,
190190
comment,
191191
hasNewline,
192-
hasNewlineAfterProp,
193192
anchor,
194193
tag,
194+
newlineAfterProp,
195195
end,
196196
start: start ?? end
197197
}

tests/doc/errors.ts

+25
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,31 @@ describe('tags on invalid nodes', () => {
378378
})
379379
})
380380

381+
describe('properties on block sequences without newline after props', () => {
382+
test('valid properties with newline', () => {
383+
const doc = YAML.parseDocument('! &a\n- b')
384+
expect(doc.errors).toMatchObject([])
385+
})
386+
387+
test('properties on sequence without newline', () => {
388+
const doc = YAML.parseDocument('!\n&a - b')
389+
expect(doc.errors).toMatchObject([
390+
{ code: 'MISSING_CHAR' },
391+
{ code: 'UNEXPECTED_TOKEN' }
392+
])
393+
})
394+
395+
test('properties on empty sequence without newline', () => {
396+
const doc = YAML.parseDocument('&a\n! -')
397+
expect(doc.errors).toMatchObject([{ code: 'MISSING_CHAR' }])
398+
})
399+
400+
test('properties on sequence with newline after item indicator', () => {
401+
const doc = YAML.parseDocument('!\n&a -\n b')
402+
expect(doc.errors).toMatchObject([{ code: 'MISSING_CHAR' }])
403+
})
404+
})
405+
381406
describe('invalid options', () => {
382407
test('unknown schema', () => {
383408
expect(() => new YAML.Document(undefined, { schema: 'foo' })).toThrow(

0 commit comments

Comments
 (0)