From 2002ce0453d8224c5efbf8a072cd823d953cc717 Mon Sep 17 00:00:00 2001 From: Ika Date: Tue, 22 Jan 2019 00:54:41 +0800 Subject: [PATCH] fix(javascript): add necessary parentheses for decorators (#5785) --- CHANGELOG.unreleased.md | 28 +++++++++++++++ src/language-js/needs-parens.js | 34 ++++++++++++++++--- .../__snapshots__/jsfmt.spec.js.snap | 20 +++++++++++ tests/decorators/parens.js | 4 +++ 4 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 tests/decorators/parens.js diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index e3d39b97e..e2d3c0e4a 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -76,5 +76,33 @@ Examples: ); ``` +- JavaScript: Add necessary parentheses for decorators ([#5785] by [@ikatyang]) + + Parentheses for decorators with nested call expressions are optional for legacy decorators + but they're required for decorators in the current [proposal](https://tc39.github.io/proposal-decorators/#sec-syntax). + + + ```js + // Input + class X { + @(computed().volatile()) + prop + } + + // Output (Prettier stable) + class X { + @computed().volatile() + prop + } + + // Output (Prettier master) + class X { + @(computed().volatile()) + prop + } + ``` + +[@ikatyang]: https://github.com/ikatyang [@simenb]: https://github.com/SimenB [#5778]: https://github.com/prettier/prettier/pull/5778 +[#5785]: https://github.com/prettier/prettier/pull/5785 diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js index 6c22a4a0a..4a6a50977 100644 --- a/src/language-js/needs-parens.js +++ b/src/language-js/needs-parens.js @@ -127,6 +127,35 @@ function needsParens(path, options) { return true; } + if (parent.type === "Decorator" && parent.expression === node) { + let hasCallExpression = false; + let hasMemberExpression = false; + let current = node; + while (current) { + switch (current.type) { + case "MemberExpression": + hasMemberExpression = true; + current = current.object; + break; + case "CallExpression": + if ( + /** @(x().y) */ hasMemberExpression || + /** @(x().y()) */ hasCallExpression + ) { + return true; + } + hasCallExpression = true; + current = current.callee; + break; + case "Identifier": + return false; + default: + return true; + } + } + return true; + } + if ( (parent.type === "ArrowFunctionExpression" && parent.body === node && @@ -276,11 +305,6 @@ function needsParens(path, options) { parent.left === node && (node.type === "TSTypeAssertion" || node.type === "TSAsExpression") ); - case "Decorator": - return ( - parent.expression === node && - (node.type === "TSTypeAssertion" || node.type === "TSAsExpression") - ); case "BinaryExpression": case "LogicalExpression": { diff --git a/tests/decorators/__snapshots__/jsfmt.spec.js.snap b/tests/decorators/__snapshots__/jsfmt.spec.js.snap index 3d6e4df51..db8f10d3c 100644 --- a/tests/decorators/__snapshots__/jsfmt.spec.js.snap +++ b/tests/decorators/__snapshots__/jsfmt.spec.js.snap @@ -311,6 +311,26 @@ const foo = { ================================================================================ `; +exports[`parens.js 1`] = ` +====================================options===================================== +parsers: ["babel"] +printWidth: 80 + | printWidth +=====================================input====================================== +class X { + @(computed().volatile()) + x +} + +=====================================output===================================== +class X { + @(computed().volatile()) + x; +} + +================================================================================ +`; + exports[`redux.js 1`] = ` ====================================options===================================== parsers: ["babel"] diff --git a/tests/decorators/parens.js b/tests/decorators/parens.js new file mode 100644 index 000000000..ed2687948 --- /dev/null +++ b/tests/decorators/parens.js @@ -0,0 +1,4 @@ +class X { + @(computed().volatile()) + x +}