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
19 changes: 16 additions & 3 deletions crates/oxc_angular_compiler/src/component/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4427,10 +4427,16 @@ fn compile_component_partial<'a>(
allocator: &'a Allocator,
template: &'a str,
metadata: &ComponentMetadata<'a>,
view_queries: &[R3QueryMetadata<'a>],
content_queries: &[R3QueryMetadata<'a>],
pool_starting_index: u32,
) -> FullCompilationResult {
let inputs =
crate::partial::PartialComponentInputs { template, is_inline: metadata.template.is_some() };
let inputs = crate::partial::PartialComponentInputs {
template,
is_inline: metadata.template.is_some(),
content_queries,
view_queries,
};
let cmp_expr =
crate::partial::compile_declare_component_from_metadata(allocator, metadata, &inputs);
let fac_expr =
Expand Down Expand Up @@ -4481,7 +4487,14 @@ fn compile_component_full<'a>(
// Partial declarations carry the template as a verbatim string and
// let the linker re-parse at consumer build time.
if matches!(options.compilation_mode, crate::CompilationMode::Partial) {
return Ok(compile_component_partial(allocator, template, metadata, pool_starting_index));
return Ok(compile_component_partial(
allocator,
template,
metadata,
&view_queries,
&content_queries,
pool_starting_index,
));
}

let mut diagnostics = Vec::new();
Expand Down
8 changes: 8 additions & 0 deletions crates/oxc_angular_compiler/src/directive/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ pub struct R3QueryMetadata<'a> {

/// Whether the query is signal-based.
pub is_signal: bool,

/// Whether a `Type` predicate was written `forwardRef(() => X)` in the
/// source and unwrapped during extraction. The partial emitter re-wraps
/// it (`convertFromMaybeForwardRefExpression` in render3/util.ts); the
/// full emitter leaves it bare because query calls run after class
/// initialization.
pub is_forward_ref: bool,
}

/// Query predicate type.
Expand All @@ -118,6 +125,7 @@ impl<'a> R3QueryMetadata<'a> {
read: None,
is_static: false,
is_signal: false,
is_forward_ref: false,
}
}
}
Expand Down
36 changes: 30 additions & 6 deletions crates/oxc_angular_compiler/src/directive/property_decorators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,9 @@ struct QueryConfig<'a> {
descendants: bool,
/// Whether a `QueryList` only notifies when its contents change.
emit_distinct_changes_only: bool,
/// Whether the predicate was a `forwardRef(() => X)` that extraction
/// unwrapped. Kept so the partial emitter can re-wrap it.
is_forward_ref: bool,
}

impl<'a> Default for QueryConfig<'a> {
Expand All @@ -916,6 +919,7 @@ impl<'a> QueryConfig<'a> {
predicate: None,
is_static: false,
read: None,
is_forward_ref: false,
// For @ContentChildren, default is false; for all others, default is true
descendants: decorator_name != "ContentChildren",
emit_distinct_changes_only: true,
Expand Down Expand Up @@ -957,6 +961,7 @@ fn parse_query_config<'a>(
read: query.read,
descendants: query.descendants,
emit_distinct_changes_only: query.emit_distinct_changes_only,
is_forward_ref: query.is_forward_ref,
},
Err(_) => QueryConfig::default_for(decorator_name),
};
Expand All @@ -972,9 +977,12 @@ fn parse_query_config<'a>(

let mut config = QueryConfig::default_for(decorator_name);

// The predicate: a string selector or a type/token.
// forwardRef isn't included in compiled output.
let node = try_unwrap_forward_ref(first_arg, None).unwrap_or(first_arg);
// The predicate: a string selector or a type/token. `forwardRef` is
// unwrapped for evaluation; `is_forward_ref` lets the partial emitter
// re-wrap it (upstream convertFromMaybeForwardRefExpression).
let unwrapped = try_unwrap_forward_ref(first_arg, None);
config.is_forward_ref = unwrapped.is_some();
let node = unwrapped.unwrap_or(first_arg);
config.predicate = match node {
Expression::StringLiteral(lit) => {
let mut list = Vec::new_in(&allocator);
Expand Down Expand Up @@ -1091,6 +1099,7 @@ fn try_parse_signal_query<'a>(

// Parse the predicate from the first argument
let predicate_arg = call_expr.arguments.first()?;
let mut is_forward_ref = false;
let predicate = match predicate_arg {
// String selector: viewChild('myRef')
Argument::StringLiteral(lit) => {
Expand All @@ -1101,8 +1110,11 @@ fn try_parse_signal_query<'a>(
// Type predicate: viewChild(TemplateRef) or viewChild(forwardRef(() => MyClass))
_ => {
let expr = predicate_arg.to_expression();
// Unwrap forwardRef if present - Angular doesn't include forwardRef in compiled output
let unwrapped_expr = try_unwrap_forward_ref(expr, consts).unwrap_or(expr);
// Unwrap forwardRef for evaluation; `is_forward_ref` records it so
// the partial emitter can re-wrap the predicate.
let unwrapped = try_unwrap_forward_ref(expr, consts);
is_forward_ref = unwrapped.is_some();
let unwrapped_expr = unwrapped.unwrap_or(expr);
// ngtsc emits a non-string locator as written (`WrappedNodeExpr`),
// so fall back to the source text for what can't be converted.
let output_expr = convert_oxc_expression(allocator, unwrapped_expr, source_text)
Expand Down Expand Up @@ -1150,6 +1162,7 @@ fn try_parse_signal_query<'a>(
read,
is_static: false, // Signal queries are never static
is_signal: true,
is_forward_ref,
},
))
}
Expand Down Expand Up @@ -1244,6 +1257,7 @@ pub(crate) fn extract_view_queries_in<'a>(
read: config.read,
is_static: config.is_static,
is_signal: false,
is_forward_ref: config.is_forward_ref,
});
}
}
Expand All @@ -1268,6 +1282,7 @@ pub(crate) fn extract_view_queries_in<'a>(
read: config.read,
is_static: config.is_static,
is_signal: false,
is_forward_ref: config.is_forward_ref,
});
}
}
Expand Down Expand Up @@ -1299,6 +1314,7 @@ pub(crate) fn extract_view_queries_in<'a>(
read: config.read,
is_static: config.is_static,
is_signal: false,
is_forward_ref: config.is_forward_ref,
});
}
}
Expand All @@ -1323,6 +1339,7 @@ pub(crate) fn extract_view_queries_in<'a>(
read: config.read,
is_static: config.is_static,
is_signal: false,
is_forward_ref: config.is_forward_ref,
});
}
}
Expand Down Expand Up @@ -1432,6 +1449,7 @@ pub(crate) fn extract_content_queries_in<'a>(
read: config.read,
is_static: config.is_static,
is_signal: false,
is_forward_ref: config.is_forward_ref,
});
}
}
Expand All @@ -1456,6 +1474,7 @@ pub(crate) fn extract_content_queries_in<'a>(
read: config.read,
is_static: config.is_static,
is_signal: false,
is_forward_ref: config.is_forward_ref,
});
}
}
Expand Down Expand Up @@ -1487,6 +1506,7 @@ pub(crate) fn extract_content_queries_in<'a>(
read: config.read,
is_static: config.is_static,
is_signal: false,
is_forward_ref: config.is_forward_ref,
});
}
}
Expand All @@ -1511,6 +1531,7 @@ pub(crate) fn extract_content_queries_in<'a>(
read: config.read,
is_static: config.is_static,
is_signal: false,
is_forward_ref: config.is_forward_ref,
});
}
}
Expand Down Expand Up @@ -2068,7 +2089,9 @@ fn decorator_query<'a>(
let Some(first) = args.first().and_then(Argument::as_expression) else {
return Err((format!("@{name} must have arguments"), span));
};
let node = try_unwrap_forward_ref(first, Some(consts)).unwrap_or(first);
let unwrapped = try_unwrap_forward_ref(first, Some(consts));
let is_forward_ref = unwrapped.is_some();
let node = unwrapped.unwrap_or(first);
let at_node = |message: String| (message, node.span());
let predicate = match evaluator.evaluate(node) {
// Like ngtsc, a reference or a value that can't be statically
Expand Down Expand Up @@ -2156,6 +2179,7 @@ fn decorator_query<'a>(
read: config.read,
is_static: config.is_static,
is_signal: false,
is_forward_ref,
})
}

Expand Down
9 changes: 9 additions & 0 deletions crates/oxc_angular_compiler/src/directive/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,7 @@ mod tests {
emit_distinct_changes_only: false,
is_static: false,
is_signal: true,
is_forward_ref: false,
read: None,
};

Expand Down Expand Up @@ -967,6 +968,7 @@ mod tests {
emit_distinct_changes_only: false,
is_static: false,
is_signal: true,
is_forward_ref: false,
read: None,
};

Expand Down Expand Up @@ -1017,6 +1019,7 @@ mod tests {
emit_distinct_changes_only: false,
is_static: false,
is_signal: true,
is_forward_ref: false,
read: None,
};

Expand All @@ -1031,6 +1034,7 @@ mod tests {
emit_distinct_changes_only: false,
is_static: false,
is_signal: true,
is_forward_ref: false,
read: None,
};

Expand Down Expand Up @@ -1088,6 +1092,7 @@ mod tests {
emit_distinct_changes_only: true,
is_static: false,
is_signal: false,
is_forward_ref: false,
read: None,
};

Expand All @@ -1102,6 +1107,7 @@ mod tests {
emit_distinct_changes_only: true,
is_static: false,
is_signal: false,
is_forward_ref: false,
read: None,
};

Expand Down Expand Up @@ -1151,6 +1157,7 @@ mod tests {
emit_distinct_changes_only: true,
is_static: false,
is_signal: false,
is_forward_ref: false,
read: None,
};

Expand All @@ -1165,6 +1172,7 @@ mod tests {
emit_distinct_changes_only: true,
is_static: false,
is_signal: false,
is_forward_ref: false,
read: None,
};

Expand Down Expand Up @@ -1215,6 +1223,7 @@ mod tests {
emit_distinct_changes_only: false,
is_static: false,
is_signal: true,
is_forward_ref: false,
read: None,
};

Expand Down
50 changes: 38 additions & 12 deletions crates/oxc_angular_compiler/src/partial/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use crate::component::{
ChangeDetectionStrategy, ComponentMetadata, DeclarationListEmitMode, HostDirectiveMetadata,
HostMetadata, TemplateDependency, TemplateDependencyKind, ViewEncapsulation,
};
use crate::directive::R3InputMetadata;
use crate::directive::{R3InputMetadata, R3QueryMetadata};
use crate::factory::{
FactoryTarget, R3ConstructorFactoryMetadata, R3DependencyMetadata, R3FactoryDeps,
R3FactoryMetadata,
Expand All @@ -55,25 +55,31 @@ use crate::r3::Identifiers;
/// Inputs the partial Component emitter needs that aren't carried on the
/// `ComponentMetadata` struct directly. Mirrors what the full-mode
/// pipeline computes separately and threads in.
pub struct PartialComponentInputs<'a> {
pub struct PartialComponentInputs<'a, 'm> {
/// The verbatim template source (inline literal or external file
/// content). Partial mode emits this as a string literal — the linker
/// re-parses it.
pub template: &'a str,
/// Whether the template came from an inline `template: '...'` literal
/// (`true`) versus an external `templateUrl` (`false`).
pub is_inline: bool,
/// Content queries (`@ContentChild*` / `contentChild*` signal queries /
/// `queries:` content entries), emitted as `queries`.
pub content_queries: &'m [R3QueryMetadata<'a>],
/// View queries (`@ViewChild*` / `viewChild*` signal queries /
/// `queries:` view entries), emitted as `viewQueries`.
pub view_queries: &'m [R3QueryMetadata<'a>],
}

/// Emits the `ɵɵngDeclareComponent` call for a component's `ɵcmp` static.
pub fn compile_declare_component_from_metadata<'a>(
allocator: &'a Allocator,
meta: &ComponentMetadata<'a>,
inputs: &PartialComponentInputs<'a>,
inputs: &PartialComponentInputs<'a, '_>,
) -> OutputExpression<'a> {
let mut entries: Vec<'a, LiteralMapEntry<'a>> = Vec::new_in(&allocator);

let min_version = compute_min_version(meta, inputs.template);
let min_version = compute_min_version(meta, inputs);
entries.push(string_entry(allocator, "minVersion", min_version));
entries.push(string_entry(allocator, "version", PLACEHOLDER_VERSION));

Expand Down Expand Up @@ -141,6 +147,24 @@ pub fn compile_declare_component_from_metadata<'a>(
));
}

// queries/viewQueries sit between providers and exportAs — where the
// upstream directive map (which createComponentDefinitionMap builds on)
// puts them (directive.ts:74-80).
if !inputs.content_queries.is_empty() {
entries.push(LiteralMapEntry::new(
Ident::from("queries"),
super::directive::compile_queries_array(allocator, inputs.content_queries),
false,
));
}
if !inputs.view_queries.is_empty() {
entries.push(LiteralMapEntry::new(
Ident::from("viewQueries"),
super::directive::compile_queries_array(allocator, inputs.view_queries),
Comment thread
Brooooooklyn marked this conversation as resolved.
false,
));
}

if !meta.export_as.is_empty() {
let mut elements: Vec<'a, OutputExpression<'a>> =
Vec::with_capacity_in(meta.export_as.len(), &allocator);
Expand Down Expand Up @@ -351,25 +375,27 @@ pub fn compile_declare_factory_for_component<'a>(

// ---- min version ---------------------------------------------------------

fn compute_min_version<'a>(meta: &ComponentMetadata<'a>, template: &str) -> &'static str {
fn compute_min_version<'a>(
meta: &ComponentMetadata<'a>,
inputs: &PartialComponentInputs<'a, '_>,
) -> &'static str {
let mut min: &'static str = "14.0.0";

if meta.inputs.iter().any(|i| i.transform_function.is_some()) {
min = "16.1.0";
}
// Component bump: control-flow blocks in template.
if template_uses_blocks(template) {
if template_uses_blocks(inputs.template) {
min = bump(min, "17.0.0");
}
if needs_new_input_partial_output(&meta.inputs) {
min = bump(min, "17.1.0");
}
// Signal queries — components carry these on the same path as
// directives, but the metadata struct doesn't include them
// directly. The dispatch layer that calls us will know if any query
// is signal-based; for now we approximate by checking inputs only.
// If signal queries land on ComponentMetadata later, bump 17.2.0
// here.
// Signal-based queries emit `isSignal: true` in the query map, which
// linkers below 17.2.0 don't know (directive.ts:146-149).
if inputs.content_queries.iter().chain(inputs.view_queries.iter()).any(|q| q.is_signal) {
min = "17.2.0";
}

min
}
Expand Down
Loading
Loading