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:
Simon Lydell authored and Christopher Chedeau committed 2017-01-22 12:32:43 -08:00
1 parent e31203f4bf
commit 8ed75acf46
4 files changed
+336 -115

No files matched your search

+56 -47
View File
@@ -2330,59 +2330,68 @@ function nodeStr(node, options) {
return node.raw; 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 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 || const double = { quote: '"', regex: /"/g };
!isSingleQuote && !shouldUseSingleQuote) { const single = { quote: "'", regex: /'/g };
return raw;
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 // It might sound unnecessary to use `makeString` even if `node.raw` already
// backslash before. In order to know how many backslash to put, // is enclosed with `enclosingQuote`, but it isn't. `node.raw` could contain
// I manually did the first few examples and extracted a general rule // unnecessary escapes (such as in `"\'"`). Always using `makeString` makes
// out of it. // sure that we consistently output the minimum amount of escaped quotes.
// return makeString(rawContent, enclosingQuote);
// 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;
return '\\'.repeat(nextNumBackslash) + quote; function makeString(rawContent, enclosingQuote) {
}) + nextQuote; 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) { function isFirstStatement(path) {
+220 -56
View File
@@ -29,95 +29,259 @@ function b(object, key) {
`; `;
exports[`test strings.js 1`] = ` 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\' \'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\";
\"abc\"; \"abc\";
\"\'\"; // Escape.
\'\"\';
\'\\\"\';
\'\\\\\"\';
\"\'\";
\"\\\'\";
\"\\\\\'\";
\"\'\\\"\";
\"\'\\\"\";
\"\\\\\";
\"\\\\\";
\"\\0\"; \"\\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`] = ` 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\' \'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\';
\'abc\'; \'abc\';
\"\'\"; // Escape.
\'\"\';
\'\\\"\';
\'\\\\\"\';
\"\'\";
\"\\\'\";
\"\\\\\'\";
\'\\\'\"\';
\'\\\'\"\';
\'\\\\\';
\'\\\\\';
\'\\0\'; \'\\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
View File
@@ -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"
'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\", \"abc\",
\"\'\", \"\'\",
\'\"\', \'\"\',
\'\\\"\', \'\"\',
\'\\\\\"\', \'\\\\\"\',
\"\'\", \"\'\",
\"\\\'\", \"\'\",
\"\\\\\'\", \"\\\\\'\",
\"\'\\\"\", \"\'\\\"\",
\"\'\\\"\", \"\'\\\"\",