src/nodes/strings.js in prettier-0.17.0 vs src/nodes/strings.js in prettier-0.18.0

- old
+ new

@@ -5,43 +5,35 @@ indent, literalline, 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 // contains the interpolation pattern ("#{"), then we are locked into whichever // quote the user chose. (If they chose single quotes, then double quoting // would activate the escape sequence, and if they chose double quotes, then // single quotes would deactivate it.) const isQuoteLocked = string => string.body.some( part => part.type === "@tstring_content" && - (escapePattern.test(part.body) || part.body.includes("#{")) + (part.body.includes("#{") || part.body.includes("\\")) ); // A string is considered to be able to use single quotes if it contains only // plain string content and that content does not contain a single quote. const isSingleQuotable = string => string.body.every( part => part.type === "@tstring_content" && !part.body.includes("'") ); -const getStringQuote = (string, preferSingleQuotes) => { - if (isQuoteLocked(string)) { - return string.quote; - } - - return preferSingleQuotes && isSingleQuotable(string) ? "'" : '"'; -}; - const quotePattern = new RegExp("\\\\([\\s\\S])|(['\"])", "g"); -const makeString = (content, enclosingQuote, originalQuote) => { +const normalizeQuotes = (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`. @@ -60,55 +52,28 @@ 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 = []; +const quotePairs = { + "(": ")", + "[": "]", + "{": "}", + "<": ">" +}; - 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; - } +const getClosingQuote = quote => { + if (!quote.startsWith("%")) { + return quote; + } - 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); + const boundary = /%q?(.)/.exec(quote)[1]; + if (boundary in quotePairs) { + return quotePairs[boundary]; } - return lines; + return boundary; }; module.exports = { "@CHAR": (path, { preferSingleQuotes }, _print) => { const { body } = path.getValue(); @@ -119,25 +84,28 @@ const quote = preferSingleQuotes ? "'" : '"'; return body.length === 2 ? concat([quote, body.slice(1), quote]) : body; }, dyna_symbol: (path, opts, print) => { - const { quote } = path.getValue().body[0]; + const { quote } = path.getValue(); 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")); + const { beging, body, ending } = path.getValue(); - return concat([ - beging, - literalline, - join(literalline, lines), - literalline, - ending - ]); + const parts = body.map((part, index) => { + if (part.type !== "@tstring_content") { + // In this case, the part of the string is an embedded expression + return path.call(print, "body", index); + } + + // In this case, the part of the string is just regular string content + return join(literalline, part.body.split("\n")); + }); + + return concat([beging, literalline, concat(parts), ending]); }, string: makeList, string_concat: (path, opts, print) => group( concat([ @@ -160,11 +128,12 @@ return group( concat(["#{", indent(concat([softline, parts])), concat([softline, "}"])]) ); }, string_literal: (path, { preferSingleQuotes }, print) => { - const string = path.getValue().body[0]; + const stringLiteral = path.getValue(); + const string = stringLiteral.body[0]; // If this string is actually a heredoc, bail out and return to the print // function for heredocs if (string.type === "heredoc") { return path.call(print, "body", 0); @@ -174,23 +143,31 @@ // quotes corresponding to the config if (string.body.length === 0) { return preferSingleQuotes ? "''" : '""'; } - const quote = getStringQuote(string, preferSingleQuotes); - const parts = []; + // Determine the quote that should enclose the new string + let quote; + if (isQuoteLocked(string)) { + ({ quote } = stringLiteral); + } else { + quote = preferSingleQuotes && isSingleQuotable(string) ? "'" : '"'; + } - 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, string.quote)); - } else { + const parts = string.body.map((part, index) => { + if (part.type !== "@tstring_content") { // In this case, the part of the string is an embedded expression - parts.push(path.call(print, "body", 0, "body", index)); + return path.call(print, "body", 0, "body", index); } + + // In this case, the part of the string is just regular string content + return join( + literalline, + normalizeQuotes(part.body, quote, stringLiteral.quote).split("\n") + ); }); - return concat([quote].concat(parts).concat([quote])); + return concat([quote].concat(parts).concat(getClosingQuote(quote))); }, symbol: prefix(":"), symbol_literal: concatBody, word_add: concatBody, word_new: empty,