From 45407d82b91e4dbbeabc0f7159c5b72ea538c900 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Thu, 17 Dec 2020 10:39:02 +0800 Subject: [PATCH] Refactor yaml (#9629) --- src/language-yaml/print-preprocess.js | 30 + src/language-yaml/print/block.js | 84 ++ .../print/flow-mapping-sequence.js | 71 ++ src/language-yaml/print/mapping-item.js | 185 +++++ src/language-yaml/print/misc.js | 55 ++ src/language-yaml/printer-yaml.js | 753 ++++++------------ src/language-yaml/utils.js | 20 + 7 files changed, 667 insertions(+), 531 deletions(-) create mode 100644 src/language-yaml/print-preprocess.js create mode 100644 src/language-yaml/print/block.js create mode 100644 src/language-yaml/print/flow-mapping-sequence.js create mode 100644 src/language-yaml/print/mapping-item.js create mode 100644 src/language-yaml/print/misc.js diff --git a/src/language-yaml/print-preprocess.js b/src/language-yaml/print-preprocess.js new file mode 100644 index 000000000..79912a1c0 --- /dev/null +++ b/src/language-yaml/print-preprocess.js @@ -0,0 +1,30 @@ +"use strict"; +const { defineShortcut, mapNode } = require("./utils"); + +function preprocess(ast) { + return mapNode(ast, defineShortcuts); +} + +function defineShortcuts(node) { + switch (node.type) { + case "document": + defineShortcut(node, "head", () => node.children[0]); + defineShortcut(node, "body", () => node.children[1]); + break; + case "documentBody": + case "sequenceItem": + case "flowSequenceItem": + case "mappingKey": + case "mappingValue": + defineShortcut(node, "content", () => node.children[0]); + break; + case "mappingItem": + case "flowMappingItem": + defineShortcut(node, "key", () => node.children[0]); + defineShortcut(node, "value", () => node.children[1]); + break; + } + return node; +} + +module.exports = preprocess; diff --git a/src/language-yaml/print/block.js b/src/language-yaml/print/block.js new file mode 100644 index 000000000..ce98e4236 --- /dev/null +++ b/src/language-yaml/print/block.js @@ -0,0 +1,84 @@ +"use strict"; + +/** @typedef {import("../../document").Doc} Doc */ + +const { + builders: { + concat, + dedent, + dedentToRoot, + fill, + hardline, + join, + line, + literalline, + markAsRoot, + }, +} = require("../../document"); +const { + getAncestorCount, + getBlockValueLineContents, + hasIndicatorComment, + isLastDescendantNode, + isNode, +} = require("../utils"); +const { alignWithSpaces } = require("./misc"); + +function printBlock(path, print, options) { + const node = path.getValue(); + const parentIndent = getAncestorCount(path, (ancestorNode) => + isNode(ancestorNode, ["sequence", "mapping"]) + ); + const isLastDescendant = isLastDescendantNode(path); + /** @type {Doc[]} */ + const parts = [node.type === "blockFolded" ? ">" : "|"]; + if (node.indent !== null) { + parts.push(node.indent.toString()); + } + + if (node.chomping !== "clip") { + parts.push(node.chomping === "keep" ? "+" : "-"); + } + + if (hasIndicatorComment(node)) { + parts.push(" ", path.call(print, "indicatorComment")); + } + + const lineContents = getBlockValueLineContents(node, { + parentIndent, + isLastDescendant, + options, + }); + /** @type {Doc[]} */ + const contentsParts = []; + for (const [index, lineWords] of lineContents.entries()) { + if (index === 0) { + contentsParts.push(hardline); + } + contentsParts.push(fill(join(line, lineWords).parts)); + if (index !== lineContents.length - 1) { + contentsParts.push( + lineWords.length === 0 ? hardline : markAsRoot(literalline) + ); + } else if (node.chomping === "keep" && isLastDescendant) { + contentsParts.push( + dedentToRoot(lineWords.length === 0 ? hardline : literalline) + ); + } + } + if (node.indent === null) { + parts.push( + dedent(alignWithSpaces(options.tabWidth, concat(contentsParts))) + ); + } else { + parts.push( + dedentToRoot( + alignWithSpaces(node.indent - 1 + parentIndent, concat(contentsParts)) + ) + ); + } + + return concat(parts); +} + +module.exports = printBlock; diff --git a/src/language-yaml/print/flow-mapping-sequence.js b/src/language-yaml/print/flow-mapping-sequence.js new file mode 100644 index 000000000..5d96763bb --- /dev/null +++ b/src/language-yaml/print/flow-mapping-sequence.js @@ -0,0 +1,71 @@ +"use strict"; + +const { + builders: { concat, ifBreak, line, softline, hardline, join }, +} = require("../../document"); +const { isEmptyNode, getLast, hasEndComments } = require("../utils"); +const { printNextEmptyLine, alignWithSpaces } = require("./misc"); + +function printFlowMapping(path, print, options) { + const node = path.getValue(); + const isMapping = node.type === "flowMapping"; + const openMarker = isMapping ? "{" : "["; + const closeMarker = isMapping ? "}" : "]"; + + /** @type {softline | line} */ + let bracketSpacing = softline; + if (isMapping && node.children.length !== 0 && options.bracketSpacing) { + bracketSpacing = line; + } + const lastItem = getLast(node.children); + const isLastItemEmptyMappingItem = + lastItem && + lastItem.type === "flowMappingItem" && + isEmptyNode(lastItem.key) && + isEmptyNode(lastItem.value); + + return concat([ + openMarker, + alignWithSpaces( + options.tabWidth, + concat([ + bracketSpacing, + printChildren(path, print, options), + options.trailingComma === "none" ? "" : ifBreak(",", ""), + hasEndComments(node) + ? concat([hardline, join(hardline, path.map(print, "endComments"))]) + : "", + ]) + ), + isLastItemEmptyMappingItem ? "" : bracketSpacing, + closeMarker, + ]); +} + +function printChildren(path, print, options) { + const node = path.getValue(); + const parts = path.map( + (childPath, index) => + concat([ + print(childPath), + index === node.children.length - 1 + ? "" + : concat([ + ",", + line, + node.children[index].position.start.line !== + node.children[index + 1].position.start.line + ? printNextEmptyLine(childPath, options.originalText) + : "", + ]), + ]), + "children" + ); + return concat(parts); +} + +module.exports = { + printFlowMapping, + // Alias + printFlowSequence: printFlowMapping, +}; diff --git a/src/language-yaml/print/mapping-item.js b/src/language-yaml/print/mapping-item.js new file mode 100644 index 000000000..90aa6c6b1 --- /dev/null +++ b/src/language-yaml/print/mapping-item.js @@ -0,0 +1,185 @@ +"use strict"; + +/** @typedef {import("../../document").Doc} Doc */ + +const { + builders: { conditionalGroup, concat, group, hardline, ifBreak, join, line }, +} = require("../../document"); +const { + hasLeadingComments, + hasMiddleComments, + hasTrailingComment, + hasEndComments, + isNode, + isEmptyNode, + isInlineNode, +} = require("../utils"); +const { alignWithSpaces } = require("./misc"); + +function printMappingItem(node, parentNode, path, print, options) { + const { key, value } = node; + + const isEmptyMappingKey = isEmptyNode(key); + const isEmptyMappingValue = isEmptyNode(value); + + if (isEmptyMappingKey && isEmptyMappingValue) { + return concat([": "]); + } + + const printedKey = path.call(print, "key"); + const spaceBeforeColon = needsSpaceInFrontOfMappingValue(node) ? " " : ""; + + if (isEmptyMappingValue) { + if (node.type === "flowMappingItem" && parentNode.type === "flowMapping") { + return printedKey; + } + + if ( + node.type === "mappingItem" && + isAbsolutelyPrintedAsSingleLineNode(key.content, options) && + !hasTrailingComment(key.content) && + (!parentNode.tag || parentNode.tag.value !== "tag:yaml.org,2002:set") + ) { + return concat([printedKey, spaceBeforeColon, ":"]); + } + + return concat(["? ", alignWithSpaces(2, printedKey)]); + } + + const printedValue = path.call(print, "value"); + if (isEmptyMappingKey) { + return concat([": ", alignWithSpaces(2, printedValue)]); + } + + // force explicit Key + if (hasLeadingComments(value) || !isInlineNode(key.content)) { + return concat([ + "? ", + alignWithSpaces(2, printedKey), + hardline, + join( + "", + path + .map(print, "value", "leadingComments") + .map((comment) => concat([comment, hardline])) + ), + ": ", + alignWithSpaces(2, printedValue), + ]); + } + + // force singleline + if ( + isSingleLineNode(key.content) && + !hasLeadingComments(key.content) && + !hasMiddleComments(key.content) && + !hasTrailingComment(key.content) && + !hasEndComments(key) && + !hasLeadingComments(value.content) && + !hasMiddleComments(value.content) && + !hasEndComments(value) && + isAbsolutelyPrintedAsSingleLineNode(value.content, options) + ) { + return concat([printedKey, spaceBeforeColon, ": ", printedValue]); + } + + const groupId = Symbol("mappingKey"); + const groupedKey = group( + concat([ + ifBreak("? "), + group(alignWithSpaces(2, printedKey), { id: groupId }), + ]) + ); + const breakValue = concat([hardline, ": ", alignWithSpaces(2, printedValue)]); + /** @type {Doc[]} */ + const flatValueParts = [spaceBeforeColon, ":"]; + if ( + hasLeadingComments(value.content) || + (hasEndComments(value) && + value.content && + !isNode(value.content, ["mapping", "sequence"])) || + (parentNode.type === "mapping" && + hasTrailingComment(key.content) && + isInlineNode(value.content)) || + (isNode(value.content, ["mapping", "sequence"]) && + value.content.tag === null && + value.content.anchor === null) + ) { + flatValueParts.push(hardline); + } else if (value.content) { + flatValueParts.push(line); + } + flatValueParts.push(printedValue); + const flatValue = alignWithSpaces(options.tabWidth, concat(flatValueParts)); + + return conditionalGroup([ + concat([groupedKey, ifBreak(breakValue, flatValue, { groupId })]), + ]); +} + +function isAbsolutelyPrintedAsSingleLineNode(node, options) { + if (!node) { + return true; + } + + switch (node.type) { + case "plain": + case "quoteSingle": + case "quoteDouble": + break; + case "alias": + return true; + default: + return false; + } + + if (options.proseWrap === "preserve") { + return node.position.start.line === node.position.end.line; + } + + if ( + // backslash-newline + /\\$/m.test( + options.originalText.slice( + node.position.start.offset, + node.position.end.offset + ) + ) + ) { + return false; + } + + switch (options.proseWrap) { + case "never": + return !node.value.includes("\n"); + case "always": + return !/[\n ]/.test(node.value); + // istanbul ignore next + default: + return false; + } +} + +function needsSpaceInFrontOfMappingValue(node) { + return node.key.content && node.key.content.type === "alias"; +} + +function isSingleLineNode(node) { + /* istanbul ignore next */ + if (!node) { + return true; + } + + switch (node.type) { + case "plain": + case "quoteDouble": + case "quoteSingle": + return node.position.start.line === node.position.end.line; + case "alias": + return true; + default: + return false; + } +} + +module.exports = printMappingItem; diff --git a/src/language-yaml/print/misc.js b/src/language-yaml/print/misc.js new file mode 100644 index 000000000..06783d0ed --- /dev/null +++ b/src/language-yaml/print/misc.js @@ -0,0 +1,55 @@ +"use strict"; + +const { + builders: { softline, align }, +} = require("../../document"); + +const { hasEndComments, isNextLineEmpty, isNode } = require("../utils"); + +const printedEmptyLineCache = new WeakMap(); +function printNextEmptyLine(path, originalText) { + const node = path.getValue(); + const root = path.stack[0]; + + let isNextEmptyLinePrintedSet; + if (printedEmptyLineCache.has(root)) { + isNextEmptyLinePrintedSet = printedEmptyLineCache.get(root); + } else { + isNextEmptyLinePrintedSet = new Set(); + printedEmptyLineCache.set(root, isNextEmptyLinePrintedSet); + } + + if (!isNextEmptyLinePrintedSet.has(node.position.end.line)) { + isNextEmptyLinePrintedSet.add(node.position.end.line); + if ( + isNextLineEmpty(node, originalText) && + !shouldPrintEndComments(path.getParentNode()) + ) { + return softline; + } + } + + return ""; +} + +function shouldPrintEndComments(node) { + return ( + hasEndComments(node) && + !isNode(node, [ + "documentHead", + "documentBody", + "flowMapping", + "flowSequence", + ]) + ); +} + +function alignWithSpaces(n, doc) { + return align(" ".repeat(n), doc); +} + +module.exports = { + alignWithSpaces, + shouldPrintEndComments, + printNextEmptyLine, +}; diff --git a/src/language-yaml/printer-yaml.js b/src/language-yaml/printer-yaml.js index f1dbd538c..b7f4544aa 100644 --- a/src/language-yaml/printer-yaml.js +++ b/src/language-yaml/printer-yaml.js @@ -1,81 +1,75 @@ "use strict"; -const docBuilders = require("../document").builders; +/** @typedef {import("../document").Doc} Doc */ + const { - conditionalGroup, - breakParent, - concat, - dedent, - dedentToRoot, - fill, - group, - hardline, - ifBreak, - join, - line, - lineSuffix, - literalline, - markAsRoot, - softline, -} = docBuilders; + builders: { + breakParent, + concat, + fill, + group, + hardline, + join, + line, + lineSuffix, + literalline, + }, +} = require("../document"); const { replaceEndOfLineWith, isPreviousLineEmpty } = require("../common/util"); const { insertPragma, isPragma } = require("./pragma"); const { locStart } = require("./loc"); const embed = require("./embed"); const { - getAncestorCount, - getBlockValueLineContents, getFlowScalarLineContents, - getLast, getLastDescendantNode, hasLeadingComments, hasMiddleComments, - hasIndicatorComment, hasTrailingComment, hasEndComments, hasPrettierIgnore, isLastDescendantNode, - isNextLineEmpty, isNode, - isEmptyNode, - defineShortcut, - mapNode, + isInlineNode, } = require("./utils"); - -function preprocess(ast) { - return mapNode(ast, defineShortcuts); -} - -function defineShortcuts(node) { - switch (node.type) { - case "document": - defineShortcut(node, "head", () => node.children[0]); - defineShortcut(node, "body", () => node.children[1]); - break; - case "documentBody": - case "sequenceItem": - case "flowSequenceItem": - case "mappingKey": - case "mappingValue": - defineShortcut(node, "content", () => node.children[0]); - break; - case "mappingItem": - case "flowMappingItem": - defineShortcut(node, "key", () => node.children[0]); - defineShortcut(node, "value", () => node.children[1]); - break; - } - return node; -} +const preprocess = require("./print-preprocess"); +const { + alignWithSpaces, + printNextEmptyLine, + shouldPrintEndComments, +} = require("./print/misc"); +const { + printFlowMapping, + printFlowSequence, +} = require("./print/flow-mapping-sequence"); +const printMappingItem = require("./print/mapping-item"); +const printBlock = require("./print/block"); function genericPrint(path, options, print) { const node = path.getValue(); - const parentNode = path.getParentNode(); + /** @type {Doc[]} */ + const parts = []; - const tag = !node.tag ? "" : path.call(print, "tag"); - const anchor = !node.anchor ? "" : path.call(print, "anchor"); + if (node.type !== "mappingValue" && hasLeadingComments(node)) { + parts.push( + concat([join(hardline, path.map(print, "leadingComments")), hardline]) + ); + } - const nextEmptyLine = + const { tag, anchor } = node; + if (tag) { + parts.push(path.call(print, "tag")); + } + if (tag && anchor) { + parts.push(" "); + } + if (anchor) { + parts.push(path.call(print, "anchor")); + } + + /** @type {Doc} */ + let nextEmptyLine = ""; + + if ( isNode(node, [ "mapping", "sequence", @@ -83,167 +77,184 @@ function genericPrint(path, options, print) { "directive", "mappingItem", "sequenceItem", - ]) && !isLastDescendantNode(path) - ? printNextEmptyLine(path, options.originalText) - : ""; + ]) && + !isLastDescendantNode(path) + ) { + nextEmptyLine = printNextEmptyLine(path, options.originalText); + } - return concat([ - node.type !== "mappingValue" && hasLeadingComments(node) - ? concat([join(hardline, path.map(print, "leadingComments")), hardline]) - : "", - tag, - tag && anchor ? " " : "", - anchor, - tag || anchor - ? isNode(node, ["sequence", "mapping"]) && !hasMiddleComments(node) - ? hardline - : " " - : "", - hasMiddleComments(node) - ? concat([ - node.middleComments.length === 1 ? "" : hardline, - join(hardline, path.map(print, "middleComments")), - hardline, + if (tag || anchor) { + if (isNode(node, ["sequence", "mapping"]) && !hasMiddleComments(node)) { + parts.push(hardline); + } else { + parts.push(" "); + } + } + + if (hasMiddleComments(node)) { + parts.push( + concat([ + node.middleComments.length === 1 ? "" : hardline, + join(hardline, path.map(print, "middleComments")), + hardline, + ]) + ); + } + + const parentNode = path.getParentNode(); + if (hasPrettierIgnore(path)) { + parts.push( + concat( + replaceEndOfLineWith( + options.originalText + .slice(node.position.start.offset, node.position.end.offset) + .trimEnd(), + literalline + ) + ) + ); + } else { + parts.push(group(printNode(node, parentNode, path, options, print))); + } + + if (hasTrailingComment(node) && !isNode(node, ["document", "documentHead"])) { + parts.push( + lineSuffix( + concat([ + node.type === "mappingValue" && !node.content ? "" : " ", + parentNode.type === "mappingKey" && + path.getParentNode(2).type === "mapping" && + isInlineNode(node) + ? "" + : breakParent, + path.call(print, "trailingComment"), ]) - : "", - hasPrettierIgnore(path) - ? concat( - replaceEndOfLineWith( - options.originalText - .slice(node.position.start.offset, node.position.end.offset) - .trimEnd(), - literalline - ) - ) - : group(_print(node, parentNode, path, options, print)), - hasTrailingComment(node) && !isNode(node, ["document", "documentHead"]) - ? lineSuffix( - concat([ - node.type === "mappingValue" && !node.content ? "" : " ", - parentNode.type === "mappingKey" && - path.getParentNode(2).type === "mapping" && - isInlineNode(node) - ? "" - : breakParent, - path.call(print, "trailingComment"), - ]) - ) - : "", - shouldPrintEndComments(node) - ? align( - node.type === "sequenceItem" ? 2 : 0, - concat([ + ) + ); + } + + if (shouldPrintEndComments(node)) { + parts.push( + alignWithSpaces( + node.type === "sequenceItem" ? 2 : 0, + concat([ + hardline, + join( hardline, - join( - hardline, - path.map( - (path) => - concat([ - isPreviousLineEmpty( - options.originalText, - path.getValue(), - locStart - ) - ? hardline - : "", - print(path), - ]), - "endComments" - ) - ), - ]) - ) - : "", - nextEmptyLine, - ]); + path.map( + (path) => + concat([ + isPreviousLineEmpty( + options.originalText, + path.getValue(), + locStart + ) + ? hardline + : "", + print(path), + ]), + "endComments" + ) + ), + ]) + ) + ); + } + parts.push(nextEmptyLine); + return concat(parts); } -function _print(node, parentNode, path, options, print) { +function printNode(node, parentNode, path, options, print) { switch (node.type) { - case "root": - return concat([ - join( - hardline, - path.map((childPath, index) => { - const document = node.children[index]; - const nextDocument = node.children[index + 1]; - return concat([ - print(childPath), - shouldPrintDocumentEndMarker(document, nextDocument) - ? concat([ - hardline, - "...", - hasTrailingComment(document) - ? concat([" ", path.call(print, "trailingComment")]) - : "", - ]) - : !nextDocument || hasTrailingComment(nextDocument.head) - ? "" - : concat([hardline, "---"]), - ]); - }, "children") - ), - node.children.length === 0 || - ((lastDescendantNode) => - isNode(lastDescendantNode, ["blockLiteral", "blockFolded"]) && - lastDescendantNode.chomping === "keep")(getLastDescendantNode(node)) - ? "" - : hardline, - ]); + case "root": { + const { children } = node; + const parts = []; + path.each((childPath, index) => { + const document = children[index]; + const nextDocument = children[index + 1]; + if (index !== 0) { + parts.push(hardline); + } + parts.push(print(childPath)); + if (shouldPrintDocumentEndMarker(document, nextDocument)) { + parts.push(hardline, "..."); + if (hasTrailingComment(document)) { + parts.push(" ", path.call(print, "trailingComment")); + } + } else if (nextDocument && !hasTrailingComment(nextDocument.head)) { + parts.push(hardline, "---"); + } + }, "children"); + + const lastDescendantNode = getLastDescendantNode(node); + if ( + !isNode(lastDescendantNode, ["blockLiteral", "blockFolded"]) || + lastDescendantNode.chomping !== "keep" + ) { + parts.push(hardline); + } + return concat(parts); + } case "document": { const nextDocument = parentNode.children[path.getName() + 1]; - return join( - hardline, - [ - shouldPrintDocumentHeadEndMarker( - node, - nextDocument, - parentNode, - options - ) === "head" - ? join( - hardline, - [ - node.head.children.length === 0 && - node.head.endComments.length === 0 - ? "" - : path.call(print, "head"), - concat([ - "---", - hasTrailingComment(node.head) - ? concat([ - " ", - path.call(print, "head", "trailingComment"), - ]) - : "", - ]), - ].filter(Boolean) - ) - : "", - shouldPrintDocumentBody(node) ? path.call(print, "body") : "", - ].filter(Boolean) - ); + const parts = []; + if ( + shouldPrintDocumentHeadEndMarker( + node, + nextDocument, + parentNode, + options + ) === "head" + ) { + if ( + node.head.children.length !== 0 || + node.head.endComments.length !== 0 + ) { + parts.push(path.call(print, "head")); + } + + if (hasTrailingComment(node.head)) { + parts.push( + concat(["---", " ", path.call(print, "head", "trailingComment")]) + ); + } else { + parts.push("---"); + } + } + + if (shouldPrintDocumentBody(node)) { + parts.push(path.call(print, "body")); + } + + return join(hardline, parts); } case "documentHead": - return join( - hardline, - [].concat(path.map(print, "children"), path.map(print, "endComments")) - ); + return join(hardline, [ + ...path.map(print, "children"), + ...path.map(print, "endComments"), + ]); case "documentBody": { - const children = join(hardline, path.map(print, "children")).parts; - const endComments = join(hardline, path.map(print, "endComments")).parts; - const separator = - children.length === 0 || endComments.length === 0 - ? "" - : ((lastDescendantNode) => - isNode(lastDescendantNode, ["blockFolded", "blockLiteral"]) - ? lastDescendantNode.chomping === "keep" - ? // there's already a newline printed at the end of blockValue (chomping=keep, lastDescendant=true) - "" - : // an extra newline for better readability - concat([hardline, hardline]) - : hardline)(getLastDescendantNode(node)); - return concat([].concat(children, separator, endComments)); + const { children, endComments } = node; + /** @type {Doc} */ + let separator = ""; + if (children.length !== 0 && endComments.length !== 0) { + const lastDescendantNode = getLastDescendantNode(node); + // there's already a newline printed at the end of blockValue (chomping=keep, lastDescendant=true) + if (isNode(lastDescendantNode, ["blockFolded", "blockLiteral"])) { + // an extra newline for better readability + if (lastDescendantNode.chomping !== "keep") { + separator = concat([hardline, hardline]); + } + } else { + separator = hardline; + } + } + + return concat([ + join(hardline, path.map(print, "children")), + separator, + join(hardline, path.map(print, "endComments")), + ]); } case "directive": return concat(["%", join(" ", [node.name].concat(node.parameters))]); @@ -331,55 +342,14 @@ function _print(node, parentNode, path, options, print) { } case "blockFolded": case "blockLiteral": { - const parentIndent = getAncestorCount(path, (ancestorNode) => - isNode(ancestorNode, ["sequence", "mapping"]) - ); - const isLastDescendant = isLastDescendantNode(path); - return concat([ - node.type === "blockFolded" ? ">" : "|", - node.indent === null ? "" : node.indent.toString(), - node.chomping === "clip" ? "" : node.chomping === "keep" ? "+" : "-", - hasIndicatorComment(node) - ? concat([" ", path.call(print, "indicatorComment")]) - : "", - (node.indent === null ? dedent : dedentToRoot)( - align( - node.indent === null - ? options.tabWidth - : node.indent - 1 + parentIndent, - concat( - getBlockValueLineContents(node, { - parentIndent, - isLastDescendant, - options, - }).reduce( - (reduced, lineWords, index, lineContents) => - reduced.concat( - index === 0 ? hardline : "", - fill(join(line, lineWords).parts), - index !== lineContents.length - 1 - ? lineWords.length === 0 - ? hardline - : markAsRoot(literalline) - : node.chomping === "keep" && isLastDescendant - ? lineWords.length === 0 - ? dedentToRoot(hardline) - : dedentToRoot(literalline) - : "" - ), - [] - ) - ) - ) - ), - ]); + return printBlock(path, print, options); } case "sequence": return join(hardline, path.map(print, "children")); case "sequenceItem": return concat([ "- ", - align(2, !node.content ? "" : path.call(print, "content")), + alignWithSpaces(2, !node.content ? "" : path.call(print, "content")), ]); case "mappingKey": return !node.content ? "" : path.call(print, "content"); @@ -389,212 +359,18 @@ function _print(node, parentNode, path, options, print) { return join(hardline, path.map(print, "children")); case "mappingItem": case "flowMappingItem": { - const isEmptyMappingKey = isEmptyNode(node.key); - const isEmptyMappingValue = isEmptyNode(node.value); - - if (isEmptyMappingKey && isEmptyMappingValue) { - return concat([": "]); - } - - const key = path.call(print, "key"); - const value = path.call(print, "value"); - - if (isEmptyMappingValue) { - return node.type === "flowMappingItem" && - parentNode.type === "flowMapping" - ? key - : node.type === "mappingItem" && - isAbsolutelyPrintedAsSingleLineNode(node.key.content, options) && - !hasTrailingComment(node.key.content) && - (!parentNode.tag || - parentNode.tag.value !== "tag:yaml.org,2002:set") - ? concat([key, needsSpaceInFrontOfMappingValue(node) ? " " : "", ":"]) - : concat(["? ", align(2, key)]); - } - - if (isEmptyMappingKey) { - return concat([": ", align(2, value)]); - } - - const groupId = Symbol("mappingKey"); - - const forceExplicitKey = - hasLeadingComments(node.value) || !isInlineNode(node.key.content); - - return forceExplicitKey - ? concat([ - "? ", - align(2, key), - hardline, - join( - "", - path - .map(print, "value", "leadingComments") - .map((comment) => concat([comment, hardline])) - ), - ": ", - align(2, value), - ]) - : // force singleline - isSingleLineNode(node.key.content) && - !hasLeadingComments(node.key.content) && - !hasMiddleComments(node.key.content) && - !hasTrailingComment(node.key.content) && - !hasEndComments(node.key) && - !hasLeadingComments(node.value.content) && - !hasMiddleComments(node.value.content) && - !hasEndComments(node.value) && - isAbsolutelyPrintedAsSingleLineNode(node.value.content, options) - ? concat([ - key, - needsSpaceInFrontOfMappingValue(node) ? " " : "", - ": ", - value, - ]) - : conditionalGroup([ - concat([ - group( - concat([ifBreak("? "), group(align(2, key), { id: groupId })]) - ), - ifBreak( - concat([hardline, ": ", align(2, value)]), - indent( - concat([ - needsSpaceInFrontOfMappingValue(node) ? " " : "", - ":", - hasLeadingComments(node.value.content) || - (hasEndComments(node.value) && - node.value.content && - !isNode(node.value.content, ["mapping", "sequence"])) || - (parentNode.type === "mapping" && - hasTrailingComment(node.key.content) && - isInlineNode(node.value.content)) || - (isNode(node.value.content, ["mapping", "sequence"]) && - node.value.content.tag === null && - node.value.content.anchor === null) - ? hardline - : !node.value.content - ? "" - : line, - value, - ]) - ), - { groupId } - ), - ]), - ]); + return printMappingItem(node, parentNode, path, print, options); } case "flowMapping": - case "flowSequence": { - const openMarker = node.type === "flowMapping" ? "{" : "["; - const closeMarker = node.type === "flowMapping" ? "}" : "]"; - const bracketSpacing = - node.type === "flowMapping" && - node.children.length !== 0 && - options.bracketSpacing - ? line - : softline; - const isLastItemEmptyMappingItem = - node.children.length !== 0 && - ((lastItem) => - lastItem.type === "flowMappingItem" && - isEmptyNode(lastItem.key) && - isEmptyNode(lastItem.value))(getLast(node.children)); - const trailingComma = - options.trailingComma === "none" ? "" : ifBreak(",", ""); - return concat([ - openMarker, - indent( - concat([ - bracketSpacing, - concat( - path.map( - (childPath, index) => - concat([ - print(childPath), - index === node.children.length - 1 - ? "" - : concat([ - ",", - line, - node.children[index].position.start.line !== - node.children[index + 1].position.start.line - ? printNextEmptyLine( - childPath, - options.originalText - ) - : "", - ]), - ]), - "children" - ) - ), - trailingComma, - hasEndComments(node) - ? concat([ - hardline, - join(hardline, path.map(print, "endComments")), - ]) - : "", - ]) - ), - isLastItemEmptyMappingItem ? "" : bracketSpacing, - closeMarker, - ]); - } + return printFlowMapping(path, print, options); + case "flowSequence": + return printFlowSequence(path, print, options); case "flowSequenceItem": return path.call(print, "content"); // istanbul ignore next default: throw new Error(`Unexpected node type ${node.type}`); } - - function indent(doc) { - return docBuilders.align(" ".repeat(options.tabWidth), doc); - } -} - -function align(n, doc) { - return typeof n === "number" && n > 0 - ? docBuilders.align(" ".repeat(n), doc) - : docBuilders.align(n, doc); -} - -function isInlineNode(node) { - /* istanbul ignore next */ - if (!node) { - return true; - } - - switch (node.type) { - case "plain": - case "quoteDouble": - case "quoteSingle": - case "alias": - case "flowMapping": - case "flowSequence": - return true; - default: - return false; - } -} - -function isSingleLineNode(node) { - /* istanbul ignore next */ - if (!node) { - return true; - } - - switch (node.type) { - case "plain": - case "quoteDouble": - case "quoteSingle": - return node.position.start.line === node.position.end.line; - case "alias": - return true; - default: - return false; - } } function shouldPrintDocumentBody(document) { @@ -663,91 +439,6 @@ function shouldPrintDocumentHeadEndMarker( return nextDocument ? "root" : false; } -function isAbsolutelyPrintedAsSingleLineNode(node, options) { - if (!node) { - return true; - } - - switch (node.type) { - case "plain": - case "quoteSingle": - case "quoteDouble": - break; - case "alias": - return true; - default: - return false; - } - - if (options.proseWrap === "preserve") { - return node.position.start.line === node.position.end.line; - } - - if ( - // backslash-newline - /\\$/m.test( - options.originalText.slice( - node.position.start.offset, - node.position.end.offset - ) - ) - ) { - return false; - } - - switch (options.proseWrap) { - case "never": - return !node.value.includes("\n"); - case "always": - return !/[\n ]/.test(node.value); - // istanbul ignore next - default: - return false; - } -} - -function needsSpaceInFrontOfMappingValue(node) { - return node.key.content && node.key.content.type === "alias"; -} - -function shouldPrintEndComments(node) { - return ( - hasEndComments(node) && - !isNode(node, [ - "documentHead", - "documentBody", - "flowMapping", - "flowSequence", - ]) - ); -} - -const printedEmptyLineCache = new WeakMap(); -function printNextEmptyLine(path, originalText) { - const node = path.getValue(); - const root = path.stack[0]; - - let isNextEmptyLinePrintedSet; - if (printedEmptyLineCache.has(root)) { - isNextEmptyLinePrintedSet = printedEmptyLineCache.get(root); - } else { - isNextEmptyLinePrintedSet = new Set(); - printedEmptyLineCache.set(root, isNextEmptyLinePrintedSet); - } - - if (!isNextEmptyLinePrintedSet.has(node.position.end.line)) { - isNextEmptyLinePrintedSet.add(node.position.end.line); - if ( - isNextLineEmpty(node, originalText) && - !shouldPrintEndComments(path.getParentNode()) - ) { - return softline; - } - } - - return ""; -} - function printFlowScalarContent(nodeType, content, options) { const lineContents = getFlowScalarLineContents(nodeType, content, options); return join( diff --git a/src/language-yaml/utils.js b/src/language-yaml/utils.js index d645938a8..615be327e 100644 --- a/src/language-yaml/utils.js +++ b/src/language-yaml/utils.js @@ -327,11 +327,31 @@ function getBlockValueLineContents( } } +function isInlineNode(node) { + /* istanbul ignore next */ + if (!node) { + return true; + } + + switch (node.type) { + case "plain": + case "quoteDouble": + case "quoteSingle": + case "alias": + case "flowMapping": + case "flowSequence": + return true; + default: + return false; + } +} + module.exports = { getLast, getAncestorCount, isNode, isEmptyNode, + isInlineNode, mapNode, defineShortcut, isNextLineEmpty,