Skip to content

Commit b6576d9

Browse files
committed
fix test runner
1 parent ebd8de7 commit b6576d9

File tree

22 files changed

+1306
-464
lines changed

22 files changed

+1306
-464
lines changed

crates/oxc_angular_compiler/src/component/definition.rs

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ use crate::output::ast::{
2929
};
3030
use crate::pipeline::compilation::{ComponentCompilationJob, ConstValue};
3131
use crate::pipeline::emit::HostBindingCompilationResult;
32-
use crate::pipeline::selector::CssSelector;
3332

3433
/// Result of generating component definitions.
3534
pub struct ComponentDefinitions<'a> {
@@ -49,6 +48,8 @@ pub struct ComponentDefinitions<'a> {
4948
/// * `job` - The compilation job with template compilation results
5049
/// * `template_fn` - The compiled template function
5150
/// * `host_binding_result` - Optional host binding compilation result (function, hostAttrs, hostVars)
51+
/// * `attrs_ref` - Optional pre-pooled attrs constant reference (pooled before template compilation)
52+
/// * `view_query_fn` - Optional view query function (with pre-pooled predicates)
5253
///
5354
/// # Returns
5455
///
@@ -59,9 +60,18 @@ pub fn generate_component_definitions<'a>(
5960
job: &mut ComponentCompilationJob<'a>,
6061
template_fn: FunctionExpr<'a>,
6162
host_binding_result: Option<HostBindingCompilationResult<'a>>,
63+
attrs_ref: Option<OutputExpression<'a>>,
64+
view_query_fn: Option<OutputExpression<'a>>,
6265
) -> ComponentDefinitions<'a> {
63-
let cmp_definition =
64-
generate_cmp_definition(allocator, metadata, job, template_fn, host_binding_result);
66+
let cmp_definition = generate_cmp_definition(
67+
allocator,
68+
metadata,
69+
job,
70+
template_fn,
71+
host_binding_result,
72+
attrs_ref,
73+
view_query_fn,
74+
);
6575
let fac_definition = generate_fac_definition(allocator, metadata);
6676

6777
ComponentDefinitions { cmp_definition, fac_definition }
@@ -88,6 +98,8 @@ fn generate_cmp_definition<'a>(
8898
job: &mut ComponentCompilationJob<'a>,
8999
template_fn: FunctionExpr<'a>,
90100
host_binding_result: Option<HostBindingCompilationResult<'a>>,
101+
attrs_ref: Option<OutputExpression<'a>>,
102+
view_query_fn: Option<OutputExpression<'a>>,
91103
) -> OutputExpression<'a> {
92104
let mut entries: OxcVec<'a, LiteralMapEntry<'a>> = OxcVec::new_in(allocator);
93105

@@ -109,35 +121,15 @@ fn generate_cmp_definition<'a>(
109121
value: selector_entries,
110122
quoted: false,
111123
});
124+
}
112125

113-
// attrs: ["class", "..."] - only if first selector has attributes
114-
// This is optional and only included if the first selector specifies attributes.
115-
// Ported from Angular compiler.ts lines 195-212.
116-
let parsed_selectors = CssSelector::parse(selector);
117-
if let Some(first_selector) = parsed_selectors.first() {
118-
let selector_attrs = first_selector.get_attrs();
119-
if !selector_attrs.is_empty() {
120-
let mut attr_entries: OxcVec<'a, OutputExpression<'a>> =
121-
OxcVec::with_capacity_in(selector_attrs.len(), allocator);
122-
for attr in selector_attrs {
123-
attr_entries.push(OutputExpression::Literal(Box::new_in(
124-
LiteralExpr {
125-
value: LiteralValue::String(Atom::from_in(attr.as_str(), allocator)),
126-
source_span: None,
127-
},
128-
allocator,
129-
)));
130-
}
131-
entries.push(LiteralMapEntry {
132-
key: Atom::from("attrs"),
133-
value: OutputExpression::LiteralArray(Box::new_in(
134-
LiteralArrayExpr { entries: attr_entries, source_span: None },
135-
allocator,
136-
)),
137-
quoted: false,
138-
});
139-
}
140-
}
126+
// attrs: ["class", "..."] - only if first selector has attributes
127+
// This is optional and only included if the first selector specifies attributes.
128+
// Ported from Angular compiler.ts lines 195-212.
129+
// The attrs_ref is pre-pooled BEFORE template compilation to ensure correct constant ordering.
130+
// TypeScript Angular adds attrs to the pool BEFORE template ingestion/compilation.
131+
if let Some(attrs) = attrs_ref {
132+
entries.push(LiteralMapEntry { key: Atom::from("attrs"), value: attrs, quoted: false });
141133
}
142134

143135
// standalone: true/false
@@ -331,6 +323,17 @@ fn generate_cmp_definition<'a>(
331323
}
332324
}
333325

326+
// viewQuery: function(rf, ctx) { ... } (if any)
327+
// This handles @ViewChild/@ViewChildren decorators.
328+
// The predicate arrays are pre-pooled to ensure correct constant ordering.
329+
if let Some(view_query) = view_query_fn {
330+
entries.push(LiteralMapEntry {
331+
key: Atom::from("viewQuery"),
332+
value: view_query,
333+
quoted: false,
334+
});
335+
}
336+
334337
// consts: [...] or consts: function() { ...initializers...; return [...]; }
335338
// Per Angular compiler.ts lines 260-268:
336339
// - If there are const initializers (e.g., for i18n dual-mode), wrap in arrow function

crates/oxc_angular_compiler/src/component/transform.rs

Lines changed: 127 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ use super::metadata::{AngularVersion, ComponentMetadata, HostMetadata};
1818
use crate::ast::expression::{BindingType, ParsedEventType};
1919
use crate::ast::r3::{R3BoundAttribute, R3BoundEvent, SecurityContext};
2020
use crate::directive::{
21-
extract_directive_metadata, find_directive_decorator_span, generate_directive_definitions,
21+
R3QueryMetadata, create_view_queries_function, extract_directive_metadata,
22+
extract_view_queries, find_directive_decorator_span, generate_directive_definitions,
2223
};
2324
use crate::injectable::{
2425
extract_injectable_metadata, find_injectable_decorator_span,
@@ -331,6 +332,13 @@ pub fn transform_angular_file(
331332
let mut decorator_spans_to_remove: Vec<Span> = Vec::new();
332333
let mut needs_angular_core_import = false;
333334

335+
// Shared constant pool index across all components in this file.
336+
// This ensures constant names (_c0, _c1, etc.) don't conflict when
337+
// multiple components are compiled in the same file.
338+
// TypeScript Angular uses ONE file-level constant pool; we simulate this
339+
// by tracking the next index and passing it to each component.
340+
let mut shared_pool_index: u32 = 0;
341+
334342
// 2. Walk AST to find @Component decorated classes and extract metadata
335343
for stmt in &parser_ret.program.body {
336344
let class = match stmt {
@@ -361,10 +369,25 @@ pub fn transform_angular_file(
361369
let class_name = metadata.class_name.to_string();
362370

363371
if let Some(template) = template_source {
372+
// 4.5 Extract view queries from the class (for @ViewChild/@ViewChildren)
373+
// These need to be passed to compile_component_full so predicates can be pooled
374+
let view_queries = extract_view_queries(allocator, class);
375+
364376
// 4. Compile the template and generate ɵcmp/ɵfac
365-
match compile_component_full(allocator, &template, &mut metadata, path, options)
366-
{
377+
// Pass the shared pool index to ensure unique constant names
378+
match compile_component_full(
379+
allocator,
380+
&template,
381+
&mut metadata,
382+
path,
383+
options,
384+
view_queries,
385+
shared_pool_index,
386+
) {
367387
Ok(compilation_result) => {
388+
// Update the shared pool index for the next component
389+
shared_pool_index = compilation_result.next_pool_index;
390+
368391
let component_id = format!("{}@{}", path, class_name);
369392

370393
// Store for HMR if enabled
@@ -623,15 +646,25 @@ struct FullCompilationResult {
623646

624647
/// HMR initialization code (if HMR is enabled).
625648
hmr_initializer_js: Option<String>,
649+
650+
/// The next constant pool index to use for the next component.
651+
/// This is used to share pool state across multiple components in the same file.
652+
next_pool_index: u32,
626653
}
627654

628655
/// Compile a component template and generate ɵcmp/ɵfac definitions.
656+
///
657+
/// The `pool_starting_index` parameter is used to ensure constant names don't conflict
658+
/// when compiling multiple components in the same file. Each component continues from
659+
/// where the previous component's pool left off.
629660
fn compile_component_full<'a>(
630661
allocator: &'a Allocator,
631662
template: &'a str,
632663
metadata: &mut ComponentMetadata<'a>,
633664
file_path: &str,
634665
options: &TransformOptions,
666+
view_queries: OxcVec<'a, R3QueryMetadata<'a>>,
667+
pool_starting_index: u32,
635668
) -> Result<FullCompilationResult, Vec<OxcDiagnostic>> {
636669
use oxc_allocator::FromIn;
637670

@@ -734,6 +767,9 @@ fn compile_component_full<'a>(
734767
// In PerComponent mode, this would be provided by the build tool
735768
// For now, we don't have a way to pass it from NAPI, so it's None
736769
all_deferrable_deps_fn: None,
770+
// Use the shared pool starting index to avoid duplicate constant names
771+
// when compiling multiple components in the same file
772+
pool_starting_index,
737773
};
738774

739775
let mut job = ingest_component_with_options(
@@ -742,18 +778,43 @@ fn compile_component_full<'a>(
742778
r3_result.nodes,
743779
ingest_options,
744780
);
781+
782+
// BEFORE template compilation: Pool attrs const if first selector has attributes.
783+
// This matches TypeScript Angular which adds attrs to the pool BEFORE template ingestion.
784+
// By pooling attrs first, it gets _c0 (or the first available index).
785+
// See: packages/compiler/src/render3/view/compiler.ts lines 192-212
786+
let attrs_ref = pool_selector_attrs(allocator, &mut job, metadata);
787+
788+
// BEFORE template compilation: Pool view query predicates.
789+
// TypeScript Angular pools query predicates BEFORE template compilation and pure functions.
790+
// This ensures correct constant ordering: attrs -> query predicates -> pure functions.
791+
let view_query_fn = if !view_queries.is_empty() {
792+
let fn_name = Some(metadata.class_name.as_str());
793+
Some(create_view_queries_function(
794+
allocator,
795+
view_queries.as_slice(),
796+
fn_name,
797+
Some(&mut job.pool),
798+
))
799+
} else {
800+
None
801+
};
802+
745803
let compiled = compile_template(&mut job);
746804

747805
// Stage 6: Compile host bindings (if any)
748806
let host_binding_result = compile_component_host_bindings(allocator, metadata);
749807

750808
// Stage 7: Generate ɵcmp/ɵfac definitions
809+
// Pass the pre-pooled attrs_ref so the attrs entry uses the correct constant index
751810
let definitions = generate_component_definitions(
752811
allocator,
753812
metadata,
754813
&mut job,
755814
compiled.template_fn,
756815
host_binding_result,
816+
attrs_ref,
817+
view_query_fn,
757818
);
758819

759820
// Emit JavaScript
@@ -809,6 +870,9 @@ fn compile_component_full<'a>(
809870
None
810871
};
811872

873+
// Get the next pool index for the next component in this file
874+
let next_pool_index = job.pool.next_name_index();
875+
812876
// Collect any diagnostics from the compilation job
813877
// (Done after using job to avoid borrow issues)
814878
diagnostics.extend(job.diagnostics);
@@ -818,7 +882,14 @@ fn compile_component_full<'a>(
818882
// Errors would have been caught earlier in parsing/transformation
819883
}
820884

821-
Ok(FullCompilationResult { template_js, cmp_js, fac_js, declarations_js, hmr_initializer_js })
885+
Ok(FullCompilationResult {
886+
template_js,
887+
cmp_js,
888+
fac_js,
889+
declarations_js,
890+
hmr_initializer_js,
891+
next_pool_index,
892+
})
822893
}
823894

824895
/// Resolve template content from inline or external source.
@@ -1050,6 +1121,7 @@ pub fn compile_template_to_js_with_options<'a>(
10501121
enable_debug_locations,
10511122
template_source: Some(template),
10521123
all_deferrable_deps_fn: None,
1124+
pool_starting_index: 0, // Standalone template compilation starts from 0
10531125
};
10541126

10551127
// Stage 3-5: Ingest and compile
@@ -1216,6 +1288,7 @@ pub fn compile_template_for_hmr<'a>(
12161288
enable_debug_locations,
12171289
template_source: Some(template),
12181290
all_deferrable_deps_fn: None,
1291+
pool_starting_index: 0, // HMR template compilation starts from 0
12191292
};
12201293

12211294
// Stage 3-5: Ingest and compile
@@ -1546,6 +1619,56 @@ fn convert_host_metadata_input_to_host_metadata<'a>(
15461619
}
15471620
}
15481621

1622+
/// Pool selector attrs constant BEFORE template compilation.
1623+
///
1624+
/// This matches TypeScript Angular's behavior where the attrs constant is added to
1625+
/// the constant pool BEFORE template ingestion and compilation. By pooling it first,
1626+
/// attrs gets the first available constant index (_c0), ensuring the correct constant
1627+
/// ordering in the output.
1628+
///
1629+
/// See: packages/compiler/src/render3/view/compiler.ts lines 192-212
1630+
fn pool_selector_attrs<'a>(
1631+
allocator: &'a Allocator,
1632+
job: &mut crate::pipeline::compilation::ComponentCompilationJob<'a>,
1633+
metadata: &super::metadata::ComponentMetadata<'a>,
1634+
) -> Option<crate::output::ast::OutputExpression<'a>> {
1635+
use crate::output::ast::{LiteralArrayExpr, LiteralExpr, LiteralValue, OutputExpression};
1636+
use crate::pipeline::selector::CssSelector;
1637+
use oxc_allocator::FromIn;
1638+
1639+
let selector = metadata.selector.as_ref()?;
1640+
let parsed_selectors = CssSelector::parse(selector);
1641+
let first_selector = parsed_selectors.first()?;
1642+
let selector_attrs = first_selector.get_attrs();
1643+
1644+
if selector_attrs.is_empty() {
1645+
return None;
1646+
}
1647+
1648+
// Build the attrs array: ["attrName", "attrValue", ...]
1649+
let mut attr_entries: OxcVec<'a, OutputExpression<'a>> =
1650+
OxcVec::with_capacity_in(selector_attrs.len(), allocator);
1651+
for attr in selector_attrs {
1652+
attr_entries.push(OutputExpression::Literal(oxc_allocator::Box::new_in(
1653+
LiteralExpr {
1654+
value: LiteralValue::String(Atom::from_in(attr.as_str(), allocator)),
1655+
source_span: None,
1656+
},
1657+
allocator,
1658+
)));
1659+
}
1660+
1661+
// Create the attrs array literal
1662+
let attrs_array = OutputExpression::LiteralArray(oxc_allocator::Box::new_in(
1663+
LiteralArrayExpr { entries: attr_entries, source_span: None },
1664+
allocator,
1665+
));
1666+
1667+
// Pool the attrs array using constantPool.getConstLiteral() with forceShared=true
1668+
// This extracts it to a const like: const _c0 = ["bitBadge", ""];
1669+
Some(job.pool.get_const_literal(attrs_array, true))
1670+
}
1671+
15491672
/// Compile host bindings from HostMetadataInput (owned strings).
15501673
///
15511674
/// This is used by `compile_template_to_js_with_options` when host metadata is provided

crates/oxc_angular_compiler/src/directive/compiler.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,13 @@ fn build_base_directive_fields<'a>(
9898

9999
// contentQueries: (rf, ctx, dirIndex) => { ... }
100100
if !metadata.queries.is_empty() {
101+
// Note: Directive compiler doesn't have access to constant pool, so predicates
102+
// are not pooled. For components, pool is passed from component compilation.
101103
let content_queries_fn = super::query::create_content_queries_function(
102104
allocator,
103105
&metadata.queries,
104106
Some(metadata.name.as_str()),
107+
None,
105108
);
106109
entries.push(LiteralMapEntry {
107110
key: Atom::from("contentQueries"),
@@ -112,10 +115,13 @@ fn build_base_directive_fields<'a>(
112115

113116
// viewQuery: (rf, ctx) => { ... }
114117
if !metadata.view_queries.is_empty() {
118+
// Note: Directive compiler doesn't have access to constant pool, so predicates
119+
// are not pooled. For components, pool is passed from component compilation.
115120
let view_queries_fn = super::query::create_view_queries_function(
116121
allocator,
117122
&metadata.view_queries,
118123
Some(metadata.name.as_str()),
124+
None,
119125
);
120126
entries.push(LiteralMapEntry {
121127
key: Atom::from("viewQuery"),

0 commit comments

Comments
 (0)