Repository: prettier/prettier (JavaScript)
Date: 2023-03-21 07:41:07+00:00
Task type: Bug Fix

Issue:
Decorators on accessor private fields report SyntaxError
**Prettier 2.8.5**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAucAbAhgZ0wAgII7AA6UOOAAgCZxgAUAlDumGHNhAE44DEAZgJZxUlANwkAviAA0ICAAcY-aJmSh0HDhADuABXUIVKdKi3oAnipkAjDswDWcGAGV0AWzgAZflDjJexzDhrWzAHZzlmbwBzZBgOAFcgkDhXKzhKakoPdCgo+PQouAAxTld0GEVc5BB0eJgIaRAACxhXVAB1Jv54TAjWJwNu-gA3brNqsGxG70COGB1bKLK-AKSAK0wADydo1DgARXiIeBXUQJkIjlnqmDM5NjAOfgVGuSfYdv5KGCbkAA4AAwXTSBdq2OTVN5sOAcYa+GQARyO8AW8kMNUwAFofOl0o0OHAkfwCQsCsskP4zklAq5+LEEtTdnA8BUnlY6nAdDCvD5TucQJgmYdjr4KasZDB0FZPt9fkgAEwS2z8VDRADCEFc5OSmAArI14oEACpSwyU-nDRIASSg1FgTkezxgeFtTluez5cHE4iAA)
<!-- prettier-ignore -->
```sh
--parser typescript
```

**Input:**
<!-- prettier-ignore -->
```tsx
class A {
  @dec() accessor #field;
}
```

**Output:**
<!-- prettier-ignore -->
```tsx
SyntaxError: Decorators are not valid here. (2:3)
  1 | class A {
> 2 |   @dec() accessor #field;
    |   ^^^^^^
  3 | }
```

**Expected behavior:**

No SyntaxError. Change the parser from "typescript" to "babel-ts" will make it work.

Prettier 2.8.4 works fine, seems 2.8.5 introduced this.



Issue discussion:
TypeScript seems don't allow it [playground](https://www.typescriptlang.org/play?#code/MYGwhgzhAECC0G8BQ1oAEAmBTYAKAlNGMMFlAPYBO0AxAGYCWWIGA3EgL5A)
You should unset the `experimentalDecorators` option. While it's enabled, it uses stage 2 decorator proposal, not the latest stage 3 proposal

Solution patch:
diff --git a/changelog_unreleased/typescript/14548.md b/changelog_unreleased/typescript/14548.md
new file mode 100644
index 000000000000..82262a8462ea
--- /dev/null
+++ b/changelog_unreleased/typescript/14548.md
@@ -0,0 +1,24 @@
+#### Allow decorators on private members and class expressions (#14548 by @fisker)
+
+<!-- prettier-ignore -->
+```ts
+// Input
+class A {
+  @decorator()
+  #privateMethod () {}
+}
+
+// Prettier stable
+SyntaxError: Decorators are not valid here. (2:3)
+  1 | class A {
+> 2 |   @decorator()
+    |   ^^^^^^^^^^^^
+  3 |   #privateMethod () {}
+  4 | }
+
+// Prettier main
+class A {
+  @decorator()
+  #privateMethod() {}
+}
+```
diff --git a/src/language-js/parse/postprocess/typescript.js b/src/language-js/parse/postprocess/typescript.js
index 9b343a766a74..926f60432b8e 100644
--- a/src/language-js/parse/postprocess/typescript.js
+++ b/src/language-js/parse/postprocess/typescript.js
@@ -30,6 +30,20 @@ function throwErrorOnTsNode(node, message) {
   throwSyntaxError({ loc: { start, end } }, message);
 }
 
+function nodeCanBeDecorated(node) {
+  const ts = require("typescript");
+
+  return [true, false].some((useLegacyDecorators) =>
+    // @ts-expect-error -- internal?
+    ts.nodeCanBeDecorated(
+      useLegacyDecorators,
+      node,
+      node.parent,
+      node.parent.parent
+    )
+  );
+}
+
 // Invalid decorators are removed since `@typescript-eslint/typescript-estree` v4
 // https://github.com/typescript-eslint/typescript-eslint/pull/2375
 // There is a `checkGrammarDecorators` in `typescript` package, consider use it directly in future
@@ -44,16 +58,7 @@ function throwErrorForInvalidDecorator(node) {
   const { SyntaxKind } = ts;
   for (const modifier of modifiers) {
     if (ts.isDecorator(modifier)) {
-      const legacyDecorators = true;
-      if (
-        // @ts-expect-error -- internal?
-        !ts.nodeCanBeDecorated(
-          legacyDecorators,
-          node,
-          node.parent,
-          node.parent.parent
-        )
-      ) {
+      if (!nodeCanBeDecorated(node)) {
         if (
           node.kind === SyntaxKind.MethodDeclaration &&
           // @ts-expect-error -- internal?
@@ -67,9 +72,8 @@ function throwErrorForInvalidDecorator(node) {
           throwErrorOnTsNode(modifier, "Decorators are not valid here.");
         }
       } else if (
-        legacyDecorators &&
-        (node.kind === SyntaxKind.GetAccessor ||
-          node.kind === SyntaxKind.SetAccessor)
+        node.kind === SyntaxKind.GetAccessor ||
+        node.kind === SyntaxKind.SetAccessor
       ) {
         // @ts-expect-error -- internal?
         const accessors = ts.getAllAccessorDeclarations(


Test patch:
diff --git a/tests/format/js/decorators/class-expression/__snapshots__/jsfmt.spec.js.snap b/tests/format/js/decorators/class-expression/__snapshots__/jsfmt.spec.js.snap
index 123d72efa2a2..38356821e791 100644
--- a/tests/format/js/decorators/class-expression/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/js/decorators/class-expression/__snapshots__/jsfmt.spec.js.snap
@@ -24,14 +24,6 @@ exports[`arguments.js [flow] format 1`] = `
   3 |"
 `;
 
-exports[`arguments.js [typescript] format 1`] = `
-"Decorators are not valid here. (1:13)
-> 1 | console.log(@deco class Foo {})
-    |             ^^^^^
-  2 | console.log(@deco class {})
-  3 |"
-`;
-
 exports[`arguments.js - {"semi":false} [acorn] format 1`] = `
 "Unexpected character '@' (1:13)
 > 1 | console.log(@deco class Foo {})
@@ -56,14 +48,6 @@ exports[`arguments.js - {"semi":false} [flow] format 1`] = `
   3 |"
 `;
 
-exports[`arguments.js - {"semi":false} [typescript] format 1`] = `
-"Decorators are not valid here. (1:13)
-> 1 | console.log(@deco class Foo {})
-    |             ^^^^^
-  2 | console.log(@deco class {})
-  3 |"
-`;
-
 exports[`arguments.js - {"semi":false} format 1`] = `
 ====================================options=====================================
 parsers: ["babel", "flow", "typescript"]
@@ -144,15 +128,6 @@ exports[`class-expression.js [flow] format 1`] = `
   4 | (@deco class Foo {});"
 `;
 
-exports[`class-expression.js [typescript] format 1`] = `
-"Decorators are not valid here. (1:13)
-> 1 | const a1 = (@deco class Foo {});
-    |             ^^^^^
-  2 | const a2 = (@deco class {});
-  3 |
-  4 | (@deco class Foo {});"
-`;
-
 exports[`class-expression.js - {"semi":false} [acorn] format 1`] = `
 "Unexpected character '@' (1:13)
 > 1 | const a1 = (@deco class Foo {});
@@ -180,15 +155,6 @@ exports[`class-expression.js - {"semi":false} [flow] format 1`] = `
   4 | (@deco class Foo {});"
 `;
 
-exports[`class-expression.js - {"semi":false} [typescript] format 1`] = `
-"Decorators are not valid here. (1:13)
-> 1 | const a1 = (@deco class Foo {});
-    |             ^^^^^
-  2 | const a2 = (@deco class {});
-  3 |
-  4 | (@deco class Foo {});"
-`;
-
 exports[`class-expression.js - {"semi":false} format 1`] = `
 ====================================options=====================================
 parsers: ["babel", "flow", "typescript"]
@@ -336,14 +302,6 @@ exports[`member-expression.js [flow] format 1`] = `
   3 |"
 `;
 
-exports[`member-expression.js [typescript] format 1`] = `
-"Decorators are not valid here. (1:2)
-> 1 | (@deco class Foo {}).name;
-    |  ^^^^^
-  2 | (@deco class {}).name;
-  3 |"
-`;
-
 exports[`member-expression.js - {"semi":false} [acorn] format 1`] = `
 "Unexpected character '@' (1:2)
 > 1 | (@deco class Foo {}).name;
@@ -368,14 +326,6 @@ exports[`member-expression.js - {"semi":false} [flow] format 1`] = `
   3 |"
 `;
 
-exports[`member-expression.js - {"semi":false} [typescript] format 1`] = `
-"Decorators are not valid here. (1:2)
-> 1 | (@deco class Foo {}).name;
-    |  ^^^^^
-  2 | (@deco class {}).name;
-  3 |"
-`;
-
 exports[`member-expression.js - {"semi":false} format 1`] = `
 ====================================options=====================================
 parsers: ["babel", "flow", "typescript"]
@@ -448,15 +398,6 @@ exports[`super-class.js [flow] format 1`] = `
   4 |"
 `;
 
-exports[`super-class.js [typescript] format 1`] = `
-"Decorators are not valid here. (1:20)
-> 1 | class Foo extends (@deco class Foo {}){}
-    |                    ^^^^^
-  2 |
-  3 | class Foo extends (@deco class {}){}
-  4 |"
-`;
-
 exports[`super-class.js - {"semi":false} [acorn] format 1`] = `
 "Unexpected character '@' (1:20)
 > 1 | class Foo extends (@deco class Foo {}){}
@@ -484,15 +425,6 @@ exports[`super-class.js - {"semi":false} [flow] format 1`] = `
   4 |"
 `;
 
-exports[`super-class.js - {"semi":false} [typescript] format 1`] = `
-"Decorators are not valid here. (1:20)
-> 1 | class Foo extends (@deco class Foo {}){}
-    |                    ^^^^^
-  2 |
-  3 | class Foo extends (@deco class {}){}
-  4 |"
-`;
-
 exports[`super-class.js - {"semi":false} format 1`] = `
 ====================================options=====================================
 parsers: ["babel", "flow", "typescript"]
diff --git a/tests/format/js/decorators/class-expression/jsfmt.spec.js b/tests/format/js/decorators/class-expression/jsfmt.spec.js
index b7c768416a69..028ed3fa8eac 100644
--- a/tests/format/js/decorators/class-expression/jsfmt.spec.js
+++ b/tests/format/js/decorators/class-expression/jsfmt.spec.js
@@ -1,6 +1,5 @@
 const errors = {
   flow: true,
-  typescript: true,
   acorn: true,
   espree: true,
 };
diff --git a/tests/format/typescript/decorators/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/decorators/__snapshots__/jsfmt.spec.js.snap
index 97157ebd0b56..501c86771b3b 100644
--- a/tests/format/typescript/decorators/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/typescript/decorators/__snapshots__/jsfmt.spec.js.snap
@@ -497,6 +497,48 @@ class MyContainerComponent {
 ================================================================================
 `;
 
+exports[`legacy.ts format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+[
+  @decorator() class {},
+  @decorator() class A {},
+];
+
+class A {
+  @decorator() accessor #field;
+}
+
+class B {
+  @decorator() #field () {}
+}
+
+=====================================output=====================================
+[
+  (
+    @decorator()
+    class {}
+  ),
+  (
+    @decorator()
+    class A {}
+  ),
+];
+
+class A {
+  @decorator() accessor #field;
+}
+
+class B {
+  @decorator() #field() {}
+}
+
+================================================================================
+`;
+
 exports[`mobx.ts format 1`] = `
 ====================================options=====================================
 parsers: ["typescript"]
diff --git a/tests/format/typescript/decorators/legacy.ts b/tests/format/typescript/decorators/legacy.ts
new file mode 100644
index 000000000000..450e40cbcbba
--- /dev/null
+++ b/tests/format/typescript/decorators/legacy.ts
@@ -0,0 +1,12 @@
+[
+  @decorator() class {},
+  @decorator() class A {},
+];
+
+class A {
+  @decorator() accessor #field;
+}
+
+class B {
+  @decorator() #field () {}
+}
diff --git a/tests/format/typescript/type-arguments-bit-shift-left-like/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/type-arguments-bit-shift-left-like/__snapshots__/jsfmt.spec.js.snap
index 8c1278a79a21..409110a22a02 100644
--- a/tests/format/typescript/type-arguments-bit-shift-left-like/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/typescript/type-arguments-bit-shift-left-like/__snapshots__/jsfmt.spec.js.snap
@@ -49,7 +49,7 @@ exports[`3.ts [typescript] format 1`] = `
   2 |"
 `;
 
-exports[`4.ts [babel-ts] format 1`] = `
+exports[`4.ts format 1`] = `
 ====================================options=====================================
 parsers: ["typescript"]
 printWidth: 80
@@ -66,13 +66,6 @@ printWidth: 80
 ================================================================================
 `;
 
-exports[`4.ts [typescript] format 1`] = `
-"Decorators are not valid here. (1:2)
-> 1 | (@f<<T>(v: T) => void>() class {});
-    |  ^^^^^^^^^^^^^^^^^^^^^^^
-  2 |"
-`;
-
 exports[`5.tsx [babel-ts] format 1`] = `
 ====================================options=====================================
 parsers: ["typescript"]
diff --git a/tests/format/typescript/type-arguments-bit-shift-left-like/jsfmt.spec.js b/tests/format/typescript/type-arguments-bit-shift-left-like/jsfmt.spec.js
index 2775ae27811f..b3acebe65b16 100644
--- a/tests/format/typescript/type-arguments-bit-shift-left-like/jsfmt.spec.js
+++ b/tests/format/typescript/type-arguments-bit-shift-left-like/jsfmt.spec.js
@@ -1,3 +1,3 @@
 run_spec(__dirname, ["typescript"], {
-  errors: { typescript: ["3.ts", "4.ts", "5.tsx"] },
+  errors: { typescript: ["3.ts", "5.tsx"] },
 });