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));
}
});