Output strings with the minimum amount of escaped quotes (#390)
* Add tests for quotes * Update test snapshots * Output strings with the minimum amount of escaped quotes * Update test snapshots * Move tests/prettier/quotes.js into tests/quotes/strings.js * Update test snapshots
This commit is contained in:
1 parent
e31203f4bf
commit
8ed75acf46
4 files changed
+336
-115
No files matched your search
+56
-47
@@ -2330,59 +2330,68 @@ function nodeStr(node, options) {
|
||||
return node.raw;
|
||||
}
|
||||
|
||||
const containsSingleQuote = str.indexOf("'") !== -1;
|
||||
const containsDoubleQuote = str.indexOf('"') !== -1;
|
||||
|
||||
let shouldUseSingleQuote = options.singleQuote;
|
||||
if (options.singleQuote && containsSingleQuote && !containsDoubleQuote) {
|
||||
shouldUseSingleQuote = false;
|
||||
}
|
||||
if (!options.singleQuote && !containsSingleQuote && containsDoubleQuote) {
|
||||
shouldUseSingleQuote = true;
|
||||
}
|
||||
|
||||
const raw = node.extra ? node.extra.raw : node.raw;
|
||||
const isSingleQuote = raw.charAt(0) === "'";
|
||||
// `rawContent` is the string exactly like it appeared in the input source
|
||||
// code, with its enclosing quote.
|
||||
const rawContent = raw.slice(1, -1);
|
||||
|
||||
if (isSingleQuote && shouldUseSingleQuote ||
|
||||
!isSingleQuote && !shouldUseSingleQuote) {
|
||||
return raw;
|
||||
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 `rawContent` 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 (rawContent.includes(preferred.quote)) {
|
||||
const numPreferredQuotes = (rawContent.match(preferred.regex) || []).length;
|
||||
const numAlternateQuotes = (rawContent.match(alternate.regex) || []).length;
|
||||
|
||||
shouldUseAlternateQuote = numPreferredQuotes > numAlternateQuotes;
|
||||
}
|
||||
|
||||
const nextQuote = shouldUseSingleQuote ? "'" : '"';
|
||||
const enclosingQuote = shouldUseAlternateQuote
|
||||
? alternate.quote
|
||||
: preferred.quote;
|
||||
|
||||
// When we change the outer quotes, we need to change the number of
|
||||
// backslash before. In order to know how many backslash to put,
|
||||
// I manually did the first few examples and extracted a general rule
|
||||
// out of it.
|
||||
//
|
||||
// shouldSwitch === true
|
||||
//
|
||||
// 3 -> 3 "\\\'" -> '\\\''
|
||||
// 2 -> 3 "\\'" -> '\\\''
|
||||
// 1 -> 1 "\'" -> '\''
|
||||
// 0 -> 1 "\'" -> '\''
|
||||
//
|
||||
// Rule: n -> Math.floor(n / 2) + 1
|
||||
//
|
||||
// shouldSwitch === false
|
||||
//
|
||||
// 3 -> 2 '\\\'' -> "\\'"
|
||||
// 1 -> 0 '\'' -> "'"
|
||||
//
|
||||
// Rule: n -> n - 1
|
||||
return nextQuote + raw.slice(1, -1)
|
||||
.replace(/\\*['"]/g, (match) => {
|
||||
const quote = match[match.length - 1];
|
||||
const shouldSwitch = quote === "'" && shouldUseSingleQuote ||
|
||||
quote === '"' && !shouldUseSingleQuote;
|
||||
const numBackslash = match.length - 1;
|
||||
const nextNumBackslash = shouldSwitch ?
|
||||
Math.floor(numBackslash / 2) + 1 :
|
||||
numBackslash - 1;
|
||||
// It might sound unnecessary to use `makeString` even if `node.raw` already
|
||||
// is enclosed with `enclosingQuote`, but it isn't. `node.raw` could contain
|
||||
// unnecessary escapes (such as in `"\'"`). Always using `makeString` makes
|
||||
// sure that we consistently output the minimum amount of escaped quotes.
|
||||
return makeString(rawContent, enclosingQuote);
|
||||
}
|
||||
|
||||
return '\\'.repeat(nextNumBackslash) + quote;
|
||||
}) + nextQuote;
|
||||
function makeString(rawContent, enclosingQuote) {
|
||||
const otherQuote = enclosingQuote === '"' ? "'" : '"';
|
||||
|
||||
// Matches _any_ escape and unescaped quotes (both single and double).
|
||||
const regex = /\\([\s\S])|(['"])/g;
|
||||
|
||||
// Escape and unescape single and double quotes as needed to be able to
|
||||
// enclose `rawContent` with `enclosingQuote`.
|
||||
const newContent = rawContent.replace(regex, (match, escaped, quote) => {
|
||||
// If we matched an escape, and the escaped character is a quote of the
|
||||
// other type than we intend to enclose the string with, there's no need for
|
||||
// it to be escaped, so return it _without_ the backslash.
|
||||
if (escaped === otherQuote) {
|
||||
return escaped;
|
||||
}
|
||||
|
||||
// If we matched an unescaped quote and it is of the _same_ type as we
|
||||
// intend to enclose the string with, it must be escaped, so return it with
|
||||
// a backslash.
|
||||
if (quote === enclosingQuote) {
|
||||
return "\\" + quote;
|
||||
}
|
||||
|
||||
// Otherwise return the escape or unescaped quote as-is.
|
||||
return match;
|
||||
});
|
||||
|
||||
return enclosingQuote + newContent + enclosingQuote;
|
||||
}
|
||||
|
||||
function isFirstStatement(path) {
|
||||
|
||||
@@ -29,95 +29,259 @@ function b(object, key) {
|
||||
`;
|
||||
|
||||
exports[`test strings.js 1`] = `
|
||||
"\"abc\"
|
||||
"// Every string will be changed to double quotes, unless we end up with fewer
|
||||
// escaped quotes by using single quotes. (Vice versa if the \"singleQuote\"
|
||||
// option is true).
|
||||
//
|
||||
// Note that even if a string already has the correct enclosing quotes, it is
|
||||
// still processed in order to remove unnecessarily escaped quotes within it,
|
||||
// for consistency.
|
||||
|
||||
// Simple strings.
|
||||
\"abc\"
|
||||
\'abc\'
|
||||
|
||||
// Escape.
|
||||
\'\\0\'
|
||||
|
||||
// Emoji.
|
||||
\'🐶\'
|
||||
|
||||
// Empty string.
|
||||
\"\"
|
||||
\'\'
|
||||
|
||||
// Single double quote.
|
||||
\"\\\"\"
|
||||
\'\"\'
|
||||
|
||||
// Single single quote.
|
||||
\"\'\"
|
||||
\'\\\'\'
|
||||
|
||||
\'\"\'
|
||||
\'\\\"\'
|
||||
\'\\\\\"\'
|
||||
|
||||
\"\'\"
|
||||
// Unnecessary escapes.
|
||||
\"\\\'\"
|
||||
\"\\\\\'\"
|
||||
\'\\\"\'
|
||||
|
||||
\"\'\\\"\"
|
||||
\'\\\'\"\'
|
||||
// One of each.
|
||||
\"\\\"\'\"
|
||||
\'\"\\\'\'
|
||||
|
||||
// One of each with unnecessary escapes.
|
||||
\"\\\"\\\'\"
|
||||
\'\\\"\\\'\'
|
||||
|
||||
// More double quotes than single quotes.
|
||||
\"\\\"\'\\\"\"
|
||||
\'\"\\\'\"\'
|
||||
|
||||
// More single quotes than double quotes.
|
||||
\"\\\"\'\'\"
|
||||
\'\"\\\'\\\'\'
|
||||
|
||||
// Two of each.
|
||||
\"\\\"\\\"\'\'\"
|
||||
\'\"\"\\\'\\\'\'
|
||||
|
||||
// Single backslash.
|
||||
\'\\\\\'
|
||||
\"\\\\\"
|
||||
|
||||
\'\\0\'
|
||||
\'🐶\'
|
||||
// Backslases.
|
||||
\"\\\"\\\\\\\"\\\\\\\\\\\" \'\\\'\\\\\'\\\\\\\'\\\\\\\\\'\"
|
||||
\'\\\'\\\\\\\'\\\\\\\\\\\' \"\\\"\\\\\"\\\\\\\"\\\\\\\\\"\'
|
||||
|
||||
// Somewhat more real-word example.
|
||||
\"He\'s sayin\': \\\"How\'s it goin\'?\\\" Don\'t ask me why.\"
|
||||
\'He\\\'s sayin\\\': \"How\\\'s it goin\\\'?\" Don\\\'t ask me why.\'
|
||||
|
||||
// Somewhat more real-word example 2.
|
||||
\"var backslash = \\\"\\\\\\\", doubleQuote = \'\\\"\';\"
|
||||
\'var backslash = \"\\\\\", doubleQuote = \\\'\"\\\';\'
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Every string will be changed to double quotes, unless we end up with fewer
|
||||
// escaped quotes by using single quotes. (Vice versa if the \"singleQuote\"
|
||||
// option is true).
|
||||
//
|
||||
// Note that even if a string already has the correct enclosing quotes, it is
|
||||
// still processed in order to remove unnecessarily escaped quotes within it,
|
||||
// for consistency.
|
||||
|
||||
// Simple strings.
|
||||
\"abc\";
|
||||
\"abc\";
|
||||
|
||||
\"\'\";
|
||||
|
||||
\'\"\';
|
||||
\'\\\"\';
|
||||
\'\\\\\"\';
|
||||
|
||||
\"\'\";
|
||||
\"\\\'\";
|
||||
\"\\\\\'\";
|
||||
|
||||
\"\'\\\"\";
|
||||
\"\'\\\"\";
|
||||
|
||||
\"\\\\\";
|
||||
\"\\\\\";
|
||||
|
||||
// Escape.
|
||||
\"\\0\";
|
||||
\"🐶\";
|
||||
|
||||
// Emoji.
|
||||
\"🐶\"; // Empty string.
|
||||
\"\";
|
||||
|
||||
\"\"; // Single double quote.
|
||||
\'\"\';
|
||||
|
||||
\'\"\'; // Single single quote.
|
||||
\"\'\";
|
||||
|
||||
\"\'\"; // Unnecessary escapes.
|
||||
\"\'\";
|
||||
|
||||
\'\"\'; // One of each.
|
||||
\"\\\"\'\";
|
||||
|
||||
\"\\\"\'\"; // One of each with unnecessary escapes.
|
||||
\"\\\"\'\";
|
||||
|
||||
\"\\\"\'\"; // More double quotes than single quotes.
|
||||
\'\"\\\'\"\';
|
||||
|
||||
\'\"\\\'\"\'; // More single quotes than double quotes.
|
||||
\"\\\"\'\'\";
|
||||
|
||||
\"\\\"\'\'\"; // Two of each.
|
||||
\"\\\"\\\"\'\'\";
|
||||
|
||||
\"\\\"\\\"\'\'\"; // Single backslash.
|
||||
\"\\\\\";
|
||||
|
||||
\"\\\\\"; // Backslases.
|
||||
\"\\\"\\\\\\\"\\\\\\\\\\\" \'\'\\\\\'\\\\\'\\\\\\\\\'\";
|
||||
|
||||
\'\\\'\\\\\\\'\\\\\\\\\\\' \"\"\\\\\"\\\\\"\\\\\\\\\"\'; // Somewhat more real-word example.
|
||||
\"He\'s sayin\': \\\"How\'s it goin\'?\\\" Don\'t ask me why.\";
|
||||
|
||||
\"He\'s sayin\': \\\"How\'s it goin\'?\\\" Don\'t ask me why.\"; // Somewhat more real-word example 2.
|
||||
\'var backslash = \"\\\\\", doubleQuote = \\\'\"\\\';\';
|
||||
\'var backslash = \"\\\\\", doubleQuote = \\\'\"\\\';\';
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`test strings.js 2`] = `
|
||||
"\"abc\"
|
||||
"// Every string will be changed to double quotes, unless we end up with fewer
|
||||
// escaped quotes by using single quotes. (Vice versa if the \"singleQuote\"
|
||||
// option is true).
|
||||
//
|
||||
// Note that even if a string already has the correct enclosing quotes, it is
|
||||
// still processed in order to remove unnecessarily escaped quotes within it,
|
||||
// for consistency.
|
||||
|
||||
// Simple strings.
|
||||
\"abc\"
|
||||
\'abc\'
|
||||
|
||||
// Escape.
|
||||
\'\\0\'
|
||||
|
||||
// Emoji.
|
||||
\'🐶\'
|
||||
|
||||
// Empty string.
|
||||
\"\"
|
||||
\'\'
|
||||
|
||||
// Single double quote.
|
||||
\"\\\"\"
|
||||
\'\"\'
|
||||
|
||||
// Single single quote.
|
||||
\"\'\"
|
||||
\'\\\'\'
|
||||
|
||||
\'\"\'
|
||||
\'\\\"\'
|
||||
\'\\\\\"\'
|
||||
|
||||
\"\'\"
|
||||
// Unnecessary escapes.
|
||||
\"\\\'\"
|
||||
\"\\\\\'\"
|
||||
\'\\\"\'
|
||||
|
||||
\"\'\\\"\"
|
||||
\'\\\'\"\'
|
||||
// One of each.
|
||||
\"\\\"\'\"
|
||||
\'\"\\\'\'
|
||||
|
||||
// One of each with unnecessary escapes.
|
||||
\"\\\"\\\'\"
|
||||
\'\\\"\\\'\'
|
||||
|
||||
// More double quotes than single quotes.
|
||||
\"\\\"\'\\\"\"
|
||||
\'\"\\\'\"\'
|
||||
|
||||
// More single quotes than double quotes.
|
||||
\"\\\"\'\'\"
|
||||
\'\"\\\'\\\'\'
|
||||
|
||||
// Two of each.
|
||||
\"\\\"\\\"\'\'\"
|
||||
\'\"\"\\\'\\\'\'
|
||||
|
||||
// Single backslash.
|
||||
\'\\\\\'
|
||||
\"\\\\\"
|
||||
|
||||
\'\\0\'
|
||||
\'🐶\'
|
||||
// Backslases.
|
||||
\"\\\"\\\\\\\"\\\\\\\\\\\" \'\\\'\\\\\'\\\\\\\'\\\\\\\\\'\"
|
||||
\'\\\'\\\\\\\'\\\\\\\\\\\' \"\\\"\\\\\"\\\\\\\"\\\\\\\\\"\'
|
||||
|
||||
// Somewhat more real-word example.
|
||||
\"He\'s sayin\': \\\"How\'s it goin\'?\\\" Don\'t ask me why.\"
|
||||
\'He\\\'s sayin\\\': \"How\\\'s it goin\\\'?\" Don\\\'t ask me why.\'
|
||||
|
||||
// Somewhat more real-word example 2.
|
||||
\"var backslash = \\\"\\\\\\\", doubleQuote = \'\\\"\';\"
|
||||
\'var backslash = \"\\\\\", doubleQuote = \\\'\"\\\';\'
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Every string will be changed to double quotes, unless we end up with fewer
|
||||
// escaped quotes by using single quotes. (Vice versa if the \"singleQuote\"
|
||||
// option is true).
|
||||
//
|
||||
// Note that even if a string already has the correct enclosing quotes, it is
|
||||
// still processed in order to remove unnecessarily escaped quotes within it,
|
||||
// for consistency.
|
||||
|
||||
// Simple strings.
|
||||
\'abc\';
|
||||
\'abc\';
|
||||
|
||||
\"\'\";
|
||||
|
||||
\'\"\';
|
||||
\'\\\"\';
|
||||
\'\\\\\"\';
|
||||
|
||||
\"\'\";
|
||||
\"\\\'\";
|
||||
\"\\\\\'\";
|
||||
|
||||
\'\\\'\"\';
|
||||
\'\\\'\"\';
|
||||
|
||||
\'\\\\\';
|
||||
\'\\\\\';
|
||||
|
||||
// Escape.
|
||||
\'\\0\';
|
||||
\'🐶\';
|
||||
|
||||
// Emoji.
|
||||
\'🐶\'; // Empty string.
|
||||
\'\';
|
||||
|
||||
\'\'; // Single double quote.
|
||||
\'\"\';
|
||||
|
||||
\'\"\'; // Single single quote.
|
||||
\"\'\";
|
||||
|
||||
\"\'\"; // Unnecessary escapes.
|
||||
\"\'\";
|
||||
|
||||
\'\"\'; // One of each.
|
||||
\'\"\\\'\';
|
||||
|
||||
\'\"\\\'\'; // One of each with unnecessary escapes.
|
||||
\'\"\\\'\';
|
||||
|
||||
\'\"\\\'\'; // More double quotes than single quotes.
|
||||
\'\"\\\'\"\';
|
||||
|
||||
\'\"\\\'\"\'; // More single quotes than double quotes.
|
||||
\"\\\"\'\'\";
|
||||
|
||||
\"\\\"\'\'\"; // Two of each.
|
||||
\'\"\"\\\'\\\'\';
|
||||
|
||||
\'\"\"\\\'\\\'\'; // Single backslash.
|
||||
\'\\\\\';
|
||||
|
||||
\'\\\\\'; // Backslases.
|
||||
\"\\\"\\\\\\\"\\\\\\\\\\\" \'\'\\\\\'\\\\\'\\\\\\\\\'\";
|
||||
|
||||
\'\\\'\\\\\\\'\\\\\\\\\\\' \"\"\\\\\"\\\\\"\\\\\\\\\"\'; // Somewhat more real-word example.
|
||||
\"He\'s sayin\': \\\"How\'s it goin\'?\\\" Don\'t ask me why.\";
|
||||
|
||||
\"He\'s sayin\': \\\"How\'s it goin\'?\\\" Don\'t ask me why.\"; // Somewhat more real-word example 2.
|
||||
\'var backslash = \"\\\\\", doubleQuote = \\\'\"\\\';\';
|
||||
\'var backslash = \"\\\\\", doubleQuote = \\\'\"\\\';\';
|
||||
"
|
||||
`;
|
||||
+58
-10
@@ -1,21 +1,69 @@
|
||||
// Every string will be changed to double quotes, unless we end up with fewer
|
||||
// escaped quotes by using single quotes. (Vice versa if the "singleQuote"
|
||||
// option is true).
|
||||
//
|
||||
// Note that even if a string already has the correct enclosing quotes, it is
|
||||
// still processed in order to remove unnecessarily escaped quotes within it,
|
||||
// for consistency.
|
||||
|
||||
// Simple strings.
|
||||
"abc"
|
||||
'abc'
|
||||
|
||||
// Escape.
|
||||
'\0'
|
||||
|
||||
// Emoji.
|
||||
'🐶'
|
||||
|
||||
// Empty string.
|
||||
""
|
||||
''
|
||||
|
||||
// Single double quote.
|
||||
"\""
|
||||
'"'
|
||||
|
||||
// Single single quote.
|
||||
"'"
|
||||
'\''
|
||||
|
||||
'"'
|
||||
'\"'
|
||||
'\\"'
|
||||
|
||||
"'"
|
||||
// Unnecessary escapes.
|
||||
"\'"
|
||||
"\\'"
|
||||
'\"'
|
||||
|
||||
"'\""
|
||||
'\'"'
|
||||
// One of each.
|
||||
"\"'"
|
||||
'"\''
|
||||
|
||||
// One of each with unnecessary escapes.
|
||||
"\"\'"
|
||||
'\"\''
|
||||
|
||||
// More double quotes than single quotes.
|
||||
"\"'\""
|
||||
'"\'"'
|
||||
|
||||
// More single quotes than double quotes.
|
||||
"\"''"
|
||||
'"\'\''
|
||||
|
||||
// Two of each.
|
||||
"\"\"''"
|
||||
'""\'\''
|
||||
|
||||
// Single backslash.
|
||||
'\\'
|
||||
"\\"
|
||||
|
||||
'\0'
|
||||
'🐶'
|
||||
// Backslases.
|
||||
"\"\\\"\\\\\" '\'\\'\\\'\\\\'"
|
||||
'\'\\\'\\\\\' "\"\\"\\\"\\\\"'
|
||||
|
||||
// Somewhat more real-word example.
|
||||
"He's sayin': \"How's it goin'?\" Don't ask me why."
|
||||
'He\'s sayin\': "How\'s it goin\'?" Don\'t ask me why.'
|
||||
|
||||
// Somewhat more real-word example 2.
|
||||
"var backslash = \"\\\", doubleQuote = '\"';"
|
||||
'var backslash = "\\", doubleQuote = \'"\';'
|
||||
@@ -30,10 +30,10 @@ exports[`test strings.js 1`] = `
|
||||
\"abc\",
|
||||
\"\'\",
|
||||
\'\"\',
|
||||
\'\\\"\',
|
||||
\'\"\',
|
||||
\'\\\\\"\',
|
||||
\"\'\",
|
||||
\"\\\'\",
|
||||
\"\'\",
|
||||
\"\\\\\'\",
|
||||
\"\'\\\"\",
|
||||
\"\'\\\"\",
|
||||
|
||||
Reference in new issue
Block a user