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 = ( ; +[<>, <>]; +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 = ( ; + +[<>, <>]; +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/typescript_tsx/__snapshots__/jsfmt.spec.js.snap b/tests/typescript_tsx/__snapshots__/jsfmt.spec.js.snap index 062380b2..761df1b1 100644 --- a/tests/typescript_tsx/__snapshots__/jsfmt.spec.js.snap +++ b/tests/typescript_tsx/__snapshots__/jsfmt.spec.js.snap @@ -38,6 +38,66 @@ printWidth: 80 ================================================================================ `; +exports[`member-expression.tsx 1`] = ` +====================================options===================================== +parsers: ["typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +().method(); +().property; +()["computed"]; +()["computed"](); +( +
+ foo +
+).method(); +( +
+ foo +
+).property; +( +
+ foo +
+)["computed"]; +( +
+ foo +
+)["computed"](); + +=====================================output===================================== +().method(); +().property; +()["computed"]; +()["computed"](); +( +
+ foo +
+).method(); +( +
+ foo +
+).property; +( +
+ foo +
+)["computed"]; +( +
+ foo +
+)["computed"](); + +================================================================================ +`; + exports[`not-react.ts 1`] = ` ====================================options===================================== parsers: ["typescript"] diff --git a/tests/typescript_tsx/member-expression.tsx b/tests/typescript_tsx/member-expression.tsx new file mode 100644 index 00000000..2a91dee4 --- /dev/null +++ b/tests/typescript_tsx/member-expression.tsx @@ -0,0 +1,24 @@ +().method(); +().property; +()["computed"]; +()["computed"](); +( +
+ foo +
+).method(); +( +
+ foo +
+).property; +( +
+ foo +
+)["computed"]; +( +
+ foo +
+)["computed"]();