diff --git a/src/printer.mjs b/src/printer.mjs index a9f06c170..4645c6bc8 100644 --- a/src/printer.mjs +++ b/src/printer.mjs @@ -55,6 +55,7 @@ const { literalline, align, dedentToRoot, + markAsRoot, } = doc.builders; const { willBreak } = doc.utils; const { @@ -866,6 +867,21 @@ function printLookupNodes(path, options, print) { } } +/** + * Join the body lines of a flexible heredoc or nowdoc. `hardline` trims + * trailing whitespace, which is part of the string, so a line ending in + * whitespace is followed by a `literalline` that keeps the indentation. + */ +function joinHeredocLines(lines) { + return markAsRoot( + lines.flatMap((line, index) => + index === 0 + ? [line] + : [/[\t ]$/.test(lines[index - 1]) ? literalline : hardline, line] + ) + ); +} + function getEncapsedQuotes(node, { opening = true } = {}) { if (node.type === "heredoc") { return opening ? `<<<${node.label}` : node.label; @@ -2866,25 +2882,23 @@ function printNode(path, options, print) { const parentParent = path.grandparent; let closingTagIndentation = 0; const flexible = options.phpVersion >= 7.3; - let linebreak = literalline; if (parentParent.type === "heredoc") { - linebreak = flexible ? hardline : literalline; const lines = parentParent.raw.split("\n"); closingTagIndentation = lines[lines.length - 1].search(/\S/); if (closingTagIndentation === -1) { closingTagIndentation = lines[lines.length - 2].search(/\S/); } } - return join( - linebreak, - node.raw - .split("\n") - .map((s, i) => - i > 0 || node.loc.start.column === 0 - ? s.substring(closingTagIndentation) - : s - ) - ); + const valueLines = node.raw + .split("\n") + .map((s, i) => + i > 0 || node.loc.start.column === 0 + ? s.substring(closingTagIndentation) + : s + ); + return parentParent.type === "heredoc" && flexible + ? joinHeredocLines(valueLines) + : join(literalline, valueLines); } const quote = useDoubleQuote(node, options) ? '"' : "'"; @@ -2960,15 +2974,13 @@ function printNode(path, options, print) { return node.value; case "nowdoc": { const flexible = options.phpVersion >= 7.3; - const linebreak = flexible ? hardline : literalline; + const lines = [...node.value.split("\n"), node.label]; return [ "<<<'", node.label, "'", - linebreak, - join(linebreak, node.value.split("\n")), - linebreak, - node.label, + flexible ? hardline : literalline, + flexible ? joinHeredocLines(lines) : join(literalline, lines), docShouldHaveTrailingNewline(path) ? hardline : "", ]; } diff --git a/tests/encapsed/__snapshots__/jsfmt.spec.mjs.snap b/tests/encapsed/__snapshots__/jsfmt.spec.mjs.snap index 0b8acbec2..bca3eb23f 100644 --- a/tests/encapsed/__snapshots__/jsfmt.spec.mjs.snap +++ b/tests/encapsed/__snapshots__/jsfmt.spec.mjs.snap @@ -2077,6 +2077,96 @@ printf( ================================================================================ `; +exports[`heredoc-trailing-whitespace.php 1`] = ` +====================================options===================================== +parsers: ["php"] +phpVersion: "7.0" +printWidth: 80 + | printWidth +=====================================input====================================== +