diff --git a/src/common/util.js b/src/common/util.js index aa0446106..ab7383db7 100644 --- a/src/common/util.js +++ b/src/common/util.js @@ -343,24 +343,21 @@ function getIndentSize(value, tabWidth) { /** * - * @param {string} raw + * @param {string} rawContent * @param {Quote} preferredQuote - * @returns {Quote} + * @returns {{ quote: Quote, regex: RegExp, escaped: string }} */ -function getPreferredQuote(raw, preferredQuote) { - // `rawContent` is the string exactly like it appeared in the input source - // code, without its enclosing quotes. - const rawContent = raw.slice(1, -1); - /** @type {{ quote: '"', regex: RegExp }} */ - const double = { quote: '"', regex: /"/g }; - /** @type {{ quote: "'", regex: RegExp }} */ - const single = { quote: "'", regex: /'/g }; +function getPreferredQuote(rawContent, preferredQuote) { + /** @type {{ quote: '"', regex: RegExp, escaped: """ }} */ + const double = { quote: '"', regex: /"/g, escaped: """ }; + /** @type {{ quote: "'", regex: RegExp, escaped: "'" }} */ + const single = { quote: "'", regex: /'/g, escaped: "'" }; const preferred = preferredQuote === "'" ? single : double; const alternate = preferred === single ? double : single; - let result = preferred.quote; + let result = preferred; // If `rawContent` contains at least one of the quote preferred for enclosing // the string, we might want to enclose with the alternate quote instead, to @@ -372,10 +369,7 @@ function getPreferredQuote(raw, preferredQuote) { const numPreferredQuotes = (rawContent.match(preferred.regex) || []).length; const numAlternateQuotes = (rawContent.match(alternate.regex) || []).length; - result = - numPreferredQuotes > numAlternateQuotes - ? alternate.quote - : preferred.quote; + result = numPreferredQuotes > numAlternateQuotes ? alternate : preferred; } return result; @@ -395,7 +389,7 @@ function printString(raw, options) { ? '"' : options.__isInHtmlAttribute ? "'" - : getPreferredQuote(raw, options.singleQuote ? "'" : '"'); + : getPreferredQuote(rawContent, options.singleQuote ? "'" : '"').quote; // It might sound unnecessary to use `makeString` even if the string already // is enclosed with `enclosingQuote`, but it isn't. The string could contain diff --git a/src/language-handlebars/printer-glimmer.js b/src/language-handlebars/printer-glimmer.js index dea09f495..7e980950e 100644 --- a/src/language-handlebars/printer-glimmer.js +++ b/src/language-handlebars/printer-glimmer.js @@ -14,7 +14,7 @@ const { }, utils: { getDocParts, replaceTextEndOfLine }, } = require("../document/index.js"); -const { isNonEmptyArray } = require("../common/util.js"); +const { getPreferredQuote, isNonEmptyArray } = require("../common/util.js"); const { locStart, locEnd } = require("./loc.js"); const clean = require("./clean.js"); const { @@ -47,6 +47,8 @@ function print(path, options, print) { return options.originalText.slice(locStart(node), locEnd(node)); } + const favoriteQuote = options.singleQuote ? "'" : '"'; + switch (node.type) { case "Block": case "Program": @@ -153,14 +155,14 @@ function print(path, options, print) { // Let's assume quotes inside the content of text nodes are already // properly escaped with entities, otherwise the parse wouldn't have parsed them. const quote = isText - ? chooseEnclosingQuote(options, node.value.chars).quote + ? getPreferredQuote(node.value.chars, favoriteQuote).quote : node.value.type === "ConcatStatement" - ? chooseEnclosingQuote( - options, + ? getPreferredQuote( node.value.parts .filter((part) => part.type === "TextNode") .map((part) => part.chars) - .join("") + .join(""), + favoriteQuote ).quote : ""; @@ -397,7 +399,7 @@ function print(path, options, print) { return [""]; } case "StringLiteral": { - return printStringLiteral(node.value, options); + return printStringLiteral(node.value, favoriteQuote); } case "NumberLiteral": { return String(node.value); @@ -691,6 +693,8 @@ function generateHardlines(number = 0) { /* StringLiteral print helpers */ +/** @typedef {import("../common/util").Quote} Quote */ + /** * Prints a string literal with the correct surrounding quotes based on * `options.singleQuote` and the number of escaped quotes contained in @@ -698,40 +702,13 @@ function generateHardlines(number = 0) { * in `common/util`, but has differences because of the way escaped characters * are treated in hbs string literals. * @param {string} stringLiteral - the string literal value - * @param {object} options - the prettier options object + * @param {Quote} favoriteQuote - the user's preferred quote: `'` or `"` */ -function printStringLiteral(stringLiteral, options) { - const { quote, regex } = chooseEnclosingQuote(options, stringLiteral); +function printStringLiteral(stringLiteral, favoriteQuote) { + const { quote, regex } = getPreferredQuote(stringLiteral, favoriteQuote); return [quote, stringLiteral.replace(regex, `\\${quote}`), quote]; } -function chooseEnclosingQuote(options, stringLiteral) { - const double = { quote: '"', regex: /"/g }; - const single = { quote: "'", regex: /'/g }; - - const preferred = options.singleQuote ? single : double; - const alternate = preferred === single ? double : single; - - let shouldUseAlternateQuote = false; - - // If `stringLiteral` contains at least one of the quote preferred for - // enclosing the string, we might want to enclose with the alternate quote - // instead, to minimize the number of escaped quotes. - if ( - stringLiteral.includes(preferred.quote) || - stringLiteral.includes(alternate.quote) - ) { - const numPreferredQuotes = (stringLiteral.match(preferred.regex) || []) - .length; - const numAlternateQuotes = (stringLiteral.match(alternate.regex) || []) - .length; - - shouldUseAlternateQuote = numPreferredQuotes > numAlternateQuotes; - } - - return shouldUseAlternateQuote ? alternate : preferred; -} - /* SubExpression print helpers */ function printSubExpressionPathAndParams(path, print) { diff --git a/src/language-js/print/jsx.js b/src/language-js/print/jsx.js index 4d7f2d021..441ac4ad1 100644 --- a/src/language-js/print/jsx.js +++ b/src/language-js/print/jsx.js @@ -479,14 +479,17 @@ function printJsxAttribute(path, options, print) { let res; if (isStringLiteral(node.value)) { const raw = rawText(node.value); - // Unescape all quotes so we get an accurate preferred quote - let final = raw.replace(/'/g, "'").replace(/"/g, '"'); - const quote = getPreferredQuote( + // Remove enclosing quotes and unescape + // all quotes so we get an accurate preferred quote + let final = raw + .slice(1, -1) + .replace(/'/g, "'") + .replace(/"/g, '"'); + const { escaped, quote, regex } = getPreferredQuote( final, options.jsxSingleQuote ? "'" : '"' ); - const escape = quote === "'" ? "'" : """; - final = final.slice(1, -1).replace(new RegExp(quote, "g"), escape); + final = final.replace(regex, escaped); res = [quote, final, quote]; } else { res = print("value");