@@ -18,7 +18,8 @@ use super::metadata::{AngularVersion, ComponentMetadata, HostMetadata};
1818use crate :: ast:: expression:: { BindingType , ParsedEventType } ;
1919use crate :: ast:: r3:: { R3BoundAttribute , R3BoundEvent , SecurityContext } ;
2020use 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} ;
2324use 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.
629660fn 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
0 commit comments