Skip to content
4 changes: 3 additions & 1 deletion crates/oxc_angular_compiler/src/component/decorator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use super::transform::ImportMap;
use crate::directive::{
StringConsts, extract_host_bindings, extract_host_listeners, extract_input_metadata,
extract_output_metadata, merge_by_class_property, parse_decorator_io,
resolve_member_transforms,
};
use crate::output::oxc_converter::convert_oxc_expression;

Expand Down Expand Up @@ -263,12 +264,13 @@ pub fn extract_component_metadata<'a>(

// Inputs/outputs from the `inputs:`/`outputs:` metadata, overridden by
// @Input/@Output/signal members ({...fromMeta, ...fromFields} in ngtsc).
let io = parse_decorator_io(allocator, config_obj, source_text, consts);
let io = parse_decorator_io(allocator, config_obj, class, source_text, consts);
metadata.inputs = merge_by_class_property(
io.inputs,
extract_input_metadata(allocator, class, source_text),
|i| i.class_property_name.as_str(),
);
resolve_member_transforms(allocator, class, source_text, consts, &mut metadata.inputs);
metadata.outputs =
merge_by_class_property(io.outputs, extract_output_metadata(allocator, class), |o| {
o.0.as_str()
Expand Down
24 changes: 24 additions & 0 deletions crates/oxc_angular_compiler/src/component/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2536,6 +2536,30 @@ pub fn transform_angular_file(
}
}

// `@core.Input()` and the other member decorators through a namespace
// import of `@angular/core`, as written: `resolved_imports` may have
// pointed the import map's entry at another path.
let _core_namespaces = crate::directive::CoreNamespaces::enter(
parser_ret
.program
.body
.iter()
.filter_map(|stmt| match stmt {
Statement::ImportDeclaration(import) if import.source.value == "@angular/core" => {
import.specifiers.as_ref()
}
_ => None,
})
.flatten()
.filter_map(|spec| match spec {
ImportDeclarationSpecifier::ImportNamespaceSpecifier(ns) => {
Some(ns.local.name.to_string())
}
_ => None,
})
.collect(),
);

// 2. Walk AST to find @Component decorated classes and extract metadata
for stmt in &parser_ret.program.body {
let (class, stmt_start) = match stmt {
Expand Down
222 changes: 184 additions & 38 deletions crates/oxc_angular_compiler/src/directive/decorator.rs

Large diffs are not rendered by default.

909 changes: 812 additions & 97 deletions crates/oxc_angular_compiler/src/directive/evaluator.rs

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion crates/oxc_angular_compiler/src/directive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@ pub use decorator::{
find_directive_decorator_span,
};
pub(crate) use decorator::{extract_string_value, resolve_template_literal};
pub(crate) use decorator::{merge_by_class_property, parse_decorator_io};
pub(crate) use decorator::{
merge_by_class_property, parse_decorator_io, resolve_member_transforms,
};
pub use definition::{DirectiveDefinitions, generate_directive_definitions};
pub use metadata::{
QueryPredicate, R3DirectiveMetadata, R3DirectiveMetadataBuilder, R3HostDirectiveMetadata,
R3HostMetadata, R3InputMetadata, R3QueryMetadata,
};
pub(crate) use property_decorators::CoreNamespaces;
pub use property_decorators::{
extract_content_queries, extract_host_bindings, extract_host_listeners, extract_input_metadata,
extract_output_metadata, extract_view_queries,
Expand Down
56 changes: 51 additions & 5 deletions crates/oxc_angular_compiler/src/directive/property_decorators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
//! These decorators are found on class properties and methods, and define
//! how the directive/component interacts with its parent context.

use std::cell::RefCell;

use oxc_allocator::{Allocator, Vec};
use oxc_ast::ast::{
Argument, ArrayExpressionElement, Class, ClassElement, Decorator, Expression,
Expand All @@ -26,27 +28,71 @@ use crate::output::oxc_converter::convert_oxc_expression;
// Helper Functions
// ============================================================================

thread_local! {
/// The local names of `import * as ns from '@angular/core'` in the file
/// being compiled (see [`CoreNamespaces`]).
static CORE_NAMESPACES: RefCell<std::vec::Vec<String>> =
const { RefCell::new(std::vec::Vec::new()) };
}

/// While alive, member decorators written through these namespace imports of
/// `@angular/core` (`@core.Input()`) are recognised. ngtsc only reads a
/// namespaced member decorator whose namespace imports `@angular/core`, and the
/// functions here that extract member decorators don't see the file's imports.
pub(crate) struct CoreNamespaces(std::vec::Vec<String>);

impl CoreNamespaces {
pub(crate) fn enter(names: std::vec::Vec<String>) -> Self {
Self(CORE_NAMESPACES.with(|current| current.replace(names)))
}
}

impl Drop for CoreNamespaces {
fn drop(&mut self) {
let previous = std::mem::take(&mut self.0);
CORE_NAMESPACES.with(|current| *current.borrow_mut() = previous);
}
}

/// Find a decorator by name from a list of decorators.
///
/// Searches for decorators that are either:
/// - Simple identifiers: `@Input`
/// - Call expressions: `@Input()` or `@Input('alias')`
/// - Either of those through a namespace import of `@angular/core` (see
/// [`CoreNamespaces`]): `@core.Input()`
///
/// Returns the first matching decorator.
fn find_decorator_by_name<'a>(
decorators: &'a oxc_allocator::Vec<'a, Decorator<'a>>,
name: &str,
) -> Option<&'a Decorator<'a>> {
decorators.iter().find(|d| match &d.expression {
Expression::CallExpression(call) => match &call.callee {
Expression::Identifier(id) => id.name == name,
_ => false,
},
let is_core_namespace =
|ns: &str| CORE_NAMESPACES.with(|names| names.borrow().iter().any(|n| n == ns));
let is_name = |expr: &Expression<'_>| match expr {
Expression::Identifier(id) => id.name == name,
Expression::StaticMemberExpression(m) => {
m.property.name == name
&& matches!(&m.object, Expression::Identifier(ns) if is_core_namespace(&ns.name))
}
_ => false,
};
decorators.iter().find(|d| match &d.expression {
Expression::CallExpression(call) => is_name(&call.callee),
expr => is_name(expr),
})
}

/// The options argument of an `@Input(...)` decorator, if any.
pub(crate) fn input_decorator_options<'a>(
decorators: &'a oxc_allocator::Vec<'a, Decorator<'a>>,
) -> Option<&'a Expression<'a>> {
match &find_decorator_by_name(decorators, "Input")?.expression {
Expression::CallExpression(call) => call.arguments.first()?.as_expression(),
_ => None,
}
}

/// Get the property key name as an Atom.
///
/// Handles both identifier keys and string literal keys.
Expand Down
Loading
Loading