fix: strip uninitialized class fields (useDefineForClassFields: false)#206
Open
ashley-hunter wants to merge 5 commits intovoidzero-dev:mainfrom
Open
fix: strip uninitialized class fields (useDefineForClassFields: false)#206ashley-hunter wants to merge 5 commits intovoidzero-dev:mainfrom
ashley-hunter wants to merge 5 commits intovoidzero-dev:mainfrom
Conversation
When `useDefineForClassFields: false` (Angular's default), TypeScript treats uninitialized fields as type-only declarations that produce no JS output. The compiler was preserving these fields, causing `vite:oxc` to lower them to `_defineProperty(this, "field", void 0)` which shadows prototype getters set up by legacy decorators (@select, @dispatch). AOT path: scan all classes for PropertyDefinition nodes without initializers and emit Edit::delete() spans for them. JIT path: enable oxc_transformer's existing `remove_class_fields_without_initializer` option in strip_typescript(). Controlled by new `strip_uninitialized_fields` option (default: true). Closes voidzero-dev#73 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
0c3d78c to
2078247
Compare
Private fields (#foo) are JavaScript runtime syntax that declares a private slot on the class. They must not be stripped even without an initializer, unlike public fields which are TypeScript type annotations under useDefineForClassFields: false. Stripping them causes rolldown to panic because this.#foo references remain but the declaration is gone. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When an uninitialized field has a non-Angular decorator (@select, @dispatch, etc.), the field declaration is stripped but the decorator must survive as a __decorate() call on the prototype. This matches tsc's output with useDefineForClassFields: false — the field is gone from the class body but the decorator is applied via __decorate(). Without this, stripping the field also removes the decorator, so the prototype getter is never set up and the property is undefined at runtime. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Recent commits introduced unformatted Rust code in transform.rs and integration_test.rs, causing the `cargo fmt --check` CI step to fail. https://claude.ai/code/session_01DWNfhcEuAQRhSXyzv3abY6
…elds mismatch The strip_uninitialized_fields feature (2078247) strips uninitialized class fields matching Angular's useDefineForClassFields:false default. The comparison test's TS compiler uses useDefineForClassFields:true (ESNext default), which preserves them as bare declarations (name;). In real Angular projects both compilers agree. Setting useDefineForClassFields:false globally in the comparison test would fix this fixture but breaks 11 provider fixtures (TS moves initialized fields into constructors, while OXC keeps them as class fields). The regression is covered by 9 Rust unit tests (test_strip_uninitialized_*). Also removes redundant inline comments in edge-cases/class-field-declarations that duplicated the skipReason text. https://claude.ai/code/session_01DFJstK1r8UqrQzMqAzAkFw
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
useDefineForClassFields: falsebehavior (Angular's default)_defineProperty(this, "field", void 0)shadows prototype getters set up by legacy decorators (@Select,@Dispatch)PropertyDefinitionnodes without initializers and deletes them via text editsoxc_transformer's existingremove_class_fields_without_initializeroptionstrip_uninitialized_fieldsoption (default:true) onTransformOptions#foo) — these are runtime syntax, not TypeScript type annotations__decorate()calls for non-Angular decorators on stripped fields (e.g.,@Select,@Dispatch), matching tsc's output where the field is removed from the class body but the decorator is applied via__decorate([...], Class.prototype, "field", void 0)__decoratefromtslibwhen decorator calls are emittedPossibly Closes #73
Field stripping rules
#)?foo = 'bar'@Dispatch() action = () => ...@Select(...) field$: Observable<T>__decorate()callfield: Observable<T>#private: stringstatic field: stringTest plan
declarefields, initializer preservation, opt-out, decorated fields with__decorateemission, private field preservation, JIT mode@Select/@Dispatchdecorators🤖 Generated with Claude Code