diff --git a/src/stringify/stringifyPair.ts b/src/stringify/stringifyPair.ts index 46a874cd..1c9bdb50 100644 --- a/src/stringify/stringifyPair.ts +++ b/src/stringify/stringifyPair.ts @@ -82,17 +82,16 @@ export function stringifyPair( str += lineComment(str, ctx.indent, commentString(keyComment)) } - let vcb = '' - let valueComment = null + let vsb, vcb, valueComment if (isNode(value)) { - if (value.spaceBefore) vcb = '\n' - if (value.commentBefore) { - const cs = commentString(value.commentBefore) - vcb += `\n${indentComment(cs, ctx.indent)}` - } + vsb = !!value.spaceBefore + vcb = value.commentBefore valueComment = value.comment - } else if (value && typeof value === 'object') { - value = doc.createNode(value) + } else { + vsb = false + vcb = null + valueComment = null + if (value && typeof value === 'object') value = doc.createNode(value) } ctx.implicitKey = false if (!explicitKey && !keyComment && isScalar(value)) @@ -109,7 +108,7 @@ export function stringifyPair( !value.anchor ) { // If indentSeq === false, consider '- ' as part of indentation where possible - ctx.indent = ctx.indent.substr(2) + ctx.indent = ctx.indent.substring(2) } let valueCommentDone = false @@ -120,13 +119,41 @@ export function stringifyPair( () => (chompKeep = true) ) let ws = ' ' - if (vcb || keyComment) { - if (valueStr === '' && !ctx.inFlow) ws = vcb === '\n' ? '\n\n' : vcb - else ws = `${vcb}\n${ctx.indent}` + if (keyComment || vsb || vcb) { + ws = vsb ? '\n' : '' + if (vcb) { + const cs = commentString(vcb) + ws += `\n${indentComment(cs, ctx.indent)}` + } + if (valueStr === '' && !ctx.inFlow) { + if (ws === '\n') ws = '\n\n' + } else { + ws += `\n${ctx.indent}` + } } else if (!explicitKey && isCollection(value)) { - const flow = valueStr[0] === '[' || valueStr[0] === '{' - if (!flow || valueStr.includes('\n')) ws = `\n${ctx.indent}` - } else if (valueStr === '' || valueStr[0] === '\n') ws = '' + const vs0 = valueStr[0] + const nl0 = valueStr.indexOf('\n') + const hasNewline = nl0 !== -1 + const flow = ctx.inFlow ?? value.flow ?? value.items.length === 0 + if (hasNewline || !flow) { + let hasPropsLine = false + if (hasNewline && (vs0 === '&' || vs0 === '!')) { + let sp0 = valueStr.indexOf(' ') + if ( + vs0 === '&' && + sp0 !== -1 && + sp0 < nl0 && + valueStr[sp0 + 1] === '!' + ) { + sp0 = valueStr.indexOf(' ', sp0 + 1) + } + if (sp0 === -1 || nl0 < sp0) hasPropsLine = true + } + if (!hasPropsLine) ws = `\n${ctx.indent}` + } + } else if (valueStr === '' || valueStr[0] === '\n') { + ws = '' + } str += ws + valueStr if (ctx.inFlow) { diff --git a/tests/doc/stringify.js b/tests/doc/stringify.js index 04a3b1e6..2e33bfa1 100644 --- a/tests/doc/stringify.js +++ b/tests/doc/stringify.js @@ -253,8 +253,7 @@ foo: const seq = [{ foo: { bar: { baz } } }, { fe: { fi: { fo: { baz } } } }] expect(YAML.stringify(seq)).toBe(`- foo: bar: - baz: - &a1 + baz: &a1 a: 1 - fe: fi: @@ -364,6 +363,137 @@ z: ) }) }) + + describe('properties on collections in block mapping', () => { + test('explicit tag on block sequence', () => { + const src = source` + key: !tag + - one + - two + ` + const doc = YAML.parseDocument(src) + expect(String(doc)).toBe(src) + }) + test('explicit tag on block mapping', () => { + const src = source` + key: !tag + one: 1 + two: 2 + ` + const doc = YAML.parseDocument(src) + expect(String(doc)).toBe(src) + }) + test('single-line comment on block sequence', () => { + const src = source` + key: #comment + - one + - two + ` + const doc = YAML.parseDocument(src) + expect(String(doc)).toBe(source` + key: + #comment + - one + - two + `) + }) + test('single-line comment and explicit tag on block sequence', () => { + const src = source` + key: #comment + !tag + - one + - two + ` + const doc = YAML.parseDocument(src) + expect(String(doc)).toBe(source` + key: + #comment + !tag + - one + - two + `) + }) + test('explicit tag and comment on block sequence', () => { + const src = source` + key: !tag + #comment + - one + - two + ` + const doc = YAML.parseDocument(src) + expect(String(doc)).toBe(source` + key: + #comment + !tag + - one + - two + `) + }) + test('anchor on block sequence', () => { + const src = source` + key: &anchor + - one + - two + ` + const doc = YAML.parseDocument(src) + expect(String(doc)).toBe(src) + }) + test('anchor and explicit tag on block sequence', () => { + const src = source` + key: &anchor !tag + - one + - two + ` + const doc = YAML.parseDocument(src) + expect(String(doc)).toBe(src) + }) + test('explicit tag on empty mapping', () => { + const doc = new YAML.Document({ key: {} }) + doc.get('key').tag = '!tag' + expect(String(doc)).toBe(source` + key: !tag {} + `) + }) + test('explicit tag on single-line flow mapping', () => { + const src = source` + key: !tag { one: 1, two: 2 } + ` + const doc = YAML.parseDocument(src) + expect(String(doc)).toBe(src) + }) + test('explicit tag on multi-line flow mapping', () => { + const src = source` + key: !tag { + one: aaaaaaaaaa, + two: bbbbbbbbbb, + three: cccccccccc, + four: dddddddddd, + five: eeeeeeeeee + } + ` + const doc = YAML.parseDocument(src) + expect(String(doc)).toBe(source` + key: + !tag { + one: aaaaaaaaaa, + two: bbbbbbbbbb, + three: cccccccccc, + four: dddddddddd, + five: eeeeeeeeee + } + `) + }) + test('explicit tag on explicit-value block sequence', () => { + const src = source` + ? [ key ] + : !tag + - one + - two + ` + const doc = YAML.parseDocument(src) + expect(String(doc)).toBe(src) + }) + }) }) test('eemeli/yaml#43: Quoting colons', () => { @@ -702,7 +832,7 @@ describe('indentSeq: false', () => { b: 2 map: seq: - #sc + #sc - a `) }) @@ -714,7 +844,7 @@ describe('indentSeq: false', () => { b: 2 map: seq: - #sc + #sc - a `) }) @@ -1182,15 +1312,15 @@ describe('YAML.stringify on ast Document', () => { }) describe('flow collection padding', () => { - const doc = new YAML.Document(); - doc.contents = new YAML.YAMLSeq(); - doc.contents.items = [1, 2]; - doc.contents.flow = true; + const doc = new YAML.Document() + doc.contents = new YAML.YAMLSeq() + doc.contents.items = [1, 2] + doc.contents.flow = true test('default', () => { expect(doc.toString()).toBe('[ 1, 2 ]\n') - }); + }) test('default', () => { - expect(doc.toString({flowCollectionPadding: false})).toBe('[1, 2]\n') - }); -}) \ No newline at end of file + expect(doc.toString({ flowCollectionPadding: false })).toBe('[1, 2]\n') + }) +}) diff --git a/tests/doc/types.js b/tests/doc/types.js index 5d5bc6ca..c5e65767 100644 --- a/tests/doc/types.js +++ b/tests/doc/types.js @@ -778,7 +778,12 @@ date (00:00:00Z): 2002-12-14\n`) test('eemeli/yaml#78', () => { const set = new Set(['a', 'b', 'c']) const str = YAML.stringify({ set }, { version: '1.1' }) - expect(str).toBe(`set:\n !!set\n ? a\n ? b\n ? c\n`) + expect(str).toBe(source` + set: !!set + ? a + ? b + ? c + `) }) })