diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md
index 3e144431..a5632ea5 100644
--- a/CHANGELOG.unreleased.md
+++ b/CHANGELOG.unreleased.md
@@ -898,6 +898,20 @@ function doSmth() {
}
```
+#### TypeScript: sometimes removing parentheses around JSX made the code unparseable ([#6640] by [@sosukesuzuki])
+
+
+```tsx
+// Input
+().toString();
+
+// Prettier (stable)
+.toString():
+
+// Prettier (master)
+().toString();
+```
+
[#5910]: https://github.com/prettier/prettier/pull/5910
[#6033]: https://github.com/prettier/prettier/pull/6033
[#6186]: https://github.com/prettier/prettier/pull/6186
@@ -927,6 +941,7 @@ function doSmth() {
[#6604]: https://github.com/prettier/prettier/pull/6604
[#6496]: https://github.com/prettier/prettier/pull/6496
[#6605]: https://github.com/prettier/prettier/pull/6605
+[#6640]: https://github.com/prettier/prettier/pull/6640
[@brainkim]: https://github.com/brainkim
[@duailibe]: https://github.com/duailibe
[@gavinjoyce]: https://github.com/gavinjoyce
diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js
index 0c0ec43e..c8596ecf 100644
--- a/src/language-js/needs-parens.js
+++ b/src/language-js/needs-parens.js
@@ -6,8 +6,8 @@ const util = require("../common/util");
const comments = require("./comments");
const {
getLeftSidePathName,
- hasNakedLeftSide,
- hasFlowShorthandAnnotationComment
+ hasFlowShorthandAnnotationComment,
+ hasNakedLeftSide
} = require("./utils");
function hasClosureCompilerTypeCastComment(text, path) {
@@ -722,6 +722,29 @@ function needsParens(path, options) {
return false;
}
return true;
+ case "JSXFragment":
+ case "JSXElement":
+ return (
+ parent.type !== "ArrayExpression" &&
+ parent.type !== "ArrowFunctionExpression" &&
+ parent.type !== "AssignmentExpression" &&
+ parent.type !== "AssignmentPattern" &&
+ parent.type !== "BinaryExpression" &&
+ parent.type !== "CallExpression" &&
+ parent.type !== "ConditionalExpression" &&
+ parent.type !== "ExpressionStatement" &&
+ parent.type !== "JsExpressionRoot" &&
+ parent.type !== "JSXAttribute" &&
+ parent.type !== "JSXElement" &&
+ parent.type !== "JSXExpressionContainer" &&
+ parent.type !== "JSXFragment" &&
+ parent.type !== "LogicalExpression" &&
+ parent.type !== "ObjectProperty" &&
+ parent.type !== "Property" &&
+ parent.type !== "ReturnStatement" &&
+ parent.type !== "TypeCastExpression" &&
+ parent.type !== "VariableDeclarator"
+ );
}
return false;
diff --git a/src/language-js/printer-estree.js b/src/language-js/printer-estree.js
index 0bda945f..44233d11 100644
--- a/src/language-js/printer-estree.js
+++ b/src/language-js/printer-estree.js
@@ -84,6 +84,7 @@ const {
isTemplateOnItsOwnLine,
isTestCall,
isTheOnlyJSXElementInMarkdown,
+ isTSXFile,
isTypeAnnotationAFunction,
matchJsxWhitespaceRegex,
needsHardlineAfterDanglingComment,
@@ -2152,7 +2153,7 @@ function printPathNoParens(path, options, print, args) {
() => printJSXElement(path, options, print),
options
);
- return maybeWrapJSXElementInParens(path, elem);
+ return maybeWrapJSXElementInParens(path, elem, options);
}
case "JSXOpeningElement": {
const n = path.getValue();
@@ -3005,8 +3006,7 @@ function printPathNoParens(path, options, print, args) {
if (
parent.params &&
parent.params.length === 1 &&
- options.filepath &&
- /\.tsx$/i.test(options.filepath) &&
+ isTSXFile(options) &&
!n.constraint &&
grandParent.type === "ArrowFunctionExpression"
) {
@@ -5385,7 +5385,7 @@ function printJSXElement(path, options, print) {
]);
}
-function maybeWrapJSXElementInParens(path, elem) {
+function maybeWrapJSXElementInParens(path, elem, options) {
const parent = path.getParentNode();
if (!parent) {
return elem;
@@ -5413,12 +5413,14 @@ function maybeWrapJSXElementInParens(path, elem) {
"JSXExpressionContainer"
]);
+ const needsParens = pathNeedsParens(path, options);
+
return group(
concat([
- ifBreak("("),
+ needsParens ? "" : ifBreak("("),
indent(concat([softline, elem])),
softline,
- ifBreak(")")
+ needsParens ? "" : ifBreak(")")
]),
{ shouldBreak }
);
diff --git a/src/language-js/utils.js b/src/language-js/utils.js
index 33289db0..cd60e378 100644
--- a/src/language-js/utils.js
+++ b/src/language-js/utils.js
@@ -888,6 +888,10 @@ function identity(x) {
return x;
}
+function isTSXFile(options) {
+ return options.filepath && /\.tsx$/i.test(options.filepath);
+}
+
module.exports = {
classChildNeedsASIProtection,
classPropMayCauseASIProblems,
@@ -934,6 +938,7 @@ module.exports = {
isTemplateOnItsOwnLine,
isTestCall,
isTheOnlyJSXElementInMarkdown,
+ isTSXFile,
isTypeAnnotationAFunction,
matchJsxWhitespaceRegex,
needsHardlineAfterDanglingComment,
diff --git a/tests/jsx_fragment/__snapshots__/jsfmt.spec.js.snap b/tests/jsx_fragment/__snapshots__/jsfmt.spec.js.snap
index 7091ec5e..d28daccb 100644
--- a/tests/jsx_fragment/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/jsx_fragment/__snapshots__/jsfmt.spec.js.snap
@@ -55,6 +55,30 @@ foo = (
// close fragment
>;
+[<>>, <>>];
+const fun1 = () => <>>;
+x = <>>
+function fun2(param = <>>) {}
+1 + <>>;
+1 || <>>;
+fun2(<>>);
+test ? <>> : x;
+<>>;
+
+ <>>
+;
+const obj = {
+ foo: <>>
+};
+const fragmentVar = <>>;
+function fun3() {
+ return <>>;
+}
+(<>>).toString();
+(<>>).props;
+(<>>)["computed"];
+(<>>)["computed"]();
+
=====================================output=====================================
<>>;
@@ -105,5 +129,29 @@ foo = (
// close fragment
>;
+[<>>, <>>];
+const fun1 = () => <>>;
+x = <>>;
+function fun2(param = <>>) {}
+1 + <>>;
+1 || <>>;
+fun2(<>>);
+test ? <>> : x;
+<>>;
+
+ <>>
+;
+const obj = {
+ foo: <>>
+};
+const fragmentVar = <>>;
+function fun3() {
+ return <>>;
+}
+(<>>).toString();
+(<>>).props;
+(<>>)["computed"];
+(<>>)["computed"]();
+
================================================================================
`;
diff --git a/tests/jsx_fragment/fragment.js b/tests/jsx_fragment/fragment.js
index fcf30ee1..c8c4657f 100644
--- a/tests/jsx_fragment/fragment.js
+++ b/tests/jsx_fragment/fragment.js
@@ -46,3 +46,27 @@ foo = (