Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions crates/oxc_angular_compiler/src/class_metadata/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::output::oxc_converter::convert_oxc_expression;
pub fn build_decorator_metadata_array<'a>(
allocator: &'a Allocator,
decorators: &[&Decorator<'a>],
source_text: Option<&'a str>,
) -> OutputExpression<'a> {
let mut decorator_entries = AllocVec::new_in(allocator);

Expand All @@ -38,7 +39,7 @@ pub fn build_decorator_metadata_array<'a>(
))),
Expression::StaticMemberExpression(member) => {
// Handle namespaced decorators like ng.Component
convert_oxc_expression(allocator, &member.object).map(|receiver| {
convert_oxc_expression(allocator, &member.object, source_text).map(|receiver| {
OutputExpression::ReadProp(Box::new_in(
ReadPropExpr {
receiver: Box::new_in(receiver, allocator),
Expand Down Expand Up @@ -77,7 +78,7 @@ pub fn build_decorator_metadata_array<'a>(
let mut args = AllocVec::new_in(allocator);
for arg in &call.arguments {
let expr = arg.to_expression();
if let Some(converted) = convert_oxc_expression(allocator, expr) {
if let Some(converted) = convert_oxc_expression(allocator, expr, source_text) {
args.push(converted);
}
}
Expand Down Expand Up @@ -122,6 +123,7 @@ pub fn build_ctor_params_metadata<'a>(
constructor_deps: Option<&[R3DependencyMetadata<'a>]>,
namespace_registry: &mut NamespaceRegistry<'a>,
import_map: &ImportMap<'a>,
source_text: Option<&'a str>,
) -> Option<OutputExpression<'a>> {
// Find constructor
let constructor = class.body.body.iter().find_map(|element| {
Expand Down Expand Up @@ -163,7 +165,8 @@ pub fn build_ctor_params_metadata<'a>(
// Extract decorators from the parameter
let param_decorators = extract_angular_decorators_from_param(param);
if !param_decorators.is_empty() {
let decorators_array = build_decorator_metadata_array(allocator, &param_decorators);
let decorators_array =
build_decorator_metadata_array(allocator, &param_decorators, source_text);
map_entries.push(LiteralMapEntry {
key: Ident::from("decorators"),
value: decorators_array,
Expand Down Expand Up @@ -205,6 +208,7 @@ pub fn build_ctor_params_metadata<'a>(
pub fn build_prop_decorators_metadata<'a>(
allocator: &'a Allocator,
class: &Class<'a>,
source_text: Option<&'a str>,
) -> Option<OutputExpression<'a>> {
const ANGULAR_PROP_DECORATORS: &[&str] = &[
"Input",
Expand Down Expand Up @@ -251,7 +255,8 @@ pub fn build_prop_decorators_metadata<'a>(
}

// Build decorators array for this property
let decorators_array = build_decorator_metadata_array(allocator, &angular_decorators);
let decorators_array =
build_decorator_metadata_array(allocator, &angular_decorators, source_text);

prop_entries.push(LiteralMapEntry {
key: prop_name,
Expand Down
25 changes: 17 additions & 8 deletions crates/oxc_angular_compiler/src/component/decorator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub fn extract_component_metadata<'a>(
class: &'a Class<'a>,
implicit_standalone: bool,
import_map: &ImportMap<'a>,
source_text: Option<&'a str>,
) -> Option<ComponentMetadata<'a>> {
// Get the class name
let class_name: Ident<'a> = class.id.as_ref()?.name.clone().into();
Expand Down Expand Up @@ -130,7 +131,8 @@ pub fn extract_component_metadata<'a>(
// 1. The identifier list for local analysis
metadata.imports = extract_identifier_array(allocator, &prop.value);
// 2. The raw expression to pass to ɵɵgetComponentDepsFactory in RuntimeResolved mode
metadata.raw_imports = convert_oxc_expression(allocator, &prop.value);
metadata.raw_imports =
convert_oxc_expression(allocator, &prop.value, source_text);
}
"exportAs" => {
// exportAs can be comma-separated: "foo, bar"
Expand All @@ -150,7 +152,8 @@ pub fn extract_component_metadata<'a>(
"animations" => {
// Extract animations expression as full OutputExpression
// Handles both identifier references and complex array expressions
metadata.animations = convert_oxc_expression(allocator, &prop.value);
metadata.animations =
convert_oxc_expression(allocator, &prop.value, source_text);
}
"schemas" => {
// Extract schemas identifiers (e.g., [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA])
Expand All @@ -159,11 +162,13 @@ pub fn extract_component_metadata<'a>(
"providers" => {
// Extract providers as full OutputExpression
// Handles complex expressions like [{provide: TOKEN, useFactory: Factory}]
metadata.providers = convert_oxc_expression(allocator, &prop.value);
metadata.providers =
convert_oxc_expression(allocator, &prop.value, source_text);
}
"viewProviders" => {
// Extract view providers as full OutputExpression
metadata.view_providers = convert_oxc_expression(allocator, &prop.value);
metadata.view_providers =
convert_oxc_expression(allocator, &prop.value, source_text);
}
"hostDirectives" => {
// Extract host directives array
Expand Down Expand Up @@ -236,7 +241,7 @@ pub fn extract_component_metadata<'a>(
extract_constructor_deps(allocator, class, import_map, has_superclass);

// Extract inputs from @Input decorators on class members
metadata.inputs = extract_input_metadata(allocator, class);
metadata.inputs = extract_input_metadata(allocator, class, source_text);

// Extract outputs from @Output decorators on class members
metadata.outputs = extract_output_metadata(allocator, class);
Expand Down Expand Up @@ -1134,9 +1139,13 @@ mod tests {
};

if let Some(class) = class {
if let Some(metadata) =
extract_component_metadata(&allocator, class, implicit_standalone, &import_map)
{
if let Some(metadata) = extract_component_metadata(
&allocator,
class,
implicit_standalone,
&import_map,
Some(code),
) {
found_metadata = Some(metadata);
break;
}
Expand Down
40 changes: 26 additions & 14 deletions crates/oxc_angular_compiler/src/component/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1616,9 +1616,13 @@ pub fn transform_angular_file(
// Compute implicit_standalone based on Angular version
let implicit_standalone = options.implicit_standalone();

if let Some(mut metadata) =
extract_component_metadata(allocator, class, implicit_standalone, &import_map)
{
if let Some(mut metadata) = extract_component_metadata(
allocator,
class,
implicit_standalone,
&import_map,
Some(source),
) {
// 3. Resolve external styles and merge into metadata
resolve_styles(allocator, &mut metadata, resolved_resources);

Expand All @@ -1632,11 +1636,11 @@ pub fn transform_angular_file(
let template = allocator.alloc_str(&template_string);
// 4.5 Extract view queries from the class (for @ViewChild/@ViewChildren)
// These need to be passed to compile_component_full so predicates can be pooled
let view_queries = extract_view_queries(allocator, class);
let view_queries = extract_view_queries(allocator, class, Some(source));

// 4.6 Extract content queries from the class (for @ContentChild/@ContentChildren)
// Signal-based queries (contentChild(), contentChildren()) are also detected here
let content_queries = extract_content_queries(allocator, class);
let content_queries = extract_content_queries(allocator, class, Some(source));

// Collect content query property names for .d.ts generation
// (before content_queries is moved into compile_component_full)
Expand Down Expand Up @@ -1696,7 +1700,8 @@ pub fn transform_angular_file(

// Check if the class also has an @Injectable decorator.
// @Injectable is SHARED precedence and can coexist with @Component.
let has_injectable = extract_injectable_metadata(allocator, class);
let has_injectable =
extract_injectable_metadata(allocator, class, Some(source));
if let Some(injectable_metadata) = &has_injectable {
if let Some(span) = find_injectable_decorator_span(class) {
decorator_spans_to_remove.push(span);
Expand Down Expand Up @@ -1758,16 +1763,20 @@ pub fn transform_angular_file(
decorators: build_decorator_metadata_array(
allocator,
&[decorator],
Some(source),
),
ctor_parameters: build_ctor_params_metadata(
allocator,
class,
ctor_deps_slice,
&mut file_namespace_registry,
&import_map,
Some(source),
),
prop_decorators: build_prop_decorators_metadata(
allocator, class,
allocator,
class,
Some(source),
),
};

Expand Down Expand Up @@ -1848,7 +1857,7 @@ pub fn transform_angular_file(
// the directive and creating conflicting property definitions (like
// ɵfac getters) that interfere with the AOT-compiled assignments.
if let Some(mut directive_metadata) =
extract_directive_metadata(allocator, class, implicit_standalone)
extract_directive_metadata(allocator, class, implicit_standalone, Some(source))
{
// Track decorator span for removal
if let Some(span) = find_directive_decorator_span(class) {
Expand Down Expand Up @@ -1906,7 +1915,8 @@ pub fn transform_angular_file(

// Check if the class also has an @Injectable decorator.
// @Injectable is SHARED precedence and can coexist with @Directive.
let has_injectable = extract_injectable_metadata(allocator, class);
let has_injectable =
extract_injectable_metadata(allocator, class, Some(source));
if let Some(injectable_metadata) = &has_injectable {
if let Some(span) = find_injectable_decorator_span(class) {
decorator_spans_to_remove.push(span);
Expand Down Expand Up @@ -1939,7 +1949,7 @@ pub fn transform_angular_file(
class_definitions
.insert(class_name, (property_assignments, String::new(), String::new()));
} else if let Some(mut pipe_metadata) =
extract_pipe_metadata(allocator, class, implicit_standalone)
extract_pipe_metadata(allocator, class, implicit_standalone, Some(source))
{
// Not a @Component or @Directive - check if it's a @Pipe (PRIMARY)
// We need to compile @Pipe classes to generate ɵpipe and ɵfac definitions.
Expand Down Expand Up @@ -1980,7 +1990,8 @@ pub fn transform_angular_file(

// Check if the class also has an @Injectable decorator (issue #65).
// @Injectable is SHARED precedence and can coexist with @Pipe.
let has_injectable = extract_injectable_metadata(allocator, class);
let has_injectable =
extract_injectable_metadata(allocator, class, Some(source));
if let Some(injectable_metadata) = &has_injectable {
if let Some(span) = find_injectable_decorator_span(class) {
decorator_spans_to_remove.push(span);
Expand Down Expand Up @@ -2017,7 +2028,7 @@ pub fn transform_angular_file(
);
}
} else if let Some(mut ng_module_metadata) =
extract_ng_module_metadata(allocator, class)
extract_ng_module_metadata(allocator, class, Some(source))
{
// Not a @Component, @Directive, @Injectable, or @Pipe - check if it's an @NgModule
// We need to compile @NgModule classes to generate ɵmod, ɵfac, and ɵinj definitions.
Expand Down Expand Up @@ -2061,7 +2072,8 @@ pub fn transform_angular_file(

// Check if the class also has an @Injectable decorator.
// @Injectable is SHARED precedence and can coexist with @NgModule.
let has_injectable = extract_injectable_metadata(allocator, class);
let has_injectable =
extract_injectable_metadata(allocator, class, Some(source));
if let Some(injectable_metadata) = &has_injectable {
if let Some(span) = find_injectable_decorator_span(class) {
decorator_spans_to_remove.push(span);
Expand Down Expand Up @@ -2108,7 +2120,7 @@ pub fn transform_angular_file(
);
}
} else if let Some(mut injectable_metadata) =
extract_injectable_metadata(allocator, class)
extract_injectable_metadata(allocator, class, Some(source))
{
// Standalone @Injectable (no PRIMARY decorator on the class)
// We need to compile @Injectable classes to generate ɵprov and ɵfac definitions.
Expand Down
18 changes: 12 additions & 6 deletions crates/oxc_angular_compiler/src/directive/decorator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ pub fn extract_directive_metadata<'a>(
allocator: &'a Allocator,
class: &'a Class<'a>,
implicit_standalone: bool,
source_text: Option<&'a str>,
) -> Option<R3DirectiveMetadata<'a>> {
// Get the class name
let class_name: Ident<'a> = class.id.as_ref()?.name.clone().into();
Expand Down Expand Up @@ -142,7 +143,9 @@ pub fn extract_directive_metadata<'a>(
}
}
"providers" => {
if let Some(providers) = convert_oxc_expression(allocator, &prop.value) {
if let Some(providers) =
convert_oxc_expression(allocator, &prop.value, source_text)
{
builder = builder.providers(providers);
}
}
Expand All @@ -164,7 +167,7 @@ pub fn extract_directive_metadata<'a>(
}

// Extract @Input/@Output/@HostBinding/@HostListener from class members
builder = builder.extract_from_class(allocator, class);
builder = builder.extract_from_class(allocator, class, source_text);

// Detect if ngOnChanges lifecycle hook is implemented
// Similar to Angular's: const usesOnChanges = members.some(member => ...)
Expand All @@ -180,7 +183,7 @@ pub fn extract_directive_metadata<'a>(
// Extract constructor dependencies for factory generation
// This enables proper DI for directive constructors
// See: packages/compiler-cli/src/ngtsc/annotations/common/src/di.ts
let constructor_deps = extract_constructor_deps(allocator, class, has_superclass);
let constructor_deps = extract_constructor_deps(allocator, class, has_superclass, source_text);
if let Some(deps) = constructor_deps {
builder = builder.deps(deps);
}
Expand Down Expand Up @@ -252,6 +255,7 @@ fn extract_constructor_deps<'a>(
allocator: &'a Allocator,
class: &'a Class<'a>,
has_superclass: bool,
source_text: Option<&'a str>,
) -> Option<Vec<'a, R3DependencyMetadata<'a>>> {
// Find the constructor method
let constructor = class.body.body.iter().find_map(|element| {
Expand All @@ -270,7 +274,7 @@ fn extract_constructor_deps<'a>(
let mut deps = Vec::with_capacity_in(params.items.len(), allocator);

for param in &params.items {
let dep = extract_param_dependency(allocator, param);
let dep = extract_param_dependency(allocator, param, source_text);
deps.push(dep);
}

Expand All @@ -290,6 +294,7 @@ fn extract_constructor_deps<'a>(
fn extract_param_dependency<'a>(
allocator: &'a Allocator,
param: &oxc_ast::ast::FormalParameter<'a>,
source_text: Option<&'a str>,
) -> R3DependencyMetadata<'a> {
// Extract flags and @Inject token from decorators
let mut optional = false;
Expand All @@ -306,7 +311,8 @@ fn extract_param_dependency<'a>(
// @Inject(TOKEN) - extract the token
if let Expression::CallExpression(call) = &decorator.expression {
if let Some(arg) = call.arguments.first() {
inject_token = convert_oxc_expression(allocator, arg.to_expression());
inject_token =
convert_oxc_expression(allocator, arg.to_expression(), source_text);
}
}
}
Expand Down Expand Up @@ -885,7 +891,7 @@ mod tests {

if let Some(class) = class {
if let Some(metadata) =
extract_directive_metadata(&allocator, class, implicit_standalone)
extract_directive_metadata(&allocator, class, implicit_standalone, Some(code))
{
found_metadata = Some(metadata);
break;
Expand Down
Loading
Loading