diff --git a/changelog_unreleased/javascript/10781.md b/changelog_unreleased/javascript/10781.md new file mode 100644 index 000000000..eec318f35 --- /dev/null +++ b/changelog_unreleased/javascript/10781.md @@ -0,0 +1,17 @@ +#### Fix missing parentheses on `async` inside `for..of` (#10781 by @fisker) + + +```jsx +// Input +for ((async) of []); + +// Prettier stable +for (async of []); + +// Prettier stable (second format) +SyntaxError: Unexpected token, expected "=>" (1:15) +> 1 | for (async of []); + +// Prettier main +for ((async) of []); +``` diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js index 1006a21b0..6deab2bf0 100644 --- a/src/language-js/needs-parens.js +++ b/src/language-js/needs-parens.js @@ -63,6 +63,17 @@ function needsParens(path, options) { ) { return true; } + + // `for (async of []);` is invalid + if ( + name === "left" && + node.name === "async" && + parent.type === "ForOfStatement" && + !parent.await + ) { + return true; + } + return false; } diff --git a/tests/format/js/for-of/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/for-of/__snapshots__/jsfmt.spec.js.snap new file mode 100644 index 000000000..1dcc6d0b4 --- /dev/null +++ b/tests/format/js/for-of/__snapshots__/jsfmt.spec.js.snap @@ -0,0 +1,44 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`async-identifier.js [espree] format 1`] = ` +"Unexpected token [ (6:23) + 4 | + 5 | async function f() { +> 6 | for await (async of []); + | ^ + 7 | for await ((async) of []); + 8 | for await ((foo) of async); + 9 | for await ((foo) of []) async;" +`; + +exports[`async-identifier.js format 1`] = ` +====================================options===================================== +parsers: ["babel", "flow", "typescript"] +printWidth: 80 + | printWidth +=====================================input====================================== +for ((async) of []); +for ((foo) of async); +for ((foo) of []) async; + +async function f() { + for await (async of []); + for await ((async) of []); + for await ((foo) of async); + for await ((foo) of []) async; +} + +=====================================output===================================== +for ((async) of []); +for (foo of async); +for (foo of []) async; + +async function f() { + for await (async of []); + for await (async of []); + for await (foo of async); + for await (foo of []) async; +} + +================================================================================ +`; diff --git a/tests/format/js/for-of/async-identifier.js b/tests/format/js/for-of/async-identifier.js new file mode 100644 index 000000000..683107a85 --- /dev/null +++ b/tests/format/js/for-of/async-identifier.js @@ -0,0 +1,10 @@ +for ((async) of []); +for ((foo) of async); +for ((foo) of []) async; + +async function f() { + for await (async of []); + for await ((async) of []); + for await ((foo) of async); + for await ((foo) of []) async; +} diff --git a/tests/format/js/for-of/jsfmt.spec.js b/tests/format/js/for-of/jsfmt.spec.js new file mode 100644 index 000000000..ac56eed1b --- /dev/null +++ b/tests/format/js/for-of/jsfmt.spec.js @@ -0,0 +1,3 @@ +run_spec(__dirname, ["babel", "flow", "typescript"], { + errors: { espree: ["async-identifier.js"] }, +}); diff --git a/tests/format/misc/errors/js/for-of/__snapshots__/jsfmt.spec.js.snap b/tests/format/misc/errors/js/for-of/__snapshots__/jsfmt.spec.js.snap index 55b11f6e9..7a7418dd9 100644 --- a/tests/format/misc/errors/js/for-of/__snapshots__/jsfmt.spec.js.snap +++ b/tests/format/misc/errors/js/for-of/__snapshots__/jsfmt.spec.js.snap @@ -6,18 +6,42 @@ exports[`snippet: #0 [babel] format 1`] = ` | ^" `; +exports[`snippet: #0 [babel] format 2`] = ` +"The left-hand side of a for-of loop may not be 'async'. (1:6) +> 1 | for (async of []); + | ^" +`; + exports[`snippet: #0 [babel-flow] format 1`] = ` "The left-hand side of a for-of loop may not start with 'let'. (1:6) > 1 | for (let.foo of []); | ^" `; +exports[`snippet: #0 [babel-flow] format 2`] = ` +"The left-hand side of a for-of loop may not be 'async'. (1:6) +> 1 | for (async of []); + | ^" +`; + exports[`snippet: #0 [babel-ts] format 1`] = ` "The left-hand side of a for-of loop may not start with 'let'. (1:6) > 1 | for (let.foo of []); | ^" `; +exports[`snippet: #0 [babel-ts] format 2`] = ` +"The left-hand side of a for-of loop may not be 'async'. (1:6) +> 1 | for (async of []); + | ^" +`; + +exports[`snippet: #0 [espree] format 1`] = ` +"Unexpected token [ (1:15) +> 1 | for (async of []); + | ^" +`; + exports[`snippet: #0 [flow] format 1`] = ` "Unexpected token \`.\`, expected an identifier (1:9) > 1 | for (let.foo of []); diff --git a/tests/format/misc/errors/js/for-of/jsfmt.spec.js b/tests/format/misc/errors/js/for-of/jsfmt.spec.js index 13881bedd..62793da4f 100644 --- a/tests/format/misc/errors/js/for-of/jsfmt.spec.js +++ b/tests/format/misc/errors/js/for-of/jsfmt.spec.js @@ -9,7 +9,7 @@ run_spec( }, [ "babel", - // Espree didn't throw https://github.com/acornjs/acorn/issues/1009 + // `espree` didn't throw https://github.com/acornjs/acorn/issues/1009 // "espree", "meriyah", "flow", @@ -18,3 +18,22 @@ run_spec( "babel-ts", ] ); + +run_spec( + { + dirname: __dirname, + snippets: ["for (async of []);"], + }, + [ + "babel", + "espree", + // `meriyah` didn't throw https://github.com/meriyah/meriyah/issues/190 + // "meriyah", + // `flow` didn't throw https://github.com/facebook/flow/issues/8651 + // "flow", + // `typescript` didn't throw + // "typescript", + "babel-flow", + "babel-ts", + ] +);