Add an option to print single quotes in JSX (#4798)

* feat(option-singleQuote-jsx): Added jsSingleQuote

* feat(option-singleQuote-jsx): Refactored to use a simple jsxSingleQuote flag

* feat(option-singleQuote-jsx): Fixed borked rebase

* feat(option-singleQuote-jsx): Updated snapshots

* feat(option-singleQuote-jsx): Resolved pending comments

* feat(option-singleQuote-jsx): Removed assert

* Fixed merge conflicts

* Updated snapshots after merge conflicts
master
Stefan Mirea 2018-11-04 16:36:00 -05:00 committed by Jed Fox
parent f6a95dd2e0
commit e17512adcd
11 changed files with 3632 additions and 25 deletions

View File

@ -60,7 +60,7 @@ Use single quotes instead of double quotes.
Notes:
- Quotes in JSX will always be double and ignore this setting.
- JSX quotes ignore this option see [jsx-single-quote](#jsx-single-quote).
- If the number of quotes outweighs the other quote, the quote which is less used will be used to format the string - Example: `"I'm double quoted"` results in `"I'm double quoted"` and `"This \"example\" is single quoted"` results in `'This "example" is single quoted'`.
See the [strings rationale](rationale.md#strings) for more information.
@ -69,6 +69,14 @@ See the [strings rationale](rationale.md#strings) for more information.
| ------- | ---------------- | --------------------- |
| `false` | `--single-quote` | `singleQuote: <bool>` |
## JSX Quotes
Use single quotes instead of double quotes in JSX.
| Default | CLI Override | API Override |
| ------- | -------------------- | ------------------------ |
| `false` | `--jsx-single-quote` | `jsxSingleQuote: <bool>` |
## Trailing Commas
Print trailing commas wherever possible when multi-line. (A single-line array, for example, never gets trailing commas.)

View File

@ -430,7 +430,7 @@ function getIndentSize(value, tabWidth) {
);
}
function printString(raw, options, isDirectiveLiteral) {
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);
@ -438,17 +438,14 @@ function printString(raw, options, isDirectiveLiteral) {
const double = { quote: '"', regex: /"/g };
const single = { quote: "'", regex: /'/g };
const preferred = options.singleQuote ? single : double;
const preferred = preferredQuote === "'" ? single : double;
const alternate = preferred === single ? double : single;
let shouldUseAlternateQuote = false;
let canChangeDirectiveQuotes = false;
let result = preferred.quote;
// 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.
// Also check for the alternate quote, to determine if we're allowed to swap
// the quotes on a DirectiveLiteral.
if (
rawContent.includes(preferred.quote) ||
rawContent.includes(alternate.quote)
@ -456,19 +453,31 @@ function printString(raw, options, isDirectiveLiteral) {
const numPreferredQuotes = (rawContent.match(preferred.regex) || []).length;
const numAlternateQuotes = (rawContent.match(alternate.regex) || []).length;
shouldUseAlternateQuote = numPreferredQuotes > numAlternateQuotes;
} else {
canChangeDirectiveQuotes = true;
result =
numPreferredQuotes > numAlternateQuotes
? alternate.quote
: preferred.quote;
}
return result;
}
function printString(raw, options, isDirectiveLiteral) {
// `rawContent` is the string exactly like it appeared in the input source
// code, without its enclosing quotes.
const rawContent = raw.slice(1, -1);
// Check for the alternate quote, to determine if we're allowed to swap
// the quotes on a DirectiveLiteral.
const canChangeDirectiveQuotes =
!rawContent.includes('"') && !rawContent.includes("'");
const enclosingQuote =
options.parser === "json"
? double.quote
? '"'
: options.__isInHtmlAttribute
? single.quote
: shouldUseAlternateQuote
? alternate.quote
: preferred.quote;
? "'"
: getPreferredQuote(raw, options.singleQuote ? "'" : '"');
// Directives are exact code unit sequences, which means that you can't
// change the escape sequences they use.
@ -691,6 +700,7 @@ module.exports = {
startsWithNoLookaheadToken,
getAlignmentSize,
getIndentSize,
getPreferredQuote,
printString,
printNumber,
hasIgnoreComment,

View File

@ -41,6 +41,13 @@ module.exports = {
"Do not print semicolons, except at the beginning of lines which may need them."
},
singleQuote: commonOptions.singleQuote,
jsxSingleQuote: {
since: "1.15.0",
category: CATEGORY_JAVASCRIPT,
type: "boolean",
default: false,
description: "Use single quotes in JSX."
},
trailingComma: {
since: "0.0.0",
category: CATEGORY_JAVASCRIPT,

View File

@ -20,7 +20,8 @@ const {
getPenultimate,
startsWithNoLookaheadToken,
getIndentSize,
matchAncestorTypes
matchAncestorTypes,
getPreferredQuote
} = require("../common/util");
const {
isNextLineEmpty,
@ -33,8 +34,8 @@ const clean = require("./clean");
const insertPragma = require("./pragma").insertPragma;
const handleComments = require("./comments");
const pathNeedsParens = require("./needs-parens");
const preprocess = require("./preprocess");
const { printHtmlBinding } = require("./html-binding");
const preprocess = require("./preprocess");
const {
hasNode,
hasFlowAnnotationComment,
@ -1948,8 +1949,16 @@ function printPathNoParens(path, options, print, args) {
if (n.value) {
let res;
if (isStringLiteral(n.value)) {
const value = rawText(n.value);
res = '"' + value.slice(1, -1).replace(/"/g, "&quot;") + '"';
const raw = rawText(n.value);
// Unescape all quotes so we get an accurate preferred quote
let final = raw.replace(/&apos;/g, "'").replace(/&quot;/g, '"');
const quote = getPreferredQuote(
final,
options.jsxSingleQuote ? "'" : '"'
);
const escape = quote === "'" ? "&apos;" : "&quot;";
final = final.slice(1, -1).replace(new RegExp(quote, "g"), escape);
res = concat([quote, final, quote]);
} else {
res = path.call(print, "value");
}
@ -5257,7 +5266,7 @@ function printJSXElement(path, options, print) {
containsMultipleAttributes ||
containsMultipleExpressions;
const rawJsxWhitespace = options.singleQuote ? "{' '}" : '{" "}';
const rawJsxWhitespace = options.jsxSingleQuote ? "{' '}" : '{" "}';
const jsxWhitespace = ifBreak(concat([rawJsxWhitespace, softline]), " ");
const isFacebookTranslationTag =

File diff suppressed because it is too large Load Diff

View File

@ -1 +1,16 @@
run_spec(__dirname, ["flow", "babylon", "typescript"]);
run_spec(__dirname, ["flow", "babylon", "typescript"], {
singleQuote: false,
jsxSingleQuote: false
});
run_spec(__dirname, ["flow", "babylon", "typescript"], {
singleQuote: false,
jsxSingleQuote: true
});
run_spec(__dirname, ["flow", "babylon", "typescript"], {
singleQuote: true,
jsxSingleQuote: false
});
run_spec(__dirname, ["flow", "babylon", "typescript"], {
singleQuote: true,
jsxSingleQuote: true
});

View File

@ -1,3 +1,36 @@
<div id="&quot;'<>&amp;quot;" />;
<div id='"&#39;<>&amp;quot;' />;
<div id={'\'"&quot;<>&amp;quot;'} />;
<div id='123' />;
<div id='&#39;&quot;' />;
<div id={'\'\"\\\''} />;
<div
single='foo'
single2={'foo'}
double="bar"
double2={"bar"}
singleDouble='"'
singleDouble2={'"'}
doubleSingle="'"
doubleSingle2={"'"}
singleEscaped={'\''}
singleEscaped2='&apos;'
doubleEscaped={"\""}
doubleEscaped2="&quot;"
singleBothEscaped={'\'"'}
singleBothEscaped2='&apos;"'
singleBoth='&apos; "'
singleBoth2={'\' "'}
singleBoth3='&apos; &apos; "'
doubleBoth="&quot; '"
doubleBoth2={"\" '"}
doubleBoth3="&quot; &apos; '"
/>;

View File

@ -65,6 +65,8 @@ Format options:
Defaults to css.
--jsx-bracket-same-line Put > on the last line instead of at a new line.
Defaults to false.
--jsx-single-quote Use single quotes in JSX.
Defaults to false.
--parser <flow|babylon|typescript|css|less|scss|json|json5|json-stringify|graphql|markdown|mdx|vue|yaml|html|angular>
Which parser to use.
--print-width <int> The line length where Prettier will try wrap.
@ -206,6 +208,8 @@ Format options:
Defaults to css.
--jsx-bracket-same-line Put > on the last line instead of at a new line.
Defaults to false.
--jsx-single-quote Use single quotes in JSX.
Defaults to false.
--parser <flow|babylon|typescript|css|less|scss|json|json5|json-stringify|graphql|markdown|mdx|vue|yaml|html|angular>
Which parser to use.
--print-width <int> The line length where Prettier will try wrap.

View File

@ -196,6 +196,19 @@ Default: false
exports[`show detailed usage with --help jsx-bracket-same-line (write) 1`] = `Array []`;
exports[`show detailed usage with --help jsx-single-quote (stderr) 1`] = `""`;
exports[`show detailed usage with --help jsx-single-quote (stdout) 1`] = `
"--jsx-single-quote
Use single quotes in JSX.
Default: false
"
`;
exports[`show detailed usage with --help jsx-single-quote (write) 1`] = `Array []`;
exports[`show detailed usage with --help list-different (stderr) 1`] = `""`;
exports[`show detailed usage with --help list-different (stdout) 1`] = `

View File

@ -82,6 +82,11 @@ This option cannot be used with --range-start and --range-end.",
"description": "Put > on the last line instead of at a new line.",
"type": "boolean",
},
"jsxSingleQuote": Object {
"default": false,
"description": "Use single quotes in JSX.",
"type": "boolean",
},
"parser": Object {
"default": undefined,
"description": "Which parser to use.",

View File

@ -489,7 +489,7 @@ exports[`API getSupportInfo() with version 1.8.2 -> undefined 1`] = `
\\"type\\": \\"boolean\\",
},
\\"cursorOffset\\": Object {
@@ -56,10 +85,19 @@
@@ -56,33 +85,61 @@
},
\\"filepath\\": Object {
\\"default\\": undefined,
@ -509,7 +509,17 @@ exports[`API getSupportInfo() with version 1.8.2 -> undefined 1`] = `
\\"type\\": \\"boolean\\",
},
\\"jsxBracketSameLine\\": Object {
@@ -73,16 +111,31 @@
\\"default\\": false,
\\"type\\": \\"boolean\\",
},
+ \\"jsxSingleQuote\\": Object {
+ \\"default\\": false,
+ \\"type\\": \\"boolean\\",
+ },
\\"parser\\": Object {
\\"choices\\": Array [
\\"flow\\",
\\"babylon\\",
\\"typescript\\",
\\"css\\",
\\"less\\",
@ -542,7 +552,7 @@ exports[`API getSupportInfo() with version 1.8.2 -> undefined 1`] = `
\\"range\\": Object {
\\"end\\": Infinity,
\\"start\\": 0,
@@ -90,14 +143,15 @@
@@ -90,14 +147,15 @@
},
\\"type\\": \\"int\\",
},
@ -1019,6 +1029,15 @@ exports[`CLI --support-info (stdout) 1`] = `
\\"since\\": \\"0.17.0\\",
\\"type\\": \\"boolean\\"
},
{
\\"category\\": \\"JavaScript\\",
\\"default\\": false,
\\"description\\": \\"Use single quotes in JSX.\\",
\\"name\\": \\"jsxSingleQuote\\",
\\"pluginDefaults\\": {},
\\"since\\": \\"1.15.0\\",
\\"type\\": \\"boolean\\"
},
{
\\"category\\": \\"Global\\",
\\"choices\\": [