diff --git a/crates/oxc_angular_compiler/src/component/transform.rs b/crates/oxc_angular_compiler/src/component/transform.rs index f2c8c6ba0..66fa9af14 100644 --- a/crates/oxc_angular_compiler/src/component/transform.rs +++ b/crates/oxc_angular_compiler/src/component/transform.rs @@ -1275,7 +1275,10 @@ fn extract_all_jit_member_decorators( string_consts, core_namespace, ); - angular_decs.extend(synthesized); + // Upstream inserts the synthesized decorator ahead of the member's + // existing decorators (`[newDecorator, ...member.node.modifiers]`), + // so it precedes any explicit ones in `propDecorators`. + angular_decs.splice(0..0, synthesized); } if !angular_decs.is_empty() { diff --git a/crates/oxc_angular_compiler/tests/jit_signal_initializer_test.rs b/crates/oxc_angular_compiler/tests/jit_signal_initializer_test.rs index 3f20f49f5..2918d996b 100644 --- a/crates/oxc_angular_compiler/tests/jit_signal_initializer_test.rs +++ b/crates/oxc_angular_compiler/tests/jit_signal_initializer_test.rs @@ -334,3 +334,31 @@ fn member_decorators_named_like_angular_ones_count_only_from_angular_core() { "aliased:\n{aliased}\nplain:\n{plain}" ); } + +#[test] +fn synthesized_decorator_precedes_explicit_member_decorators() { + // Issue #540: upstream prepends the synthesized signal-API decorator ahead + // of the member's existing decorators + // (`[newDecorator, ...member.node.modifiers]`), so a member like + // `@Output() x = input(0)` lists the synthesized `i0.Input` before the + // explicit `Output` in `propDecorators` — not after it. + let out = compile_jit(&component(" @Output() x = input(0);\n", "input, Output, EventEmitter")); + let compact: String = out.chars().filter(|c| !c.is_whitespace()).collect(); + let input_pos = compact.find("type:i0.Input").expect("synthesized i0.Input missing"); + let output_pos = compact.find("type:Output").expect("explicit Output missing"); + assert!( + input_pos < output_pos, + "synthesized i0.Input should precede the explicit @Output in propDecorators. Got:\n{out}" + ); + + // model() synthesizes two decorators; both precede an explicit decorator. + let model_out = + compile_jit(&component(" @HostBinding('class.x') m = model(0);\n", "model, HostBinding")); + let compact: String = model_out.chars().filter(|c| !c.is_whitespace()).collect(); + let host_pos = compact.find("type:HostBinding").expect("explicit HostBinding missing"); + assert!( + compact.find("type:i0.Input").expect("synthesized i0.Input missing") < host_pos + && compact.find("type:i0.Output").expect("synthesized i0.Output missing") < host_pos, + "synthesized model() decorators should precede the explicit @HostBinding. Got:\n{model_out}" + ); +}