44//! containing Angular components into compiled JavaScript.
55
66use std:: collections:: HashMap ;
7- use std:: path:: PathBuf ;
87
98use oxc_allocator:: Allocator ;
109use oxc_ast:: ast:: { Declaration , ExportDefaultDeclarationKind , Statement } ;
11- use oxc_codegen:: { Codegen , CodegenOptions } ;
1210use oxc_diagnostics:: OxcDiagnostic ;
1311use oxc_parser:: Parser ;
14- use oxc_span:: { Atom , SourceType } ;
12+ use oxc_span:: { Atom , SourceType , Span } ;
1513
16- use super :: decorator:: extract_component_metadata;
14+ use super :: decorator:: { extract_component_metadata, find_component_decorator_span } ;
1715use super :: definition:: generate_component_definitions;
1816use super :: metadata:: ComponentMetadata ;
1917use crate :: output:: ast:: FunctionExpr ;
@@ -162,8 +160,9 @@ pub fn transform_angular_file(
162160 // Still continue to try to generate output for partial results
163161 }
164162
165- // Collect component definitions to append after codegen
163+ // Collect component definitions to append and decorator spans to remove
166164 let mut component_definitions: Vec < String > = Vec :: new ( ) ;
165+ let mut decorator_spans_to_remove: Vec < Span > = Vec :: new ( ) ;
167166 let mut needs_angular_core_import = false ;
168167
169168 // 2. Walk AST to find @Component decorated classes and extract metadata
@@ -201,9 +200,16 @@ pub fn transform_angular_file(
201200 ) ;
202201 }
203202
203+ // Track the decorator span to remove
204+ if let Some ( span) = find_component_decorator_span ( class) {
205+ decorator_spans_to_remove. push ( span) ;
206+ }
207+
204208 // Store the ɵcmp/ɵfac definitions to append later
209+ // Include declarations (child view functions) before ɵcmp
205210 component_definitions. push ( format ! (
206- "{}.ɵcmp = {};\n {}.ɵfac = {};" ,
211+ "{}{}.ɵcmp = {};\n {}.ɵfac = {};" ,
212+ compilation_result. declarations_js,
207213 class_name,
208214 compilation_result. cmp_js,
209215 class_name,
@@ -236,13 +242,36 @@ pub fn transform_angular_file(
236242 }
237243 }
238244
239- // 5. Generate output code using oxc_codegen
240- let codegen_options = CodegenOptions {
241- single_quote : true ,
242- source_map_path : if options. sourcemap { Some ( PathBuf :: from ( path) ) } else { None } ,
243- ..Default :: default ( )
244- } ;
245- let codegen_ret = Codegen :: new ( ) . with_options ( codegen_options) . build ( & parser_ret. program ) ;
245+ // 5. Generate output code by modifying the original source
246+ // We use string manipulation instead of oxc_codegen to preserve
247+ // the original code structure and avoid decorator placement issues.
248+
249+ // Sort decorator spans in reverse order so we can remove them from back to front
250+ decorator_spans_to_remove. sort_by ( |a, b| b. start . cmp ( & a. start ) ) ;
251+
252+ // Start with the original source
253+ let mut modified_source = source. to_string ( ) ;
254+
255+ // Remove each decorator span from the source
256+ for span in decorator_spans_to_remove {
257+ let start = span. start as usize ;
258+ let end = span. end as usize ;
259+
260+ // Find any trailing whitespace/newline after the decorator to also remove
261+ let mut actual_end = end;
262+ let bytes = modified_source. as_bytes ( ) ;
263+ while actual_end < bytes. len ( ) {
264+ let c = bytes[ actual_end] ;
265+ if c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' {
266+ actual_end += 1 ;
267+ } else {
268+ break ;
269+ }
270+ }
271+
272+ // Remove the decorator and trailing whitespace
273+ modified_source. replace_range ( start..actual_end, "" ) ;
274+ }
246275
247276 // Build final code with imports and definitions
248277 let mut code = String :: new ( ) ;
@@ -252,8 +281,8 @@ pub fn transform_angular_file(
252281 code. push_str ( "import * as i0 from '@angular/core';\n " ) ;
253282 }
254283
255- // Append the original code
256- code. push_str ( & codegen_ret . code ) ;
284+ // Append the modified original code
285+ code. push_str ( & modified_source ) ;
257286
258287 // Append ɵcmp/ɵfac definitions after the generated code
259288 if !component_definitions. is_empty ( ) {
@@ -265,7 +294,8 @@ pub fn transform_angular_file(
265294 }
266295
267296 result. code = code;
268- result. map = codegen_ret. map . map ( |m| m. to_json_string ( ) ) ;
297+ // Note: source maps not supported with string manipulation approach
298+ result. map = None ;
269299
270300 result
271301}
@@ -280,6 +310,9 @@ struct FullCompilationResult {
280310
281311 /// ɵfac factory function as JavaScript.
282312 fac_js : String ,
313+
314+ /// Additional declarations (child view functions, constants).
315+ declarations_js : String ,
283316}
284317
285318/// Compile a component template and generate ɵcmp/ɵfac definitions.
@@ -332,6 +365,13 @@ fn compile_component_full<'a>(
332365 // Emit JavaScript
333366 let emitter = JsEmitter :: new ( ) ;
334367
368+ // Emit declarations (child view functions, constants)
369+ let mut declarations_js = String :: new ( ) ;
370+ for decl in compiled. declarations . iter ( ) {
371+ declarations_js. push_str ( & emitter. emit_statement ( decl) ) ;
372+ declarations_js. push ( '\n' ) ;
373+ }
374+
335375 // For HMR, we emit the template separately using compile_template_to_js
336376 // The ɵcmp already contains the template function inline
337377 let template_js =
@@ -354,7 +394,7 @@ fn compile_component_full<'a>(
354394 // Errors would have been caught earlier in parsing/transformation
355395 }
356396
357- Ok ( FullCompilationResult { template_js, cmp_js, fac_js } )
397+ Ok ( FullCompilationResult { template_js, cmp_js, fac_js, declarations_js } )
358398}
359399
360400/// Resolve template content from inline or external source.
0 commit comments