build: fix private-to-property rewrite bugs, add syntax check#308724
Merged
build: fix private-to-property rewrite bugs, add syntax check#308724
Conversation
…ax check - Fix heritage clause scope resolution: walk extends expressions before pushing the inner class scope so #field references resolve to the enclosing class (matching JS lexical scoping) - Fix name collision: skip generated names that collide with existing public member names on the same class - Add syntax validation for post-processed JS bundles using esbuild - Add 5 tests covering both bugs and the brand-check variant
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes correctness issues in the build-time convertPrivateFields transformation (private #x → short public properties like $a) and adds a post-processing syntax validation step to catch bundle corruption during the build pipeline.
Changes:
- Fix private-name resolution in
extends/heritage clauses so nested classes resolve to the correct (outer) lexical private scope. - Avoid generated replacement-name collisions with existing public class member names.
- Add a build-time syntax check for JS bundles that were post-processed (mangle-privates and/or NLS edits) using
esbuild.transform()parsing.
Show a summary per file
| File | Description |
|---|---|
| build/next/private-to-property.ts | Adjusts class walking order for heritage clauses and adds collision avoidance during replacement-name generation. |
| build/next/test/private-to-property.test.ts | Adds regression tests covering heritage-clause scope resolution, name-collision behavior, and brand checks in heritage clauses. |
| build/next/index.ts | Adds a post-processing syntax parse check for affected output JS files to fail builds early on syntax corruption. |
Copilot's findings
- Files reviewed: 3/3 changed files
- Comments generated: 3
…deterministic errors
chrmarti
approved these changes
Apr 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fix two correctness bugs in the
convertPrivateFieldsrewrite step that could produce invalid or semantically broken JavaScript output, and add a build-time syntax check as a safety net.Session Context
Key decisions from the development session:
extendsexpression is evaluated in the enclosing scope.do/whileloop skips generated names ($a,$b, …) that collide with existing public member names on the same class. This avoids silent property shadowing.esbuild.transform(): Chosen overnode --check(which doesn't handle ESM.jsfiles without"type": "module") andvm.Script(which only parses CJS).esbuildis already imported, handles ESM natively, runs in-process with no child process overhead, and checks all files in parallel.Changes
build/next/private-to-property.tsextendsexpressions before pushing the inner class's scope onto the stack. Previously, a nested class declaring the same#fieldas its outer class would causeresolvePrivateNamein the heritage clause to pick the inner scope — wrong, since extends is evaluated in the enclosing lexical scope. This could causeTypeError: Class extends value undefinedat runtime.$a,$b, … that already exist as public members. Previously, a class with both$a(public) and#x(private) would get two$adeclarations, silently breaking runtime behavior.walkInClassintocreateWalkInClass()so heritage clauses and members can be walked separately with different stack states.build/next/test/private-to-property.test.tsbuild/next/index.tsesbuild.transform()to catch any syntax corruption at build time rather than at runtime.