Repository: alangpierce/sucrase (ts)

Issue:
`disableESTransform` has unexpected side effects
```js
import { transform } from "sucrase";

const tsCode = `abstract class Test {
    abstract readonly result: string
  }
`;

console.log({
  withESTransform: transform(tsCode, {
    transforms: ["typescript"],
    disableESTransforms: false,
  }).code,

  withoutESTransform: transform(tsCode, {
    transforms: ["typescript"],
    disableESTransforms: true,
  }).code,
});
```

Result:

```js
{
  withESTransform: ' class Test {\n    \n  }\n',
  withoutESTransform: ' class Test {\n      result\n  }\n'
}
```

TypeScript has the same output as the `withESTransform` one, and that's the one I expected: https://www.typescriptlang.org/play?#code/IYIwzgLgTsDGEAJYBthjAgKgU0gg3gLABQCCokM8CU2wAJgPYB2yAnjbgK7IQBcCSgEtmAcxIBfEkA

For context, I'm using `disableESTransforms` because I want to keep the code as close as possible to the source.

It matters to me because my code looks like this:

```typescript
abstract class Parent {
  abstract readonly result: string
}
class Child extends Parent {
  get result() { return 'expected' }
}
console.log(new Child().result) // undefined instead of 'expected'
```

Pull request description:
Remove abstract fields when disableESTransforms is enabled
Fixes #731

`disableESTransforms` is intended to match the more spec-compliant mode
`useDefineForClassFields` provided by TypeScript, but it looks like in TS 4.2
they updated the behavior so that `abstract` fields are removed in addition to
`declare` fields:
https://github.com/microsoft/TypeScript/pull/40699

In Sucrase, we can implement the same behavior by simply testing for `abstract`
as well in the place where we test for `declare`.

Patch:
diff --git a/src/util/getClassInfo.ts b/src/util/getClassInfo.ts
index 2d6d09c..80aee82 100644
--- a/src/util/getClassInfo.ts
+++ b/src/util/getClassInfo.ts
@@ -83,7 +83,7 @@ export default function getClassInfo(
       const statementStartIndex = tokens.currentIndex();
       let isStatic = false;
       let isESPrivate = false;
-      let isDeclare = false;
+      let isDeclareOrAbstract = false;
       while (isAccessModifier(tokens.currentToken())) {
         if (tokens.matches1(tt._static)) {
           isStatic = true;
@@ -91,8 +91,8 @@ export default function getClassInfo(
         if (tokens.matches1(tt.hash)) {
           isESPrivate = true;
         }
-        if (tokens.matches1(tt._declare)) {
-          isDeclare = true;
+        if (tokens.matches1(tt._declare) || tokens.matches1(tt._abstract)) {
+          isDeclareOrAbstract = true;
         }
         tokens.nextToken();
       }
@@ -151,12 +151,12 @@ export default function getClassInfo(
           start: nameStartIndex,
           end: tokens.currentIndex(),
         });
-      } else if (!disableESTransforms || isDeclare) {
+      } else if (!disableESTransforms || isDeclareOrAbstract) {
         // This is a regular field declaration, like `x;`. With the class transform enabled, we just
         // remove the line so that no output is produced. With the class transform disabled, we
         // usually want to preserve the declaration (but still strip types), but if the `declare`
-        // keyword is specified, we should remove the line to avoid initializing the value to
-        // undefined.
+        // or `abstract` keyword is specified, we should remove the line to avoid initializing the
+        // value to undefined.
         rangesToRemove.push({start: statementStartIndex, end: tokens.currentIndex()});
       }
     }


Test patch:
diff --git a/test/typescript-test.ts b/test/typescript-test.ts
index 16bde5d..1f70928 100644
--- a/test/typescript-test.ts
+++ b/test/typescript-test.ts
@@ -3078,18 +3078,24 @@ describe("typescript transform", () => {
     assertTypeScriptESMResult(
       `
       class A {
-        abstract readonly a: number = 3;
+        abstract readonly a: number;
         declare b: string;
         declare static c: string;
         static declare d: string;
+        readonly e: string;
+        private f: number;
+        public g: string = 'hello';
       }
     `,
       `
       class A {
-          a = 3;
         ;
         ;
         ;
+        ;
+         e;
+         f;
+         g = 'hello';
       }
     `,
       {disableESTransforms: true},