Repository navigation
Conversation
|
Updated 3:06 AM PT - Aug 28th, 2026
❌ @robobun, your commit e9b7b98 has 1 failures in
🧪 To try this PR locally: bunx bun-pr 38125That installs a local version of the PR into your bun-38125 --bun |
|
Warning Review limit reached
On-demand reviews are free for the next 23 days. After that, they cost $0.25 per reviewed file. Or wait 7 minutes for your next included review. View limit detailsLimit details: You’ve used all 5 included reviews currently available. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (11)
Comment |
|
Reproduced on main with the released build: in a directory whose Fix and tests are in this PR; the new tests in Consolidated with #29201 (closed): its test file was run against this branch and every case passes except the compile error it deliberately emitted for classes mixing legacy decorators with Follow-ups from comparing more shapes against tsc 6.0 on this branch: a decorated |
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
|
Not duplicates, for the record:
|
There was a problem hiding this comment.
I re-reviewed after 460ddf4 and 3a37487 — the double-evaluation of a decorated computed accessor key I flagged earlier is fixed (decorators now ride on the setter, and the new runtime test asserts keyEvaluations: 1 with the decorator seeing k0). No further issues found, but this adds a new lowering pass that interacts with the legacy-decorator lowering, useDefineForClassFields, the bundler renamer and metadata emission, so a human look is still worthwhile.
What was reviewed:
lower_auto_accessors_in_place: private-name uniqueness across enclosing scopes, computed-key temp declaration in the hoisting scope withdeclared_symbolsregistration, and placement before theuseDefineForClassFieldsblock.can_be_movednow inspectsstatic accessorinitializers — checked that the standard-decorator path still routes those classes throughshould_lower_standard_decoratorsso this pass never runs for them.- The new
IsLoweredAutoAccessormetadata arm emits onlydesign:type(matching tsc), andts_metadataon the setter carries the declared type.
Extended reasoning...
Overview
The PR removes the features.standard_decorators gate on the accessor keyword in parse_property.rs and adds a new in-place lowering (lower_auto_accessors.rs, ~260 lines) called from visit_class for classes not routed through the standard-decorator lowering. It also fixes Class::can_be_moved to treat static accessor initializers as side effects, adds a new flags::Property::IsLoweredAutoAccessor variant consumed by emit_decorator_metadata_for_prop, and makes four small helpers in lower_decorators.rs pub(crate). ~520 lines of tests cover snapshot output, runtime semantics, emitDecoratorMetadata, useDefineForClassFields: false, and a two-file Bun.build renamer collision.
Prior review
My earlier inline comment identified that carrying the decorators on the getter meant lower_class copied the [_computedKey = expr] assignment into the __legacyDecorateClassTS call, evaluating a computed key twice. Commit 460ddf4 moved ts_decorators/ts_metadata to the setter (whose key is the bare temp) and added the PropertyKind::Set if IsLoweredAutoAccessor metadata arm; the new "legacy decorators on accessors receive the property descriptor" runtime test now asserts keyEvaluations: 1 with the decorator receiving key k0 and a get/set descriptor. Commit 3a37487 trimmed the flagged long comments. Both concerns are resolved.
Security risks
None. This is a syntactic transform in the transpiler; no I/O, network, permissions or untrusted-length arithmetic is involved. The new pass allocates only in the parser arena.
Level of scrutiny
High. The transpiler is production-critical and this introduces a new lowering that must compose correctly with three other passes (lower_class legacy decorators, the useDefineForClassFields: false constructor-relocation block, and the bundler's top-level renamer). The private-name collision avoidance walks the scope chain and the computed-key temp is registered in declared_symbols with an is_top_level bit — both are subtle enough that a maintainer familiar with the renamer should confirm the approach.
Other factors
Test coverage is thorough and each edge (private-name collision with an enclosing class, decorated computed-key accessor, metadata shape vs a plain getter, bundler cross-file naming) has both a snapshot and a runtime assertion. The PR description states the standard-decorator path is byte-identical before/after and that the listed test suites pass. Related open PRs #31926 and #35708 touch adjacent code on the standard-decorator side; the author has explained the non-overlap, but a maintainer may want to weigh in on the shared-implementation direction.
There was a problem hiding this comment.
The double-evaluation of computed keys I flagged earlier is fixed in 460ddf4 (decorators/metadata now ride on the setter, whose key is the bare temp), and the new snapshot + keyEvaluations: 1 runtime test cover it. No further issues found this pass.
Given this adds a new ~260-line lowering pass that composes with lower_class, useDefineForClassFields, and the bundler renamer — and deliberately mirrors the desugaring shape of #31926 for later unification — a maintainer sign-off on the placement and the private-name/temp-ref scheme would still be worthwhile.
What was reviewed
- Computed-key single-evaluation with legacy decorators — setter carries
ts_decorators, decorate call gets the bare temp; runtime test assertskeyEvaluations: 1. declare_var_temp_refregisters the hoisted temp indeclared_symbolswithis_top_levelmatched to the hoist scope, so the bundler renamer sees it (covered by the two-fileBun.buildtest).- Backing-name collision walk uses
is_declared_in_enclosing_scopeswhile the class-body scope is current, so both this class's and enclosing classes' private names are avoided. emit_decorator_metadata_for_proparm matchesGet | SetwithIsLoweredAutoAccessor, so metadata emits once (on the setter) asdesign:typeonly.
Extended reasoning...
Overview
The PR unblocks accessor in TypeScript files that set experimentalDecorators/emitDecoratorMetadata, which previously failed to parse. It removes the standard_decorators gate in parse_property.rs, adds a new in-place lowering pass (lower_auto_accessors.rs, ~266 lines) invoked from visit_class for classes not routed through the standard-decorator lowering, adds an IsLoweredAutoAccessor property flag so emit_decorator_metadata_for_prop emits only design:type for the pair, fixes Class::can_be_moved to inspect static accessor initializers, and makes four lower_decorators.rs helpers pub(crate). ~600 lines of new tests cover snapshot output shapes and runtime semantics across the variant matrix (static/private/computed/literal keys, decorated + metadata, useDefineForClassFields: false, name collisions, bundling).
Security risks
None. This is a syntax-lowering change in the transpiler; no untrusted-input parsing beyond what the JS parser already does, no I/O, no auth/crypto.
Level of scrutiny
High. The JS parser/transpiler is a critical, correctness-sensitive path where a wrong lowering silently changes program semantics for every user with the affected tsconfig. The new pass has non-local interactions: it runs before the useDefineForClassFields: false block so generated backing fields get relocated, it hands legacy decorators to lower_class via the setter so the decorate call reuses the bare computed-key temp, and it registers hoisted var temps in declared_symbols so the bundler's cross-file renamer sees them. Each of these is tested, but the design (in-place lowering here vs. inside lower_decorators.rs, and the intended future unification with #31926) is a call a maintainer should confirm.
Other factors
My earlier review found that @dec accessor [expr] evaluated expr twice because decorators were carried on the getter (whose key is [_tmp = expr]). Commit 460ddf4 moved them to the setter, and the PR now has both a snapshot test showing __legacyDecorateClassTS([dec], A.prototype, __bun_temp_ref_1$, null) and a runtime test asserting keyEvaluations: 1 with the decorator receiving the correct descriptor. The comment-cop feedback was addressed in 3a37487 (comments trimmed). The bug-hunting pass on the current head found nothing. Test coverage is thorough and follows harness conventions (tempDir, bunEnv, drained pipes, test.concurrent for subprocess tests, exit-code asserted last). Given the scope and the overlap with two other open PRs implementing the same desugaring on the standard-decorator path, deferring for a human look rather than auto-approving.
|
CI is green on all lanes for the latest push and there are no open review findings. Ready for a maintainer to look at the placement of the new lowering pass and the private-name/temp-ref scheme, per the review above. |
There was a problem hiding this comment.
Both earlier findings are addressed (460ddf4 moved the decorators/metadata to the setter so a decorated computed-key accessor evaluates its key once; 04aa1be gated the abstract-accessor widening on !standard_decorators so standard-mode output is unchanged), and this pass found nothing new. Given this adds a new lowering pass whose placement, private-name collision scheme, and declared_symbols registration for the bundler renamer you already flagged for a maintainer, a human look at those design points is still the right next step.
What was reviewed:
lower_auto_accessors_in_place: backing-field naming walks enclosing scopes, computed-key temp declared in the var-hoisting scope and registered top-level for the bundler renamer, decorators on the setter solower_classreuses the bare temp key.- Interactions with
useDefineForClassFields: false,emitDecoratorMetadata(IsLoweredAutoAccessorarm emits onlydesign:type), andcan_be_movedforstatic accessorinitializers (cache version bumped). - Confirmed the pass is skipped for
should_lower_standard_decoratorsclasses and the abstract-accessor branch now only fires in legacy mode.
Extended reasoning...
Overview
The PR makes accessor parse in TypeScript files with experimentalDecorators/emitDecoratorMetadata and lowers it in place to #x; get x(){} set x(v){}, since JSC doesn't implement the keyword and the existing lowering only runs on the standard-decorator path. It adds a ~260-line pass (lower_auto_accessors.rs) called from visit_class, a new IsLoweredAutoAccessor property flag consumed by emit_decorator_metadata_for_prop and threaded through lower_class, widens can_be_moved to treat static accessor initializers as side effects (with a runtime-transpiler-cache version bump), and removes the standard_decorators gate on the accessor keyword in parse_property.rs. Four helpers in lower_decorators.rs are made pub(crate). ~600 lines of new snapshot + runtime tests cover key shapes, name collisions, decorated accessors, metadata, useDefineForClassFields: false, and a two-file bundle.
Security risks
None identified. This is transpiler output-shape work; inputs are source text already going through the parser, and the only new state introduced is arena-backed AST nodes and symbols. No untrusted length arithmetic, no filesystem/network, no user-overridable JS reached from native code.
Level of scrutiny
High. The JS parser/lowering is production-critical (every .ts file with experimentalDecorators now flows through the new pass), and the change sits at the intersection of several subtle subsystems: scope/symbol declaration during the visit pass, private-name printing (never renamed except by the minifier, so generated names must be unique by construction), the bundler's cross-file top-level renamer (the declared_symbols registration is load-bearing, as the bundling test demonstrates), and the legacy-decorator lowering in lower_class. The author explicitly asked for a maintainer to review the pass placement and the private-name/temp-ref scheme.
Other factors
- Both concrete bugs I raised on earlier revisions have been fixed with tests pinning the behavior (the
keyEvaluations: 1runtime assertion for decorated computed-key accessors, and the standard-mode@dec abstract accessorsnapshot ines-decorators.test.ts). - CI is reported green on all lanes for the latest push.
- Two comment-cop nits remain open (on
parse_property.rs:453andRuntimeTranspilerCache.rs:56); the author has justified keeping both as one-line facts rather than workaround explanations, which reads reasonable to me. - Test coverage is thorough for the shapes described, but the design choices themselves (running the pass inside
visit_classwhile the class-body scope is current, walking enclosing scopes for private-name avoidance, hoisting the computed-keyvarto the nearest hoisting scope and marking it top-level) are exactly the kind of thing a Bun parser maintainer should confirm matches how the rest of the lowering machinery expects symbols to be introduced.
|
Green again on the latest head after the follow-ups (decorated abstract accessors in legacy mode only, standard-mode output re-verified byte-identical, transpiler cache version bump). All review threads are resolved, so this is ready for a maintainer look at the lowering pass placement and the private-name/temp-ref scheme. |
|
I independently wrote the same fix for this (branch
function k() { return "key"; }
enum E {
A = (globalThis.K = class { accessor [k()] = 1 }, 0),
}The same window already bites the standard lowering on main: without the tsconfig, the file above fails with The fix I ended up with is to move the bun/src/js_parser/visit/mod.rs Lines 1317 to 1324 in ab11e8b git show ab11e8bba9 -- src/js_parser/visit/mod.rs). Tests for both modes are in that commit: decorators.test.ts "computed keys in classes nested in functions, parameters, static blocks and enums" and es-decorators.test.ts "class expressions inside enum initializers"; the second one fails on the released build with the ReferenceError above. Feel free to lift the hunk and the tests into this PR.
|
c5be06f to
00c18ae
Compare
There was a problem hiding this comment.
Re-reviewed after the rebase and didn't find any new bugs — both earlier findings are fixed: the accessor's ts_decorators/ts_metadata now ride on the setter so @ dec accessor [expr] evaluates the key once, and the abstract accessor widening in parse_property.rs is gated on !standard_decorators with a covering test in es-decorators.test.ts. Given the scope (a new lowering pass plus parser/visitor/cache-version changes), a human pass is still worthwhile.
What was reviewed:
lower_auto_accessors.rs: decorators on the setter (202-203) paired with theGet | Set if IsLoweredAutoAccessormetadata arm inp.rs; private-name collision walk over generated names + enclosing scopes; computed-key temp registered indeclared_symbolswithis_top_levelfor the bundler renamer.g.rscan_be_movednow treatsstatic accessorinitializers as side effects, andRuntimeTranspilerCacheEXPECTED_VERSIONbumped 26→27 to invalidate the older hoisted output.parse_property.rs:accessorrecognized in both decorator modes; decoratedabstract accessorkept only in legacy mode, standard mode still drops it.
Extended reasoning...
Overview
This PR makes the parser accept the accessor class-member keyword under TypeScript's experimentalDecorators mode and adds a dedicated in-place lowering pass (src/js_parser/lower/lower_auto_accessors.rs, 266 lines) that desugars each accessor x = init into a private backing field plus a getter/setter pair. Supporting changes wire the pass into visit_class before the useDefineForClassFields lowering, add a flags::Property::IsLoweredAutoAccessor bit consumed by emit_decorator_metadata_for_prop (so only design:type is emitted, matching tsc), extend Class::can_be_moved to treat static accessor initializers as side effects, and bump the runtime transpiler cache version 26→27. Roughly 600 lines of transpiler-output and runtime-fixture tests were added across decorators.test.ts and es-decorators.test.ts.
Security risks
None identified. This is transpiler/AST-lowering logic operating on parsed source; there is no I/O, network, credential, or path handling. Allocations go through the parser arena via existing helpers (bump_name, BumpVec, StoreSlice); the cache-version bump is the correct format-version invalidation REVIEW.md requires for output-shape changes.
Level of scrutiny
High. This is a new lowering pass in the transpiler core with several interacting axes (static vs. instance, computed vs. literal vs. private keys, legacy decorator interaction, useDefineForClassFields, name collision across enclosing class scopes, bundler-renamer visibility of generated temps). The two concrete issues I raised on the previous push — computed-key double-evaluation when the decorators sat on the getter, and the abstract accessor widening leaking into standard-decorator mode — were both addressed exactly as suggested (commits 7fd7af80 and 4d87a374), and the cache bump landed in 00c18aee. This run's hunt exited on a dry streak with no new candidates.
Other factors
Test coverage is substantial and targeted: snapshot tests for each key/modifier variant, backing-name collision including an enclosing class, emitDecoratorMetadata shape vs. a real getter, a runtime fixture asserting a computed-key decorator's key function runs exactly once, a two-file Bun.build for renamer collisions on the generated _computedKey temp, and the static-accessor hoisting regression in both decorator modes. All prior third-party inline threads are bot-authored and self-resolved by the PR author; there are no outstanding human CHANGES_REQUESTED reviews. The change is well-shaped and well-tested but large enough, and central enough to the transpiler, that a maintainer should sign off rather than an automated approve.
…erimentalDecorators The parser only recognized `accessor` when standard decorators were enabled, so a .ts file in a project with experimentalDecorators (or emitDecoratorMetadata) failed to parse a plain auto-accessor with "Expected ";" but found ...". TypeScript accepts the keyword in every decorator mode. Parse the keyword regardless of the decorator mode and, for classes the standard-decorator lowering does not process, desugar each accessor in place into a private backing field plus a getter/setter pair. The backing name avoids the private names declared by the class and by the classes enclosing it; computed keys are evaluated once through a hoisted temporary that is declared as a top-level symbol when the class is top level, so the bundler's renamer keeps it apart from other files' bindings. Legacy decorators on an accessor move to the generated getter, which lower_class then decorates with the property descriptor and, with emitDecoratorMetadata, the member's declared type, matching tsc. Also stop hoisting classes whose static accessor initializers have side effects: Class::can_be_moved only looked at plain static fields, so such classes were moved above the bindings their initializers read.
…ted key is evaluated once lower_class builds the __legacyDecorateClassTS call from the decorated member's key. The getter's key is the assignment that evaluates a computed key expression, so decorating through the getter ran the expression a second time; the setter's key is the bare temporary.
…with legacy decorators
00c18ae to
e9b7b98
Compare
|
CI on the rebased head has one red test, test/js/web/url/url.test.ts on macOS x64. It is a pre-existing failure on main (the Unicode 16 IDNA table test) and this PR does not touch URL code. It is reported for main-break triage. Every other failure in build 107601 passed on retry. The decorator test suites pass on all lanes, so this stays ready for a maintainer. |
|
A maintainer asked for one change that covers every experimental decorator lowering bug in this area. #40830 does that, and it includes the fix this PR makes (decorated fields stay in the class body, computed keys are captured once, parameter decorators use the enclosing scope, decorators that read a private name run in a static block, export default @dec class, accessor lowering). If #40830 lands, this PR can be closed. |
|
#40830 fixes the What #40830 covers: the parse change in What this PR still fixes and #40830 does not:
So this PR stays open. Once #40830 lands, rebase this branch onto it. The parse change and the |
Fixes #29197
Replaces #29201 (closed) and the parked branch
farm/57075cac/ts-legacy-decorators-accessor. #29201 routed these classes through the standard-decorator lowering and reported a compile error for a class that mixes legacy decorators withaccessormembers; tsc accepts that combination, so the in-place lowering here was kept. Its test file was run against this branch: every case passes except that deliberate error and a standard-mode (experimentalDecoratorsoff) computed-key case, which belongs to #31926. The one case it covered that this suite lacked (TypeScript modifiers on accessors) was added here, together with the mixed class.Problem
experimentalDecorators(oremitDecoratorMetadata), any.tsclass using theaccessorkeyword fails to parse:class A { accessor name = "A" }giveserror: Expected ";" but found "name". The same file works when the tsconfig does not enable legacy decorators. tsc accepts the keyword in both decorator modes (it is class syntax from the decorators proposal, independent of which decorator flavor is configured).src/js_parser/parse/parse_property.rsonly recognized the keyword whenfeatures.standard_decoratorswas set, andParseTask.rs/transpiler.rsclear that flag for TypeScript files withexperimentalDecoratorsoremitDecoratorMetadata.accessorthen parsed as a field namedaccessor.accessor, and the only lowering for it lives in the standard-decorator path (lower_decorators.rs), which these classes never take because their decorators are TypeScript legacy decorators lowered byP::lower_class.Class::can_be_moved(src/ast/g.rs) only looked at plain static fields, so a class whose only side effect is astatic accessorinitializer was hoisted to the top of the module by the runtime transpiler and read bindings before their declaration (ReferenceError: Cannot access 'names' before initialization). Reproduces on main with a.jsfile too.Fix
parse_property.rs: recognizeaccessorin class bodies in every decorator mode. The newline (ASI) and raw-spelling checks are unchanged, soaccessoras a member name still parses the way tsc parses it (covered by a snapshot test). InexperimentalDecoratorsmode theabstractbranch there turns a decoratedabstract accessor xinto the sameAbstractproperty a decoratedabstract xbecomes, so its decorators are emitted as__legacyDecorateClassTS(decorators, proto, "x", undefined)plusdesign:type, which is what tsc emits; before, the member (and its decorators) was dropped like an undecorated abstract member. Standard-decorator mode is left as it was (the member is still dropped; tsc rejects standard decorators on abstract members), covered ines-decorators.test.ts.src/js_parser/lower/lower_auto_accessors.rs, called fromvisit_classfor classes thatshould_lower_standard_decoratorsdoes not claim. Each accessor is replaced at its own position by the desugaring the proposal defines and tsc emits:this, like the proposal and the existing standard-mode output.visit_class, before theuseDefineForClassFields: falseblock: the generated backing field is then relocated into the constructor in source order together with the other instance fields, which is what tsc emits for that option.#x(#_pforaccessor #p,#_accessor_storagefor other keys), with a numeric suffix when that spelling is declared by this class or by an enclosing class, found by walking the scope chain while the class body scope is still current. Private names are printed verbatim by the non-minifying renamer, and a name declared by an enclosing class may be referenced from inside this class body, so both sets have to be avoided.get [_computedKey = expr]() {} set [_computedKey]() {}. The temporary comes fromgenerate_temp_ref(collision-safe name in the runtime transpiler), is registered in the scope avarthere hoists to, and is recorded as a top-level declared symbol of the part when that scope is the module scope, so the bundler's renamer gives it a name distinct from other files' top-level bindings. Without the last step, the temporary of an earlier file and a top-level binding of a later file could be assigned the same name (verified while writing the bundling test).lower_classthen emits__legacyDecorateClassTS(decorators, target, key, null), which is what tsc's__decoratereceives for a legacy-decorated accessor (the decorator gets the get/set descriptor and can replace it). The setter rather than the getter becauselower_classreuses the decorated member's key expression in that call: the setter's key is the bare_computedKeytemporary, so a computed key is still evaluated once (tsc emits__decorate(..., _a, null)the same way). The pair is flaggedIsLoweredAutoAccessor(newflags::Propertyvariant) and the setter carries the member's declared type, soemitDecoratorMetadataemitsdesign:typeonly, as tsc does for accessors, instead of a getter's or setter'sdesign:type+design:paramtypes.should_lower_standard_decoratorsset, so the new pass never runs for them (checked thatbun build --no-bundleoutput for JS/TS files withoutexperimentalDecoratorsis byte-identical before and after). Lower accessor-only classes in place instead of relocating static elements #31926, which makes the standard-mode lowering of accessor-only classes in place as well, is independent of this; the new pass is written so that path could share it later.src/ast/g.rs:can_be_movedalso inspectsstatic accessorinitializers. Because this changes the output of files that already transpiled before (the other changes only affect files that used to fail to parse),EXPECTED_VERSIONinsrc/jsc/RuntimeTranspilerCache.rsis bumped to 26; the cache key is source hash plus features, not the bun version, so without the bump a warm cache would keep serving the hoisted output for such files until they are edited.lower_decorators.rsbecamepub(crate)for reuse; the obsoleteaccessorTODO inlower_classand the stale comment inemit_decorator_metadata_for_propwere removed.test/bundler/transpiler/decorators.test.ts, newdescribe("accessor keyword with experimentalDecorators"): output shapes viaBun.Transpiler(basic, static/private/literal/computed keys, name collisions incl. an enclosing class, decorated instance/static/computed-key accessors,private/protected readonly/public static/static override/abstract/ decoratedabstractmodifiers in a class whose other members carry legacy decorators, metadata vs. a real getter,useDefineForClassFields: false,accessoras a plain member name) and runtime fixtures with a tsconfig on disk (getter/setter semantics incl. subclass override andsuper, computed key evaluated once, anonymousexport defaultkeeps.name === "default", legacy decorators receiving and replacing the descriptor plusdesign:type, including on a computed-key accessor whose key function must run exactly once,useDefineForClassFields: falseordering, and a two-fileBun.buildwhere the temporaries, a user function and an export all want the same name). 11 of the 12 fail on the released build (parse error), all pass with this branch.test/bundler/transpiler/es-decorators.test.ts:.jsclass statement andexport default classwith static accessor initializers are no longer hoisted above the binding they read; fails on the released build with theReferenceErrorabove.decorators,decorator-metadata,es-decorators,es-decorators-esbuild,ts-use-define-for-class-fields,bundler_decorator_metadata,esbuild/ts,esbuild/lower,bundler_edgecaseand the internal source lints pass with a debug build;cargo clippy -p bun_js_parseris clean.Background
accessor x = vdeclares a getter/setter pair backed by a hidden per-instance slot; the proposal specifies it as exactly the#x+get/setdesugaring above. TypeScript has accepted it since 4.9 under bothexperimentalDecoratorsand standard decorators, and its legacy decorator transform decorates such a member with the descriptor form (__decorate(..., key, null)), emitting onlydesign:typeas metadata..jsfiles and.tsfiles withoutexperimentalDecoratorsuse the standard (TC39) lowering inlower_decorators.rs, which also lowers accessors;.tsfiles withexperimentalDecorators/emitDecoratorMetadatahavefeatures.standard_decoratorscleared and get their decorators lowered byP::lower_classinto__legacyDecorateClassTScalls after the class.G::Class::should_lower_standard_decoratorsis what routes a class to the former.useDefineForClassFields: false: tsc option under which instance field initializers are turned intothis.x = initassignments in the constructor; bun implements it at the end ofvisit_class.generate_temp_refprovides), the bundler renames top-level symbols across all files of a chunk and then nested scopes per part (so a symbol declared by a top-levelvarhas to be registered as a top-level declaration to take part in the cross-file pass), and private names are never renamed except by the minifier.Class::can_be_moved: the runtime transpiler hoists top-level classes without observable side effects to the top of the module to make some import cycles work; a static initializer is such a side effect.[review] gate passed · iteration 0 · 11 files touched
fails on main (without fix)
passes on PR (with fix)
diff hotspot
gate history · 4 passed · 0 rejected · iteration 0
evidence per changed file
root cause · written by the author bot
The parser did not support the
accessorclass-member keyword under TypeScript's experimentalDecorators mode, so auto-accessors were rejected or mishandled instead of being desugared like tsc does. The fix teaches the parser to acceptaccessorand adds a dedicated lowering pass that rewrites eachaccessor x = initinto a private backing field with a generated getter and setter pair, attaching decorators and metadata to the setter so computed keys evaluate exactly once and onlydesign:typeis emitted. It also marks static accessor initializers as side effects inClass::can_be_moved…