From c396e536c7a3c26b6af10ba3c497efb565eff836 Mon Sep 17 00:00:00 2001 From: Eemeli Aro Date: Fri, 15 Apr 2022 22:21:25 +0300 Subject: [PATCH] fix: Tags and anchors on map keys (#378) --- src/compose/resolve-block-map.ts | 16 +++++++++------- src/compose/resolve-props.ts | 3 +++ src/parse/parser.ts | 32 ++++++++++++++++++-------------- tests/doc/parse.js | 20 ++++++++++++++++++++ 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/compose/resolve-block-map.ts b/src/compose/resolve-block-map.ts index fe3a63d5..922b0eb7 100644 --- a/src/compose/resolve-block-map.ts +++ b/src/compose/resolve-block-map.ts @@ -52,14 +52,16 @@ export function resolveBlockMap( } continue } - } else if (keyProps.found?.indent !== bm.indent) + if (keyProps.hasNewlineAfterProp || containsNewline(key)) { + onError( + key ?? start[start.length - 1], + 'MULTILINE_IMPLICIT_KEY', + 'Implicit keys need to be on a single line' + ) + } + } else if (keyProps.found?.indent !== bm.indent) { onError(offset, 'BAD_INDENT', startColMsg) - if (implicitKey && containsNewline(key)) - onError( - key as Token, // checked by containsNewline() - 'MULTILINE_IMPLICIT_KEY', - 'Implicit keys need to be on a single line' - ) + } // key value const keyStart = keyProps.end diff --git a/src/compose/resolve-props.ts b/src/compose/resolve-props.ts index c67ca629..593f2735 100644 --- a/src/compose/resolve-props.ts +++ b/src/compose/resolve-props.ts @@ -20,6 +20,7 @@ export function resolveProps( let comment = '' let commentSep = '' let hasNewline = false + let hasNewlineAfterProp = false let reqSpace = false let anchor: SourceToken | null = null let tag: SourceToken | null = null @@ -75,6 +76,7 @@ export function resolveProps( } else commentSep += token.source atNewline = true hasNewline = true + if (anchor || tag) hasNewlineAfterProp = true hasSpace = true break case 'anchor': @@ -162,6 +164,7 @@ export function resolveProps( spaceBefore, comment, hasNewline, + hasNewlineAfterProp, anchor, tag, end, diff --git a/src/parse/parser.ts b/src/parse/parser.ts index c6f2b6fc..528280fe 100644 --- a/src/parse/parser.ts +++ b/src/parse/parser.ts @@ -19,7 +19,7 @@ function includesToken(list: SourceToken[], type: SourceToken['type']) { return false } -function includesNonEmpty(list: SourceToken[]) { +function findNonEmptyIndex(list: SourceToken[]) { for (let i = 0; i < list.length; ++i) { switch (list[i].type) { case 'space': @@ -27,10 +27,10 @@ function includesNonEmpty(list: SourceToken[]) { case 'newline': break default: - return true + return i } } - return false + return -1 } function isFlowToken( @@ -362,7 +362,7 @@ export class Parser { !last.sep && !last.value && last.start.length > 0 && - !includesNonEmpty(last.start) && + findNonEmptyIndex(last.start) === -1 && (token.indent === 0 || last.start.every( st => st.type !== 'comment' || st.indent < token.indent @@ -411,7 +411,7 @@ export class Parser { if (doc.value) return yield* this.lineEnd(doc) switch (this.type) { case 'doc-start': { - if (includesNonEmpty(doc.start)) { + if (findNonEmptyIndex(doc.start) !== -1) { yield* this.pop() yield* this.step() } else doc.start.push(this.sourceToken) @@ -490,6 +490,7 @@ export class Parser { private *blockMap(map: BlockMap) { const it = map.items[map.items.length - 1] + // it.sep is true-ish if pair already has key or : separator switch (this.type) { case 'newline': @@ -499,14 +500,19 @@ export class Parser { const last = Array.isArray(end) ? end[end.length - 1] : undefined if (last?.type === 'comment') end?.push(this.sourceToken) else map.items.push({ start: [this.sourceToken] }) - } else if (it.sep) it.sep.push(this.sourceToken) - else it.start.push(this.sourceToken) + } else if (it.sep) { + it.sep.push(this.sourceToken) + } else { + it.start.push(this.sourceToken) + } return case 'space': case 'comment': - if (it.value) map.items.push({ start: [this.sourceToken] }) - else if (it.sep) it.sep.push(this.sourceToken) - else { + if (it.value) { + map.items.push({ start: [this.sourceToken] }) + } else if (it.sep) { + it.sep.push(this.sourceToken) + } else { if (this.atIndentedComment(it.start, map.indent)) { const prev = map.items[map.items.length - 2] const end = (prev?.value as { end: SourceToken[] })?.end @@ -521,11 +527,9 @@ export class Parser { } return } + if (this.indent >= map.indent) { - const atNextItem = - !this.onKeyLine && - this.indent === map.indent && - (it.sep || includesNonEmpty(it.start)) + const atNextItem = !this.onKeyLine && this.indent === map.indent && it.sep // For empty nodes, assign newline-separated not indented empty tokens to following node let start: SourceToken[] = [] diff --git a/tests/doc/parse.js b/tests/doc/parse.js index c429706e..56ac8ba3 100644 --- a/tests/doc/parse.js +++ b/tests/doc/parse.js @@ -300,6 +300,26 @@ test('comment between key & : in flow collection (eemeli/yaml#149)', () => { expect(doc.errors).toMatchObject([{ code: 'MISSING_CHAR' }]) }) +describe('indented key with anchor (eemeli/yaml#378)', () => { + test('followed by value', () => { + const src1 = '&a foo: 1\n&b bar: 2' + expect(YAML.parse(src1)).toEqual({ foo: 1, bar: 2 }) + + const src2 = ' &a foo: 3\n &b bar: 4' + expect(YAML.parse(src2)).toEqual({ foo: 3, bar: 4 }) + }) + + test('with : on separate line', () => { + const src1 = 'a: 1\n&b\nc: 2' + const doc1 = YAML.parseDocument(src1) + expect(doc1.errors).toMatchObject([{ code: 'MULTILINE_IMPLICIT_KEY' }]) + + const src2 = ' a: 1\n &b\n : 2' + const doc2 = YAML.parseDocument(src2) + expect(doc2.errors).toMatchObject([{ code: 'MULTILINE_IMPLICIT_KEY' }]) + }) +}) + describe('empty(ish) nodes', () => { test('empty node position', () => { const doc = YAML.parseDocument('\r\na: # 123\r\n')