Repository: prettier/prettier (JavaScript)

Issue:
Missing parentheses `for ((let) of []);`
**Prettier 2.2.1**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEAzCAnABAChwGzhgEosJUsBtAXWIG4QAaECABxgEtoBnZUAQwwYIAdwAKghLxT98I-gE9ezAEYZ+YANZEAyqw0coAc2QwMAVzjM4AWxVwAJg8cAZfsfP8jcAGKYb-DCcxsgg-OYwEEwgABYwNvgA6jEc8Nz6YHA6UqkcAG6pCqFg3MoghtxwGDBi6kYByKiylcwAVtwAHgBC6lq6-DZwLoZwjc1WIO0dOoZGhACK5hDwY-gtIPoYlRihKvz2+NGsGIYwiRwOMDHIABwADMzHEJWJ6qyhx3DbeaPMAI5LeC1NjSMLcAC0UDgjkc0QwcABHHhtS8DSQTTWE0qNg4pgsWNmC0Bo3R42YMH250u1yQACZyeoOPhZgBhCA2NEgL4AVmi5kqABV9tIMes8pYAJJQZywHRgE7sACC0p0MAUhFWlQAvlqgA)
<!-- prettier-ignore -->
```sh
--parser babel
```

**Input:**
<!-- prettier-ignore -->
```jsx
for ((let) of []);
```

**Output:**
<!-- prettier-ignore -->
```jsx
for (let of []);

```

**Second Output:**
<!-- prettier-ignore -->
```jsx
SyntaxError: Unexpected token, expected ";" (1:13)
> 1 | for (let of []);
    |             ^
  2 | 
```

The parens are also needed in `for ((let) of []);` and `for ((let.foo.bar) of []);`.

See https://tc39.es/ecma262/#sec-for-in-and-for-of-statements

_Originally posted by @thorn0 in https://github.com/prettier/prettier/issues/10777#issuecomment-829191658_


Hints:
## summary

according to spec

**for in:**

```js
for (expression1 in expression2) {}
```

If `expressin1` starts with `let[`, it should be wrapped by parens.

**for of:**

```js
for (expression1 of expression2) {}
```

If `expression1` starts with `let` or `async of`, it should be wrapped by parens.

**for await of:**

```js
for await (expression1 of expression2) {}
```

If `expression1` starts with `let`, it should be wrapped by parens.

Patch:
diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js
--- a/src/language-js/needs-parens.js
+++ b/src/language-js/needs-parens.js
@@ -67,16 +67,32 @@ function needsParens(path, options) {
       return true;
     }
 
-    // `for (async of []);` is invalid
+    // `for ((async) of []);` and `for ((let) of []);`
     if (
       name === "left" &&
-      node.name === "async" &&
+      (node.name === "async" || node.name === "let") &&
       parent.type === "ForOfStatement" &&
       !parent.await
     ) {
       return true;
     }
 
+    // `for ((let.a) of []);`
+    if (node.name === "let") {
+      const expression = path.findAncestor(
+        (node) => node.type === "ForOfStatement"
+      )?.left;
+      if (
+        expression &&
+        startsWithNoLookaheadToken(
+          expression,
+          (leftmostNode) => leftmostNode === node
+        )
+      ) {
+        return true;
+      }
+    }
+
     // `(let)[a] = 1`
     if (
       name === "object" &&
@@ -89,8 +105,7 @@ function needsParens(path, options) {
         (node) =>
           node.type === "ExpressionStatement" ||
           node.type === "ForStatement" ||
-          node.type === "ForInStatement" ||
-          node.type === "ForOfStatement"
+          node.type === "ForInStatement"
       );
       const expression = !statement
         ? undefined


Test patch:
diff --git a/tests/format/js/identifier/for-of/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/identifier/for-of/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
--- /dev/null
+++ b/tests/format/js/identifier/for-of/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,57 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`await.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+async function a() {
+  for await((let).a of foo);
+  for await((let)[a] of foo);
+  for await((let)()[a] of foo);
+}
+
+=====================================output=====================================
+async function a() {
+  for await ((let).a of foo);
+  for await ((let)[a] of foo);
+  for await ((let)()[a] of foo);
+}
+
+================================================================================
+`;
+
+exports[`let.js format 1`] = `
+====================================options=====================================
+parsers: ["babel", "typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+for ((let) of foo);
+for (foo of let);
+for (foo of let.a);
+for (foo of let[a]);
+for ((let.a) of foo);
+for ((let[a]) of foo);
+for ((let)().a of foo);
+for (letFoo of foo);
+
+for ((let.a) in foo);
+for ((let[a]) in foo);
+
+=====================================output=====================================
+for ((let) of foo);
+for (foo of let);
+for (foo of let.a);
+for (foo of let[a]);
+for ((let).a of foo);
+for ((let)[a] of foo);
+for ((let)().a of foo);
+for (letFoo of foo);
+
+for (let.a in foo);
+for ((let)[a] in foo);
+
+================================================================================
+`;
diff --git a/tests/format/js/identifier/for-of/await.js b/tests/format/js/identifier/for-of/await.js
new file mode 100644
--- /dev/null
+++ b/tests/format/js/identifier/for-of/await.js
@@ -0,0 +1,5 @@
+async function a() {
+  for await((let).a of foo);
+  for await((let)[a] of foo);
+  for await((let)()[a] of foo);
+}
diff --git a/tests/format/js/identifier/for-of/jsfmt.spec.js b/tests/format/js/identifier/for-of/jsfmt.spec.js
new file mode 100644
--- /dev/null
+++ b/tests/format/js/identifier/for-of/jsfmt.spec.js
@@ -0,0 +1,5 @@
+run_spec(__dirname, [
+  "babel",
+  // "flow",
+  "typescript",
+]);
diff --git a/tests/format/js/identifier/for-of/let.js b/tests/format/js/identifier/for-of/let.js
new file mode 100644
--- /dev/null
+++ b/tests/format/js/identifier/for-of/let.js
@@ -0,0 +1,11 @@
+for ((let) of foo);
+for (foo of let);
+for (foo of let.a);
+for (foo of let[a]);
+for ((let.a) of foo);
+for ((let[a]) of foo);
+for ((let)().a of foo);
+for (letFoo of foo);
+
+for ((let.a) in foo);
+for ((let[a]) in foo);