diff --git a/src/stringify.js b/src/stringify.js index e8427475..45e0f74d 100644 --- a/src/stringify.js +++ b/src/stringify.js @@ -13,6 +13,8 @@ const getFoldOptions = ({ indentAtStart }) => ? Object.assign({ indentAtStart }, strOptions.fold) : strOptions.fold +const containsDocumentMarker = str => /^(---|\.\.\.)/m.test(str) + export function stringifyNumber({ format, minFractionDigits, tag, value }) { if (typeof value === 'bigint') return String(value) if (!isFinite(value)) @@ -49,10 +51,11 @@ function lineLengthOverLimit(str, limit) { } function doubleQuotedString(value, ctx) { - const { implicitKey, indent } = ctx + const { implicitKey } = ctx const { jsonEncoding, minMultiLineLength } = strOptions.doubleQuoted const json = JSON.stringify(value) if (jsonEncoding) return json + const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : '') let str = '' let start = 0 for (let i = 0, ch = json[i]; ch; ch = json[++i]) { @@ -138,16 +141,16 @@ function doubleQuotedString(value, ctx) { } function singleQuotedString(value, ctx) { - const { indent, implicitKey } = ctx - if (implicitKey) { + if (ctx.implicitKey) { if (/\n/.test(value)) return doubleQuotedString(value, ctx) } else { // single quoted string can't have leading or trailing whitespace around newline if (/[ \t]\n|\n[ \t]/.test(value)) return doubleQuotedString(value, ctx) } + const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : '') const res = "'" + value.replace(/'/g, "''").replace(/\n+/g, `$&\n${indent}`) + "'" - return implicitKey + return ctx.implicitKey ? res : foldFlowLines(res, indent, FOLD_FLOW, getFoldOptions(ctx)) } @@ -158,7 +161,9 @@ function blockString({ comment, type, value }, ctx, onComment, onChompKeep) { if (/\n[\t ]+$/.test(value) || /^\s*$/.test(value)) { return doubleQuotedString(value, ctx) } - const indent = ctx.indent || (ctx.forceBlockIndent ? ' ' : '') + const indent = + ctx.indent || + (ctx.forceBlockIndent || containsDocumentMarker(value) ? ' ' : '') const indentSize = indent ? '2' : '1' // root is at -1 const literal = type === Type.BLOCK_FOLDED @@ -254,6 +259,10 @@ function plainString(item, ctx, onComment, onChompKeep) { // Where allowed & type not set explicitly, prefer block style for multiline strings return blockString(item, ctx, onComment, onChompKeep) } + if (indent === '' && containsDocumentMarker(value)) { + ctx.forceBlockIndent = true + return blockString(item, ctx, onComment, onChompKeep) + } const str = value.replace(/\n+/g, `$&\n${indent}`) // Verify that output will be parsed as a string, as e.g. plain numbers and // booleans get parsed with those types in v1.2 (e.g. '42', 'true' & '0.9e-3'), diff --git a/tests/doc/comments.js b/tests/doc/comments.js index cbcb3353..8f15ba7d 100644 --- a/tests/doc/comments.js +++ b/tests/doc/comments.js @@ -513,7 +513,7 @@ describe('blank lines', () => { } test('as contents', () => { - const src = '|+\n a\n\n#c\n' + const src = '|+\n a\n\n#c\n' const doc = YAML.parseDocument(src) expect(doc).toMatchObject({ comment: 'c', diff --git a/tests/doc/stringify.js b/tests/doc/stringify.js index 6eb5bd88..e6458f52 100644 --- a/tests/doc/stringify.js +++ b/tests/doc/stringify.js @@ -622,3 +622,53 @@ describe('indentSeq: false', () => { ) }) }) + +describe('Document markers in top-level scalars', () => { + let origDoubleQuotedOptions + beforeAll(() => { + origDoubleQuotedOptions = YAML.scalarOptions.str.doubleQuoted + YAML.scalarOptions.str.doubleQuoted = { + jsonEncoding: false, + minMultiLineLength: 0 + } + }) + afterAll(() => { + YAML.scalarOptions.str.doubleQuoted = origDoubleQuotedOptions + }) + + test('---', () => { + const str = YAML.stringify('---') + expect(str).toBe('|-\n ---\n') + expect(YAML.parse(str)).toBe('---') + }) + + test('...', () => { + const str = YAML.stringify('...') + expect(str).toBe('|-\n ...\n') + expect(YAML.parse(str)).toBe('...') + }) + + test('foo\\n...\\n', () => { + const str = YAML.stringify('foo\n...\n') + expect(str).toBe('|\n foo\n ...\n') + expect(YAML.parse(str)).toBe('foo\n...\n') + }) + + test("'foo\\n...'", () => { + const doc = new YAML.Document() + doc.contents = YAML.createNode('foo\n...', true) + doc.contents.type = Type.QUOTE_SINGLE + const str = String(doc) + expect(str).toBe("'foo\n\n ...'\n") + expect(YAML.parse(str)).toBe('foo\n...') + }) + + test('"foo\\n..."', () => { + const doc = new YAML.Document() + doc.contents = YAML.createNode('foo\n...', true) + doc.contents.type = Type.QUOTE_DOUBLE + const str = String(doc) + expect(str).toBe('"foo\n\n ..."\n') + expect(YAML.parse(str)).toBe('foo\n...') + }) +})