Repository: prettier/prettier (JavaScript)
Date: 2021-04-29 07:39:47+00:00
Task type: Bug Fix

Issue:
Missing parentheses `for ((async) of []);`
This is valid even in [main branch](https://deploy-preview-10775--prettier.netlify.app/playground/#N4Igxg9gdgLgprEAuEAzCAnABAChwQwGcBPKMASiwlSwG0BdcgbhABoQIAHGAS2kOSh8GDBADuABWEIBKfABsx+YgPYAjDPjABrODADKnLTygBzZDAwBXOOzgBbNXAAmzlwBl8Zq-lNwAYpj2+DC8Zsgg+FYwEGwgABYw9vIA6vE88IRGYHD6Mhk8AG4ZxBFghKogJoRwGDASmqbByKgKNewAVoQAHgBCmjp6+vj2cO4mcC1ttiBd3fompvJwAIpWEPBT8u0gRhg1GBFq+E7ycZwYJjApPM4w8cgAHAAM7BcQNSmanBEXcAeFSbsACO63gDS4skihAAtFA4C4XHEMHBQTwUQ1fM0kK1tjMavYeBZrPjFss1htJjjpuwYCcbncHkgAEy0zQ8eSLADCEHs2JA-wArHErDUAConWS4naFGwASSgblg+jAl24AEFFfoYMRllsagBfA1AA), [related babel pr](https://github.com/babel/babel/pull/13208)

**Prettier 2.2.1**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEAzCAnABAChwQwGcBPKMASiwlSwG0BdcgbhABoQIAHGAS2kOSh8GDBADuABWEIBKfABsx+YgPYAjDPjABrODADKnLTygBzZDAwBXOOzgBbNXAAmzlwBl8Zq-lNwAYpj2+DC8Zsgg+FYwEGwgABYw9vIA6vE88IRGYHD6Mhk8AG4ZxBFghKogJoRwGDASmqbByKgKNewAVoQAHgBCmjp6+vj2cO4mcC1ttiBd3fompvJwAIpWEPBT8u0gRhg1GBFq+E7ycZwYJjApPM4w8cgAHAAM7BcQNSmanBEXcAeFSbsACO63gDS4skihAAtFA4C4XHEMHBQTwUQ1fM0kK1tjMavYeBZrPjFss1htJjjpuwYCcbncHkgAEy0zQ8eSLADCEHs2JA-wArHErDUAConWS4naFGwASSgblg+jAl24AEFFfoYMRllsagBfA1AA)
<!-- prettier-ignore -->
```sh
--parser babel
```

**Input:**
<!-- prettier-ignore -->
```jsx
for ((async) of []);
```

**Output:**
<!-- prettier-ignore -->
```jsx
for (async of []);

```

**Second Output:**
<!-- prettier-ignore -->
```jsx
SyntaxError: Unexpected token, expected "=>" (1:15)
> 1 | for (async of []);
    |               ^
  2 | 
```

**Expected behavior:**



Issue discussion:

Solution patch:
diff --git a/changelog_unreleased/javascript/10781.md b/changelog_unreleased/javascript/10781.md
new file mode 100644
index 000000000000..eec318f35841
--- /dev/null
+++ b/changelog_unreleased/javascript/10781.md
@@ -0,0 +1,17 @@
+#### Fix missing parentheses on `async` inside `for..of` (#10781 by @fisker)
+
+<!-- prettier-ignore -->
+```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 1006a21b0fbb..6deab2bf0c07 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;
   }
 


Test patch:
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 000000000000..1dcc6d0b41a3
--- /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 000000000000..683107a85cdd
--- /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 000000000000..ac56eed1b51a
--- /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 55b11f6e9bc3..7a7418dd93a9 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 13881bedd56d..62793da4f372 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",
+  ]
+);