diff --git a/crates/oxc_angular_compiler/src/linker/mod.rs b/crates/oxc_angular_compiler/src/linker/mod.rs index 417944c7b..47cddb3c9 100644 --- a/crates/oxc_angular_compiler/src/linker/mod.rs +++ b/crates/oxc_angular_compiler/src/linker/mod.rs @@ -1221,6 +1221,7 @@ fn link_class_metadata_async( /// Declaration format (`ɵɵngDeclareDirective`): /// - `propertyName: "publicName"` (simple) /// - `propertyName: ["publicName", "classPropertyName"]` (aliased) +/// - `propertyName: ["publicName", "classPropertyName", transformFn]` (aliased with transform) /// - `propertyName: { classPropertyName: "...", publicName: "...", isRequired: bool, /// isSignal: bool, transformFunction: expr }` (Angular 16+ object format) /// @@ -1256,28 +1257,27 @@ fn convert_inputs_to_definition_format(inputs_obj: &ObjectExpression<'_>, source // Array: check if it's declaration format [publicName, classPropertyName] // and convert to definition format [InputFlags, publicName, classPropertyName] Expression::ArrayExpression(arr) => { - if arr.elements.len() == 2 { - // Check if first element is a string (declaration format) - let first_is_string = matches!( - arr.elements.first(), - Some(ArrayExpressionElement::StringLiteral(_)) - ); - if first_is_string { - // Declaration format: ["publicName", "classPropertyName"] - // Convert to: [0, "publicName", "classPropertyName"] - let arr_source = - &source[arr.span.start as usize + 1..arr.span.end as usize - 1]; - entries.push(format!("{quoted_key}: [0, {arr_source}]")); - } else { - // Already in definition format or unknown, keep as is - let val = - &source[p.value.span().start as usize..p.value.span().end as usize]; - entries.push(format!("{quoted_key}: {val}")); - } - } else { - // 3+ elements likely already in definition format, keep as is - let val = &source[p.value.span().start as usize..p.value.span().end as usize]; + let val = &source[p.value.span().start as usize..p.value.span().end as usize]; + let first_is_string = + matches!(arr.elements.first(), Some(ArrayExpressionElement::StringLiteral(_))); + + if !first_is_string { + // Already in definition format or unknown, keep as is. entries.push(format!("{quoted_key}: {val}")); + continue; + } + + let arr_source = &source[arr.span.start as usize + 1..arr.span.end as usize - 1]; + match arr.elements.len() { + // Declaration format: ["publicName", "classPropertyName"] + // Convert to: [0, "publicName", "classPropertyName"] + 2 => entries.push(format!("{quoted_key}: [0, {arr_source}]")), + // Since Angular 16.1, support multi-directive inputs, so we need to convert to the definition format. + // Declaration format: ["publicName", "classPropertyName", transformFn] + // Convert to: [2, "publicName", "classPropertyName", transformFn] + 3 => entries.push(format!("{quoted_key}: [2, {arr_source}]")), + // 4+ elements likely already in definition format, keep as is. + _ => entries.push(format!("{quoted_key}: {val}")), } } // Object: Angular 16+ format with classPropertyName, publicName, isRequired, etc. @@ -2439,6 +2439,26 @@ RxFor.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.2.1 ); } + #[test] + fn test_link_directive_input_array_with_transform() { + let allocator = Allocator::default(); + let code = r#" +import * as i0 from "@angular/core"; +class MyDirective { +} +MyDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "16.1.0", version: "20.0.0", ngImport: i0, type: MyDirective, selector: "[myDir]", inputs: { push: ["cdkConnectedOverlayPush", "push", i0.booleanAttribute] } }); +"#; + let result = link(&allocator, code, "test.mjs"); + assert!(result.linked); + assert!( + result + .code + .contains(r#"push: [2, "cdkConnectedOverlayPush", "push", i0.booleanAttribute]"#), + "Expected transform input array to be converted with InputFlags.HasDecoratorInputTransform. Got: {}", + result.code + ); + } + #[test] fn test_link_component_basic() { let allocator = Allocator::default(); diff --git a/crates/oxc_angular_compiler/tests/linker_test.rs b/crates/oxc_angular_compiler/tests/linker_test.rs index 2bfa9ac09..4a6639ecf 100644 --- a/crates/oxc_angular_compiler/tests/linker_test.rs +++ b/crates/oxc_angular_compiler/tests/linker_test.rs @@ -86,3 +86,12 @@ fn test_link_outputs_simple_identifier() { let result = link(&allocator, &code, "test.mjs"); insta::assert_snapshot!(result.code); } + +#[test] +fn test_link_inputs_array_format_with_transform_function() { + let allocator = Allocator::default(); + let code = + make_directive_source(r#"push: ["cdkConnectedOverlayPush", "push", i0.booleanAttribute]"#); + let result = link(&allocator, &code, "test.mjs"); + insta::assert_snapshot!(result.code); +} diff --git a/crates/oxc_angular_compiler/tests/snapshots/linker_test__link_inputs_array_format_with_transform_function.snap b/crates/oxc_angular_compiler/tests/snapshots/linker_test__link_inputs_array_format_with_transform_function.snap new file mode 100644 index 000000000..5dde160d7 --- /dev/null +++ b/crates/oxc_angular_compiler/tests/snapshots/linker_test__link_inputs_array_format_with_transform_function.snap @@ -0,0 +1,7 @@ +--- +source: crates/oxc_angular_compiler/tests/linker_test.rs +expression: result.code +--- +import * as i0 from "@angular/core"; +export class MyDir {} +MyDir.ɵdir = i0.ɵɵdefineDirective({ type: MyDir, selectors: [["", "myDir", ""]], inputs: { push: [2, "cdkConnectedOverlayPush", "push", i0.booleanAttribute] }, standalone: false });