Skip to content

Commit

Permalink
fix: Tags and anchors on map keys (#378)
Browse files Browse the repository at this point in the history
  • Loading branch information
eemeli committed Apr 15, 2022
1 parent fb684ad commit c396e53
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 21 deletions.
16 changes: 9 additions & 7 deletions src/compose/resolve-block-map.ts
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/compose/resolve-props.ts
Expand Up @@ -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
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -162,6 +164,7 @@ export function resolveProps(
spaceBefore,
comment,
hasNewline,
hasNewlineAfterProp,
anchor,
tag,
end,
Expand Down
32 changes: 18 additions & 14 deletions src/parse/parser.ts
Expand Up @@ -19,18 +19,18 @@ 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':
case 'comment':
case 'newline':
break
default:
return true
return i
}
}
return false
return -1
}

function isFlowToken(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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':
Expand All @@ -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
Expand All @@ -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[] = []
Expand Down
20 changes: 20 additions & 0 deletions tests/doc/parse.js
Expand Up @@ -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')
Expand Down

0 comments on commit c396e53

Please sign in to comment.