Repository: alangpierce/sucrase
Pull request #785 (2023-03-19 21:26:34)

Port babel-parser changes from 2022-10-26 to 2022-12-22
Instructions: https://github.com/alangpierce/sucrase/wiki/Porting-changes-from-Babel's-parser

dfc4b61856 Parse import reflection (#14926)
✅ Implemented a basic version with tests, without any attempt at CJS support or type import support since the behavior appears unspecified at this point.

83009601f4 Parse `using` declaration (explicit resource management) (#14968)
✅ Implemented in a similar way with some simplifications since error handling isn't necessary.

04509a3be6 v7.20.0
🚫 Release only.

292e5d6b67 fix: parse `a satisfies b` as left value (#15096)
✅ Already worked, but added a test.

283720d252 fix: Parse re-declare var in class static body  (#15102)
🚫 Error handling bug that didn't affect Sucrase.

cfc453276a Fix(estree): Reset export's start after decorator (#15107)
🚫 estree not relevant to Sucrase.

1893249b67 v7.20.1
🚫 Release only.

b5a6931c23 fix: `parser` typings for plugins (#15094)
🚫 Babel-internal change.

12a58cb58c v7.20.2
🚫 Release only.

ce09a269d8 fix: support await as for-of-lhs (#15134)
🚫 Sucrase assumes modules, and `await` is a reserved word in modules.

7c9a8015fe v7.20.3
🚫 Release only.

5fde28b37e fix: Babel 8 types (#15109)
🚫 Babel-internal change.

4dedd57f64 fix: parse `import module, ...` (#15198)
✅ Fixed in implementation above.

f6546d79d4 Bump typescript to 4.9.3 (#15202)
🚫 Babel-internal change.

4c59d9fb2c Export `ParseResult` type (#15207)
🚫 Babel-internal change.

4369b33100 Parse `using[foo]` as computed member expression (#15225)
✅ Fixed with a simpler lookahead approach.

29a97b8ea2 v7.20.5
🚫 Release only.

f543b61bab refactor: remove ModuleDeclaration usage (#15236)
🚫 AST only.

362451b764 chore: Enable eslint rule `no-unnecessary-type-assertion` (#15260)
🚫 Babel-internal change.

1203f1858d chore: Clean up `parser` comments (#15252)
🚫 Babel-internal change.

3a71c79a56 fix: Support auto accessors with TypeScript annotations (#15209)
✅ This was already working in Sucrase, but added a test.

d414940e4c v7.20.7
🚫 Release only.

codecov[bot]: ## [Codecov](https://codecov.io/gh/alangpierce/sucrase/pull/785?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce) Report
> Merging [#785](https://codecov.io/gh/alangpierce/sucrase/pull/785?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce) (81ecd44) into [main](https://codecov.io/gh/alangpierce/sucrase/commit/bf0af525518aaf21517c8399418a4fff1809fbe1?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce) (bf0af52) will **increase** coverage by `0.12%`.
> The diff coverage is `91.17%`.

```diff
@@            Coverage Diff             @@
##             main     #785      +/-   ##
==========================================
+ Coverage   87.61%   87.74%   +0.12%     
==========================================
  Files          55       55              
  Lines        5888     5915      +27     
  Branches     1395     1404       +9     
==========================================
+ Hits         5159     5190      +31     
+ Misses        461      457       -4     
  Partials      268      268              
```


| [Impacted Files](https://codecov.io/gh/alangpierce/sucrase/pull/785?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/785?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce#diff-c3JjL3BhcnNlci9wbHVnaW5zL3R5cGVzY3JpcHQudHM=) | `87.48% <0.00%> (ø)` | |
| [src/parser/tokenizer/readWordTree.ts](https://codecov.io/gh/alangpierce/sucrase/pull/785?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce#diff-c3JjL3BhcnNlci90b2tlbml6ZXIvcmVhZFdvcmRUcmVlLnRz) | `100.00% <ø> (ø)` | |
| [src/parser/traverser/statement.ts](https://codecov.io/gh/alangpierce/sucrase/pull/785?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce#diff-c3JjL3BhcnNlci90cmF2ZXJzZXIvc3RhdGVtZW50LnRz) | `84.98% <93.10%> (+0.90%)` | :arrow_up: |
| [src/parser/tokenizer/keywords.ts](https://codecov.io/gh/alangpierce/sucrase/pull/785?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce#diff-c3JjL3BhcnNlci90b2tlbml6ZXIva2V5d29yZHMudHM=) | `100.00% <100.00%> (ø)` | |
| [src/transformers/ESMImportTransformer.ts](https://codecov.io/gh/alangpierce/sucrase/pull/785?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce#diff-c3JjL3RyYW5zZm9ybWVycy9FU01JbXBvcnRUcmFuc2Zvcm1lci50cw==) | `94.96% <100.00%> (+0.09%)` | :arrow_up: |

... and [1 file with indirect coverage changes](https://codecov.io/gh/alangpierce/sucrase/pull/785/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce)

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Alan+Pierce)

github-actions[bot]: ## Benchmark results
**Before this PR:** 400.1 thousand lines per second
**After this PR:**  430.7 thousand lines per second

**Measured change:** 7.67% faster (4.46% slower to 9.55% faster)
**Summary:** Likely no significant difference

Patch:
diff --git a/generator/generateReadWordTree.ts b/generator/generateReadWordTree.ts
index 4bfadbf..3004ba3 100644
--- a/generator/generateReadWordTree.ts
+++ b/generator/generateReadWordTree.ts
@@ -80,6 +80,7 @@ const CONTEXTUAL_KEYWORDS = [
   "symbol",
   "type",
   "unique",
+  "using",
 ];
 
 const CODE = `\
diff --git a/src/parser/plugins/typescript.ts b/src/parser/plugins/typescript.ts
index 0a9eabf..9bca069 100644
--- a/src/parser/plugins/typescript.ts
+++ b/src/parser/plugins/typescript.ts
@@ -965,7 +965,7 @@ function tsTryParseDeclare(): boolean {
     case tt._var:
     case tt._let: {
       const oldIsType = pushTypeContext(1);
-      parseVarStatement(state.type);
+      parseVarStatement(state.type !== tt._var);
       popTypeContext(oldIsType);
       return true;
     }
diff --git a/src/parser/tokenizer/keywords.ts b/src/parser/tokenizer/keywords.ts
index e7ea7e8..5a29926 100644
--- a/src/parser/tokenizer/keywords.ts
+++ b/src/parser/tokenizer/keywords.ts
@@ -39,4 +39,5 @@ export enum ContextualKeyword {
   _symbol,
   _type,
   _unique,
+  _using,
 }
diff --git a/src/parser/tokenizer/readWordTree.ts b/src/parser/tokenizer/readWordTree.ts
index b61e60e..ffb8cac 100644
--- a/src/parser/tokenizer/readWordTree.ts
+++ b/src/parser/tokenizer/readWordTree.ts
@@ -5,7 +5,7 @@ import {TokenType as tt} from "./types";
 // prettier-ignore
 export const READ_WORD_TREE = new Int32Array([
   // ""
-  -1, 27, 783, 918, 1755, 2376, 2862, 3483, -1, 3699, -1, 4617, 4752, 4833, 5130, 5508, 5940, -1, 6480, 6939, 7749, 8181, 8343, 8505, -1, 8721, -1,
+  -1, 27, 783, 918, 1755, 2376, 2862, 3483, -1, 3699, -1, 4617, 4752, 4833, 5130, 5508, 5940, -1, 6480, 6939, 7749, 8181, 8451, 8613, -1, 8829, -1,
   // "a"
   -1, -1, 54, 243, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 432, -1, -1, -1, 675, -1, -1, -1,
   // "ab"
@@ -611,7 +611,7 @@ export const READ_WORD_TREE = new Int32Array([
   // "typeof"
   (tt._typeof << 1) + 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "u"
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8208, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8208, -1, -1, -1, -1, 8343, -1, -1, -1, -1, -1, -1, -1,
   // "un"
   -1, -1, -1, -1, -1, -1, -1, -1, -1, 8235, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "uni"
@@ -622,42 +622,50 @@ export const READ_WORD_TREE = new Int32Array([
   -1, -1, -1, -1, -1, 8316, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "unique"
   ContextualKeyword._unique << 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  // "us"
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, 8370, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  // "usi"
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8397, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  // "usin"
+  -1, -1, -1, -1, -1, -1, -1, 8424, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  // "using"
+  ContextualKeyword._using << 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "v"
-  -1, 8370, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8424, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  -1, 8478, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8532, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "va"
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8397, -1, -1, -1, -1, -1, -1, -1, -1,
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8505, -1, -1, -1, -1, -1, -1, -1, -1,
   // "var"
   (tt._var << 1) + 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "vo"
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, 8451, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, 8559, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "voi"
-  -1, -1, -1, -1, 8478, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  -1, -1, -1, -1, 8586, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "void"
   (tt._void << 1) + 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "w"
-  -1, -1, -1, -1, -1, -1, -1, -1, 8532, 8640, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  -1, -1, -1, -1, -1, -1, -1, -1, 8640, 8748, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "wh"
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, 8559, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, 8667, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "whi"
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8586, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8694, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "whil"
-  -1, -1, -1, -1, -1, 8613, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  -1, -1, -1, -1, -1, 8721, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "while"
   (tt._while << 1) + 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "wi"
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8667, -1, -1, -1, -1, -1, -1,
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8775, -1, -1, -1, -1, -1, -1,
   // "wit"
-  -1, -1, -1, -1, -1, -1, -1, -1, 8694, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  -1, -1, -1, -1, -1, -1, -1, -1, 8802, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "with"
   (tt._with << 1) + 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "y"
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, 8748, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, 8856, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "yi"
-  -1, -1, -1, -1, -1, 8775, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  -1, -1, -1, -1, -1, 8883, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "yie"
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8802, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8910, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "yiel"
-  -1, -1, -1, -1, 8829, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  -1, -1, -1, -1, 8937, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
   // "yield"
   (tt._yield << 1) + 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
 ]);
diff --git a/src/parser/traverser/statement.ts b/src/parser/traverser/statement.ts
index 4ba4cb0..ff641de 100644
--- a/src/parser/traverser/statement.ts
+++ b/src/parser/traverser/statement.ts
@@ -79,9 +79,11 @@ import {
   eatContextual,
   expect,
   expectContextual,
+  hasFollowingLineBreak,
   hasPrecedingLineBreak,
   isContextual,
   isLineTerminator,
+  isLookaheadContextual,
   semicolon,
   unexpected,
 } from "./util";
@@ -173,7 +175,7 @@ function parseStatementContent(declaration: boolean): void {
       if (!declaration) unexpected(); // NOTE: falls through to _var
 
     case tt._var:
-      parseVarStatement(starttype);
+      parseVarStatement(starttype !== tt._var);
       return;
 
     case tt._while:
@@ -212,6 +214,15 @@ function parseStatementContent(declaration: boolean): void {
         } else {
           state.restoreFromSnapshot(snapshot);
         }
+      } else if (
+        state.contextualKeyword === ContextualKeyword._using &&
+        !hasFollowingLineBreak() &&
+        // Statements like `using[0]` and `using in foo` aren't actual using
+        // declarations.
+        lookaheadType() === tt.name
+      ) {
+        parseVarStatement(true);
+        return;
       }
     default:
       // Do nothing.
@@ -308,6 +319,23 @@ function parseForStatement(): void {
   state.scopeDepth--;
 }
 
+/**
+ * Determine if this token is a `using` declaration (explicit resource
+ * management) as part of a loop.
+ * https://github.com/tc39/proposal-explicit-resource-management
+ */
+function isUsingInLoop(): boolean {
+  if (!isContextual(ContextualKeyword._using)) {
+    return false;
+  }
+  // This must be `for (using of`, where `using` is the name of the loop
+  // variable.
+  if (isLookaheadContextual(ContextualKeyword._of)) {
+    return false;
+  }
+  return true;
+}
+
 // Disambiguating between a `for` and a `for`/`in` or `for`/`of`
 // loop is non-trivial. Basically, we have to parse the init `var`
 // statement or expression, disallowing the `in` operator (see
@@ -333,10 +361,9 @@ function parseAmbiguousForStatement(): void {
     return;
   }
 
-  if (match(tt._var) || match(tt._let) || match(tt._const)) {
-    const varKind = state.type;
+  if (match(tt._var) || match(tt._let) || match(tt._const) || isUsingInLoop()) {
     next();
-    parseVar(true, varKind);
+    parseVar(true, state.type !== tt._var);
     if (match(tt._in) || isContextual(ContextualKeyword._of)) {
       parseForIn(forAwait);
       return;
@@ -453,9 +480,9 @@ function parseTryStatement(): void {
   }
 }
 
-export function parseVarStatement(kind: TokenType): void {
+export function parseVarStatement(isBlockScope: boolean): void {
   next();
-  parseVar(false, kind);
+  parseVar(false, isBlockScope);
   semicolon();
 }
 
@@ -543,9 +570,8 @@ function parseForIn(forAwait: boolean): void {
 
 // Parse a list of variable declarations.
 
-function parseVar(isFor: boolean, kind: TokenType): void {
+function parseVar(isFor: boolean, isBlockScope: boolean): void {
   while (true) {
-    const isBlockScope = kind === tt._const || kind === tt._let;
     parseVarHead(isBlockScope);
     if (eat(tt.eq)) {
       const eqIndex = state.tokens.length - 1;
@@ -1083,6 +1109,50 @@ function parseExportSpecifier(): void {
   }
 }
 
+/**
+ * Starting at the `module` token in an import, determine if it was truly an
+ * import reflection token or just looks like one.
+ *
+ * Returns true for:
+ * import module foo from "foo";
+ * import module from from "foo";
+ *
+ * Returns false for:
+ * import module from "foo";
+ * import module, {bar} from "foo";
+ */
+function isImportReflection(): boolean {
+  const snapshot = state.snapshot();
+  expectContextual(ContextualKeyword._module);
+  if (eatContextual(ContextualKeyword._from)) {
+    if (isContextual(ContextualKeyword._from)) {
+      state.restoreFromSnapshot(snapshot);
+      return true;
+    } else {
+      state.restoreFromSnapshot(snapshot);
+      return false;
+    }
+  } else if (match(tt.comma)) {
+    state.restoreFromSnapshot(snapshot);
+    return false;
+  } else {
+    state.restoreFromSnapshot(snapshot);
+    return true;
+  }
+}
+
+/**
+ * Eat the "module" token from the import reflection proposal.
+ * https://github.com/tc39/proposal-import-reflection
+ */
+function parseMaybeImportReflection(): void {
+  // isImportReflection does snapshot/restore, so only run it if we see the word
+  // "module".
+  if (isContextual(ContextualKeyword._module) && isImportReflection()) {
+    next();
+  }
+}
+
 // Parses import declaration.
 
 export function parseImport(): void {
@@ -1117,6 +1187,7 @@ export function parseImport(): void {
   if (match(tt.string)) {
     parseExprAtom();
   } else {
+    parseMaybeImportReflection();
     parseImportSpecifiers();
     expectContextual(ContextualKeyword._from);
     parseExprAtom();
diff --git a/src/transformers/ESMImportTransformer.ts b/src/transformers/ESMImportTransformer.ts
index 9a4db8a..6e8a0e6 100644
--- a/src/transformers/ESMImportTransformer.ts
+++ b/src/transformers/ESMImportTransformer.ts
@@ -178,6 +178,14 @@ export default class ESMImportTransformer extends Transformer {
       return false;
     }
 
+    // Skip the "module" token in import reflection.
+    if (
+      this.tokens.matchesContextual(ContextualKeyword._module) &&
+      this.tokens.matchesContextualAtIndex(this.tokens.currentIndex() + 2, ContextualKeyword._from)
+    ) {
+      this.tokens.copyToken();
+    }
+
     let foundNonTypeImport = false;
     let needsComma = false;
 


Test patch:
diff --git a/test/sucrase-test.ts b/test/sucrase-test.ts
index b774215..1fce3d4 100644
--- a/test/sucrase-test.ts
+++ b/test/sucrase-test.ts
@@ -1674,4 +1674,96 @@ describe("sucrase", () => {
       {transforms: ["jsx", "typescript"]},
     );
   });
+
+  it("passes through import reflection", () => {
+    assertResult(
+      `
+      import module foo from './foo';
+      import module from from './foo';
+      import module from './foo';
+      import module, {a} from './foo';
+    `,
+      `
+      import module foo from './foo';
+      import module from from './foo';
+      import module from './foo';
+      import module, {a} from './foo';
+    `,
+      {transforms: []},
+    );
+  });
+
+  it("does not elide imported names when used in module reflection", () => {
+    assertResult(
+      `
+      import module foo from './foo';
+      import module bar from './bar';
+      console.log(foo);
+    `,
+      `
+      import module foo from './foo';
+
+      console.log(foo);
+    `,
+      {transforms: ["typescript"]},
+    );
+  });
+
+  it("passes through explicit resource management syntax", () => {
+    assertResult(
+      `
+      using lock = acquireLock();
+      for (using x of things()) {
+        console.log(x);
+      }
+    `,
+      `
+      using lock = acquireLock();
+      for (using x of things()) {
+        console.log(x);
+      }
+    `,
+      {transforms: []},
+    );
+  });
+
+  it("is not confused by the token `using` in other contexts", () => {
+    assertResult(
+      `
+      var using = 3;
+      using
+      let x = 1;
+      using[0];
+      using in x;
+      for (using of blah()) {
+        console.log(using);
+      }
+    `,
+      `
+      var using = 3;
+      using
+      let x = 1;
+      using[0];
+      using in x;
+      for (using of blah()) {
+        console.log(using);
+      }
+    `,
+      {transforms: []},
+    );
+  });
+
+  it("correctly parses for-of loops with external loop variable", () => {
+    assertResult(
+      `
+      let a;
+      for (a of b) {}
+    `,
+      `
+      let a;
+      for (a of b) {}
+    `,
+      {transforms: []},
+    );
+  });
 });
diff --git a/test/typescript-test.ts b/test/typescript-test.ts
index a1d5627..c5399a8 100644
--- a/test/typescript-test.ts
+++ b/test/typescript-test.ts
@@ -645,6 +645,28 @@ describe("typescript transform", () => {
     );
   });
 
+  it("handles various declare declarations", () => {
+    assertTypeScriptResult(
+      `
+      declare var a;
+      declare let b;
+      declare const c;
+      declare function f() {}
+      declare class C {}
+      declare const enum E {}
+    `,
+      `
+      
+
+
+
+
+
+    `,
+      {transforms: ["typescript"]},
+    );
+  });
+
   it("handles and removes `declare module` syntax", () => {
     assertTypeScriptResult(
       `
@@ -3524,7 +3546,19 @@ describe("typescript transform", () => {
     );
   });
 
-  it("parses and removes the satisfies operator", () => {
+  it("allows satisfies in an assignment LHS", () => {
+    assertResult(
+      `
+      (a satisfies any) = null;
+    `,
+      `
+      (a ) = null;
+    `,
+      {transforms: ["typescript"]},
+    );
+  });
+
+  it("allows declare readonly fields with initializers", () => {
     assertResult(
       `
       class Foo {
@@ -3539,4 +3573,20 @@ describe("typescript transform", () => {
       {transforms: ["typescript"], disableESTransforms: true},
     );
   });
+
+  it("allows accessor properties with type annotations", () => {
+    assertResult(
+      `
+      class Foo {
+          accessor prop: string = 1;
+      }
+    `,
+      `
+      class Foo {
+          accessor prop = 1;
+      }
+    `,
+      {transforms: ["typescript"], disableESTransforms: true},
+    );
+  });
 });