src/nodes/strings.js in prettier-0.15.1 vs src/nodes/strings.js in prettier-0.16.0

- old
+ new

@@ -2,11 +2,12 @@ concat, group, hardline, indent, literalline, - softline + softline, + join } = require("../prettier"); const { concatBody, empty, makeList, prefix, surround } = require("../utils"); const escapePattern = require("../escapePattern"); // If there is some part of this string that matches an escape sequence or that @@ -36,17 +37,18 @@ return preferSingleQuotes && isSingleQuotable(string) ? "'" : '"'; }; const quotePattern = new RegExp("\\\\([\\s\\S])|(['\"])", "g"); -const makeString = (content, enclosingQuote) => { +const makeString = (content, enclosingQuote, originalQuote) => { + const replaceOther = ["'", '"'].includes(originalQuote); const otherQuote = enclosingQuote === '"' ? "'" : '"'; // Escape and unescape single and double quotes as needed to be able to // enclose `content` with `enclosingQuote`. return content.replace(quotePattern, (match, escaped, quote) => { - if (escaped === otherQuote) { + if (replaceOther && escaped === otherQuote) { return escaped; } if (quote === enclosingQuote) { return `\\${quote}`; @@ -58,10 +60,57 @@ return `\\${escaped}`; }); }; +// The `parts` argument that comes into this function is an array of either +// printed embedded expressions or plain strings (that themselves can contain +// newlines). What we want to return is an array where each element represents a +// line in the original text. So we end up tracking every line as we go and +// pushing onto a `currentLine` variable, then when we hit a new line we push +// the current line onto the `lines` variable until we've used up every part. +const makeHeredocLines = parts => { + let lines = []; + let currentLine = []; + + parts.forEach(part => { + if (part.type === "group" || !part.includes("\n")) { + // In this case we've either hit an embedded expression or the piece of + // the current line that we're looking at is in the middle of two of them, + // so we just push onto the current line and continue on. + currentLine.push(part); + return; + } + + let splits = part.split("\n"); + if (splits[splits.length - 1] === "") { + // If a line ends with a newline, then we end up with an empty string at + // the end of the splits, we just pop it off here since we'll handle the + // newlines later. + splits = splits.slice(0, -1); + } + + if (currentLine.length > 0) { + lines.push(concat(currentLine.concat(splits[0]))); + currentLine = []; + } else { + lines.push(splits[0]); + } + + if (splits.length > 1) { + lines = lines.concat(splits.slice(1, -1)); + currentLine.push(splits[splits.length - 1]); + } + }); + + if (currentLine.length > 0) { + lines = lines.concat(currentLine); + } + + return lines; +}; + module.exports = { "@CHAR": (path, { preferSingleQuotes }, _print) => { const { body } = path.getValue(); if (body.length !== 2) { @@ -76,14 +125,17 @@ return concat([":", quote, concat(path.call(print, "body", 0)), quote]); }, heredoc: (path, opts, print) => { const { beging, ending } = path.getValue(); + const lines = makeHeredocLines(path.map(print, "body")); return concat([ beging, - concat([literalline].concat(path.map(print, "body"))), + literalline, + join(literalline, lines), + literalline, ending ]); }, string: makeList, string_concat: (path, opts, print) => @@ -128,10 +180,10 @@ const parts = []; string.body.forEach((part, index) => { if (part.type === "@tstring_content") { // In this case, the part of the string is just regular string content - parts.push(makeString(part.body, quote)); + parts.push(makeString(part.body, quote, string.quote)); } else { // In this case, the part of the string is an embedded expression parts.push(path.call(print, "body", 0, "body", index)); } });