From 60a1d7ec7821a006b6135061225f058143b953c2 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Fri, 11 Sep 2026 15:10:50 +0500 Subject: [PATCH] Fix missing parentheses around `throw` expression (#20028) Co-authored-by: fisker --- changelog_unreleased/javascript/20028.md | 13 ++ .../parentheses/needs-parentheses.js | 3 +- .../__snapshots__/format.test.js.snap | 9 - .../format/js/do-expression/do-expression.js | 4 - .../__snapshots__/format.test.js.snap | 216 ++++++++++++++++++ tests/format/js/parentheses/do-expression.js | 56 +++++ tests/format/js/parentheses/format.test.js | 14 ++ 7 files changed, 301 insertions(+), 14 deletions(-) create mode 100644 changelog_unreleased/javascript/20028.md create mode 100644 tests/format/js/parentheses/__snapshots__/format.test.js.snap create mode 100644 tests/format/js/parentheses/do-expression.js create mode 100644 tests/format/js/parentheses/format.test.js diff --git a/changelog_unreleased/javascript/20028.md b/changelog_unreleased/javascript/20028.md new file mode 100644 index 000000000..64a5e3682 --- /dev/null +++ b/changelog_unreleased/javascript/20028.md @@ -0,0 +1,13 @@ +#### Keep necessary parentheses around `throw` expression (#20028 by @lazerg) + + +```jsx +// Input +(throw error); + +// Prettier stable +throw error; + +// Prettier main +(throw error); +``` diff --git a/src/language-js/parentheses/needs-parentheses.js b/src/language-js/parentheses/needs-parentheses.js index bf7432271..84a1a8dc4 100644 --- a/src/language-js/parentheses/needs-parentheses.js +++ b/src/language-js/parentheses/needs-parentheses.js @@ -62,7 +62,8 @@ function needsParentheses(path, options) { node.type === "ObjectExpression" || node.type === "FunctionExpression" || node.type === "ClassExpression" || - node.type === "DoExpression" + node.type === "DoExpression" || + (node.type === "UnaryExpression" && node.operator === "throw") ) { const expression = path.findAncestor( (node) => node.type === "ExpressionStatement", diff --git a/tests/format/js/do-expression/__snapshots__/format.test.js.snap b/tests/format/js/do-expression/__snapshots__/format.test.js.snap index 7e77749bd..c889cd819 100644 --- a/tests/format/js/do-expression/__snapshots__/format.test.js.snap +++ b/tests/format/js/do-expression/__snapshots__/format.test.js.snap @@ -287,10 +287,6 @@ function foo() { ); } -(do {}); -(do {} + 1); -(1 + do {}); -() => do {}; (do { switch(0) { @@ -358,11 +354,6 @@ function foo() { ); } -(do {}); -(do {}) + 1; -1 + do {}; -() => do {}; - (do { switch (0) { case 0: diff --git a/tests/format/js/do-expression/do-expression.js b/tests/format/js/do-expression/do-expression.js index 8e368c09d..a6bb8a6eb 100644 --- a/tests/format/js/do-expression/do-expression.js +++ b/tests/format/js/do-expression/do-expression.js @@ -34,10 +34,6 @@ function foo() { ); } -(do {}); -(do {} + 1); -(1 + do {}); -() => do {}; (do { switch(0) { diff --git a/tests/format/js/parentheses/__snapshots__/format.test.js.snap b/tests/format/js/parentheses/__snapshots__/format.test.js.snap new file mode 100644 index 000000000..4911f03f8 --- /dev/null +++ b/tests/format/js/parentheses/__snapshots__/format.test.js.snap @@ -0,0 +1,216 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`do-expression.js format 1`] = ` +====================================options===================================== +parsers: ["babel", "flow", "typescript"] + printWidth: 80 (default) | +=====================================input====================================== +(do {}); +(async do {}); +(throw error); + +(do {}) || fallback; +(async do {}) || fallback; +(throw error) || fallback; + +(do {}).message; +(async do {}).message; +(throw error).message; + +// Head, bug +((do {}), _); +((async do {}), _); +((throw error), _); +// Tail +(_, (do {})); +(_, (async do {})); +(_, (throw error)); +// Middle +( + _, + (do {}), + (async do {}), + (throw error), + _ +); + +(do {}) + 1; +(async do {}) + 1; +(throw error) + 1; + +1 + do {}; +1 + async do {}; +1 + throw error; + +() => do {}; +() => async do {}; +() => throw error; + +if (condition) (do {}); +if (condition) (async do {}); +if (condition) (throw error); + +for (;;) (do {}); +for (;;) (async do {}); +for (;;) (throw error); + +label: (do {}); +label: (async do {}); +label: (throw error); + +value = do {}; +value = async do {}; +value = throw error; + +=====================================output===================================== +(do {}); +(async do {}); +(throw error); + +(do {}) || fallback; +(async do {}) || fallback; +(throw error) || fallback; + +(do {}).message; +(async do {}).message; +(throw error).message; + +// Head, bug +((do {}), _); +((async do {}), _); +((throw error), _); +// Tail +(_, do {}); +(_, async do {}); +(_, throw error); +// Middle +(_, do {}, async do {}, throw error, _); + +(do {}) + 1; +(async do {}) + 1; +(throw error) + 1; + +1 + do {}; +1 + async do {}; +1 + throw error; + +() => do {}; +() => async do {}; +() => throw error; + +if (condition) (do {}); +if (condition) (async do {}); +if (condition) (throw error); + +for (;;) (do {}); +for (;;) (async do {}); +for (;;) (throw error); + +label: (do {}); +label: (async do {}); +label: (throw error); + +value = do {}; +value = async do {}; +value = throw error; + +================================================================================ +`; + +exports[`do-expression.js format[acorn] 1`] = ` +"Unexpected token (1:2) +> 1 | (do {}); + | ^ + 2 | (async do {}); + 3 | (throw error); + 4 | +Cause: Unexpected token (1:1)" +`; + +exports[`do-expression.js format[espree] 1`] = ` +"Unexpected token do (1:2) +> 1 | (do {}); + | ^ + 2 | (async do {}); + 3 | (throw error); + 4 | +Cause: Unexpected token do" +`; + +exports[`do-expression.js format[flow] 1`] = ` +"Unexpected token \`do\` (1:2) +> 1 | (do {}); + | ^ + 2 | (async do {}); + 3 | (throw error); + 4 | +Cause: Unexpected token \`do\` (1:1)" +`; + +exports[`do-expression.js format[hermes] 1`] = ` +"invalid expression (1:2) +> 1 | (do {}); + | ^ + 2 | (async do {}); + 3 | (throw error); + 4 | +Cause: invalid expression (1:1) +(do {}); + ^" +`; + +exports[`do-expression.js format[meriyah] 1`] = ` +"Unexpected token: 'do' (1:2) +> 1 | (do {}); + | ^^ + 2 | (async do {}); + 3 | (throw error); + 4 | +Cause: [1:1-1:3]: Unexpected token: 'do'" +`; + +exports[`do-expression.js format[oxc] 1`] = ` +"Unexpected token (1:2) +> 1 | (do {}); + | ^^ + 2 | (async do {}); + 3 | (throw error); + 4 |" +`; + +exports[`do-expression.js format[oxc-ts] 1`] = ` +"Unexpected token (1:2) +> 1 | (do {}); + | ^^ + 2 | (async do {}); + 3 | (throw error); + 4 |" +`; + +exports[`do-expression.js format[typescript] 1`] = ` +"Expression expected. (1:2) +> 1 | (do {}); + | ^ + 2 | (async do {}); + 3 | (throw error); + 4 | +Cause: Expression expected." +`; + +exports[`do-expression.js format[yuku] 1`] = ` +"'do' is reserved and cannot be used as an identifier (1:2) +> 1 | (do {}); + | ^^ + 2 | (async do {}); + 3 | (throw error); + 4 |" +`; + +exports[`do-expression.js format[yuku-ts] 1`] = ` +"'do' is reserved and cannot be used as an identifier (1:2) +> 1 | (do {}); + | ^^ + 2 | (async do {}); + 3 | (throw error); + 4 |" +`; diff --git a/tests/format/js/parentheses/do-expression.js b/tests/format/js/parentheses/do-expression.js new file mode 100644 index 000000000..a4e767e53 --- /dev/null +++ b/tests/format/js/parentheses/do-expression.js @@ -0,0 +1,56 @@ +(do {}); +(async do {}); +(throw error); + +(do {}) || fallback; +(async do {}) || fallback; +(throw error) || fallback; + +(do {}).message; +(async do {}).message; +(throw error).message; + +// Head, bug +((do {}), _); +((async do {}), _); +((throw error), _); +// Tail +(_, (do {})); +(_, (async do {})); +(_, (throw error)); +// Middle +( + _, + (do {}), + (async do {}), + (throw error), + _ +); + +(do {}) + 1; +(async do {}) + 1; +(throw error) + 1; + +1 + do {}; +1 + async do {}; +1 + throw error; + +() => do {}; +() => async do {}; +() => throw error; + +if (condition) (do {}); +if (condition) (async do {}); +if (condition) (throw error); + +for (;;) (do {}); +for (;;) (async do {}); +for (;;) (throw error); + +label: (do {}); +label: (async do {}); +label: (throw error); + +value = do {}; +value = async do {}; +value = throw error; diff --git a/tests/format/js/parentheses/format.test.js b/tests/format/js/parentheses/format.test.js new file mode 100644 index 000000000..4d4f60f02 --- /dev/null +++ b/tests/format/js/parentheses/format.test.js @@ -0,0 +1,14 @@ +runFormatTest(import.meta, ["babel", "flow", "typescript"], { + errors: { + acorn: ["do-expression.js"], + espree: ["do-expression.js"], + meriyah: ["do-expression.js"], + oxc: ["do-expression.js"], + "oxc-ts": ["do-expression.js"], + yuku: ["do-expression.js"], + "yuku-ts": ["do-expression.js"], + typescript: ["do-expression.js"], + flow: ["do-expression.js"], + hermes: ["do-expression.js"], + }, +});