Repository: alangpierce/sucrase
Pull request #721 (2022-07-13 19:36:34)

Fix parse failures when using ?? just after the end of a type
This was detected when running Sucrase on the Babel test suite.

codecov[bot]: # [Codecov](https://codecov.io/gh/alangpierce/sucrase/pull/721?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce) Report
> Merging [#721](https://codecov.io/gh/alangpierce/sucrase/pull/721?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce) (2786ffd) into [main](https://codecov.io/gh/alangpierce/sucrase/commit/d304d24f4aa572df0dee787586f278ba6c9cae62?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce) (d304d24) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

```diff
@@           Coverage Diff           @@
##             main     #721   +/-   ##
=======================================
  Coverage   87.03%   87.04%           
=======================================
  Files          54       54           
  Lines        5700     5704    +4     
  Branches     1341     1342    +1     
=======================================
+ Hits         4961     4965    +4     
  Misses        470      470           
  Partials      269      269           
```


| [Impacted Files](https://codecov.io/gh/alangpierce/sucrase/pull/721?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce) | Coverage Δ | |
|---|---|---|
| [src/parser/plugins/typescript.ts](https://codecov.io/gh/alangpierce/sucrase/pull/721/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce#diff-c3JjL3BhcnNlci9wbHVnaW5zL3R5cGVzY3JpcHQudHM=) | `87.25% <100.00%> (+0.01%)` | :arrow_up: |
| [src/parser/tokenizer/index.ts](https://codecov.io/gh/alangpierce/sucrase/pull/721/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce#diff-c3JjL3BhcnNlci90b2tlbml6ZXIvaW5kZXgudHM=) | `86.14% <100.00%> (+0.08%)` | :arrow_up: |
| [src/parser/traverser/expression.ts](https://codecov.io/gh/alangpierce/sucrase/pull/721/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce#diff-c3JjL3BhcnNlci90cmF2ZXJzZXIvZXhwcmVzc2lvbi50cw==) | `88.41% <100.00%> (ø)` | |

:mega: Codecov can now indicate which changes are the most critical in Pull Requests. [Learn more](https://about.codecov.io/product/feature/runtime-insights/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce)


Patch:
diff --git a/src/parser/plugins/typescript.ts b/src/parser/plugins/typescript.ts
index 1095061..cf57085 100644
--- a/src/parser/plugins/typescript.ts
+++ b/src/parser/plugins/typescript.ts
@@ -1242,6 +1242,9 @@ export function tsParseSubscript(
       // Tagged template with a type argument.
       parseTemplate();
     } else if (
+      // The remaining possible case is an instantiation expression, e.g.
+      // Array<number> . Check for a few cases that would disqualify it and
+      // cause us to bail out.
       // a<b>>c is not (a<b>)>c, but a<(b>>c)
       state.type === tt.greaterThan ||
       // a<b>c is (a<b)>c
diff --git a/src/parser/tokenizer/index.ts b/src/parser/tokenizer/index.ts
index 3349a60..b02acb3 100644
--- a/src/parser/tokenizer/index.ts
+++ b/src/parser/tokenizer/index.ts
@@ -536,7 +536,7 @@ 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 >= .
  */
 export function rescan_gt(): void {
   if (state.type === tt.greaterThan) {
@@ -565,7 +565,12 @@ function readToken_question(): void {
   // '?'
   const nextChar = input.charCodeAt(state.pos + 1);
   const nextChar2 = input.charCodeAt(state.pos + 2);
-  if (nextChar === charCodes.questionMark && !state.isType) {
+  if (
+    nextChar === charCodes.questionMark &&
+    // In Flow (but not TypeScript), ??string is a valid type that should be
+    // tokenized as two individual ? tokens.
+    !(isFlowEnabled && state.isType)
+  ) {
     if (nextChar2 === charCodes.equalsTo) {
       // '??='
       finishOp(tt.assign, 3);


Test patch:
diff --git a/spec-compliance-tests/babel-tests/check-babel-tests.ts b/spec-compliance-tests/babel-tests/check-babel-tests.ts
index 01f78d1..6ecae67 100644
--- a/spec-compliance-tests/babel-tests/check-babel-tests.ts
+++ b/spec-compliance-tests/babel-tests/check-babel-tests.ts
@@ -67,7 +67,6 @@ flow/scope/declare-module
 flow/this-annotation/function-type
 flow/typecasts/yield
 jsx/basic/3
-typescript/cast/as
 typescript/export/as-namespace
 typescript/import/export-import
 typescript/import/export-import-require
@@ -76,7 +75,6 @@ typescript/import/export-import-type-require
 typescript/import/import-default-id-type
 typescript/import/type-asi
 typescript/import/type-equals-require
-typescript/type-arguments/instantiation-expression-binary-operator
 `
   .split("\n")
   .filter((s) => s);
diff --git a/test/flow-test.ts b/test/flow-test.ts
index cdad922..b919f2d 100644
--- a/test/flow-test.ts
+++ b/test/flow-test.ts
@@ -688,4 +688,15 @@ describe("transform flow", () => {
     `,
     );
   });
+
+  it("handles two ? operators in a row", () => {
+    assertFlowResult(
+      `
+      type T = ??number;
+    `,
+      `"use strict";
+      
+    `,
+    );
+  });
 });
diff --git a/test/typescript-test.ts b/test/typescript-test.ts
index a657a9a..b220202 100644
--- a/test/typescript-test.ts
+++ b/test/typescript-test.ts
@@ -5,6 +5,7 @@ import {
   IMPORT_DEFAULT_PREFIX,
   IMPORT_WILDCARD_PREFIX,
   JSX_PREFIX,
+  NULLISH_COALESCE_PREFIX,
   OPTIONAL_CHAIN_PREFIX,
 } from "./prefixes";
 import {assertResult, devProps} from "./util";
@@ -2388,15 +2389,17 @@ describe("typescript transform", () => {
     );
   });
 
-  it("properly handles a >= symbol after an `as` cast", () => {
+  it("properly handles >= and ?? after `as`", () => {
     assertTypeScriptResult(
       `
       const x: string | number = 1;
       if (x as number >= 5) {}
+      if (y as unknown ?? false) {}
     `,
-      `"use strict";
+      `"use strict";${NULLISH_COALESCE_PREFIX}
       const x = 1;
       if (x  >= 5) {}
+      if (_nullishCoalesce(y , () => ( false))) {}
     `,
     );
   });
@@ -3214,6 +3217,18 @@ describe("typescript transform", () => {
     );
   });
 
+  it("allows instantiation expressions followed by ??", () => {
+    assertResult(
+      `
+      const foo = a<b> ?? c;
+    `,
+      `${NULLISH_COALESCE_PREFIX}
+      const foo = _nullishCoalesce(a, () => ( c));
+    `,
+      {transforms: ["typescript"]},
+    );
+  });
+
   it("allows extends constraints on infer type variables", () => {
     assertResult(
       `