Skip to content

Commit 90d2286

Browse files
Brooooooklynclaude
andcommitted
fix: version-gate Angular 19 runtime instructions (#107)
Angular 19.2 runtime uses different instructions than Angular 20+: - Combined `propertyInterpolate*`/`attributeInterpolate*`/`stylePropInterpolate*`/ `styleMapInterpolate*`/`classMapInterpolate*` instead of nested `property(interpolate*())` calls - `hostProperty` instead of `domProperty` This adds `supports_value_interpolation()` (>= 20) and `supports_dom_property()` (>= 20) version gates so the compiler emits the correct instructions based on the target Angular version. Also fixes `collapse_singleton_interpolations` to only collapse StyleProp/ StyleMap/ClassMap singletons on v20+ (v19 only collapsed Attribute), and refactors the chaining phase's CHAIN_COMPATIBILITY from LazyLock<FxHashMap> to a const fn match lookup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c04418c commit 90d2286

19 files changed

+1331
-112
lines changed

crates/oxc_angular_compiler/src/component/metadata.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,23 @@ impl AngularVersion {
4545
self.major >= 20
4646
}
4747

48+
/// Check if this version supports standalone `ɵɵinterpolate*` instructions (v20.0.0+).
49+
///
50+
/// Angular v20 introduced standalone `ɵɵinterpolate1`–`ɵɵinterpolateV` instructions
51+
/// used as nested calls within `ɵɵproperty`/`ɵɵattribute`. Earlier versions use
52+
/// combined `ɵɵpropertyInterpolate*`/`ɵɵattributeInterpolate*` instructions.
53+
pub fn supports_value_interpolation(&self) -> bool {
54+
self.major >= 20
55+
}
56+
57+
/// Check if this version supports `ɵɵdomProperty` (v20.0.0+).
58+
///
59+
/// Angular v20 introduced `ɵɵdomProperty` for host/DomOnly property bindings.
60+
/// Earlier versions use `ɵɵhostProperty` instead.
61+
pub fn supports_dom_property(&self) -> bool {
62+
self.major >= 20
63+
}
64+
4865
/// Parse a version string like "19.0.0" or "19.0.0-rc.1".
4966
///
5067
/// Returns `None` if the version string is invalid.

crates/oxc_angular_compiler/src/component/transform.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ use crate::pipeline::emit::{
6363
};
6464
use crate::pipeline::ingest::{
6565
HostBindingInput, IngestOptions, ingest_component, ingest_component_with_options,
66-
ingest_host_binding,
66+
ingest_host_binding, ingest_host_binding_with_version,
6767
};
6868
use crate::transform::HtmlToR3Transform;
6969
use crate::transform::html_to_r3::TransformOptions as R3TransformOptions;
@@ -2423,8 +2423,12 @@ fn compile_component_full<'a>(
24232423
// Pass the template pool's current index to ensure host binding constants
24242424
// continue from where template compilation left off (avoiding duplicate names)
24252425
let template_pool_index = job.pool.next_name_index();
2426-
let host_binding_output =
2427-
compile_component_host_bindings(allocator, metadata, template_pool_index);
2426+
let host_binding_output = compile_component_host_bindings(
2427+
allocator,
2428+
metadata,
2429+
template_pool_index,
2430+
options.angular_version,
2431+
);
24282432

24292433
// Extract the result and update pool index if host bindings were compiled
24302434
let (host_binding_result, host_binding_next_pool_index, host_binding_declarations) =
@@ -3111,6 +3115,7 @@ fn compile_component_host_bindings<'a>(
31113115
allocator: &'a Allocator,
31123116
metadata: &ComponentMetadata<'a>,
31133117
pool_starting_index: u32,
3118+
angular_version: Option<AngularVersion>,
31143119
) -> Option<HostBindingCompilationOutput<'a>> {
31153120
let host = metadata.host.as_ref()?;
31163121

@@ -3134,7 +3139,8 @@ fn compile_component_host_bindings<'a>(
31343139

31353140
// Ingest and compile the host bindings with the pool starting index
31363141
// This ensures constant names continue from where template compilation left off
3137-
let mut job = ingest_host_binding(allocator, input, pool_starting_index);
3142+
let mut job =
3143+
ingest_host_binding_with_version(allocator, input, pool_starting_index, angular_version);
31383144
let result = compile_host_bindings(&mut job);
31393145

31403146
// Get the next pool index after host binding compilation

crates/oxc_angular_compiler/src/pipeline/compilation.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,23 @@ impl<'a> ComponentCompilationJob<'a> {
261261
self.angular_version.map_or(true, |v: AngularVersion| v.supports_conditional_create())
262262
}
263263

264+
/// Check if standalone `ɵɵinterpolate*` instructions are supported (Angular 20+).
265+
///
266+
/// Returns `true` for Angular 20+ or when version is unknown (None = latest).
267+
/// Returns `false` for Angular 19 and earlier, which use combined
268+
/// `ɵɵpropertyInterpolate*`/`ɵɵattributeInterpolate*` instructions.
269+
pub fn supports_value_interpolation(&self) -> bool {
270+
self.angular_version.map_or(true, |v: AngularVersion| v.supports_value_interpolation())
271+
}
272+
273+
/// Check if `ɵɵdomProperty` is supported (Angular 20+).
274+
///
275+
/// Returns `true` for Angular 20+ or when version is unknown (None = latest).
276+
/// Returns `false` for Angular 19 and earlier, which use `ɵɵhostProperty` instead.
277+
pub fn supports_dom_property(&self) -> bool {
278+
self.angular_version.map_or(true, |v: AngularVersion| v.supports_dom_property())
279+
}
280+
264281
/// Allocates a new cross-reference ID.
265282
pub fn allocate_xref_id(&mut self) -> XrefId {
266283
let id = XrefId::new(self.next_xref_id);
@@ -601,6 +618,8 @@ pub struct HostBindingCompilationJob<'a> {
601618
pub fn_suffix: Atom<'a>,
602619
/// Diagnostics collected during compilation.
603620
pub diagnostics: std::vec::Vec<OxcDiagnostic>,
621+
/// Angular version for version-gated instruction emission.
622+
pub angular_version: Option<AngularVersion>,
604623
}
605624

606625
impl<'a> HostBindingCompilationJob<'a> {
@@ -646,6 +665,7 @@ impl<'a> HostBindingCompilationJob<'a> {
646665
mode: TemplateCompilationMode::DomOnly, // Host bindings always use DomOnly
647666
fn_suffix: Atom::from("HostBindings"),
648667
diagnostics: std::vec::Vec::new(),
668+
angular_version: None,
649669
}
650670
}
651671

@@ -654,6 +674,16 @@ impl<'a> HostBindingCompilationJob<'a> {
654674
CompilationJobKind::Host
655675
}
656676

677+
/// Check if standalone `ɵɵinterpolate*` instructions are supported (Angular 20+).
678+
pub fn supports_value_interpolation(&self) -> bool {
679+
self.angular_version.map_or(true, |v| v.supports_value_interpolation())
680+
}
681+
682+
/// Check if `ɵɵdomProperty` is supported (Angular 20+).
683+
pub fn supports_dom_property(&self) -> bool {
684+
self.angular_version.map_or(true, |v| v.supports_dom_property())
685+
}
686+
657687
/// Allocates a new cross-reference ID.
658688
pub fn allocate_xref_id(&mut self) -> XrefId {
659689
let id = XrefId::new(self.next_xref_id);

crates/oxc_angular_compiler/src/pipeline/ingest.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3941,13 +3941,24 @@ pub fn ingest_host_binding<'a>(
39413941
allocator: &'a Allocator,
39423942
input: HostBindingInput<'a>,
39433943
pool_starting_index: u32,
3944+
) -> HostBindingCompilationJob<'a> {
3945+
ingest_host_binding_with_version(allocator, input, pool_starting_index, None)
3946+
}
3947+
3948+
/// Ingest host bindings into a `HostBindingCompilationJob` with a specific Angular version.
3949+
pub fn ingest_host_binding_with_version<'a>(
3950+
allocator: &'a Allocator,
3951+
input: HostBindingInput<'a>,
3952+
pool_starting_index: u32,
3953+
angular_version: Option<crate::AngularVersion>,
39443954
) -> HostBindingCompilationJob<'a> {
39453955
let mut job = HostBindingCompilationJob::with_pool_starting_index(
39463956
allocator,
39473957
input.component_name,
39483958
input.component_selector,
39493959
pool_starting_index,
39503960
);
3961+
job.angular_version = angular_version;
39513962

39523963
// Ingest host properties
39533964
for property in input.properties {

0 commit comments

Comments
 (0)