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;
}
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) {
+220 -56
View File
@@ -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
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'
// 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\",
\"\'\",
\'\"\',
\'\\\"\',
\'\"\',
\'\\\\\"\',
\"\'\",
\"\\\'\",
\"\'\",
\"\\\\\'\",
\"\'\\\"\",
\"\'\\\"\",