Repository: alangpierce/sucrase (ts)

Issue:
Failure to parse some usages of Flow $Call (Unexpected token, expected ",")
Sucrase fails to parse some usages of the type helper `$Call` inside Flow code :

Example of code failing : 

```js
// @flow

type Account = $PropertyType<
  $Call<<T>($ReadOnlyArray<T>) => T>,
  'account'
>
```

Demo :
* [Sucrase (failing)](https://sucrase.io/#transforms=flow&code=%2F%2F%20%40flow%0A%0Atype%20Account%20%3D%20%24PropertyType%3C%0A%20%20%24Call%3C%3CT%3E(%24ReadOnlyArray%3CT%3E)%20%3D%3E%20T%3E%2C%0A%20%20'account'%0A%3E)
* [Flow compiler (accepted)](https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAoVAXAngBwKZgCCAxsXAK4B2GYAvGACQAKATnPi9gCq54A8qMIwDCAQxgw+fLgD4AFAwBKeUQBMA8pRhZCLFqKzSZASjoywsgDSCwAclGkK1W6hmogA)

Pull request:
Fix parsing of << within a type
Fixes #758

There was already some logic from #716 to handle cases like `f<<T>() => void>`, where we're transitioning from a non-type context to a type context and need to detect if `<<` is actually two open-type-parameter/argument tokens. There was a simpler missing case, though, which is that `<<` is never allowed within a type, so we should tokenize as a simple `<` in a
type context. To handle the various cases correctly, this extra logic needs to only happen for `<<`, and this PR also adds
some additional comments explaining the nuances.

Patch:
diff --git a/src/parser/plugins/typescript.ts b/src/parser/plugins/typescript.ts
index cf57085..0a9eabf 100644
--- a/src/parser/plugins/typescript.ts
+++ b/src/parser/plugins/typescript.ts
@@ -1138,9 +1138,7 @@ function tsTryParseGenericAsyncArrowFunction(): boolean {
  * where bitshift would be illegal anyway (e.g. in a class "extends" clause).
  *
  * This hack is useful to handle situations like foo<<T>() => void>() where
- * there can legitimately be two open-angle-brackets in a row in TS. This
- * situation is very obscure and (as of this writing) is handled by Babel but
- * not TypeScript itself, so it may be fine in the future to remove this case.
+ * there can legitimately be two open-angle-brackets in a row in TS.
  */
 function tsParseTypeArgumentsWithPossibleBitshift(): void {
   if (state.type === tt.bitShiftL) {
diff --git a/src/parser/tokenizer/index.ts b/src/parser/tokenizer/index.ts
index 316f782..953fd7a 100644
--- a/src/parser/tokenizer/index.ts
+++ b/src/parser/tokenizer/index.ts
@@ -512,11 +512,25 @@ function readToken_lt(): void {
       finishOp(tt.assign, 3);
       return;
     }
-    // This still might be two instances of <, e.g. the TS type argument
-    // expression f<<T>() => void>() , but parse as left shift for now and we'll
-    // retokenize if necessary. We can't use isType for this case because we
-    // don't know yet if we're in a type.
-    finishOp(tt.bitShiftL, 2);
+    // We see <<, but need to be really careful about whether to treat it as a
+    // true left-shift or as two < tokens.
+    if (state.isType) {
+      // Within a type, << might come up in a snippet like `Array<<T>() => void>`,
+      // so treat it as two < tokens. Importantly, this should only override <<
+      // rather than other tokens like <= . If we treated <= as < in a type
+      // context, then the snippet `a as T <= 1` would incorrectly start parsing
+      // a type argument on T. We don't need to worry about `a as T << 1`
+      // because TypeScript disallows that syntax.
+      finishOp(tt.lessThan, 1);
+    } else {
+      // Outside a type, this might be a true left-shift operator, or it might
+      // still be two open-type-arg tokens, such as in `f<<T>() => void>()`. We
+      // look at the token while considering the `f`, so we don't yet know that
+      // we're in a type context. In this case, we initially tokenize as a
+      // left-shift and correct after-the-fact as necessary in
+      // tsParseTypeArgumentsWithPossibleBitshift .
+      finishOp(tt.bitShiftL, 2);
+    }
     return;
   }
 
@@ -558,7 +572,14 @@ function readToken_gt(): void {
 
 /**
  * Called after `as` expressions in TS; we're switching from a type to a
- * non-type context, so a > token may actually be >= .
+ * non-type context, so a > token may actually be >= . This is needed because >=
+ * must be tokenized as a > in a type context because of code like
+ * `const x: Array<T>=[];`, but `a as T >= 1` is a code example where it must be
+ * treated as >=.
+ *
+ * Notably, this only applies to >, not <. In a code snippet like `a as T <= 1`,
+ * we must NOT tokenize as <, or else the type parser will start parsing a type
+ * argument and fail.
  */
 export function rescan_gt(): void {
   if (state.type === tt.greaterThan) {


Test patch:
diff --git a/test/flow-test.ts b/test/flow-test.ts
index b919f2d..e005af7 100644
--- a/test/flow-test.ts
+++ b/test/flow-test.ts
@@ -699,4 +699,15 @@ describe("transform flow", () => {
     `,
     );
   });
+
+  it("properly parses flow type args that look like left shift", () => {
+    assertFlowResult(
+      `
+      type A = B<<T>() => void>;
+    `,
+      `"use strict";
+      
+    `,
+    );
+  });
 });
diff --git a/test/typescript-test.ts b/test/typescript-test.ts
index 1d3f2b3..7d66114 100644
--- a/test/typescript-test.ts
+++ b/test/typescript-test.ts
@@ -2500,6 +2500,19 @@ describe("typescript transform", () => {
     );
   });
 
+  it("properly handles <= after `as` and `satisfies`", () => {
+    assertTypeScriptResult(
+      `
+      if (x as number <= 5) {}
+      if (x satisfies number <= 5) {}
+    `,
+      `"use strict";
+      if (x  <= 5) {}
+      if (x  <= 5) {}
+    `,
+    );
+  });
+
   it("handles simple template literal interpolations in types", () => {
     assertTypeScriptResult(
       `
@@ -3161,7 +3174,7 @@ describe("typescript transform", () => {
     );
   });
 
-  it("properly parses TS angle brackets that look like left shift", () => {
+  it("properly parses TS function type args that look like left shift", () => {
     assertResult(
       `
       f<<T>(value: T) => void>(g);
@@ -3173,6 +3186,18 @@ describe("typescript transform", () => {
     );
   });
 
+  it("properly parses TS type args that look like left shift", () => {
+    assertResult(
+      `
+      type A = B<<T>() => void>;
+    `,
+      `
+      
+    `,
+      {transforms: ["typescript"]},
+    );
+  });
+
   it("properly parses actual left shift", () => {
     assertResult(
       `