diff --git a/crates/oxc_angular_compiler/src/linker/mod.rs b/crates/oxc_angular_compiler/src/linker/mod.rs index d85c51762..46c40b370 100644 --- a/crates/oxc_angular_compiler/src/linker/mod.rs +++ b/crates/oxc_angular_compiler/src/linker/mod.rs @@ -378,6 +378,68 @@ fn walk_expression( Expression::ClassExpression(class_expr) => { walk_class_body(&class_expr.body, source, filename, options, edits); } + // Declarations nested in data, as Babel's linker visits every call: Deepkit's runtime types record each + // static property's initializer as a function in a `static __type = [...]` array, so a class compiled + // with them carries a second `ɵɵngDeclareComponent(...)` there. Unlinked, its `ngImport: i0` keeps all of + // @angular/core in the bundle. + Expression::ArrayExpression(array) => { + for element in &array.elements { + match element { + oxc_ast::ast::ArrayExpressionElement::SpreadElement(spread) => { + walk_expression(&spread.argument, source, filename, options, edits); + } + oxc_ast::ast::ArrayExpressionElement::Elision(_) => {} + other => { + if let Some(expr) = other.as_expression() { + walk_expression(expr, source, filename, options, edits); + } + } + } + } + } + Expression::ObjectExpression(object) => { + for property in &object.properties { + match property { + oxc_ast::ast::ObjectPropertyKind::ObjectProperty(prop) => { + walk_expression(&prop.value, source, filename, options, edits); + } + oxc_ast::ast::ObjectPropertyKind::SpreadProperty(spread) => { + walk_expression(&spread.argument, source, filename, options, edits); + } + } + } + } + Expression::NewExpression(new_expr) => { + walk_expression(&new_expr.callee, source, filename, options, edits); + for arg in &new_expr.arguments { + if let Argument::SpreadElement(_) = arg { + continue; + } + walk_expression(arg.to_expression(), source, filename, options, edits); + } + } + Expression::StaticMemberExpression(member) => { + walk_expression(&member.object, source, filename, options, edits); + } + Expression::ComputedMemberExpression(member) => { + walk_expression(&member.object, source, filename, options, edits); + walk_expression(&member.expression, source, filename, options, edits); + } + Expression::UnaryExpression(unary) => { + walk_expression(&unary.argument, source, filename, options, edits); + } + Expression::BinaryExpression(binary) => { + walk_expression(&binary.left, source, filename, options, edits); + walk_expression(&binary.right, source, filename, options, edits); + } + Expression::AwaitExpression(await_expr) => { + walk_expression(&await_expr.argument, source, filename, options, edits); + } + Expression::TemplateLiteral(template) => { + for expr in &template.expressions { + walk_expression(expr, source, filename, options, edits); + } + } _ => {} } } diff --git a/crates/oxc_angular_compiler/tests/linker_test.rs b/crates/oxc_angular_compiler/tests/linker_test.rs index b0598c00b..3ba97f817 100644 --- a/crates/oxc_angular_compiler/tests/linker_test.rs +++ b/crates/oxc_angular_compiler/tests/linker_test.rs @@ -408,3 +408,21 @@ MyDir.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "17.0.0 result.code ); } + +/// Babel's linker links every declaration call; Deepkit's runtime types keep a copy of each static initializer in a +/// `__type` array, whose declarations must not stay unlinked (their `ngImport: i0` keeps all of @angular/core) +#[test] +fn test_link_declarations_nested_in_arrays_and_objects() { + let allocator = Allocator::default(); + let code = r#"import * as i0 from "@angular/core"; +class Cmp { + static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Cmp, deps: [], target: i0.ɵɵFactoryTarget.Component }); } + static { this.__type = ['ɵfac', function () { return i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Cmp, deps: [], target: i0.ɵɵFactoryTarget.Component }); }]; } +} +const table = { factory: () => i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: Cmp, deps: [], target: i0.ɵɵFactoryTarget.Component }) }; +"#; + let result = link(&allocator, code, "test.mjs"); + assert!(result.linked); + assert!(!result.code.contains("ɵɵngDeclare"), "every declaration links:\n{}", result.code); + assert!(!result.code.contains("ngImport"), "{}", result.code); +}