fix: support outputFromObservable() from @angular/core/rxjs-interop#3
Closed
ashley-hunter wants to merge 51 commits intomainfrom
Closed
fix: support outputFromObservable() from @angular/core/rxjs-interop#3ashley-hunter wants to merge 51 commits intomainfrom
ashley-hunter wants to merge 51 commits intomainfrom
Conversation
…oidzero-dev#205) * fix(angular): preserve block-body functions in decorator providers Block-body arrow functions and function expressions in decorator properties (e.g., useFactory) were silently having unsupported statements dropped. Only return and expression statements survived, corrupting the function body and causing runtime errors. Add RawSource fallback: when convert_oxc_expression encounters a block-body arrow with unsupported statement types (const, if, for, try/catch, etc.) or a function expression, it preserves the complete source text verbatim via span slicing instead of silently dropping statements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(angular): pass source_text through build_decorator_metadata_array Thread source_text into class metadata builder so decorator arguments containing block-body arrows/function expressions are preserved via RawSource fallback instead of being silently dropped. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(angular): thread source_text through remaining metadata builders - build_ctor_params_metadata: pass source_text so @Inject(...) args with complex expressions are preserved in ɵsetClassMetadata - build_prop_decorators_metadata: pass source_text so @input({...}) with complex transform functions are preserved - extract_provided_in: propagate source_text from parent extract_injectable_metadata into forwardRef extraction Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(angular): strip TS types from RawSource, add expression-body fallback, thread source_text through property decorators Three reviewer fixes: 1. P1 - RawSource now strips TypeScript type annotations via parse-transform-codegen pipeline, preventing invalid JS output like `(dep: Dep) => { ... }` in generated code. 2. Medium - Expression-body arrows with unsupported inner expressions (e.g., `() => someUnsupportedExpr`) now fall back to RawSource instead of returning None. 3. P2 - Thread source_text through property_decorators.rs so @input({transform}), @ViewChild, @ContentChild arguments with complex expressions are preserved via RawSource fallback. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(angular): use module source type and add fast path for TS stripping - Use SourceType::ts().with_module(true) instead of script-mode ts() so import.meta and ESM-only syntax parse correctly in RawSource fallback expressions. - Add fast path: try parsing as .mjs first. If the expression is already valid JavaScript (no type annotations), return it as-is without running the heavier semantic→transform→codegen pipeline. Only expressions with actual TypeScript syntax pay the full cost. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a class has both Angular decorators (e.g. @Injectable) and non-Angular decorators (e.g. NGXS @State, @selector, @action), lower all decorators when converting class declarations to class expressions. Non-Angular member decorators are emitted as __decorate() calls matching TypeScript's tsc output: null for methods/accessors, void 0 for properties, instance members before static members. Co-authored-by: Ashley Hunter <ashh640@users.noreply.github.com>
When a component .ts file changes and only the inline `template: `...`` portion differs, route it through the existing component HMR mechanism instead of triggering a full page reload. This gives inline template components the same fast HMR experience as external .html templates. Implementation: - Cache inline template content during transform - In handleHotUpdate, compare old vs new template content - If only the template changed, add to pendingHmrUpdates and send angular:component-update event (same path as external templates) - The existing HMR middleware endpoint already handles inline templates via extractInlineTemplate(), so no middleware changes needed - Falls back to full reload if non-template code also changed
…ty (voidzero-dev#208) * fix: compile animation trigger bindings in host property to ɵɵsyntheticHostProperty convert_animations_for_host was unconditionally converting all AnimationBindingOp entries to CreateOp::Animation (ɵɵanimateEnter/ɵɵanimateLeave), which is only correct for animate.enter/animate.leave bindings. Host [@trigger] bindings (AnimationBindingKind::Value) need to remain in the update list so they are reified as ɵɵsyntheticHostProperty in the rf & 2 block. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: handle animation trigger bindings in directive host property The directive's parse_host_property_name was missing the @ animation check, causing host: { '[@slidein]': 'state' } on directives to emit ɵɵdomProperty instead of ɵɵsyntheticHostProperty. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
voidzero-dev#211) fix(vite-plugin): automatically skip Angular linker for packages in optimizeDeps.exclude The linker plugin now reads `optimizeDeps.exclude` (via `configResolved`) and adds an early return in both places where linking happens: - Rolldown pre-bundling load hook - Vite transform hook - Supports classic `node_modules` dependency structure and Yarn PnP Co-authored-by: Arnoud Bos <arnoud.bos@crunchr.com>
…ev#216) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…v#218) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…dev#230) Angular's control-directives pipeline treats [formField] as a normal property binding and then inserts separate controlCreate/control instructions. Our Rust pipeline had drifted from that logic and rewrote [formField] into a custom ControlOp carrying the bound value, which emitted legacy output like ɵɵcontrol(ctx.myField, "formField") without ever writing the directive input via ɵɵproperty("formField", ...). In Angular 21 signal forms this leaves FormField.field unset and can surface as NG0950 at runtime. This change restores the expected control flow for template bindings: - keep [formField] as a regular PropertyOp - emit a separate ControlOp after the property update - reify ControlOp to zero-arg ɵɵcontrol() - stop extracting duplicate const metadata from ControlOp itself The tests now cover both the regression and Angular's mixed-order control fixture behavior: - [formField] must emit ɵɵproperty("formField", ...) plus ɵɵcontrol() - legacy ɵɵcontrol(value, "formField") output is rejected - mixed [formField]/[value] bindings preserve update order - extracted const metadata preserves per-element binding order Verified with targeted cargo test runs for the new regression, control binding extraction, mixed property ordering, const ordering, pipe slot propagation, and the existing [field] non-control regression.
…adata (voidzero-dev#232) The Angular Linker's build_features was ignoring the controlCreate property on ɵɵngDeclareDirective, so directives like @angular/forms/signals FormField were linked without ɵɵControlFeature(passThroughInput). This left DirectiveDef.controlDef unset at runtime, making template-emitted ɵɵcontrolCreate()/ɵɵcontrol() calls no-ops and breaking [formField] bindings with NG0950. Mirrors Angular TS compiler.ts:151-155. Fixes voidzero-dev#229 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…zero-dev#231) The extract_param_dependency function in pipe/decorator.rs was missing the "Inject" arm in its decorator match, causing @Inject(TOKEN) to be silently ignored. The injection token was then extracted from the TypeScript type annotation instead — which is undefined at runtime when the type is an interface. The same fix already exists in directive/decorator.rs and injectable/decorator.rs. This brings pipe/decorator.rs in line. Surfaced by Angular 20 which added an assertDefined(token) guard in the DI runtime that throws immediately on an undefined token, whereas Angular 19 would silently pass through.
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…ev#236) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…#240) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Bumps [rand](https://github.com/rust-random/rand) from 0.8.5 to 0.8.6. - [Release notes](https://github.com/rust-random/rand/releases) - [Changelog](https://github.com/rust-random/rand/blob/0.8.6/CHANGELOG.md) - [Commits](rust-random/rand@0.8.5...0.8.6) --- updated-dependencies: - dependency-name: rand dependency-version: 0.8.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#253) Pin GitHub Actions to hashes for latest tags Agent-Logs-Url: https://github.com/voidzero-dev/oxc-angular-compiler/sessions/3826faf2-3d5a-4884-9327-778388ffca6f Co-authored-by: anthropic-code-agent[bot] <242468646+Claude@users.noreply.github.com> Co-authored-by: Brooooooklyn <3468483+Brooooooklyn@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…#251) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…anics (voidzero-dev#248) fix: use byte-safe indexing in CSS style encapsulation to prevent UTF-8 panics Several functions in the style encapsulation module used char indices to slice UTF-8 strings, causing panics on selectors containing multibyte characters (e.g. `ü`, `é`, `─`). This fixes `split_by_combinators`, `find_pseudo_element_start`, `find_pseudo_class_start`, `find_matching_paren`, and `try_scope_pseudo_function_with_context` to use either `char_indices()` or byte-level scanning for ASCII-only delimiters. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
outputFromObservable() was not recognised as an output initializer, so properties declared with it were silently dropped from the outputs metadata in the compiled ɵɵdefineComponent/ɵɵdefineDirective call. Extend try_parse_signal_output to detect both output() and outputFromObservable(). The key difference: output() takes options as its first argument while outputFromObservable(observable, options?) takes them as its second — the observable expression itself is irrelevant for metadata extraction. Adds 5 unit tests covering: simple EventEmitter arg, direct property reference, piped observable chain (the reported real-world case), alias via second arg, and mixed usage with output(). Also adds an e2e compare fixture so the output can be verified against the official Angular compiler. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.
Problem
Properties declared with
outputFromObservable()from@angular/core/rxjs-interopwere silently dropped from the compiled output metadata. The Angular runtime uses theoutputs: {}object inɵɵdefineComponent/ɵɵdefineDirectiveto wire up event bindings — missing entries mean parent components can never bind to those outputs.Example that was broken:
Root cause
try_parse_signal_outputinproperty_decorators.rsonly matched the identifier"output".outputFromObservablewas never recognised, so its properties returnedNoneand were excluded from the outputs metadata.Fix
Extend
try_parse_signal_outputto detect bothoutput()andoutputFromObservable()(including namespaced forms likecore.outputFromObservable()). The key behavioural difference is argument position:output()takes options at index 0, whileoutputFromObservable(observable, options?)takes them at index 1. The observable expression itself is irrelevant for metadata extraction and is ignored.Tests
5 new unit tests added via TDD (red → green):
outputFromObservable(new EventEmitter<string>())outputFromObservable(this.service.obs$)outputFromObservable(this.service.obs$.pipe(skip(1), debounceTime(300)))(the reported real-world case)outputFromObservable(new EventEmitter(), { alias: 'clicked' })output()andoutputFromObservable()togetherAlso adds an e2e compare fixture (
inputs-outputs/output-from-observable) covering all four scenarios so the output can be validated against the official Angular compiler.Coverage
All 8 Angular initializer API functions are now handled by the Rust compiler.
outputFromObservablewas the only missing one.