From 9d9c0e2b6ffca0d85e2cae8eb77ea81b9ce4f322 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Mon, 4 Jan 2021 10:04:30 +0800 Subject: [PATCH] Add `Doc[]` as `Doc` (#9733) Co-authored-by: Georgii Dolzhykov --- .eslintrc.yml | 4 + changelog_unreleased/api/9733.md | 13 ++ commands.md | 34 +++-- docs/plugins.md | 2 +- docs/technical-details.md | 2 +- .../index.js | 1 + .../no-doc-builder-concat.js | 35 +++++ .../test.js | 16 ++ src/document/doc-builders.js | 27 +++- src/document/doc-debug.js | 24 +-- src/document/doc-printer.js | 23 ++- src/document/doc-utils.js | 60 ++++---- src/language-css/printer-postcss.js | 9 +- src/language-handlebars/printer-glimmer.js | 3 +- src/language-html/printer-html.js | 6 +- src/language-js/print/binaryish.js | 7 +- src/language-js/print/object.js | 1 + src/language-markdown/printer-markdown.js | 4 +- src/language-yaml/print/block.js | 3 +- src/language-yaml/printer-yaml.js | 3 +- tests_integration/__tests__/debug-api.js | 10 +- tests_integration/__tests__/doc-builders.js | 138 ++++++++++++++++++ 22 files changed, 332 insertions(+), 93 deletions(-) create mode 100644 changelog_unreleased/api/9733.md create mode 100644 scripts/tools/eslint-plugin-prettier-internal-rules/no-doc-builder-concat.js create mode 100644 tests_integration/__tests__/doc-builders.js diff --git a/.eslintrc.yml b/.eslintrc.yml index c111da02a..2e630bf4e 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -109,8 +109,12 @@ overrides: prettier-internal-rules/better-parent-property-check-in-needs-parens: error - files: src/**/*.js rules: + prettier-internal-rules/no-doc-builder-concat: off prettier-internal-rules/prefer-fast-path-each: error prettier-internal-rules/prefer-is-non-empty-array: error + - files: src/document/*.js + rules: + prettier-internal-rules/no-doc-builder-concat: off - files: - src/language-*/**/*.js rules: diff --git a/changelog_unreleased/api/9733.md b/changelog_unreleased/api/9733.md new file mode 100644 index 000000000..0c903d057 --- /dev/null +++ b/changelog_unreleased/api/9733.md @@ -0,0 +1,13 @@ +#### Use arrays instead of `concat` (#9733 by @fisker, @thorn0) + +To simplify the code of AST printers, the data structure for the concatenation command has been changed from `{ type: 'concat', parts: Doc[] }` to `Doc[]`. The old format is deprecated, but for compatibility, the doc printer still supports it, and `doc.builders.concat` (as well as some other builder functions) will keep using it until the next major version of Prettier. + +If you're a plugin author, this change should only concern you if your plugin introspects or modifies composed docs. If it happens to be the case, please make your plugin compatible with future versions of Prettier by tweaking the introspecting code to support the new format. There also is an off-chance where this change can break things, namely if a plugin calls another plugin to [print an embedded language](https://prettier.io/docs/en/plugins.html#optional-embed) and then introspects the returned doc. There seems to be no reason for plugins to do that though. + +```jsx +// Prettier stable +myDoc = group(concat(["foo", line, "bar"])); + +// Prettier master +myDoc = group(["foo", line, "bar"]); +``` diff --git a/commands.md b/commands.md index 3ca932f2d..0a0145418 100644 --- a/commands.md +++ b/commands.md @@ -2,14 +2,6 @@ The core of the algorithm is implemented in `doc-{printer,builders,utils,debug}. The following commands are available: -### concat - -```ts -declare function concat(docs: Doc[]): Doc; -``` - -Combine an array into a single string. - ### group ```ts @@ -97,7 +89,7 @@ declare var breakParent: Doc; Include this anywhere to force all parent groups to break. See `group` for more info. Example: ```js -group(concat([" ", expr, " ", breakParent])); +group([" ", expr, " ", breakParent]); ``` ### join @@ -149,7 +141,7 @@ declare function lineSuffix(suffix: Doc): Doc; This is used to implement trailing comments. In practice, it is not practical to find where the line ends and you don't want to accidentally print some code at the end of the comment. `lineSuffix` will buffer the output and flush it before any new line. ```js -concat(["a", lineSuffix(" // comment"), ";", hardline]); +["a", lineSuffix(" // comment"), ";", hardline]; ``` will output @@ -167,7 +159,7 @@ declare var lineSuffixBoundary: Doc; In cases where you embed code inside of templates, comments shouldn't be able to leave the code part. lineSuffixBoundary is an explicit marker you can use to flush code in addition to newlines. ```js -concat(["{", lineSuffix(" // comment"), lineSuffixBoundary, "}", hardline]); +["{", lineSuffix(" // comment"), lineSuffixBoundary, "}", hardline]; ``` will output @@ -260,21 +252,31 @@ For an example, here's the implementation of the `ArrayExpression` node type: ```js group( - concat([ + [ "[", indent( - concat([ + [ line, join( - concat([",", line]), + [",", line], path.map(print, "elements") ) - ]) + ] ), line, "]" - ]) + ] ); ``` This is a group with opening and closing brackets, and possibly indented contents. Because it's a `group` it will always be broken up if any of the sub-expressions are broken. + +### [Deprecated] concat + +> This command is deprecated, use `Doc[]` instead. + +```ts +declare function concat(docs: Doc[]): Doc; +``` + +Combine an array into a single string. diff --git a/docs/plugins.md b/docs/plugins.md index e4a80e787..152b87209 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -170,7 +170,7 @@ export const printers = { Prettier uses an intermediate representation, called a Doc, which Prettier then turns into a string (based on options like `printWidth`). A _printer_'s job is to take the AST generated by `parsers[].parse` and return a Doc. A Doc is constructed using [builder commands](https://github.com/prettier/prettier/blob/master/commands.md): ```js -const { concat, join, line, ifBreak, group } = require("prettier").doc.builders; +const { join, line, ifBreak, group } = require("prettier").doc.builders; ``` The printing process works as follows: diff --git a/docs/technical-details.md b/docs/technical-details.md index 6c5ba5753..bd67937ca 100644 --- a/docs/technical-details.md +++ b/docs/technical-details.md @@ -7,6 +7,6 @@ This printer is a fork of [recast](https://github.com/benjamn/recast)’s printe The basic idea is that the printer takes an AST and returns an intermediate representation of the output, and the printer uses that to generate a string. The advantage is that the printer can "measure" the IR and see if the output is going to fit on a line, and break if not. -This means that most of the logic of printing an AST involves generating an abstract representation of the output involving certain commands. For example, `concat(["(", line, arg, line, ")"])` would represent a concatenation of opening parens, an argument, and closing parens. But if that doesn’t fit on one line, the printer can break where `line` is specified. +This means that most of the logic of printing an AST involves generating an abstract representation of the output involving certain commands. For example, `["(", line, arg, line, ")"]` would represent a concatenation of opening parens, an argument, and closing parens. But if that doesn’t fit on one line, the printer can break where `line` is specified. More (rough) details can be found in [commands.md](https://github.com/prettier/prettier/blob/master/commands.md). diff --git a/scripts/tools/eslint-plugin-prettier-internal-rules/index.js b/scripts/tools/eslint-plugin-prettier-internal-rules/index.js index 9837c5ae5..3bec6a3e2 100644 --- a/scripts/tools/eslint-plugin-prettier-internal-rules/index.js +++ b/scripts/tools/eslint-plugin-prettier-internal-rules/index.js @@ -5,6 +5,7 @@ module.exports = { "better-parent-property-check-in-needs-parens": require("./better-parent-property-check-in-needs-parens"), "directly-loc-start-end": require("./directly-loc-start-end"), "jsx-identifier-case": require("./jsx-identifier-case"), + "no-doc-builder-concat": require("./no-doc-builder-concat"), "no-node-comments": require("./no-node-comments"), "prefer-fast-path-each": require("./prefer-fast-path-each"), "prefer-is-non-empty-array": require("./prefer-is-non-empty-array"), diff --git a/scripts/tools/eslint-plugin-prettier-internal-rules/no-doc-builder-concat.js b/scripts/tools/eslint-plugin-prettier-internal-rules/no-doc-builder-concat.js new file mode 100644 index 000000000..ab0b83d12 --- /dev/null +++ b/scripts/tools/eslint-plugin-prettier-internal-rules/no-doc-builder-concat.js @@ -0,0 +1,35 @@ +"use strict"; + +const selector = [ + "CallExpression", + ">", + "Identifier.callee", + '[name="concat"]', +].join(""); + +const messageId = "no-doc-builder-concat"; + +module.exports = { + meta: { + type: "suggestion", + docs: { + url: + "https://github.com/prettier/prettier/blob/master/scripts/eslint-plugin-prettier-internal-rules/no-doc-builder-concat.js", + }, + messages: { + [messageId]: "Use array directly instead of `concat([])`", + }, + fixable: "code", + }, + create(context) { + return { + [selector](node) { + context.report({ + node, + messageId, + fix: (fixer) => fixer.replaceText(node, ""), + }); + }, + }; + }, +}; diff --git a/scripts/tools/eslint-plugin-prettier-internal-rules/test.js b/scripts/tools/eslint-plugin-prettier-internal-rules/test.js index b0e6ddd04..e02c8008e 100644 --- a/scripts/tools/eslint-plugin-prettier-internal-rules/test.js +++ b/scripts/tools/eslint-plugin-prettier-internal-rules/test.js @@ -94,6 +94,22 @@ test("jsx-identifier-case", { ], }); +test("no-doc-builder-concat", { + valid: ["notConcat([])", "concat", "[].concat([])"], + invalid: [ + { + code: "concat(parts)", + output: "(parts)", + errors: 1, + }, + { + code: "concat(['foo', line])", + output: "(['foo', line])", + errors: 1, + }, + ], +}); + test("no-node-comments", { valid: [ "const comments = node.notComments", diff --git a/src/document/doc-builders.js b/src/document/doc-builders.js index 78bb870a8..8d04efe5f 100644 --- a/src/document/doc-builders.js +++ b/src/document/doc-builders.js @@ -8,21 +8,32 @@ * @property {boolean} [hard] * @property {boolean} [literal] * - * @typedef {string | DocObject} Doc + * @typedef {Doc[]} DocArray + * + * @typedef {string | DocObject | DocArray} Doc */ /** * @param {Doc} val */ function assertDoc(val) { - /* istanbul ignore if */ - if ( - !(typeof val === "string" || (val != null && typeof val.type === "string")) - ) { - throw new Error( - "Value " + JSON.stringify(val) + " is not a valid document" - ); + if (typeof val === "string") { + return; } + + if (Array.isArray(val)) { + for (const doc of val) { + assertDoc(doc); + } + return; + } + + if (val && typeof val.type === "string") { + return; + } + + /* istanbul ignore next */ + throw new Error("Value " + JSON.stringify(val) + " is not a valid document"); } /** diff --git a/src/document/doc-debug.js b/src/document/doc-debug.js index d9a9a010a..0c31aa99f 100644 --- a/src/document/doc-debug.js +++ b/src/document/doc-debug.js @@ -1,12 +1,14 @@ "use strict"; -function flattenDoc(doc) { - if (doc.type === "concat") { - const res = []; +const { isConcat, getDocParts } = require("./doc-utils"); - for (let i = 0; i < doc.parts.length; ++i) { - const doc2 = doc.parts[i]; - if (typeof doc2 !== "string" && doc2.type === "concat") { +function flattenDoc(doc) { + if (isConcat(doc)) { + const res = []; + const parts = getDocParts(doc); + for (let i = 0; i < parts.length; ++i) { + const doc2 = parts[i]; + if (typeof doc2 !== "string" && isConcat(doc2)) { res.push(...flattenDoc(doc2).parts); } else { const flattened = flattenDoc(doc2); @@ -16,7 +18,7 @@ function flattenDoc(doc) { } } - return { ...doc, parts: res }; + return { type: "concat", parts: res }; } else if (doc.type === "if-break") { return { ...doc, @@ -44,6 +46,10 @@ function printDoc(doc) { return JSON.stringify(doc); } + if (isConcat(doc)) { + return "[" + getDocParts(doc).map(printDoc).join(", ") + "]"; + } + if (doc.type === "line") { if (doc.literal) { return "literalline"; @@ -65,10 +71,6 @@ function printDoc(doc) { return "trim"; } - if (doc.type === "concat") { - return "[" + doc.parts.map(printDoc).join(", ") + "]"; - } - if (doc.type === "indent") { return "indent(" + printDoc(doc.contents) + ")"; } diff --git a/src/document/doc-printer.js b/src/document/doc-printer.js index 354d3e68c..9a3dfb9f7 100644 --- a/src/document/doc-printer.js +++ b/src/document/doc-printer.js @@ -3,6 +3,7 @@ const { getStringWidth } = require("../common/util"); const { convertEndOfLineToChars } = require("../common/end-of-line"); const { concat, fill, cursor } = require("./doc-builders"); +const { isConcat, getDocParts } = require("./doc-utils"); /** @type {Record} */ let groupModeMap; @@ -167,14 +168,13 @@ function fits(next, restCommands, width, options, mustBeFlat) { out.push(doc); width -= getStringWidth(doc); + } else if (isConcat(doc)) { + const parts = getDocParts(doc); + for (let i = parts.length - 1; i >= 0; i--) { + cmds.push([ind, mode, parts[i]]); + } } else { switch (doc.type) { - case "concat": - for (let i = doc.parts.length - 1; i >= 0; i--) { - cmds.push([ind, mode, doc.parts[i]]); - } - - break; case "indent": cmds.push([makeIndent(ind, options), mode, doc.contents]); @@ -264,17 +264,16 @@ function printDocToString(doc, options) { const formatted = newLine !== "\n" ? doc.replace(/\n/g, newLine) : doc; out.push(formatted); pos += getStringWidth(formatted); + } else if (isConcat(doc)) { + const parts = getDocParts(doc); + for (let i = parts.length - 1; i >= 0; i--) { + cmds.push([ind, mode, parts[i]]); + } } else { switch (doc.type) { case "cursor": out.push(cursor.placeholder); - break; - case "concat": - for (let i = doc.parts.length - 1; i >= 0; i--) { - cmds.push([ind, mode, doc.parts[i]]); - } - break; case "indent": cmds.push([makeIndent(ind, options), mode, doc.contents]); diff --git a/src/document/doc-utils.js b/src/document/doc-utils.js index 453db6107..135056681 100644 --- a/src/document/doc-utils.js +++ b/src/document/doc-utils.js @@ -2,6 +2,9 @@ const { literalline, concat } = require("./doc-builders"); +const isConcat = (doc) => Array.isArray(doc) || (doc && doc.type === "concat"); +const getDocParts = (doc) => (Array.isArray(doc) ? doc : doc.parts); + // Using a unique object to compare by reference. const traverseDocOnExitStackMarker = {}; @@ -29,9 +32,10 @@ function traverseDoc(doc, onEnter, onExit, shouldTraverseConditionalGroups) { // the parts need to be pushed onto the stack in reverse order, // so that they are processed in the original order // when the stack is popped. - if (doc.type === "concat" || doc.type === "fill") { - for (let ic = doc.parts.length, i = ic - 1; i >= 0; --i) { - docsStack.push(doc.parts[i]); + if (isConcat(doc) || doc.type === "fill") { + const parts = getDocParts(doc); + for (let ic = parts.length, i = ic - 1; i >= 0; --i) { + docsStack.push(parts[i]); } } else if (doc.type === "if-break") { if (doc.flatContents) { @@ -56,7 +60,9 @@ function traverseDoc(doc, onEnter, onExit, shouldTraverseConditionalGroups) { } function mapDoc(doc, cb) { - if (doc.type === "concat" || doc.type === "fill") { + if (Array.isArray(doc)) { + return cb(doc.map((part) => mapDoc(part, cb))); + } else if (doc.type === "concat" || doc.type === "fill") { const parts = doc.parts.map((part) => mapDoc(part, cb)); return cb({ ...doc, parts }); } else if (doc.type === "if-break") { @@ -191,23 +197,21 @@ function stripDocTrailingHardlineFromDoc(doc) { return doc; } - switch (doc.type) { - case "concat": - case "fill": { - const parts = [...doc.parts]; + if (isConcat(doc) || doc.type === "fill") { + const parts = getDocParts(doc); - while (parts.length > 1 && isHardline(...parts.slice(-2))) { - parts.length -= 2; - } - - if (parts.length > 0) { - const lastPart = stripDocTrailingHardlineFromDoc( - parts[parts.length - 1] - ); - parts[parts.length - 1] = lastPart; - } - return { ...doc, parts }; + while (parts.length > 1 && isHardline(...parts.slice(-2))) { + parts.length -= 2; } + + if (parts.length > 0) { + const lastPart = stripDocTrailingHardlineFromDoc(parts[parts.length - 1]); + parts[parts.length - 1] = lastPart; + } + return Array.isArray(doc) ? parts : { ...doc, parts }; + } + + switch (doc.type) { case "align": case "indent": case "group": @@ -230,7 +234,6 @@ function stripTrailingHardline(doc) { return stripDocTrailingHardlineFromDoc(cleanDoc(doc)); } -const isConcat = (doc) => doc && doc.type === "concat"; function cleanDocFn(doc) { switch (doc.type) { case "fill": @@ -271,11 +274,13 @@ function cleanDocFn(doc) { } const parts = []; - for (const part of doc.parts) { + for (const part of getDocParts(doc)) { if (!part) { continue; } - const [currentPart, ...restParts] = isConcat(part) ? part.parts : [part]; + const [currentPart, ...restParts] = isConcat(part) + ? getDocParts(part) + : [part]; if ( typeof currentPart === "string" && typeof parts[parts.length - 1] === "string" @@ -294,7 +299,7 @@ function cleanDocFn(doc) { if (parts.length === 1) { return parts[0]; } - return { ...doc, parts }; + return Array.isArray(doc) ? parts : { ...doc, parts }; } // A safer version of `normalizeDoc` // - `normalizeDoc` concat strings and flat "concat" in `fill`, while `cleanDoc` don't @@ -315,8 +320,8 @@ function normalizeParts(parts) { continue; } - if (part.type === "concat") { - restParts.unshift(...part.parts); + if (isConcat(part)) { + restParts.unshift(...getDocParts(part)); continue; } @@ -337,6 +342,9 @@ function normalizeParts(parts) { function normalizeDoc(doc) { return mapDoc(doc, (currentDoc) => { + if (Array.isArray(currentDoc)) { + return normalizeParts(currentDoc); + } if (!currentDoc.parts) { return currentDoc; } @@ -360,6 +368,8 @@ function replaceNewlinesWithLiterallines(doc) { } module.exports = { + isConcat, + getDocParts, isEmpty, willBreak, isLineNext, diff --git a/src/language-css/printer-postcss.js b/src/language-css/printer-postcss.js index 8386d0bb3..a975379a2 100644 --- a/src/language-css/printer-postcss.js +++ b/src/language-css/printer-postcss.js @@ -22,7 +22,7 @@ const { ifBreak, breakParent, }, - utils: { removeLines }, + utils: { removeLines, getDocParts }, } = require("../document"); const clean = require("./clean"); const embed = require("./embed"); @@ -102,7 +102,7 @@ function genericPrint(path, options, print) { return concat([ nodes, after ? ` ${after}` : "", - nodes.parts.length ? hardline : "", + getDocParts(nodes).length ? hardline : "", ]); } case "css-comment": { @@ -892,9 +892,8 @@ function genericPrint(path, options, print) { node.groups[2] && node.groups[2].type === "value-paren_group" ) { - printed.contents.contents.parts[1] = group( - printed.contents.contents.parts[1] - ); + const parts = getDocParts(printed.contents.contents); + parts[1] = group(parts[1]); return group(dedent(printed)); } diff --git a/src/language-handlebars/printer-glimmer.js b/src/language-handlebars/printer-glimmer.js index 0981f2dd3..126c3336a 100644 --- a/src/language-handlebars/printer-glimmer.js +++ b/src/language-handlebars/printer-glimmer.js @@ -2,6 +2,7 @@ const { builders: { concat, group, hardline, ifBreak, indent, join, line, softline }, + utils: { getDocParts }, } = require("../document"); const { locStart, locEnd } = require("./loc"); const clean = require("./clean"); @@ -141,7 +142,7 @@ function print(path, options, print) { } const value = path.call(print, "value"); const quotedValue = isText - ? printStringLiteral(value.parts.join(), options) + ? printStringLiteral(getDocParts(value).join(), options) : value; return concat([n.name, "=", quotedValue]); } diff --git a/src/language-html/printer-html.js b/src/language-html/printer-html.js index 9110cfde0..e0b8a6ce1 100644 --- a/src/language-html/printer-html.js +++ b/src/language-html/printer-html.js @@ -16,7 +16,7 @@ const { softline, concat, }, - utils: { mapDoc, cleanDoc }, + utils: { mapDoc, cleanDoc, getDocParts }, } = require("../document"); const { replaceEndOfLineWith, isNonEmptyArray } = require("../common/util"); const { print: printFrontMatter } = require("../utils/front-matter"); @@ -378,7 +378,7 @@ function genericPrint(path, options, print) { printClosingTagSuffix(node, options), ]) ); - return typeof printed === "string" ? printed : fill(printed.parts); + return typeof printed === "string" ? printed : fill(getDocParts(printed)); } case "docType": return concat([ @@ -981,7 +981,7 @@ function getTextValueParts(node, value = node.value) { dedentString(htmlTrimPreserveIndentation(value)), hardline ) - : join(line, splitByHtmlWhitespace(value)).parts; + : getDocParts(join(line, splitByHtmlWhitespace(value))); } function printEmbeddedAttributeValue(node, originalTextToDoc, options) { diff --git a/src/language-js/print/binaryish.js b/src/language-js/print/binaryish.js index 22fb959b2..132c3a8ae 100644 --- a/src/language-js/print/binaryish.js +++ b/src/language-js/print/binaryish.js @@ -4,7 +4,7 @@ const { printComments } = require("../../main/comments"); const { getLast } = require("../../common/util"); const { builders: { concat, join, line, softline, group, indent, align, ifBreak }, - utils: { cleanDoc }, + utils: { cleanDoc, getDocParts }, } = require("../../document"); const { hasLeadingOwnLineComment, @@ -130,7 +130,8 @@ function printBinaryishExpression(path, options, print) { const hasJsx = isJsxNode(n.right); const firstGroupIndex = parts.findIndex( - (part) => typeof part !== "string" && part.type === "group" + (part) => + typeof part !== "string" && !Array.isArray(part) && part.type === "group" ); // Separate the leftmost expression, possibly with its leading comments. @@ -278,7 +279,7 @@ function printBinaryishExpressions( if (printed.type === "string") { parts = [printed]; } else { - parts = printed.parts; + parts = getDocParts(printed); } } } else { diff --git a/src/language-js/print/object.js b/src/language-js/print/object.js index 6716773f5..5b39cf0a6 100644 --- a/src/language-js/print/object.js +++ b/src/language-js/print/object.js @@ -102,6 +102,7 @@ function printObject(path, options, print) { }, field); }); + /** @type {Doc[]} */ let separatorParts = []; const props = propsAndLoc .sort((a, b) => a.loc - b.loc) diff --git a/src/language-markdown/printer-markdown.js b/src/language-markdown/printer-markdown.js index 82af78a16..f4f280ab7 100644 --- a/src/language-markdown/printer-markdown.js +++ b/src/language-markdown/printer-markdown.js @@ -23,7 +23,7 @@ const { indent, group, }, - utils: { normalizeDoc }, + utils: { normalizeDoc, getDocParts }, printer: { printDocToString }, } = require("../document"); const { replaceEndOfLineWith } = require("../common/util"); @@ -578,7 +578,7 @@ function printLine(path, value, options) { } function printTable(path, options, print) { - const hardlineWithoutBreakParent = hardline.parts[0]; + const hardlineWithoutBreakParent = getDocParts(hardline)[0]; const node = path.getValue(); const columnMaxWidths = []; diff --git a/src/language-yaml/print/block.js b/src/language-yaml/print/block.js index ce98e4236..b99f770e9 100644 --- a/src/language-yaml/print/block.js +++ b/src/language-yaml/print/block.js @@ -14,6 +14,7 @@ const { literalline, markAsRoot, }, + utils: { getDocParts }, } = require("../../document"); const { getAncestorCount, @@ -55,7 +56,7 @@ function printBlock(path, print, options) { if (index === 0) { contentsParts.push(hardline); } - contentsParts.push(fill(join(line, lineWords).parts)); + contentsParts.push(fill(getDocParts(join(line, lineWords)))); if (index !== lineContents.length - 1) { contentsParts.push( lineWords.length === 0 ? hardline : markAsRoot(literalline) diff --git a/src/language-yaml/printer-yaml.js b/src/language-yaml/printer-yaml.js index b7f4544aa..0be8a3301 100644 --- a/src/language-yaml/printer-yaml.js +++ b/src/language-yaml/printer-yaml.js @@ -14,6 +14,7 @@ const { lineSuffix, literalline, }, + utils: { getDocParts }, } = require("../document"); const { replaceEndOfLineWith, isPreviousLineEmpty } = require("../common/util"); const { insertPragma, isPragma } = require("./pragma"); @@ -444,7 +445,7 @@ function printFlowScalarContent(nodeType, content, options) { return join( hardline, lineContents.map((lineContentWords) => - fill(join(line, lineContentWords).parts) + fill(getDocParts(join(line, lineContentWords))) ) ); } diff --git a/tests_integration/__tests__/debug-api.js b/tests_integration/__tests__/debug-api.js index ea7293206..bf49aafcd 100644 --- a/tests_integration/__tests__/debug-api.js +++ b/tests_integration/__tests__/debug-api.js @@ -27,9 +27,13 @@ describe("API", () => { }); const doc = printToDoc(code, options); - test("prettier.printToDoc", () => { - expect(doc.type).toBe("concat"); - expect(Array.isArray(doc.parts)).toBe(true); + test("prettier.printToDoc", (done) => { + // If it's array, it's a `concat` + if (!Array.isArray(doc)) { + expect(doc.type).toBe("concat"); + expect(Array.isArray(doc.parts)).toBe(true); + } + done(); }); const formatResultFromDoc = formatDoc(doc, options); diff --git a/tests_integration/__tests__/doc-builders.js b/tests_integration/__tests__/doc-builders.js new file mode 100644 index 000000000..47f25516f --- /dev/null +++ b/tests_integration/__tests__/doc-builders.js @@ -0,0 +1,138 @@ +"use strict"; + +const prettier = require("prettier-local"); +const { + concat, + join, + hardline, + literalline, + group, + fill, + lineSuffix, + breakParent, + ifBreak, + indent, + align, +} = prettier.doc.builders; + +// TODO: Make these builders to use array if possible in 3.0.0 +describe("doc builders", () => { + test.each([ + ["concat", concat(["1", "2"]), { type: "concat", parts: ["1", "2"] }], + [ + "join", + join(concat(["1"]), ["2", concat(["3"])]), + { + type: "concat", + parts: [ + "2", + { type: "concat", parts: ["1"] }, + { type: "concat", parts: ["3"] }, + ], + }, + ], + [ + "join(array)", + join(["1"], ["2", concat(["3"])]), + { type: "concat", parts: ["2", ["1"], { type: "concat", parts: ["3"] }] }, + ], + [ + "hardline", + hardline, + { type: "concat", parts: [{ type: "line", hard: true }, breakParent] }, + ], + [ + "literalline", + literalline, + { + type: "concat", + parts: [{ type: "line", hard: true, literal: true }, breakParent], + }, + ], + [ + "group", + group(concat(["1"])), + { + type: "group", + id: undefined, + contents: { type: "concat", parts: ["1"] }, + break: false, + expandedStates: undefined, + }, + ], + [ + "group (array)", + group(["1"]), + { + type: "group", + id: undefined, + contents: ["1"], + break: false, + expandedStates: undefined, + }, + ], + ["fill", fill(["1", "2"]), { type: "fill", parts: ["1", "2"] }], + [ + "line-suffix", + lineSuffix(concat(["1"])), + { + type: "line-suffix", + contents: { type: "concat", parts: ["1"] }, + }, + ], + [ + "line-suffix(array)", + lineSuffix(["1"]), + { + type: "line-suffix", + contents: ["1"], + }, + ], + [ + "if-break", + ifBreak(concat(["1"]), ["2"]), + { + type: "if-break", + breakContents: { type: "concat", parts: ["1"] }, + flatContents: ["2"], + groupId: undefined, + }, + ], + [ + "indent", + indent(concat(["1"])), + { + type: "indent", + contents: { type: "concat", parts: ["1"] }, + }, + ], + [ + "indent(array)", + indent(["1"]), + { + type: "indent", + contents: ["1"], + }, + ], + [ + "align", + align(" ", concat(["1"])), + { + type: "align", + contents: { type: "concat", parts: ["1"] }, + n: " ", + }, + ], + [ + "align(array)", + align(" ", ["1"]), + { + type: "align", + contents: ["1"], + n: " ", + }, + ], + ])("%s", (_, doc, expected) => { + expect(doc).toEqual(expected); + }); +});