src/nodes/strings.js in prettier-0.21.0 vs src/nodes/strings.js in prettier-0.22.0

- old
+ new

@@ -6,34 +6,34 @@ literalline, softline, join } = require("../prettier"); -const { concatBody, empty, makeList, prefix, surround } = require("../utils"); - // 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( +function isQuoteLocked(node) { + return node.body.some( (part) => part.type === "@tstring_content" && (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( +function isSingleQuotable(node) { + return node.body.every( (part) => part.type === "@tstring_content" && !part.body.includes("'") ); +} const quotePattern = new RegExp("\\\\([\\s\\S])|(['\"])", "g"); -const normalizeQuotes = (content, enclosingQuote, originalQuote) => { +function 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`. @@ -50,126 +50,135 @@ return quote; } return `\\${escaped}`; }); -}; +} const quotePairs = { "(": ")", "[": "]", "{": "}", "<": ">" }; -const getClosingQuote = (quote) => { +function getClosingQuote(quote) { if (!quote.startsWith("%")) { return quote; } const boundary = /%q?(.)/.exec(quote)[1]; if (boundary in quotePairs) { return quotePairs[boundary]; } return boundary; -}; +} -module.exports = { - "@CHAR": (path, { preferSingleQuotes }, _print) => { - const { body } = path.getValue(); +// Prints a @CHAR node. @CHAR nodes are special character strings that usually +// are strings of length 1. If they're any longer than we'll try to apply the +// correct quotes. +function printChar(path, { preferSingleQuotes }, _print) { + const { body } = path.getValue(); - if (body.length !== 2) { - return body; + if (body.length !== 2) { + return body; + } + + const quote = preferSingleQuotes ? "'" : '"'; + return concat([quote, body.slice(1), quote]); +} + +// Prints a dynamic symbol. Assumes there's a quote property attached to the +// node that will tell us which quote to use when printing. We're just going to +// use whatever quote was provided. +function printDynaSymbol(path, opts, print) { + const { quote } = path.getValue(); + + return concat([":", quote].concat(path.map(print, "body")).concat(quote)); +} + +// Prints out an interpolated variable in the string by converting it into an +// embedded expression. +function printStringDVar(path, opts, print) { + return concat(["#{", path.call(print, "body", 0), "}"]); +} + +// Prints out a literal string. This function does its best to respect the +// wishes of the user with regards to single versus double quotes, but if the +// string contains any escape expressions then it will just keep the original +// quotes. +function printStringLiteral(path, { preferSingleQuotes }, print) { + const node = path.getValue(); + + // If the string is empty, it will not have any parts, so just print out the + // quotes corresponding to the config + if (node.body.length === 0) { + return preferSingleQuotes ? "''" : '""'; + } + + // Determine the quote that should enclose the new string + let quote; + if (isQuoteLocked(node)) { + quote = node.quote; + } else { + quote = preferSingleQuotes && isSingleQuotable(node) ? "'" : '"'; + } + + const parts = node.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); } - const quote = preferSingleQuotes ? "'" : '"'; - return body.length === 2 ? concat([quote, body.slice(1), quote]) : body; - }, - dyna_symbol: (path, opts, print) => { - const { quote } = path.getValue(); + // In this case, the part of the string is just regular string content + return join( + literalline, + normalizeQuotes(part.body, quote, node.quote).split("\n") + ); + }); - return concat([":", quote, concat(path.call(print, "body", 0)), quote]); - }, - heredoc: (path, opts, print) => { - const { beging, body, ending } = path.getValue(); + return concat([quote].concat(parts).concat(getClosingQuote(quote))); +} - 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); - } +// Prints out a symbol literal. Its child will always be the ident that +// represents the string content of the symbol. +function printSymbolLiteral(path, opts, print) { + return concat([":", path.call(print, "body", 0)]); +} - // In this case, the part of the string is just regular string content - return join(literalline, part.body.split("\n")); - }); +// Prints out an xstring literal. Its child is an array of string parts, +// including plain string content and interpolated content. +function printXStringLiteral(path, opts, print) { + return concat(["`"].concat(path.map(print, "body")).concat("`")); +} - return concat([beging, literalline, concat(parts), ending]); - }, - string: makeList, +module.exports = { + "@CHAR": printChar, + dyna_symbol: printDynaSymbol, string_concat: (path, opts, print) => group( concat([ path.call(print, "body", 0), " \\", indent(concat([hardline, path.call(print, "body", 1)])) ]) ), - string_dvar: surround("#{", "}"), + string_dvar: printStringDVar, string_embexpr: (path, opts, print) => { const parts = path.call(print, "body", 0); // If the interpolated expression is inside of an xstring literal (a string // that gets sent to the command line) then we don't want to automatically // indent, as this can lead to some very odd looking expressions - if (path.getParentNode().type === "xstring") { + if (path.getParentNode().type === "xstring_literal") { return concat(["#{", parts, "}"]); } return group( concat(["#{", indent(concat([softline, parts])), concat([softline, "}"])]) ); }, - string_literal: (path, { preferSingleQuotes }, print) => { - const stringLiteral = path.getValue(); - const string = stringLiteral.body[0]; - - // If the string is empty, it will not have any parts, so just print out the - // quotes corresponding to the config - if (string.body.length === 0) { - return preferSingleQuotes ? "''" : '""'; - } - - // Determine the quote that should enclose the new string - let quote; - if (isQuoteLocked(string)) { - ({ quote } = stringLiteral); - } else { - quote = preferSingleQuotes && isSingleQuotable(string) ? "'" : '"'; - } - - const parts = string.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", 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(getClosingQuote(quote))); - }, - symbol: prefix(":"), - symbol_literal: concatBody, - word_add: concatBody, - word_new: empty, - xstring: makeList, - xstring_literal: (path, opts, print) => { - const parts = path.call(print, "body", 0); - - return concat(["`"].concat(parts).concat("`")); - } + string_literal: printStringLiteral, + symbol_literal: printSymbolLiteral, + xstring_literal: printXStringLiteral };