diff --git a/src/compose/compose-collection.ts b/src/compose/compose-collection.ts index 7b18756f..607f3a1d 100644 --- a/src/compose/compose-collection.ts +++ b/src/compose/compose-collection.ts @@ -43,19 +43,40 @@ function resolveCollection( return coll } +interface Props { + anchor: SourceToken | null + tag: SourceToken | null + newlineAfterProp: SourceToken | null +} + export function composeCollection( CN: ComposeNode, ctx: ComposeContext, token: BlockMap | BlockSequence | FlowCollection, - tagToken: SourceToken | null, + props: Props, onError: ComposeErrorHandler ) { + const tagToken = props.tag const tagName: string | null = !tagToken ? null : ctx.directives.tagName(tagToken.source, msg => onError(tagToken, 'TAG_RESOLVE_FAILED', msg) ) + if (token.type === 'block-seq') { + const { anchor, newlineAfterProp: nl } = props + const lastProp = + anchor && tagToken + ? anchor.offset > tagToken.offset + ? anchor + : tagToken + : anchor ?? tagToken + if (lastProp && (!nl || nl.offset < lastProp.offset)) { + const message = 'Missing newline after block sequence props' + onError(lastProp, 'MISSING_CHAR', message) + } + } + const expType: 'map' | 'seq' = token.type === 'block-map' ? 'map' @@ -72,8 +93,7 @@ export function composeCollection( !tagName || tagName === '!' || (tagName === YAMLMap.tagName && expType === 'map') || - (tagName === YAMLSeq.tagName && expType === 'seq') || - !expType + (tagName === YAMLSeq.tagName && expType === 'seq') ) { return resolveCollection(CN, ctx, token, onError, tagName) } diff --git a/src/compose/compose-node.ts b/src/compose/compose-node.ts index 2aea4383..2608d131 100644 --- a/src/compose/compose-node.ts +++ b/src/compose/compose-node.ts @@ -22,6 +22,7 @@ interface Props { comment: string anchor: SourceToken | null tag: SourceToken | null + newlineAfterProp: SourceToken | null end: number } @@ -57,7 +58,7 @@ export function composeNode( case 'block-map': case 'block-seq': case 'flow-collection': - node = composeCollection(CN, ctx, token, tag, onError) + node = composeCollection(CN, ctx, token, props, onError) if (anchor) node.anchor = anchor.source.substring(1) break default: { diff --git a/src/compose/resolve-block-map.ts b/src/compose/resolve-block-map.ts index 5de8047f..602bd38d 100644 --- a/src/compose/resolve-block-map.ts +++ b/src/compose/resolve-block-map.ts @@ -57,7 +57,7 @@ export function resolveBlockMap( } continue } - if (keyProps.hasNewlineAfterProp || containsNewline(key)) { + if (keyProps.newlineAfterProp || containsNewline(key)) { onError( key ?? start[start.length - 1], 'MULTILINE_IMPLICIT_KEY', diff --git a/src/compose/resolve-props.ts b/src/compose/resolve-props.ts index e10b5c26..4ecab4d7 100644 --- a/src/compose/resolve-props.ts +++ b/src/compose/resolve-props.ts @@ -29,11 +29,11 @@ export function resolveProps( let comment = '' let commentSep = '' let hasNewline = false - let hasNewlineAfterProp = false let reqSpace = false let tab: SourceToken | null = null let anchor: SourceToken | null = null let tag: SourceToken | null = null + let newlineAfterProp: SourceToken | null = null let comma: SourceToken | null = null let found: SourceToken | null = null let start: number | null = null @@ -92,7 +92,7 @@ export function resolveProps( } else commentSep += token.source atNewline = true hasNewline = true - if (anchor || tag) hasNewlineAfterProp = true + if (anchor || tag) newlineAfterProp = token hasSpace = true break case 'anchor': @@ -189,9 +189,9 @@ export function resolveProps( spaceBefore, comment, hasNewline, - hasNewlineAfterProp, anchor, tag, + newlineAfterProp, end, start: start ?? end } diff --git a/tests/doc/errors.ts b/tests/doc/errors.ts index 89d7cd7b..93800d2b 100644 --- a/tests/doc/errors.ts +++ b/tests/doc/errors.ts @@ -378,6 +378,31 @@ describe('tags on invalid nodes', () => { }) }) +describe('properties on block sequences without newline after props', () => { + test('valid properties with newline', () => { + const doc = YAML.parseDocument('! &a\n- b') + expect(doc.errors).toMatchObject([]) + }) + + test('properties on sequence without newline', () => { + const doc = YAML.parseDocument('!\n&a - b') + expect(doc.errors).toMatchObject([ + { code: 'MISSING_CHAR' }, + { code: 'UNEXPECTED_TOKEN' } + ]) + }) + + test('properties on empty sequence without newline', () => { + const doc = YAML.parseDocument('&a\n! -') + expect(doc.errors).toMatchObject([{ code: 'MISSING_CHAR' }]) + }) + + test('properties on sequence with newline after item indicator', () => { + const doc = YAML.parseDocument('!\n&a -\n b') + expect(doc.errors).toMatchObject([{ code: 'MISSING_CHAR' }]) + }) +}) + describe('invalid options', () => { test('unknown schema', () => { expect(() => new YAML.Document(undefined, { schema: 'foo' })).toThrow(