Skip to content

js_parser: reject yield/await/this/return that leak into TS enum and namespace bodies - #34250

Merged
dylan-conway merged 5 commits into
mainfrom
farm/b28ba2ed/ts-enum-namespace-fn-context
Jul 15, 2026
Merged

dylan-conway merged 5 commits into
mainfrom
farm/b28ba2ed/ts-enum-namespace-fn-context

Conversation

@robobun

@robobun robobun commented Jul 15, 2026 •

Copy link
Copy Markdown
Collaborator

Repro

$ printf 'function *f() { enum x { y = yield 1 } }\n' | bun build --no-bundle /dev/stdin --loader=ts
function* f() {
  var x;
  ((x) => {
    x[x["y"] = yield 1] = "y";
  })(x ||= {});
}

yield inside a non-generator arrow is a load-time SyntaxError in every engine. esbuild and tsc both reject the input:

✘ [ERROR] Cannot use "yield" outside a generator function

Siblings that emit the same kind of invalid output on main:

input emitted into the arrow IIFE
async function f() { enum x { y = await 1 } } await 1
enum x { y = await 1 } (top-level await) await 1
class C { m() { enum x { y = this } } } this (wrong binding)
namespace x { await 1; } await 1
namespace x { return 1; } return 1
namespace x { for await (const y of []); } for await

and one case of the opposite polarity: namespace x { let await = 1; } is valid TS that esbuild accepts but bun rejects, because the module-level ForbidAll for await leaked into the body.

Cause

parse_typescript_enum_stmt parses initializers with p.parse_expr(Level::Comma) and parse_type_script_namespace_stmt parses the body with p.parse_stmts_up_to(...), neither resetting p.fn_or_arrow_data_parse first. allow_yield, allow_await, allow_super_*, is_top_level, is_this_disallowed and is_return_disallowed all inherit from the enclosing function / module, so yield/await parse as expressions and end up inside the lowered arrow.

esbuild saves fnOrArrowDataParse, assigns a fresh zero-valued struct (isThisDisallowed: true, plus isReturnDisallowed: true for namespaces), and restores it after the body (ts_parser.go).

Fix

Mirror esbuild in both sites: save fn_or_arrow_data_parse, replace with FnOrArrowDataParse { is_this_disallowed: true, ..Default::default() } (and is_return_disallowed: true for namespaces), restore after the body. The Default sets allow_await/allow_yield to AllowIdent, so the existing recovery in parse_prefix.rs reports Cannot use "yield" outside a generator function / "await" can only be used inside an "async" function.

Nested functions inside an initializer establish their own context (they overwrite fn_or_arrow_data_parse themselves), so enum x { y = (function*() { yield 1 })() } and namespace x { export const y = async () => await 1; } remain valid.

Verification

Two new it() blocks in test/bundler/transpiler/transpiler.test.js (18 assertions) covering the enum and namespace cases above, plus the dotted namespace x.y { ... } form and declare enum.

$ USE_SYSTEM_BUN=1 bun test test/bundler/transpiler/transpiler.test.js -t "rejects yield|rejects await"
2 fail
$ bun bd test test/bundler/transpiler/transpiler.test.js -t "rejects yield|rejects await"
2 pass

test/bundler/transpiler/transpiler.test.js full file: 173 pass, 0 fail. test/bundler/esbuild/ts.test.ts: 57 pass, 0 fail.


[review] gate passed · iteration 0 · 3 files touched

fails on main (without fix)
ASAN without fix: 2 failed, 22 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/bundler/transpiler/react-compiler.test.ts test/bundler/transpiler/transpiler.test.js
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (3b34d0fec)

test/bundler/transpiler/transpiler.test.js:
(pass) Bun.Transpiler > handles errors when parsing macros [7.34ms]
(pass) Bun.Transpiler > normalizes \r\n [10.81ms]
1
(pass) Bun.Transpiler > doesn't hang indefinitely #2746 [7.68ms]
(pass) Bun.Transpiler > property access inlining > bails out with spread [10.62ms]
(pass) Bun.Transpiler > property access inlining > bails out with multiple items [4.29ms]
(pass) Bun.Transpiler > property access inlining > works [3.87ms]
(pass) Bun.Transpiler > property access inlining > works nested [3.32ms]
(pass) Bun.Transpiler > TypeScript > import Foo = Baz.Bar [4.62ms]
(pass) Bun.Transpiler > TypeScript > ternary should parse correctl
... (truncated)

release without fix: 22 skipped
bun test v1.4.0-canary.1 (954851635)

test/bundler/transpiler/transpiler.test.js:
(pass) Bun.Transpiler > handles errors when parsing macros [0.12ms]
(pass) Bun.Transpiler > normalizes \r\n [0.14ms]
1
(pass) Bun.Transpiler > doesn't hang indefinitely #2746 [0.07ms]
(pass) Bun.Transpiler > property access inlining > bails out with spread [0.11ms]
(pass) Bun.Transpiler > property access inlining > bails out with multiple items [0.03ms]
(pass) Bun.Transpiler > property access inlining > works [0.03ms]
(pass) Bun.Transpiler > property access inlining > works nested [0.02ms]
(pass) Bun.Transpiler > TypeScript > import Foo = Baz.Bar [0.04ms]
(pass) Bun.Transpiler > TypeScript > ternary should parse correctly when parsing typescript fails [0.04ms]
(pass) Bun.Transpiler > TypeScript > contextual keywords used as plain identifiers keep their statements [0.25ms]
(pass) Bun.Transpiler > TypeScript > does not crash when export default abstract is an expression followed by a class [0.25ms]
(pass) Bun.Transpiler > TypeScript > scope tracking stays balanced when a contextual keyword starts a larger expression [0.21ms]
(pass) Bun.Transpiler > TypeScript > scope tracking stays balan
... (truncated)
passes on PR (with fix)
ASAN with fix: 22 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/bundler/transpiler/react-compiler.test.ts test/bundler/transpiler/transpiler.test.js
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (3b34d0fec)

test/bundler/transpiler/transpiler.test.js:
(pass) Bun.Transpiler > handles errors when parsing macros [4.71ms]
(pass) Bun.Transpiler > normalizes \r\n [9.25ms]
1
(pass) Bun.Transpiler > doesn't hang indefinitely #2746 [6.56ms]
(pass) Bun.Transpiler > property access inlining > bails out with spread [10.37ms]
(pass) Bun.Transpiler > property access inlining > bails out with multiple items [4.14ms]
(pass) Bun.Transpiler > property access inlining > works [3.81ms]
(pass) Bun.Transpiler > property access inlining > works nested [2.78ms]
(pass) Bun.Transpiler > TypeScript > import Foo = Baz.Bar [3.03ms]
(pass) Bun.Transpiler > TypeScript > ternary should parse correctly
... (truncated)

release with fix: 22 skipped
$ bun scripts/build.ts --profile=release
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
[configured] bun-profile → bun (stripped) in 800ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[0/5] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: component rust-std is up to date

  nightly-2026-05-06-x86_64-unknown-linux-gnu unchanged - rustc 1.97.0-nightly (e95e73209 2026-05-05)

info: checking for self-update (current version: 1.29.0)
�[1m�[92m   Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m   Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m   Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m   Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl
... (truncated)
diff hotspot
src/js_parser/parse/parse_typescript.rs        | 24 ++++++-
 test/bundler/transpiler/react-compiler.test.ts | 26 ++++++++
 test/bundler/transpiler/transpiler.test.js     | 90 ++++++++++++++++++++++++++
 3 files changed, 139 insertions(+), 1 deletion(-)

gate history · 4 passed · 0 rejected · iteration 0

evidence per changed file
file                                            reads  edits  tests
src/js_parser/parse/parse_typescript.rs             6      9      0
test/bundler/transpiler/react-compiler.test.ts      1      1      0
test/bundler/transpiler/transpiler.test.js          3      7      0

…espace bodies

An enum or namespace body is lowered into a non-async, non-generator
arrow IIFE, so yield/await/this/return from an enclosing function or the
module top-level must not be accepted there. Bun parsed them with the
inherited context and emitted output that is a load-time SyntaxError in
the consuming engine.

Match esbuild: save fn_or_arrow_data_parse, replace it with a default
struct (is_this_disallowed for both, is_return_disallowed for
namespaces), restore after the body.
@coderabbitai

coderabbitai Bot commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: dceebf1c-9d9c-4969-b404-d09478647a63

📥 Commits

Reviewing files that changed from the base of the PR and between be77b65 and 084a8ab.

📒 Files selected for processing (2)
  • src/js_parser/parse/parse_typescript.rs
  • test/bundler/transpiler/transpiler.test.js

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Jul 15, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 4:03 PM PT - Jul 15th, 2026

❌ @autofix-ci[bot], your commit 3b34d0f has 1 failures in Build #73393 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 34250

That installs a local version of the PR into your bun-34250 executable, so you can run:

bun-34250 --bun

@robobun

robobun commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

Arrived at the identical src/ fix from a separate handoff (entry point was this → exports rather than yield). Branch farm/f52e59e6/ts-namespace-this-disallowed has a few complementary test cases (class field initializers inside a namespace, module x { ... }, dotted namespace x.y, () => this in an enum) if any are worth folding in. Not opening a second PR.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found — the save/replace/restore of fn_or_arrow_data_parse mirrors esbuild and matches the existing pattern in parse_property.rs and parse_fn.rs. Deferring to a human because this changes parser accept/reject behavior in a core path.

Checked: FnOrArrowDataParse::default() sets allow_await/allow_yield to AllowIdent as the PR relies on; the non-RAII restore skipping on ? early-return matches how the sibling has_non_local_export_declare_inside_namespace save/restore already behaves (parser is bailing anyway); nested functions overwrite the field themselves so the positive test cases hold; enum omits is_return_disallowed because initializers are expression-position.

Extended reasoning...

Overview

Two-site parser fix in src/js_parser/parse/parse_typescript.rs: parse_type_script_namespace_stmt and parse_typescript_enum_stmt now save p.fn_or_arrow_data_parse, replace it with a fresh FnOrArrowDataParse { is_this_disallowed: true, [is_return_disallowed: true,] ..Default::default() }, and restore it after parsing the body. This prevents the enclosing function/module's allow_yield/allow_await/is_top_level context from leaking into enum initializers and namespace bodies, which are lowered into arrow IIFEs where yield/await/this/return are invalid. 18 new assertions in test/bundler/transpiler/transpiler.test.js cover both the newly-rejected inputs and the still-accepted nested-function cases.

Security risks

None. This is a parser context-tracking fix; no untrusted-input allocation, no I/O, no boundary crossings.

Level of scrutiny

Medium-high. The change itself is small and mechanical — the exact save/clone/replace/restore idiom already appears at parse_property.rs:499-513 and parse_fn.rs:494-517, and it directly mirrors esbuild's ts_parser.go. But it lives in the core TS parser and changes which inputs are accepted vs. rejected. Code that previously transpiled (to broken output) will now hard-error, and one previously-rejected valid input (namespace x { let await = 1; }) is now accepted. That's the intended behavior and matches tsc/esbuild, but accept/reject changes in the parser are the kind of thing a maintainer should sign off on.

Other factors

  • I confirmed FnOrArrowDataParse::default() (parser.rs:1568) sets allow_await/allow_yield to AllowIdent, which is what the error paths in parse_prefix.rs key on for the quoted diagnostics.
  • The restore is skipped on ?-propagated errors between save and restore (e.g. the return Err(SyntaxError) in the enum member-name branch). This is consistent with the pre-existing has_non_local_export_declare_inside_namespace save/restore in the same function and with the other fn_or_arrow_data_parse save/restore sites in the codebase — on a syntax error the parse is aborting, so stale state is inconsequential.
  • Enum initializers are expression-position, so omitting is_return_disallowed there (vs. setting it for namespaces) is correct and matches esbuild.
  • Test coverage is thorough: yield-in-generator, await-in-async, TLA, this in class method, declare enum, dotted namespace, for await, plus positive cases proving nested generators/async arrows and return inside exported namespace functions still work.
  • The bug-hunting system reported no findings.

…2e59e6

Arrow-inherited this, module alias, declare forms, and class method/field
positive cases proving is_this_disallowed does not over-reject.
@robobun

robobun commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

Folded in the complementary cases from farm/f52e59e6/ts-namespace-this-disallowed in 084a8ab: arrow-inherited this, the module x { ... } alias, declare namespace/declare enum with this, and class method/field positive cases proving is_this_disallowed does not over-reject. src/ diffs were byte-identical. 26 assertions now; 2 fail on main, 2 pass with the fix.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beyond the inline nit, I also checked: the restore of fn_or_arrow_data_parse is skipped on ? early-returns, but that matches the existing has_non_local_export_declare_inside_namespace save/restore in the same function and parser errors are terminal so the unrestored state is never read. The other fields zeroed by Default::default() (allow_super_*, is_constructor, track_arrow_arg_errors, allow_missing_body_for_type_script, allow_ts_decorators) are per-function-entry transients not consulted across statement boundaries, so is_top_level is the only cross-statement consumer affected.

Extended reasoning...

The src/ change is a faithful port of esbuild's fnOrArrowDataParse reset in ts_parser.go and is well-covered by 26 new assertions (both rejection and positive nested-function cases). I'm not approving because this is parser hot-path code and the inline nit documents a real — if narrow — behavioral change to the Bun-specific React-hooks-suppression consumer of is_top_level; a maintainer should decide whether to carry is_top_level across the reset or accept the nit as-is.

Comment thread src/js_parser/parse/parse_typescript.rs
parse_fn.rs reads is_top_level from the enclosing context to decide
whether to consume a next-line react-hooks suppression after a namespace
member function. Every other is_top_level consumer is gated on
allow_await == AllowExpr, which the reset forces to AllowIdent, so
preserving the flag only keeps the react-hooks behavior at parity with
main.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the is_top_level carry-over in 3820fe6 addresses the react-hooks-suppression concern from my earlier pass, and the author's audit of the other three is_top_level readers (all gated on allow_await == AllowExpr) checks out.

Checked: save/restore of fn_or_arrow_data_parse mirrors esbuild and the neighboring has_non_local_export_declare_inside_namespace pattern (restore-on-error is not needed — the P struct is not reused after a parse error). Default::default() sets allow_await/allow_yield to AllowIdent and allow_super_* to false, which is what the lowered arrow IIFE requires. Tests cover both new rejections and the positive cases (nested generator/async, class this, let await binding) proving no over-rejection.

Extended reasoning...

Overview

Two-site parser fix in src/js_parser/parse/parse_typescript.rs: on entry to a TS namespace or enum body, save p.fn_or_arrow_data_parse, replace it with a fresh struct (is_this_disallowed: true, plus is_return_disallowed: true for namespaces and is_top_level carried over from the enclosing context), and restore after the body. This prevents yield/await/this/return/for await from the enclosing function/module context leaking into the arrow IIFE that the enum/namespace lowers to. 26 assertions added to test/bundler/transpiler/transpiler.test.js.

Security risks

None. This tightens the parser to reject inputs that previously produced load-time-SyntaxError output, and loosens one case (namespace x { let await = 1; }) to match esbuild/tsc. No untrusted-size arithmetic, no allocation changes, no FFI.

Level of scrutiny

Medium. The parser is production-critical, but the change is a direct port of esbuild's ts_parser.go save-assign-restore pattern, applied at exactly two call sites, and follows the local convention already established one line above by the has_non_local_export_declare_inside_namespace save/restore. I audited every field of FnOrArrowDataParse against its Default impl and every reader of is_top_level in src/js_parser/ — the author's claim that the three non-react-hooks readers are gated behind allow_await == AllowExpr (which the reset forces to AllowIdent) is accurate, so carrying is_top_level over affects only the intended parse_fn.rs consumer.

Other factors

  • My previous inline nit about is_top_level zeroing was addressed in commit 3820fe6 exactly as suggested; the thread is resolved.
  • The enum reset intentionally does not carry is_top_level — the author's justification (enum initializers cannot contain function declarations; function expressions there aren't top-level for React Compiler purposes) is sound and was explicitly considered.
  • Not restoring on the ? error path is consistent with the adjacent has_non_local_export_declare_inside_namespace handling and is safe because the parser does not resume after a returned Error.
  • Test coverage is thorough: error cases across enum/namespace/module/dotted/declare variants, plus positive cases (nested generator, nested async arrow, class method/field this, let await binding, return inside a namespace-member function) proving the reset does not over-reject. Full-file and esbuild/ts.test.ts runs pass.

robobun and others added 2 commits July 15, 2026 19:57
…rage

Carry is_top_level into the enum reset as well: enum initializers can
contain arrows and function expressions, which also reach the react-hooks
suppression consume in parse_fn.rs.

Tests now also cover super in enum initializers (allow_super_* is zeroed
by the reset), const enum and async-arrow enclosures, and the restore of
the enclosing context after an enum/namespace body (sibling yield/await/
super). A react-compiler case pins that a next-line react-hooks
suppression inside a namespace member does not bail the compiler out of a
following top-level component.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the is_top_level carry-through addresses the react-hooks-suppression concern from the earlier review, and the new SuppressionInsideTSNamespaceDoesNotLeak test locks it in.

Re-checked after the follow-up commits:

  • The three other is_top_level readers (parse_prefix.rs:166, parse_stmt.rs:510, mod.rs:879) are all inside allow_await == AllowExpr branches, which the reset forces to AllowIdent — so preserving is_top_level cannot spuriously set top_level_await_keyword from inside a namespace/enum body.
  • The remaining FnOrArrowDataParse fields zeroed by Default (async_range, is_constructor, track_arrow_arg_errors, decorator flags, etc.) are all per-function state that should not leak into the lowered arrow — matches esbuild's zero-valued reset.
  • Enum restore placement before expect(TCloseBrace) is fine; the unrestored-on-? error path matches the neighboring has_non_local_export_declare_inside_namespace pattern.
Extended reasoning...

Overview

Two save/assign/restore blocks around fn_or_arrow_data_parse in parse_type_script_namespace_stmt and parse_typescript_enum_stmt, so the enclosing function's allow_yield/allow_await/allow_super_*/is_return_disallowed context does not leak into bodies that lower to arrow IIFEs. This is a direct port of esbuild's ts_parser.go behavior, with one Bun-specific deviation: is_top_level is carried across the reset to keep the react-hooks-suppression consume in parse_fn.rs working. ~21 lines of src/ change plus ~90 lines of tests across transpiler.test.js (error/positive/restore matrix) and one itBundled regression test in react-compiler.test.ts.

Security risks

None. Pure parser-context manipulation of a POD struct via .clone() and reassignment; no allocation, no untrusted-input arithmetic, no FFI.

Level of scrutiny

Moderate — js_parser is a hot, correctness-critical path, but the change is a small, localized save/restore that mirrors the reference implementation. The one non-obvious interaction (Bun's react-hooks-suppression consumer of is_top_level) was flagged in my prior review, fixed exactly as suggested, and now has a dedicated bundler test. I re-verified against the FnOrArrowDataParse struct definition that no other zeroed field has a Bun-specific consumer that would regress: the other three is_top_level readers all sit behind allow_await == AllowExpr, which the reset makes unreachable, and the remaining fields are per-function-signature state that correctly resets.

Other factors

Test coverage is thorough: yield/await/this/super/return rejections across enum, const enum, declare enum, namespace, dotted namespace, module x, and declare namespace; positive cases proving nested functions/arrows/class-methods re-establish their own context; and explicit restore assertions (yield after an enum, TLA after a namespace, super.foo() after an enum). The namespace x { let await = 1; } case covers the opposite-polarity fix. Verified fails-on-main / passes-on-PR in the description. My earlier inline comment is resolved and the thread is closed.

@robobun

robobun commented Jul 15, 2026 •

Copy link
Copy Markdown
Collaborator Author

Gate has passed on every push. Review surfaced four concerns, all addressed in 9548516:

  • carry is_top_level into the enum reset as well (parity with the namespace reset)
  • cover super in enum initializers, const enum, and async-arrow enclosures
  • pin the restore of the enclosing context via sibling yield/await/super after an enum and top-level await after a namespace
  • pin the is_top_level preservation with react-compiler/SuppressionInsideTSNamespaceDoesNotLeak (verified it fails when the carry-through line is deleted)

35 assertions in transpiler.test.js plus the react-compiler case; both fail on main's src/ and pass here.

CI has run three times (73322 / 73345 / 73393) and the only reds are [pre-existing] or [flaky] per ci:errors, none of them in the parser, transpiler, or react-compiler suites: test-net-connect-memleak.js, test-worker-message-port-transfer-terminate.js, bun-build-api.test.ts (ASAN leak in bundler options), test-repl-close.js EPIPE on Windows, watch-many-dirs.test.ts, bun-run-dir.test.ts, plus a handful of one-off retried passes. Ready for review.

@dylan-conway
dylan-conway merged commit 076d4ce into main Jul 15, 2026
78 of 79 checks passed
@dylan-conway
dylan-conway deleted the farm/b28ba2ed/ts-enum-namespace-fn-context branch July 15, 2026 23:21
dylan-conway pushed a commit that referenced this pull request Jul 16, 2026
### What

`#34249` changed TypeScript enum lowering so that only **module-scope**
enums emit `var`; an enum in a function, method, or block body now emits
`let`.

`#34250` merged four minutes earlier and added `it("rejects
yield/await/this/super in enum initializers")`, whose expectations still
assert `var x` for **block-scoped** enums. Each PR was green against a
`main` that lacked the other's change, so the collision only appeared
once both had landed — and since `main` pushes run no test shards,
nothing caught it.

The result is that `main` asserts output its own parser no longer
produces. `test/bundler/transpiler/transpiler.test.js` currently fails
on every PR that merges `main`.

### The fix

Updates the five stale expectations to `let`. All five are enums nested
in a function or method body:

| Line | Case |
|---|---|
| 771 | `function *f() { enum x { y = (function*() { yield 1 })() } }` |
| 775 | `async function f() { enum x { y = (async () => await 1)() } }`
|
| 785 | `function *f() { enum x { y = 1 } yield 1; }` |
| 789 | `async function f() { enum x { y = 1 } await 1; }` |
| 793 | `class C extends B { m() { enum x { y = 1 } super.foo(); } }` |

Deliberately unchanged:

- **Line 779** — `enum x { y = (function() { return this })() }` is
top-level, so `var` is still correct.
- **Namespace expectations** — namespaces only appear at module scope or
nested in another namespace, where both the old and new predicates
agree.

### Note for reviewers

Only the line-771 failure is visible in CI: `expectPrinted_` throws at
the first mismatch, masking the other four. A fix touching only the
reported line would go red again on the next one, so all five are
updated together.

### Verification

Ran `test/bundler/transpiler/transpiler.test.js` against a build
containing `#34249`:

- pristine `main`: 176 pass / 4 fail
- with this change: 177 pass / 3 fail

The change flips exactly the one enum test and touches nothing else. The
3 remaining failures are unrelated to this diff — they are skew between
that build and two commits that landed after it (`#34254`, `#34258`),
and build from source in CI.

Also swept the repo for any other assertion of enum-lowering text (the
closure IIFE shape `(x ||= {})` / `(x = x || {})`). Only two files
assert it: this one, and
`test/js/node/module/require-extensions.test.ts:129`, whose fixture
declares a top-level enum and is correctly `var`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants