diff --git a/crates/oxc_angular_compiler/src/class_metadata/builders.rs b/crates/oxc_angular_compiler/src/class_metadata/builders.rs index 120427316..1b13695b5 100644 --- a/crates/oxc_angular_compiler/src/class_metadata/builders.rs +++ b/crates/oxc_angular_compiler/src/class_metadata/builders.rs @@ -12,8 +12,8 @@ use oxc_str::Ident; use crate::component::{ImportMap, NamespaceRegistry, R3DependencyMetadata}; use crate::directive::{ - R3InputMetadata, StringConsts, resolve_template_literal, try_parse_signal_input, - try_parse_signal_model, try_parse_signal_output, unwrap_initializer_api_expr, + QUERY_APIS, R3InputMetadata, StringConsts, initializer_api_call, resolve_template_literal, + try_parse_signal_input, try_parse_signal_model, try_parse_signal_output, }; use crate::output::ast::{ ArrowFunctionBody, ArrowFunctionExpr, LiteralArrayExpr, LiteralExpr, LiteralMapEntry, @@ -317,6 +317,9 @@ fn build_template_entry<'a>(allocator: &'a Allocator, content: &'a str) -> Liter /// using the constructor dependency metadata and namespace registry. This matches /// Angular's behavior where type-only imports need namespace imports because /// TypeScript types are erased at runtime. +/// +/// Without the file's imports, a parameter decorator is Angular's by its name; +/// [`build_ctor_params_metadata_in`] recognises only Angular's, like ngtsc. pub fn build_ctor_params_metadata<'a>( allocator: &'a Allocator, class: &Class<'a>, @@ -324,6 +327,34 @@ pub fn build_ctor_params_metadata<'a>( namespace_registry: &mut NamespaceRegistry<'a>, import_map: &ImportMap<'a>, source_text: Option<&'a str>, +) -> Option> { + build_ctor_params_metadata_in( + allocator, + class, + constructor_deps, + namespace_registry, + import_map, + source_text, + None, + ) +} + +/// [`build_ctor_params_metadata`] for a class in the file `consts` was +/// collected from. +/// +/// A parameter decorator (`@Inject()`, `@Optional()`, ...) is listed only when +/// it's Angular's, imported from `@angular/core` by name, under any alias, or +/// through a namespace import (see [`crate::directive::angular_param_decorator`]). +/// Like ngtsc, a parameter that has decorators, none of them Angular's, gets +/// `decorators: []`. +pub fn build_ctor_params_metadata_in<'a>( + allocator: &'a Allocator, + class: &Class<'a>, + constructor_deps: Option<&[R3DependencyMetadata<'a>]>, + namespace_registry: &mut NamespaceRegistry<'a>, + import_map: &ImportMap<'a>, + source_text: Option<&'a str>, + consts: Option<&StringConsts<'a>>, ) -> Option> { // Find constructor let constructor = class.body.body.iter().find_map(|element| { @@ -358,9 +389,11 @@ pub fn build_ctor_params_metadata<'a>( map_entries.push(LiteralMapEntry::new(Ident::from("type"), type_expr, false)); - // Extract decorators from the parameter - let param_decorators = extract_angular_decorators_from_param(param); - if !param_decorators.is_empty() { + // Extract decorators from the parameter. ngtsc lists the Angular ones + // whenever the parameter has decorators at all (`decorators: []` when + // none of them is Angular's). + let param_decorators = extract_angular_decorators_from_param(param, consts); + if !param.decorators.is_empty() { let decorators_array = build_decorator_metadata_array( &allocator, ¶m_decorators, @@ -407,11 +440,33 @@ pub fn build_ctor_params_metadata<'a>( /// /// Creates: `{ propName: [{ type: Input, args: [...] }], ... }` /// Returns `None` if no properties have Angular decorators. +/// +/// Without the file's imports, a signal member is recognised by the name of +/// the function it calls; [`build_prop_decorators_metadata_in`] recognises +/// only Angular's, like ngtsc. pub fn build_prop_decorators_metadata<'a>( allocator: &'a Allocator, class: &Class<'a>, source_text: Option<&'a str>, namespace_registry: &mut NamespaceRegistry<'a>, +) -> Option> { + build_prop_decorators_metadata_in(allocator, class, source_text, namespace_registry, None) +} + +/// [`build_prop_decorators_metadata`] for a class in the file `consts` was +/// collected from. +/// +/// A member decorator (`@Input()`, ...) is listed only when it's Angular's, +/// imported from `@angular/core` by name, under any alias, or through a +/// namespace import. A signal member (`input()`, `viewChild()`, ...) gets a +/// synthetic decorator only when it calls Angular's function, imported the +/// same way (`outputFromObservable` from `@angular/core/rxjs-interop`). +pub fn build_prop_decorators_metadata_in<'a>( + allocator: &'a Allocator, + class: &Class<'a>, + source_text: Option<&'a str>, + namespace_registry: &mut NamespaceRegistry<'a>, + consts: Option<&StringConsts<'a>>, ) -> Option> { const ANGULAR_PROP_DECORATORS: &[&str] = &[ "Input", @@ -444,12 +499,13 @@ pub fn build_prop_decorators_metadata<'a>( continue; }; - // Filter to Angular property decorators + // Filter to Angular property decorators: with the file's imports, only + // `@angular/core`'s (see `angular_member_decorator`), like ngtsc. let angular_decorators: std::vec::Vec<_> = decorators .iter() - .filter(|d| { - let name = get_decorator_name(d); - name.is_some_and(|n| ANGULAR_PROP_DECORATORS.contains(&n)) + .filter(|d| match consts { + Some(_) => crate::directive::angular_member_decorator(d, consts).is_some(), + None => get_decorator_name(d).is_some_and(|n| ANGULAR_PROP_DECORATORS.contains(&n)), }) .collect(); @@ -480,6 +536,7 @@ pub fn build_prop_decorators_metadata<'a>( &prop_name, source_text, namespace_registry, + consts, ) { prop_entries.push(LiteralMapEntry::new(prop_name, decorators_array, false)); @@ -505,13 +562,16 @@ fn build_initializer_api_prop_decorators<'a>( property_name: &Ident<'a>, source_text: Option<&'a str>, namespace_registry: &mut NamespaceRegistry<'a>, + consts: Option<&StringConsts<'a>>, ) -> Option> { let mut decorators = AllocVec::new_in(&allocator); - if let Some(input) = try_parse_signal_input(allocator, value, property_name.clone()) { + if let Some(input) = try_parse_signal_input(value, property_name.clone(), consts) { // input() / input.required() → `Input({ isSignal, alias, required })` decorators.push(build_signal_input_decorator(allocator, namespace_registry, &input)); - } else if let Some(model) = try_parse_signal_model(allocator, value, property_name.clone()) { + } else if let Some(model) = + try_parse_signal_model(allocator, value, property_name.clone(), consts) + { // model() → `Input({ isSignal, alias, required })` + `Output("Change")` decorators.push(build_signal_input_decorator(allocator, namespace_registry, &model.input)); decorators.push(build_core_decorator_with_string_arg( @@ -520,7 +580,8 @@ fn build_initializer_api_prop_decorators<'a>( "Output", model.output.1.clone(), )); - } else if let Some((_, binding)) = try_parse_signal_output(value, property_name.clone()) { + } else if let Some((_, binding)) = try_parse_signal_output(value, property_name.clone(), consts) + { // output() / outputFromObservable() → `Output("")` decorators.push(build_core_decorator_with_string_arg( &allocator, @@ -529,7 +590,7 @@ fn build_initializer_api_prop_decorators<'a>( binding, )); } else if let Some(query) = - build_signal_query_decorator(allocator, value, source_text, namespace_registry) + build_signal_query_decorator(allocator, value, source_text, namespace_registry, consts) { decorators.push(query); } @@ -589,11 +650,15 @@ fn build_signal_query_decorator<'a>( value: &Expression<'a>, source_text: Option<&'a str>, namespace_registry: &mut NamespaceRegistry<'a>, + consts: Option<&StringConsts<'a>>, ) -> Option> { - let Expression::CallExpression(call) = unwrap_initializer_api_expr(value) else { - return None; + let ((function, _), _, call) = initializer_api_call(value, consts, &QUERY_APIS)?; + let decorator_name = match function { + "viewChild" => "ViewChild", + "viewChildren" => "ViewChildren", + "contentChild" => "ContentChild", + _ => "ContentChildren", }; - let decorator_name = signal_query_decorator_name(&call.callee)?; // Predicate: the first positional argument (required), reused as-is. A query with // no locator is invalid (ngc errors); skip synthesis rather than emit a malformed @@ -626,40 +691,6 @@ fn build_signal_query_decorator<'a>( Some(build_core_decorator(allocator, namespace_registry, decorator_name, args)) } -/// Map a signal-query initializer callee to its decorator name, handling the direct -/// (`viewChild()`), required (`viewChild.required()`), and namespaced (`core.viewChild()`) -/// forms. -fn signal_query_decorator_name(callee: &Expression<'_>) -> Option<&'static str> { - fn name_of(function: &str) -> Option<&'static str> { - match function { - "viewChild" => Some("ViewChild"), - "viewChildren" => Some("ViewChildren"), - "contentChild" => Some("ContentChild"), - "contentChildren" => Some("ContentChildren"), - _ => None, - } - } - - match callee { - Expression::Identifier(id) => name_of(id.name.as_str()), - Expression::StaticMemberExpression(member) => { - if member.property.name == "required" { - match &member.object { - Expression::Identifier(id) => name_of(id.name.as_str()), - Expression::StaticMemberExpression(inner) => { - name_of(inner.property.name.as_str()) - } - _ => None, - } - } else { - // Namespaced call: `core.viewChild(...)`. - name_of(member.property.name.as_str()) - } - } - _ => None, - } -} - /// Build `{ type: i0., args: [""] }` for a decorator taking a single string. fn build_core_decorator_with_string_arg<'a>( allocator: &'a Allocator, @@ -896,20 +927,16 @@ fn extract_param_type_expression<'a>( } } -/// Extract Angular decorators from a constructor parameter. +/// Extract Angular's decorators from a constructor parameter (see +/// [`crate::directive::angular_param_decorator`]). fn extract_angular_decorators_from_param<'a, 'b>( param: &'b FormalParameter<'a>, + consts: Option<&StringConsts<'a>>, ) -> std::vec::Vec<&'b Decorator<'a>> { - const ANGULAR_PARAM_DECORATORS: &[&str] = - &["Inject", "Optional", "Self", "SkipSelf", "Host", "Attribute"]; - param .decorators .iter() - .filter(|d| { - let name = get_decorator_name(d); - name.is_some_and(|n| ANGULAR_PARAM_DECORATORS.contains(&n)) - }) + .filter(|d| crate::directive::angular_param_decorator(d, consts).is_some()) .collect() } diff --git a/crates/oxc_angular_compiler/src/class_metadata/mod.rs b/crates/oxc_angular_compiler/src/class_metadata/mod.rs index 71ed1d24c..60dca9852 100644 --- a/crates/oxc_angular_compiler/src/class_metadata/mod.rs +++ b/crates/oxc_angular_compiler/src/class_metadata/mod.rs @@ -15,7 +15,8 @@ mod compiler; mod metadata; pub use builders::{ - build_ctor_params_metadata, build_decorator_metadata_array, build_prop_decorators_metadata, + build_ctor_params_metadata, build_ctor_params_metadata_in, build_decorator_metadata_array, + build_prop_decorators_metadata, build_prop_decorators_metadata_in, }; pub use compiler::{ compile_class_metadata, compile_component_class_metadata, diff --git a/crates/oxc_angular_compiler/src/component/decorator.rs b/crates/oxc_angular_compiler/src/component/decorator.rs index 0a093e6b6..25536d220 100644 --- a/crates/oxc_angular_compiler/src/component/decorator.rs +++ b/crates/oxc_angular_compiler/src/component/decorator.rs @@ -20,7 +20,6 @@ use super::transform::ImportMap; use crate::directive::{ StringConsts, extract_host_bindings_in, extract_host_listeners_in, extract_input_metadata_in, extract_output_metadata_in, merge_by_class_property, parse_decorator_io, - resolve_member_transforms, }; use crate::output::oxc_converter::convert_oxc_expression; @@ -60,8 +59,8 @@ pub fn extract_component_metadata<'a>( let class_name: Ident<'a> = class.id.as_ref()?.name.clone().into(); let class_span = class.span; - // Find the @Component decorator - let component_decorator = find_component_decorator(&class.decorators)?; + // Find Angular's @Component decorator + let component_decorator = find_component_decorator(&class.decorators, consts)?; // Get the decorator call arguments let call_expr = match &component_decorator.expression { @@ -69,11 +68,6 @@ pub fn extract_component_metadata<'a>( _ => return None, }; - // Verify it's calling 'Component' - if !is_component_call(&call_expr.callee) { - return None; - } - // Get the first argument (the config object) let config_arg = call_expr.arguments.first()?; let config_obj = match config_arg { @@ -260,7 +254,7 @@ pub fn extract_component_metadata<'a>( // Extract constructor dependencies for factory generation // This enables proper DI for component constructors metadata.constructor_deps = - extract_constructor_deps(allocator, class, import_map, has_superclass); + extract_constructor_deps(allocator, class, import_map, has_superclass, consts); // Inputs/outputs from the `inputs:`/`outputs:` metadata, overridden by // @Input/@Output/signal members ({...fromMeta, ...fromFields} in ngtsc). @@ -270,7 +264,6 @@ pub fn extract_component_metadata<'a>( extract_input_metadata_in(allocator, class, source_text, Some(consts)), |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_in(allocator, class, Some(consts)), @@ -341,33 +334,14 @@ fn populate_declarations_from_imports<'a>( } } -/// Find the @Component decorator in a list of decorators. -pub fn find_component_decorator<'a>(decorators: &'a [Decorator<'a>]) -> Option<&'a Decorator<'a>> { - decorators.iter().find(|d| match &d.expression { - Expression::CallExpression(call) => is_component_call(&call.callee), - Expression::Identifier(id) => id.name == "Component", - _ => false, - }) -} - -/// Find the span of the @Component decorator on a class. -/// -/// Returns the span including any leading whitespace/newlines that should be removed -/// along with the decorator. -pub fn find_component_decorator_span(class: &Class<'_>) -> Option { - find_component_decorator(&class.decorators).map(|d| d.span) -} - -/// Check if a callee expression is a call to 'Component'. -fn is_component_call(callee: &Expression<'_>) -> bool { - match callee { - Expression::Identifier(id) => id.name == "Component", - // Handle namespaced imports like ng.Component or core.Component - Expression::StaticMemberExpression(member) => { - matches!(&member.property.name.as_str(), &"Component") - } - _ => false, - } +/// Find Angular's @Component decorator (imported from `@angular/core`, in the +/// file `consts` was collected from) in a list of decorators: see +/// [`crate::directive::find_angular_class_decorator`]. +pub fn find_component_decorator<'a>( + decorators: &'a [Decorator<'a>], + consts: &StringConsts<'_>, +) -> Option<&'a Decorator<'a>> { + crate::directive::find_angular_class_decorator(decorators, "Component", consts) } /// Get the name of a property key as a string. @@ -892,6 +866,7 @@ fn extract_constructor_deps<'a>( class: &'a Class<'a>, import_map: &ImportMap<'a>, has_superclass: bool, + consts: &StringConsts<'a>, ) -> Option>> { // Find the constructor method let constructor = class.body.body.iter().find_map(|element| { @@ -910,7 +885,7 @@ fn extract_constructor_deps<'a>( let params = &ctor.value.params; for param in ¶ms.items { - let dep = extract_param_dependency(param, import_map); + let dep = extract_param_dependency(param, import_map, consts); deps.push(dep); } @@ -930,9 +905,12 @@ fn extract_constructor_deps<'a>( /// /// The `import_map` is used to look up the source module of the token, /// enabling proper tracking of import origins for constructor dependencies. +/// Only Angular's parameter decorators count, imported from `@angular/core` +/// (see [`crate::directive::angular_param_decorator`]). fn extract_param_dependency<'a>( param: &'a oxc_ast::ast::FormalParameter<'a>, import_map: &ImportMap<'a>, + consts: &StringConsts<'a>, ) -> R3DependencyMetadata<'a> { // Extract flags and @Inject token from decorators let mut optional = false; @@ -943,8 +921,8 @@ fn extract_param_dependency<'a>( let mut attribute_name: Option> = None; for decorator in ¶m.decorators { - if let Some(name) = get_decorator_name(&decorator.expression) { - match name.as_str() { + if let Some(name) = crate::directive::angular_param_decorator(decorator, Some(consts)) { + match name { "Inject" => { // @Inject(TOKEN) - extract the token if let Expression::CallExpression(call) = &decorator.expression { @@ -1018,23 +996,6 @@ fn extract_param_dependency<'a>( dep } -/// Get the name of a decorator from its expression. -fn get_decorator_name<'a>(expr: &'a Expression<'a>) -> Option> { - match expr { - // @Optional - Expression::Identifier(id) => Some(id.name.clone().into()), - // @Optional() - Expression::CallExpression(call) => { - if let Expression::Identifier(id) = &call.callee { - Some(id.name.clone().into()) - } else { - None - } - } - _ => None, - } -} - /// Extract the injection token from an @Inject decorator argument. fn extract_inject_token<'a>(arg: &'a Argument<'a>) -> Option> { match arg { @@ -1079,30 +1040,52 @@ fn extract_param_token<'a>(param: &'a oxc_ast::ast::FormalParameter<'a>) -> Opti // Decorator Span Collection for Removal // ============================================================================= -/// Collect all decorator spans from constructor parameters. +/// Collect the spans of Angular's constructor parameter decorators. /// /// Parameter decorators like `@Optional()`, `@Inject()`, `@Host()`, `@Self()`, /// `@SkipSelf()`, and `@Attribute()` need to be removed from the output since /// their metadata is extracted into the factory function's inject flags. +/// Like ngtsc, only Angular's own count (imported from `@angular/core`, under +/// any alias or through a namespace import, see +/// [`crate::directive::angular_param_decorator`]); another module's is left +/// in place. /// /// These spans are used by `transform.rs` to remove the decorators from the /// source text during transformation. -pub fn collect_constructor_decorator_spans(class: &Class<'_>, spans: &mut std::vec::Vec) { - // Find the constructor method - for element in &class.body.body { - if let ClassElement::MethodDefinition(method) = element { - if method.kind == MethodDefinitionKind::Constructor { - // Iterate over constructor parameters - for param in &method.value.params.items { - // Collect all decorator spans from this parameter - for decorator in ¶m.decorators { - spans.push(decorator.span); - } - } - // Only one constructor per class - break; - } +pub fn collect_constructor_decorator_spans( + class: &Class<'_>, + consts: &StringConsts<'_>, + spans: &mut std::vec::Vec, +) { + for_each_constructor_param_decorator(class, |decorator| { + if crate::directive::angular_param_decorator(decorator, Some(consts)).is_some() { + spans.push(decorator.span); } + }); +} + +/// Collect the spans of every constructor parameter decorator, Angular's or +/// not. JIT output removes them all: Angular's go into `ctorParameters`, the +/// rest into the class's `__decorate` call as `__param(...)`. +pub fn collect_all_constructor_decorator_spans(class: &Class<'_>, spans: &mut std::vec::Vec) { + for_each_constructor_param_decorator(class, |decorator| spans.push(decorator.span)); +} + +/// Call `f` with each decorator on the constructor's parameters, in order. +fn for_each_constructor_param_decorator<'a>( + class: &'a Class<'a>, + mut f: impl FnMut(&'a Decorator<'a>), +) { + let constructor = class.body.body.iter().find_map(|element| match element { + ClassElement::MethodDefinition(method) + if method.kind == MethodDefinitionKind::Constructor => + { + Some(method) + } + _ => None, + }); + for param in constructor.into_iter().flat_map(|ctor| &ctor.value.params.items) { + param.decorators.iter().for_each(&mut f); } } @@ -1111,10 +1094,18 @@ pub fn collect_constructor_decorator_spans(class: &Class<'_>, spans: &mut std::v /// Member decorators like `@Input()`, `@Output()`, `@HostBinding()`, `@HostListener()`, /// `@ViewChild()`, `@ViewChildren()`, `@ContentChild()`, and `@ContentChildren()` need /// to be removed from the output since their metadata is compiled into the definition. +/// Like ngtsc, only Angular's own count (imported from `@angular/core`, under any +/// alias or through a namespace import, see +/// [`crate::directive::angular_member_decorator`]); a same-named decorator from +/// another module is left in place. /// /// These spans are used by `transform.rs` to remove the decorators from the /// source text during transformation. -pub fn collect_member_decorator_spans(class: &Class<'_>, spans: &mut std::vec::Vec) { +pub fn collect_member_decorator_spans( + class: &Class<'_>, + consts: &StringConsts<'_>, + spans: &mut std::vec::Vec, +) { for element in &class.body.body { let decorators = match element { ClassElement::PropertyDefinition(prop) => &prop.decorators, @@ -1130,15 +1121,8 @@ pub fn collect_member_decorator_spans(class: &Class<'_>, spans: &mut std::vec::V }; for decorator in decorators { - if let Some(name) = get_decorator_name(&decorator.expression) { - // Only collect Angular-specific member decorators - match name.as_str() { - "Input" | "Output" | "HostBinding" | "HostListener" | "ViewChild" - | "ViewChildren" | "ContentChild" | "ContentChildren" => { - spans.push(decorator.span); - } - _ => {} - } + if crate::directive::angular_member_decorator(decorator, Some(consts)).is_some() { + spans.push(decorator.span); } } } @@ -1254,6 +1238,7 @@ mod tests { #[test] fn test_extract_selector() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test' }) class TestComponent {} "#; @@ -1265,6 +1250,7 @@ mod tests { #[test] fn test_extract_selector_with_attribute() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: '[appDirective]' }) class TestDirective {} "#; @@ -1276,6 +1262,7 @@ mod tests { #[test] fn test_extract_class_name() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'my-component' }) class MyAwesomeComponent {} "#; @@ -1291,6 +1278,7 @@ mod tests { #[test] fn test_extract_inline_template() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '

Hello World

' @@ -1306,6 +1294,7 @@ mod tests { #[test] fn test_extract_template_url() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', templateUrl: './test.component.html' @@ -1321,6 +1310,7 @@ mod tests { #[test] fn test_extract_template_with_backticks() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: ` @@ -1345,6 +1335,7 @@ mod tests { #[test] fn test_extract_standalone_true() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1360,6 +1351,7 @@ mod tests { #[test] fn test_extract_standalone_false() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1376,6 +1368,7 @@ mod tests { fn test_standalone_defaults_to_implicit_value_true() { // When standalone is not specified, it should use the implicit value let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -1393,6 +1386,7 @@ mod tests { fn test_standalone_defaults_to_implicit_value_false() { // When standalone is not specified, it should use the implicit value let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -1413,6 +1407,7 @@ mod tests { #[test] fn test_extract_encapsulation_none() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1428,6 +1423,7 @@ mod tests { #[test] fn test_extract_encapsulation_shadow_dom() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1443,6 +1439,7 @@ mod tests { #[test] fn test_extract_encapsulation_emulated() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1459,6 +1456,7 @@ mod tests { fn test_extract_encapsulation_numeric_none() { // Angular uses numeric values: Emulated=0, None=2, ShadowDom=3 let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1474,6 +1472,7 @@ mod tests { #[test] fn test_extract_encapsulation_numeric_shadow_dom() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1489,6 +1488,7 @@ mod tests { #[test] fn test_encapsulation_defaults_to_emulated() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -1507,6 +1507,7 @@ mod tests { #[test] fn test_extract_change_detection_on_push() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1524,6 +1525,7 @@ mod tests { // `Default` (value 1) is the pre-v22 spelling of `Eager`. It is kept as // a distinct variant so partial emit can preserve the author's member. let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1540,6 +1542,7 @@ mod tests { fn test_extract_change_detection_numeric_eager() { // Angular v22 numeric values: OnPush = 0, Eager = 1. let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1558,6 +1561,7 @@ mod tests { // `None` so the emitter applies the target version's default (OnPush in // v22+, Default/Eager before). let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -1576,6 +1580,7 @@ mod tests { #[test] fn test_extract_styles_array() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1594,6 +1599,7 @@ mod tests { fn test_extract_styles_single_string() { // Legacy support: styles can be a single string let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1610,6 +1616,7 @@ mod tests { #[test] fn test_extract_style_urls() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1628,6 +1635,7 @@ mod tests { fn test_extract_style_url_single() { // styleUrl (singular) support let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1648,6 +1656,7 @@ mod tests { #[test] fn test_extract_host_properties() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1667,6 +1676,7 @@ mod tests { #[test] fn test_extract_host_listeners() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1686,6 +1696,7 @@ mod tests { #[test] fn test_extract_host_static_attributes() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1705,6 +1716,7 @@ mod tests { #[test] fn test_extract_host_class_attr() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1723,6 +1735,7 @@ mod tests { #[test] fn test_extract_host_style_attr() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1745,6 +1758,7 @@ mod tests { #[test] fn test_extract_imports() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1767,6 +1781,7 @@ mod tests { #[test] fn test_extract_export_as() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1783,6 +1798,7 @@ mod tests { #[test] fn test_extract_export_as_multiple() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1801,6 +1817,7 @@ mod tests { #[test] fn test_extract_preserve_whitespaces_true() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1816,6 +1833,7 @@ mod tests { #[test] fn test_extract_preserve_whitespaces_false() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1831,6 +1849,7 @@ mod tests { #[test] fn test_preserve_whitespaces_defaults_to_false() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -1845,6 +1864,7 @@ mod tests { #[test] fn test_extract_schemas() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1884,6 +1904,7 @@ mod tests { #[test] fn test_empty_component_decorator() { let code = r#" + import {Component} from '@angular/core'; @Component({}) class TestComponent {} "#; @@ -1897,6 +1918,7 @@ mod tests { #[test] fn test_exported_class() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' }) export class TestComponent {} "#; @@ -1908,6 +1930,7 @@ mod tests { #[test] fn test_export_default_class() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' }) export default class TestComponent {} "#; @@ -1920,6 +1943,7 @@ mod tests { fn test_namespaced_component_decorator() { // Handle ng.Component or core.Component let code = r#" + import * as ng from '@angular/core'; @ng.Component({ selector: 'app-test', template: '' }) class TestComponent {} "#; @@ -1931,6 +1955,7 @@ mod tests { #[test] fn test_full_component_decorator() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-complete', template: '
{{title}}
', @@ -1977,6 +2002,7 @@ mod tests { #[test] fn test_extract_host_directives_simple() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -1996,6 +2022,7 @@ mod tests { #[test] fn test_extract_host_directives_multiple() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -2014,6 +2041,7 @@ mod tests { #[test] fn test_extract_host_directives_object_form() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -2032,6 +2060,7 @@ mod tests { #[test] fn test_extract_host_directives_with_input_mappings() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -2057,6 +2086,7 @@ mod tests { #[test] fn test_extract_host_directives_with_output_mappings() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -2080,6 +2110,7 @@ mod tests { #[test] fn test_extract_host_directives_with_input_output_mappings() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -2103,6 +2134,7 @@ mod tests { #[test] fn test_extract_host_directives_same_name_mapping() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -2127,6 +2159,7 @@ mod tests { #[test] fn test_extract_host_directives_tuple_array_mapping() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -2151,6 +2184,7 @@ mod tests { #[test] fn test_extract_host_directives_forward_ref() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -2168,6 +2202,7 @@ mod tests { #[test] fn test_extract_host_directives_forward_ref_in_object() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -2191,6 +2226,7 @@ mod tests { #[test] fn test_extract_host_directives_empty() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -2206,6 +2242,7 @@ mod tests { #[test] fn test_extract_host_directives_mixed() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -2237,6 +2274,7 @@ mod tests { fn test_extract_host_directives_source_module_from_import() { // Test that host directive source_module is populated from imports let code = r#" + import {Component} from '@angular/core'; import { AriaDisableDirective } from "../a11y/aria-disable.directive"; import { FocusDirective, HighlightDirective } from "@angular/cdk/a11y"; @@ -2270,6 +2308,7 @@ mod tests { fn test_extract_host_directives_source_module_object_form() { // Test that source_module is populated in object form let code = r#" + import {Component} from '@angular/core'; import { ColorDirective } from "./directives/color"; @Component({ @@ -2295,6 +2334,7 @@ mod tests { fn test_extract_host_directives_no_source_module_for_local() { // Test that local directives (not imported) have no source_module let code = r#" + import {Component, Directive} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -2317,6 +2357,7 @@ mod tests { fn test_extract_host_directives_mixed_imported_and_local() { // Test mixed imported and local directives let code = r#" + import {Component} from '@angular/core'; import { ImportedDirective } from "@angular/library"; @Component({ @@ -2351,6 +2392,7 @@ mod tests { #[test] fn test_extract_host_binding_decorator() { let code = r#" + import {Component, HostBinding, HostListener} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2371,6 +2413,7 @@ mod tests { #[test] fn test_extract_host_binding_without_name() { let code = r#" + import {Component, HostBinding, HostListener} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2391,6 +2434,7 @@ mod tests { #[test] fn test_extract_host_listener_decorator() { let code = r#" + import {Component, HostBinding, HostListener} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2412,6 +2456,7 @@ mod tests { #[test] fn test_extract_host_listener_with_args() { let code = r#" + import {Component, HostBinding, HostListener} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2433,6 +2478,7 @@ mod tests { #[test] fn test_extract_host_listener_with_multiple_args() { let code = r#" + import {Component, HostBinding, HostListener} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2454,6 +2500,7 @@ mod tests { #[test] fn test_extract_multiple_host_decorators() { let code = r#" + import {Component, HostBinding, HostListener} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2476,6 +2523,7 @@ mod tests { fn test_merge_host_decorators_with_host_property() { // Test that @HostBinding/@HostListener are merged with @Component({ host: {} }) let code = r#" + import {Component, HostBinding, HostListener} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -2501,6 +2549,7 @@ mod tests { #[test] fn test_host_binding_on_getter() { let code = r#" + import {Component, HostBinding, HostListener} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2521,6 +2570,7 @@ mod tests { #[test] fn test_no_host_decorators() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2546,6 +2596,7 @@ mod tests { // -> should use simple factory (Some with empty deps) // See: packages/compiler-cli/src/ngtsc/annotations/common/src/di.ts:47-52 let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2567,6 +2618,7 @@ mod tests { // -> should use inherited factory (None) // See: packages/compiler-cli/src/ngtsc/annotations/common/src/di.ts:47-52 let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2583,6 +2635,7 @@ mod tests { #[test] fn test_component_with_simple_constructor_deps() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-root', template: '
' @@ -2617,6 +2670,7 @@ mod tests { #[test] fn test_component_with_inject_decorator() { let code = r#" + import {Component, Inject} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2647,6 +2701,7 @@ mod tests { #[test] fn test_component_with_optional_decorator() { let code = r#" + import {Component, Optional} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2676,6 +2731,7 @@ mod tests { // Angular's reference compiler filters out `null` literal type nodes from // the union; when exactly one type remains it becomes the token. let code = r#" + import {Component, Optional} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2705,6 +2761,7 @@ mod tests { // token resolved to `MyService`. Token resolution is independent of // optionality — `@Optional()` only controls the inject flag. let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2731,6 +2788,7 @@ mod tests { fn test_component_nullable_type_null_first() { // `null` can appear in either position of the union — both must be filtered. let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2755,6 +2813,7 @@ mod tests { // unwrap it before checking for unions, or the dependency falls through // to `ɵɵinvalidFactoryDep` (issue #285 follow-up). let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2779,6 +2838,7 @@ mod tests { fn test_component_paren_around_inner_type_in_union() { // Parens around just the non-null side: `(MyService) | null`. let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2799,6 +2859,7 @@ mod tests { #[test] fn test_component_with_skip_self_decorator() { let code = r#" + import {Component, Optional, SkipSelf} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2823,6 +2884,7 @@ mod tests { #[test] fn test_component_with_self_and_host_decorators() { let code = r#" + import {Component, Self, Host} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2851,6 +2913,7 @@ mod tests { #[test] fn test_component_with_combined_decorators() { let code = r#" + import {Component, Optional, Inject} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2874,6 +2937,7 @@ mod tests { #[test] fn test_component_with_attribute_decorator() { let code = r#" + import {Component, Attribute} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2902,6 +2966,7 @@ mod tests { use super::super::metadata::DeclarationListEmitMode; let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -2921,6 +2986,7 @@ mod tests { use super::super::metadata::DeclarationListEmitMode; let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '', @@ -2941,6 +3007,7 @@ mod tests { // When implicit_standalone=false (Angular v18 and earlier behavior) let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2961,6 +3028,7 @@ mod tests { // When implicit_standalone=true (Angular v19+ behavior) let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -2980,6 +3048,7 @@ mod tests { use super::super::metadata::DeclarationListEmitMode; let code = r#" + import {Component} from '@angular/core'; import { AsyncPipe, DatePipe } from '@angular/common'; @Component({ @@ -3004,6 +3073,7 @@ mod tests { use super::super::metadata::DeclarationListEmitMode; let code = r#" + import {Component} from '@angular/core'; const MY_IMPORTS = [AsyncPipe, DatePipe]; @Component({ @@ -3031,6 +3101,7 @@ mod tests { fn test_constructor_dep_token_source_module_from_named_import() { // Test that token_source_module is populated from named imports let code = r#" + import {Component} from '@angular/core'; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; @Component({ @@ -3057,6 +3128,7 @@ mod tests { fn test_constructor_dep_token_source_module_multiple_imports() { // Test that multiple imports from the same module are tracked correctly let code = r#" + import {Component} from '@angular/core'; import { ServiceA, ServiceB } from "./services"; import { Router } from "@angular/router"; @@ -3094,6 +3166,7 @@ mod tests { fn test_constructor_dep_token_source_module_with_inject_decorator() { // Test that @Inject token source module is tracked from import let code = r#" + import {Component, Inject} from '@angular/core'; import { WINDOW } from "@bitwarden/common"; @Component({ @@ -3119,6 +3192,7 @@ mod tests { fn test_constructor_dep_no_source_module_for_local_class() { // Test that local classes (not imported) have no token_source_module let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -3143,6 +3217,7 @@ mod tests { fn test_constructor_dep_token_source_module_with_alias() { // Test that aliased imports use the local name as key let code = r#" + import {Component} from '@angular/core'; import { AuthService as Auth } from "@bitwarden/common/auth"; @Component({ @@ -3171,6 +3246,7 @@ mod tests { fn test_constructor_dep_token_source_module_default_import() { // Test that default imports are tracked let code = r#" + import {Component} from '@angular/core'; import DefaultService from "@bitwarden/common/default"; @Component({ @@ -3228,9 +3304,30 @@ mod tests { panic!("No class found in code"); } + /// [`collect_member_decorator_spans`] for `class`, declared in `code`. + fn collect_member_decorators(code: &str, class: &Class<'_>, spans: &mut std::vec::Vec) { + let allocator = Allocator::default(); + let parser_ret = Parser::new(&allocator, code, SourceType::tsx()).parse(); + let consts = crate::directive::collect_string_consts(&allocator, &parser_ret.program); + collect_member_decorator_spans(class, &consts, spans); + } + + /// [`collect_constructor_decorator_spans`] for `class`, declared in `code`. + fn collect_constructor_decorators( + code: &str, + class: &Class<'_>, + spans: &mut std::vec::Vec, + ) { + let allocator = Allocator::default(); + let parser_ret = Parser::new(&allocator, code, SourceType::tsx()).parse(); + let consts = crate::directive::collect_string_consts(&allocator, &parser_ret.program); + collect_constructor_decorator_spans(class, &consts, spans); + } + #[test] fn test_collect_constructor_decorator_spans() { let code = r#" + import { Component, Optional, Inject, Host, Self } from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -3246,13 +3343,43 @@ mod tests { "#; with_first_class(code, |class| { let mut spans = std::vec::Vec::new(); - collect_constructor_decorator_spans(class, &mut spans); + collect_constructor_decorators(code, class, &mut spans); // Should collect 4 decorators: @Optional, @Inject, @Host, @Self assert_eq!(spans.len(), 4); }); } + /// Like ngtsc, only Angular's parameter decorators are removed: aliased or + /// namespaced ones are, another module's (even named `Inject`) and a + /// local one aren't. + #[test] + fn test_collect_constructor_decorator_spans_by_import() { + let code = r#" + import * as ng from '@angular/core'; + import { Component, Inject as Inj } from '@angular/core'; + import { Inject, Optional } from './other'; + function Self(): ParameterDecorator { return () => {}; } + @Component({ selector: 'app-test', template: '' }) + class TestComponent { + constructor( + @Inj(TOKEN) a: A, + @ng.Optional() b: B, + @Inject(TOKEN) c: C, + @Optional() d: D, + @Self() e: E, + ) {} + } + "#; + with_first_class(code, |class| { + let mut spans = std::vec::Vec::new(); + collect_constructor_decorators(code, class, &mut spans); + let removed: std::vec::Vec<&str> = + spans.iter().map(|span| &code[span.start as usize..span.end as usize]).collect(); + assert_eq!(removed, ["@Inj(TOKEN)", "@ng.Optional()"]); + }); + } + #[test] fn test_collect_constructor_decorator_spans_empty() { let code = r#" @@ -3266,7 +3393,7 @@ mod tests { "#; with_first_class(code, |class| { let mut spans = std::vec::Vec::new(); - collect_constructor_decorator_spans(class, &mut spans); + collect_constructor_decorators(code, class, &mut spans); // No parameter decorators assert_eq!(spans.len(), 0); @@ -3284,7 +3411,7 @@ mod tests { "#; with_first_class(code, |class| { let mut spans = std::vec::Vec::new(); - collect_constructor_decorator_spans(class, &mut spans); + collect_constructor_decorators(code, class, &mut spans); // No constructor assert_eq!(spans.len(), 0); @@ -3294,6 +3421,7 @@ mod tests { #[test] fn test_collect_member_decorator_spans() { let code = r#" + import {Component, Input, Output, HostBinding, HostListener, ViewChild, ViewChildren, ContentChild, ContentChildren} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -3314,7 +3442,7 @@ mod tests { "#; with_first_class(code, |class| { let mut spans = std::vec::Vec::new(); - collect_member_decorator_spans(class, &mut spans); + collect_member_decorators(code, class, &mut spans); // Should collect 9 member decorators (all @Input, @Output, @Host*, @*Child) assert_eq!(spans.len(), 9); @@ -3324,6 +3452,7 @@ mod tests { #[test] fn test_collect_member_decorator_spans_ignores_non_angular() { let code = r#" + import {Component, Input} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -3336,13 +3465,47 @@ mod tests { "#; with_first_class(code, |class| { let mut spans = std::vec::Vec::new(); - collect_member_decorator_spans(class, &mut spans); + collect_member_decorators(code, class, &mut spans); // Should only collect @Input, ignoring custom decorators assert_eq!(spans.len(), 1); }); } + /// Like ngtsc, only `@angular/core`'s decorators are compiled and removed: + /// under an alias or a namespace too, but not another module's, a local + /// or an undeclared one with the same name. + #[test] + fn test_collect_member_decorator_spans_by_import() { + let code = r#" + import {Component, Input as In, Output} from '@angular/core'; + import * as core from '@angular/core'; + import {HostBinding} from './other'; + import * as other from './other'; + function ViewChild(s: string): any { return () => {}; } + @Component({ + selector: 'app-test', + template: '' + }) + class TestComponent { + @In() aliased: string; + @core.Input() namespaced: string; + @Output() out: any; + @HostBinding('class.a') foreign = true; + @other.Input() foreignNamespace: string; + @ViewChild('ref') local: any; + @ContentChild('ref') undeclared: any; + } + "#; + with_first_class(code, |class| { + let mut spans = std::vec::Vec::new(); + collect_member_decorators(code, class, &mut spans); + let texts: std::vec::Vec<&str> = + spans.iter().map(|s| &code[s.start as usize..s.end as usize]).collect(); + assert_eq!(texts, ["@In()", "@core.Input()", "@Output()"]); + }); + } + #[test] fn test_collect_member_decorator_spans_empty() { let code = r#" @@ -3357,7 +3520,7 @@ mod tests { "#; with_first_class(code, |class| { let mut spans = std::vec::Vec::new(); - collect_member_decorator_spans(class, &mut spans); + collect_member_decorators(code, class, &mut spans); // No Angular member decorators assert_eq!(spans.len(), 0); @@ -3371,6 +3534,7 @@ mod tests { #[test] fn test_lifecycle_ng_on_changes_detected() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -3392,6 +3556,7 @@ mod tests { #[test] fn test_lifecycle_ng_on_changes_not_detected_without_method() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -3413,6 +3578,7 @@ mod tests { #[test] fn test_lifecycle_ng_on_changes_not_detected_for_static_method() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' @@ -3434,6 +3600,7 @@ mod tests { #[test] fn test_lifecycle_ng_on_changes_async_method() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' diff --git a/crates/oxc_angular_compiler/src/component/hoist.rs b/crates/oxc_angular_compiler/src/component/hoist.rs index 8100c64c0..1f12add6a 100644 --- a/crates/oxc_angular_compiler/src/component/hoist.rs +++ b/crates/oxc_angular_compiler/src/component/hoist.rs @@ -44,6 +44,7 @@ use oxc_semantic::Semantic; use oxc_span::GetSpan; use oxc_syntax::symbol::SymbolId; +use crate::directive::StringConsts; use crate::optimizer::Edit; use super::transform::{ImportMap, is_angular_core_export, is_angular_core_namespace}; @@ -98,6 +99,7 @@ pub fn collect_hoist_edits<'a>( source: &str, semantic: &Semantic<'a>, import_map: &ImportMap<'a>, + consts: &StringConsts<'_>, ) -> Vec { // Step 1: index top-level bindings (keyed by SymbolId). // - `symbol_to_stmt`: binding SymbolId → containing statement's `start`. @@ -162,7 +164,7 @@ pub fn collect_hoist_edits<'a>( let mut classes: Vec<(&Class<'a>, u32, HashSet, HashSet)> = Vec::new(); for stmt in &program.body { let Some((class, stmt_start_pos)) = class_of(stmt) else { continue }; - if !has_hoistable_angular_decorator(class, import_map) { + if !has_hoistable_angular_decorator(class, import_map, consts) { continue; } let mut direct: HashSet = HashSet::new(); @@ -887,64 +889,38 @@ fn class_of<'a, 'src>(stmt: &'src Statement<'a>) -> Option<(&'src Class<'a>, u32 } } -/// Does this class carry any decorator that Angular's compiler emits eager -/// definitions for? We don't try to be precise here — any of the well-known -/// Angular decorators makes the class a candidate. -/// -/// Name-only and import-agnostic: used by the cheap pre-check -/// [`program_has_angular_decorated_class`], where a false positive only costs -/// a wasted scan. The actual hoist filter uses the import-aware -/// [`has_hoistable_angular_decorator`]. -fn has_angular_decorator(class: &Class<'_>) -> bool { - class.decorators.iter().any(|d| { - let callee = match &d.expression { - Expression::CallExpression(call) => &call.callee, - expr => expr, - }; - let name = match callee { - Expression::Identifier(id) => id.name.as_str(), - Expression::StaticMemberExpression(member) => member.property.name.as_str(), - _ => return false, - }; - matches!(name, "Component" | "Directive" | "Pipe" | "NgModule" | "Injectable" | "Service") - }) -} - /// Whether this class carries an Angular decorator that warrants hoisting its -/// referenced declarations. -/// -/// Like [`has_angular_decorator`], but verifies `@Service` resolves to -/// `@angular/core` before treating it as Angular. `Service` is a common name -/// in non-Angular code, and hoisting a third-party `@Service` class's -/// referenced declarations would reorder statements and change that class's -/// runtime evaluation semantics. The other decorator names predate this check -/// and stay name-only — over-triggering there is the long-standing behavior and -/// only ever hoists a TDZ-safe declaration earlier. -fn has_hoistable_angular_decorator<'a>(class: &Class<'a>, import_map: &ImportMap<'a>) -> bool { +/// referenced declarations: one the compiler compiles the class for, imported +/// from `@angular/core` (see [`crate::directive::angular_class_decorator`]), +/// or an `@Service` that resolves to `@angular/core` (under any alias, or +/// through a namespace import). A same-named decorator from another library +/// (`Service` is a common name in non-Angular code) leaves the class alone, and +/// hoisting its referenced declarations would reorder statements and change +/// that class's runtime evaluation semantics. +fn has_hoistable_angular_decorator<'a>( + class: &Class<'a>, + import_map: &ImportMap<'a>, + consts: &StringConsts<'_>, +) -> bool { class.decorators.iter().any(|d| { + if crate::directive::angular_class_decorator(d, consts).is_some() { + return true; + } let callee = match &d.expression { Expression::CallExpression(call) => &call.callee, expr => expr, }; match callee { - Expression::Identifier(id) => { - let name = id.name.as_str(); - if name == "Service" { - is_angular_core_export(import_map, name, "Service") - } else { - matches!(name, "Component" | "Directive" | "Pipe" | "NgModule" | "Injectable") - } - } + // `@Service()`, or `@NgService()` for `import { Service as NgService }`: + // like the compile path (`find_angular_decorator`), the import's + // exported name decides, not the local one. + Expression::Identifier(id) => is_angular_core_export(import_map, &id.name, "Service"), + // `@ns.Service()` — accept only when `ns` is a namespace import + // from `@angular/core`. Expression::StaticMemberExpression(member) => { - let name = member.property.name.as_str(); - if name == "Service" { - // `@ns.Service()` — accept only when `ns` is a namespace - // import from `@angular/core`. - matches!(&member.object, Expression::Identifier(ns) + member.property.name == "Service" + && matches!(&member.object, Expression::Identifier(ns) if is_angular_core_namespace(import_map, ns.name.as_str())) - } else { - matches!(name, "Component" | "Directive" | "Pipe" | "NgModule" | "Injectable") - } } _ => false, } @@ -952,16 +928,21 @@ fn has_hoistable_angular_decorator<'a>(class: &Class<'a>, import_map: &ImportMap } /// Cheap pre-check: does `program` contain any top-level class statement -/// carrying one of the Angular decorators recognized by [`has_angular_decorator`]? +/// carrying one of the Angular decorators recognized by +/// [`has_hoistable_angular_decorator`]? /// /// Used by the AOT transform pipeline to skip the `Semantic` build and the /// full hoist scan for files with no decorated classes (plain TS helpers, /// type-only modules, classes with no Angular decorator, …). This walks /// `program.body` only and never descends into class bodies or expressions, /// so it's O(top-level statements) with a tiny per-statement cost. -pub(crate) fn program_has_angular_decorated_class(program: &Program<'_>) -> bool { +pub(crate) fn program_has_angular_decorated_class<'a>( + program: &Program<'a>, + import_map: &ImportMap<'a>, + consts: &StringConsts<'_>, +) -> bool { program.body.iter().any(|stmt| match class_of(stmt) { - Some((class, _)) => has_angular_decorator(class), + Some((class, _)) => has_hoistable_angular_decorator(class, import_map, consts), None => false, }) } diff --git a/crates/oxc_angular_compiler/src/component/import_elision.rs b/crates/oxc_angular_compiler/src/component/import_elision.rs index 3613e089a..7c91bcf07 100644 --- a/crates/oxc_angular_compiler/src/component/import_elision.rs +++ b/crates/oxc_angular_compiler/src/component/import_elision.rs @@ -71,7 +71,9 @@ impl<'a> ImportElisionAnalyzer<'a> { // First, collect all symbols that are used ONLY in constructor parameter decorators. // These should be elided because Angular removes these decorators during compilation. - let ctor_param_decorator_only = Self::collect_ctor_param_decorator_only_imports(program); + let consts = crate::directive::StringConsts::declarations_of(program); + let ctor_param_decorator_only = + Self::collect_ctor_param_decorator_only_imports(program, &consts); // Analyze each import declaration for stmt in &program.body { @@ -326,7 +328,10 @@ impl<'a> ImportElisionAnalyzer<'a> { /// TypeScript, so their decorator arguments have no runtime value references /// /// Reference: packages/compiler-cli/src/ngtsc/transform/jit/src/downlevel_decorators_transform.ts - fn collect_ctor_param_decorator_only_imports(program: &'a Program<'a>) -> FxHashSet<&'a str> { + fn collect_ctor_param_decorator_only_imports( + program: &'a Program<'a>, + consts: &crate::directive::StringConsts<'a>, + ) -> FxHashSet<&'a str> { let mut result = FxHashSet::default(); // Track: @@ -342,6 +347,7 @@ impl<'a> ImportElisionAnalyzer<'a> { for stmt in &program.body { Self::collect_uses_from_statement( stmt, + consts, &mut ctor_param_decorator_uses, &mut inject_arg_uses, &mut declare_prop_decorator_uses, @@ -373,6 +379,7 @@ impl<'a> ImportElisionAnalyzer<'a> { /// Recursively collect uses from a statement. fn collect_uses_from_statement( stmt: &'a Statement<'a>, + consts: &crate::directive::StringConsts<'a>, ctor_param_decorator_uses: &mut FxHashSet<&'a str>, inject_arg_uses: &mut FxHashSet<&'a str>, declare_prop_decorator_uses: &mut FxHashSet<&'a str>, @@ -382,6 +389,7 @@ impl<'a> ImportElisionAnalyzer<'a> { Statement::ClassDeclaration(class) => { Self::collect_uses_from_class( class, + consts, ctor_param_decorator_uses, inject_arg_uses, declare_prop_decorator_uses, @@ -394,6 +402,7 @@ impl<'a> ImportElisionAnalyzer<'a> { { Self::collect_uses_from_class( class, + consts, ctor_param_decorator_uses, inject_arg_uses, declare_prop_decorator_uses, @@ -405,6 +414,7 @@ impl<'a> ImportElisionAnalyzer<'a> { if let oxc_ast::ast::Declaration::ClassDeclaration(class) = &export.declaration { Self::collect_uses_from_class( class, + consts, ctor_param_decorator_uses, inject_arg_uses, declare_prop_decorator_uses, @@ -431,6 +441,7 @@ impl<'a> ImportElisionAnalyzer<'a> { /// Collect uses from a class declaration. fn collect_uses_from_class( class: &'a oxc_ast::ast::Class<'a>, + consts: &crate::directive::StringConsts<'a>, ctor_param_decorator_uses: &mut FxHashSet<&'a str>, inject_arg_uses: &mut FxHashSet<&'a str>, declare_prop_decorator_uses: &mut FxHashSet<&'a str>, @@ -454,6 +465,7 @@ impl<'a> ImportElisionAnalyzer<'a> { // This is the constructor - process parameter decorators specially Self::collect_uses_from_constructor_params( &method.value.params, + consts, ctor_param_decorator_uses, inject_arg_uses, ); @@ -499,6 +511,7 @@ impl<'a> ImportElisionAnalyzer<'a> { /// This is the key function that identifies parameter decorators and their arguments. fn collect_uses_from_constructor_params( params: &'a oxc_ast::ast::FormalParameters<'a>, + consts: &crate::directive::StringConsts<'a>, ctor_param_decorator_uses: &mut FxHashSet<&'a str>, inject_arg_uses: &mut FxHashSet<&'a str>, ) { @@ -520,8 +533,13 @@ impl<'a> ImportElisionAnalyzer<'a> { }; if let Some(name) = decorator_name { - // Check if this is a known parameter decorator - if PARAM_DECORATORS.contains(&name) { + // Check if this is one of Angular's parameter decorators, + // which compilation removes. Another module's `@Inject` + // stays in the output (like ngtsc), so its import must too. + if PARAM_DECORATORS.contains(&name) + && crate::directive::angular_param_decorator(decorator, Some(consts)) + .is_some() + { ctor_param_decorator_uses.insert(name); // If this is @Inject(TOKEN), collect the TOKEN argument diff --git a/crates/oxc_angular_compiler/src/component/transform.rs b/crates/oxc_angular_compiler/src/component/transform.rs index a707fc36c..a0bd22ea9 100644 --- a/crates/oxc_angular_compiler/src/component/transform.rs +++ b/crates/oxc_angular_compiler/src/component/transform.rs @@ -24,7 +24,7 @@ use crate::optimizer::{Edit, apply_edits, apply_edits_with_sourcemap}; use super::cross_file_elision::CrossFileAnalyzer; use super::decorator::{ collect_constructor_decorator_spans, collect_member_decorator_spans, - extract_component_metadata, find_component_decorator, find_component_decorator_span, + extract_component_metadata, find_component_decorator, }; use super::definition::{const_value_to_expression, generate_component_definitions}; use super::hoist::{collect_hoist_edits, program_has_angular_decorated_class}; @@ -34,24 +34,23 @@ use super::namespace_registry::NamespaceRegistry; use crate::ast::expression::{BindingType, ParsedEventType}; use crate::ast::r3::{R3BoundAttribute, R3BoundEvent, SecurityContext}; use crate::class_metadata::{ - R3ClassMetadata, R3DeferPerComponentDependency, build_ctor_params_metadata, - build_decorator_metadata_array, build_prop_decorators_metadata, compile_class_metadata, + R3ClassMetadata, R3DeferPerComponentDependency, build_ctor_params_metadata_in, + build_decorator_metadata_array, build_prop_decorators_metadata_in, compile_class_metadata, compile_component_class_metadata, }; use crate::directive::collect_string_consts; use crate::directive::{ R3QueryMetadata, create_content_queries_function, create_view_queries_function, decorator_io_errors, extract_class_queries, extract_directive_metadata, - find_directive_decorator, find_directive_decorator_span, generate_directive_definitions, + find_directive_decorator, generate_directive_definitions, }; use crate::dts; use crate::injectable::{ - extract_injectable_metadata, find_injectable_decorator, find_injectable_decorator_span, + extract_injectable_metadata_in, find_injectable_decorator, generate_injectable_definition_from_decorator, }; use crate::ng_module::{ - extract_ng_module_metadata, find_ng_module_decorator, find_ng_module_decorator_span, - generate_full_ng_module_definition, + extract_ng_module_metadata_in, find_ng_module_decorator, generate_full_ng_module_definition, }; use crate::output::ast::{ DeclareFunctionStmt, FunctionExpr, OutputExpression, OutputStatement, ReadPropExpr, @@ -62,8 +61,7 @@ use crate::parser::ParseTemplateOptions; use crate::parser::expression::BindingParser; use crate::parser::html::{HtmlParser, remove_whitespaces}; use crate::pipe::{ - extract_pipe_metadata, find_pipe_decorator, find_pipe_decorator_span, - generate_full_pipe_definition_from_decorator, + extract_pipe_metadata_in, find_pipe_decorator, generate_full_pipe_definition_from_decorator, }; use crate::pipeline::compilation::{DeferBlockDepsEmitMode, TemplateCompilationMode}; use crate::pipeline::emit::{ @@ -311,6 +309,13 @@ pub struct TransformResult { /// Maps component ID to list of styles. pub style_updates: HashMap>, + /// For HMR: maps the ID of each compiled component (path@ClassName) to its + /// `@Component` decorator's callee as written in the source (`Component`, + /// `Cmp` for `import { Component as Cmp }`, `ng.Component` for a namespace + /// import). It lets a build tool find the decorator the compiler took + /// without deciding again which decorators are Angular's. + pub component_decorators: HashMap, + /// Compilation diagnostics (errors and warnings). pub diagnostics: Vec, @@ -783,19 +788,21 @@ fn build_set_class_metadata_decls<'a>( None, Some(string_consts), ), - ctor_parameters: build_ctor_params_metadata( + ctor_parameters: build_ctor_params_metadata_in( &allocator, class, None, namespace_registry, import_map, Some(source), + Some(string_consts), ), - prop_decorators: build_prop_decorators_metadata( + prop_decorators: build_prop_decorators_metadata_in( &allocator, class, Some(source), namespace_registry, + Some(string_consts), ), }; let metadata_expr = match options.compilation_mode { @@ -847,6 +854,10 @@ struct JitClassInfo { member_decorators: std::vec::Vec, /// All class-level decorator expression texts for __decorate call, in source order. all_class_decorator_texts: std::vec::Vec, + /// Constructor parameter decorators that aren't Angular's, as + /// `__param(index, decorator)` texts for the class's __decorate call, after + /// the class decorators (TypeScript's order). + param_decorator_texts: std::vec::Vec, /// Non-Angular member decorators that need __decorate() calls. non_angular_member_decorators: std::vec::Vec, } @@ -987,88 +998,41 @@ pub(crate) fn is_angular_core_namespace(import_map: &ImportMap<'_>, local_name: /// Return the name of the first non-`Service` `@angular/core` decorator on /// the class, if any. Used to enforce upstream's collision rule (see /// `service.ts:101-116`): `@Service` cannot coexist with another Angular -/// decorator on the same class. -fn find_conflicting_angular_decorator<'a>( - class: &'a oxc_ast::ast::Class<'a>, - import_map: &ImportMap<'a>, -) -> Option<&'a str> { - const ANGULAR_DECORATORS: &[&str] = - &["Component", "Directive", "Pipe", "Injectable", "NgModule"]; - - for decorator in &class.decorators { - let name = match &decorator.expression { - Expression::CallExpression(call) => match &call.callee { - Expression::Identifier(id) => id.name.as_str(), - Expression::StaticMemberExpression(member) => member.property.name.as_str(), - _ => continue, - }, - _ => continue, - }; - if !ANGULAR_DECORATORS.contains(&name) { - continue; - } - // Verify the import resolves to @angular/core for both identifier - // and namespace callees. Without the namespace lookup, an unrelated - // `@thirdParty.Component()` on an @Service class would falsely - // trigger the collision preflight and block valid services. - let is_angular = if let Expression::CallExpression(call) = &decorator.expression { - match &call.callee { - Expression::Identifier(id) => import_map - .get(&Ident::from(id.name.as_str())) - .map(|info| info.source_module.as_str() == "@angular/core") - .unwrap_or(false), - Expression::StaticMemberExpression(member) => match &member.object { - Expression::Identifier(ns) => { - is_angular_core_namespace(import_map, ns.name.as_str()) - } - _ => false, - }, - _ => false, - } - } else { - false - }; - if is_angular { - return Some(name); - } - } - None +/// decorator on the same class. Only Angular's count (imported from +/// `@angular/core`, see [`crate::directive::angular_class_decorator`]): an +/// unrelated `@thirdParty.Component()` on an @Service class doesn't block it. +fn find_conflicting_angular_decorator( + class: &oxc_ast::ast::Class<'_>, + string_consts: &crate::directive::StringConsts<'_>, +) -> Option<&'static str> { + class + .decorators + .iter() + .filter(|d| matches!(d.expression, Expression::CallExpression(_))) + .find_map(|d| crate::directive::angular_class_decorator(d, string_consts)) } /// Find any Angular decorator on a class and return its kind and the decorator reference. /// -/// For the `Service` identifier specifically, the import map is consulted so a -/// bare `@Service()` from a non-Angular library doesn't shadow a real Angular -/// decorator that follows it on the same class. `Service` is common enough as a -/// library export name (DI containers, web frameworks) that name-only matching -/// would cause the JIT pipeline to either misclassify the class or, on -/// pre-v22 targets, emit a misleading diagnostic and skip the sibling -/// `@Component`/`@Injectable`/etc. Other Angular decorator names are unique -/// enough in practice that the same check isn't applied to them — and doing so -/// would regress namespace-style usage like `@core.Component()` where the -/// identifier isn't directly in the import map. +/// Like ngtsc, a decorator is Angular's only when it's imported from +/// `@angular/core`, by name under any alias or through a namespace import (see +/// [`crate::directive::angular_class_decorator`]); a same-named decorator from +/// another library, a local or an undeclared one is left alone. `@Service` is +/// matched against the import map the same way. fn find_angular_decorator<'a>( class: &'a oxc_ast::ast::Class<'a>, import_map: &ImportMap<'a>, + string_consts: &crate::directive::StringConsts<'_>, ) -> Option<(AngularDecoratorKind, &'a oxc_ast::ast::Decorator<'a>)> { for decorator in &class.decorators { - if let Expression::CallExpression(call) = &decorator.expression { - let name = match &call.callee { - Expression::Identifier(id) => Some(id.name.as_str()), - Expression::StaticMemberExpression(member) => Some(member.property.name.as_str()), - _ => None, - }; - let kind = match name { - Some("Component") => Some(AngularDecoratorKind::Component), - Some("Directive") => Some(AngularDecoratorKind::Directive), - Some("Pipe") => Some(AngularDecoratorKind::Pipe), - Some("Injectable") => Some(AngularDecoratorKind::Injectable), - Some("Service") => Some(AngularDecoratorKind::Service), - Some("NgModule") => Some(AngularDecoratorKind::NgModule), - _ => None, - }; - - if matches!(kind, Some(AngularDecoratorKind::Service)) { + let Expression::CallExpression(call) = &decorator.expression else { continue }; + let kind = match crate::directive::angular_class_decorator(decorator, string_consts) { + Some("Component") => AngularDecoratorKind::Component, + Some("Directive") => AngularDecoratorKind::Directive, + Some("Pipe") => AngularDecoratorKind::Pipe, + Some("Injectable") => AngularDecoratorKind::Injectable, + Some("NgModule") => AngularDecoratorKind::NgModule, + _ => { let from_angular_core = match &call.callee { Expression::Identifier(id) => { is_angular_core_export(import_map, id.name.as_str(), "Service") @@ -1077,32 +1041,38 @@ fn find_angular_decorator<'a>( // namespace import from `@angular/core`. Without this, // any `@third.Service()` from a third-party namespace // import would classify as the v22 decorator. - Expression::StaticMemberExpression(member) => match &member.object { - Expression::Identifier(ns) => { - is_angular_core_namespace(import_map, ns.name.as_str()) - } - _ => false, - }, + Expression::StaticMemberExpression(member) => { + member.property.name == "Service" + && matches!(&member.object, Expression::Identifier(ns) + if is_angular_core_namespace(import_map, ns.name.as_str())) + } _ => false, }; if !from_angular_core { continue; } + AngularDecoratorKind::Service } - - if let Some(k) = kind { - return Some((k, decorator)); - } - } + }; + return Some((kind, decorator)); } None } /// Extract constructor parameter info for JIT ctorParameters generation. +/// +/// Like Angular's JIT transform (`downlevel_decorators_transform.ts`), a +/// parameter decorator goes into `ctorParameters` only when it's Angular's, +/// imported from `@angular/core` (see +/// [`crate::directive::angular_param_decorator`]), listed as written +/// (`{ type: Inj }`, `{ type: ng.Optional }`). Any other one stays a decorator +/// of the class, lowered as `__param(index, decorator)`; those are returned +/// second, in source order. fn extract_jit_ctor_params( source: &str, class: &oxc_ast::ast::Class<'_>, -) -> std::vec::Vec { + consts: &crate::directive::StringConsts<'_>, +) -> (std::vec::Vec, std::vec::Vec) { use oxc_ast::ast::{ClassElement, MethodDefinitionKind}; let constructor = class.body.body.iter().find_map(|element| { @@ -1115,75 +1085,58 @@ fn extract_jit_ctor_params( }); let Some(ctor) = constructor else { - return std::vec::Vec::new(); + return (std::vec::Vec::new(), std::vec::Vec::new()); }; let mut params = std::vec::Vec::new(); - for param in &ctor.value.params.items { + let mut other_decorators = std::vec::Vec::new(); + for (index, param) in ctor.value.params.items.iter().enumerate() { // Extract type name from type annotation (directly on FormalParameter) let type_name = param .type_annotation .as_ref() .and_then(|ann| extract_type_name_from_annotation(&ann.type_annotation)); - // Extract Angular decorators + // Angular's decorators go into ctorParameters, by their written name. let mut decorators = std::vec::Vec::new(); for decorator in ¶m.decorators { - if let Expression::CallExpression(call) = &decorator.expression { - let dec_name = match &call.callee { - Expression::Identifier(id) => Some(id.name.to_string()), - _ => None, - }; - if let Some(name) = dec_name { - match name.as_str() { - "Inject" | "Optional" | "SkipSelf" | "Self" | "Host" | "Attribute" => { - let args = if call.arguments.is_empty() { - None - } else { - // Extract args from source - let args_start = call.arguments.first().unwrap().span().start; - let args_end = call.arguments.last().unwrap().span().end; - Some(source[args_start as usize..args_end as usize].to_string()) - }; - decorators.push(JitParamDecorator { name, args }); - } - _ => {} - } - } - } else if let Expression::Identifier(id) = &decorator.expression { - let name = id.name.to_string(); - match name.as_str() { - "Optional" | "SkipSelf" | "Self" | "Host" => { - decorators.push(JitParamDecorator { name, args: None }); - } - _ => {} - } + if crate::directive::angular_param_decorator(decorator, Some(consts)).is_none() { + let expr = decorator.expression.span(); + other_decorators.push(format!( + "__param({index}, {})", + &source[expr.start as usize..expr.end as usize] + )); + continue; } + let (callee, args) = match &decorator.expression { + Expression::CallExpression(call) => { + let args = match (call.arguments.first(), call.arguments.last()) { + (Some(first), Some(last)) => Some( + source[first.span().start as usize..last.span().end as usize] + .to_string(), + ), + _ => None, + }; + (call.callee.span(), args) + } + expr => (expr.span(), None), + }; + let name = source[callee.start as usize..callee.end as usize].to_string(); + decorators.push(JitParamDecorator { name, args }); } params.push(JitCtorParam { type_name, decorators }); } - params + (params, other_decorators) } -/// Angular field decorators that go into `static propDecorators`. -/// Matches Angular's official `FIELD_DECORATORS` constant from `@angular/compiler-cli`. -const ANGULAR_FIELD_DECORATORS: &[&str] = &[ - "Input", - "Output", - "HostBinding", - "HostListener", - "ViewChild", - "ViewChildren", - "ContentChild", - "ContentChildren", -]; - -/// All Angular decorator names from `@angular/core`. -/// Any decorator with one of these names is treated as Angular and excluded from -/// non-Angular `__decorate()` lowering. Angular identifies decorators by import source; -/// we use names since they're unique to `@angular/core`. +/// Angular's decorators. On a member, one imported from `@angular/core` (see +/// [`crate::directive::angular_core_decorator`]) goes to `propDecorators` if it's +/// a field decorator and is dropped otherwise (ngtsc lists those in +/// `propDecorators` too). Like ngtsc's JIT transform, where a decorator comes +/// from decides, not its name: another module's `@Inject` stays a `__decorate()` +/// call. const ANGULAR_DECORATOR_NAMES: &[&str] = &[ // Field decorators (→ propDecorators) "Input", @@ -1218,6 +1171,8 @@ const ANGULAR_DECORATOR_NAMES: &[&str] = &[ fn extract_all_jit_member_decorators( source: &str, class: &oxc_ast::ast::Class<'_>, + string_consts: &crate::directive::StringConsts<'_>, + core_namespace: &str, ) -> (std::vec::Vec, std::vec::Vec) { use oxc_ast::ast::{ClassElement, MethodDefinitionKind, PropertyKey}; @@ -1262,13 +1217,8 @@ fn extract_all_jit_member_decorators( rustc_hash::FxHashSet::default(); for decorator in decorators { - let (dec_name, call_args) = match &decorator.expression { + let (callee, call_args) = match &decorator.expression { Expression::CallExpression(call) => { - let name = match &call.callee { - Expression::Identifier(id) => id.name.to_string(), - Expression::StaticMemberExpression(m) => m.property.name.to_string(), - _ => continue, - }; let args = if call.arguments.is_empty() { None } else { @@ -1276,18 +1226,33 @@ fn extract_all_jit_member_decorators( let end = call.arguments.last().unwrap().span().end; Some(source[start as usize..end as usize].to_string()) }; - (name, args) + (&call.callee, args) } - Expression::Identifier(id) => (id.name.to_string(), None), - _ => continue, + expr => (expr, None), }; + if !matches!(callee, Expression::Identifier(_) | Expression::StaticMemberExpression(_)) + { + continue; + } - if ANGULAR_FIELD_DECORATORS.contains(&dec_name.as_str()) { - // Angular field decorator → goes into propDecorators - explicit_field_decorators.insert(dec_name.clone()); - angular_decs.push(JitParamDecorator { name: dec_name, args: call_args }); - } else if !ANGULAR_DECORATOR_NAMES.contains(&dec_name.as_str()) { - // Non-Angular decorator → goes into __decorate() call + if let Some(field) = + crate::directive::angular_member_decorator(decorator, Some(string_consts)) + { + // Angular field decorator (imported from `@angular/core`) → goes into + // propDecorators, referenced as written (`In`, `core.Input`), like ngtsc. + explicit_field_decorators.insert(field.to_string()); + let name = + source[callee.span().start as usize..callee.span().end as usize].to_string(); + angular_decs.push(JitParamDecorator { name, args: call_args }); + } else if crate::directive::angular_core_decorator( + decorator, + Some(string_consts), + ANGULAR_DECORATOR_NAMES, + ) + .is_none() + { + // Non-Angular decorator (another module's `@Input` or `@Inject` + // too) → goes into __decorate() call let expr_start = decorator.expression.span().start; let expr_end = decorator.expression.span().end; non_angular_texts.push(source[expr_start as usize..expr_end as usize].to_string()); @@ -1307,6 +1272,8 @@ fn extract_all_jit_member_decorators( init, &member_name, &explicit_field_decorators, + string_consts, + core_namespace, ); angular_decs.extend(synthesized); } @@ -1361,58 +1328,40 @@ enum InitializerApiKind { /// Identify which initializer API a call expression represents. /// -/// Handles three call shapes: -/// - bare identifier: `input(...)`, `output(...)` -/// - `.required` member: `input.required(...)`, `model.required(...)` -/// - namespaced: `core.input(...)`, `core.viewChild.required(...)` -fn classify_initializer_api(callee: &Expression<'_>) -> Option { - fn match_name(name: &str) -> Option { - match name { - "input" => Some(InitializerApiKind::Input), - "output" => Some(InitializerApiKind::Output), - "outputFromObservable" => Some(InitializerApiKind::OutputFromObservable), - "model" => Some(InitializerApiKind::Model), - "viewChild" => Some(InitializerApiKind::ViewChild), - "viewChildren" => Some(InitializerApiKind::ViewChildren), - "contentChild" => Some(InitializerApiKind::ContentChild), - "contentChildren" => Some(InitializerApiKind::ContentChildren), - _ => None, - } - } - fn required_variant(base: InitializerApiKind) -> Option { - match base { - InitializerApiKind::Input => Some(InitializerApiKind::InputRequired), - InitializerApiKind::Model => Some(InitializerApiKind::ModelRequired), - InitializerApiKind::ViewChild => Some(InitializerApiKind::ViewChildRequired), - InitializerApiKind::ContentChild => Some(InitializerApiKind::ContentChildRequired), - _ => None, - } - } - - match callee { - Expression::Identifier(id) => match_name(id.name.as_str()), - Expression::StaticMemberExpression(member) => { - // `.required` — find the underlying base API and promote it. - if member.property.name == "required" { - let base = match &member.object { - Expression::Identifier(id) => match_name(id.name.as_str())?, - Expression::StaticMemberExpression(inner) => { - // Namespaced: `core.input.required` - match_name(inner.property.name.as_str())? - } - _ => return None, - }; - required_variant(base) - } else { - // Namespaced: `core.input(...)`. The outer property *is* the function name. - match &member.object { - Expression::Identifier(_) => match_name(member.property.name.as_str()), - _ => None, - } - } - } - _ => None, - } +/// Like ngtsc's `tryParseInitializerApi`, only Angular's functions count: +/// `input(...)` / `input.required(...)` with `input` imported by name from +/// `@angular/core` (`outputFromObservable` from `@angular/core/rxjs-interop`), +/// under any alias, or `core.input(...)` / `core.input.required(...)` through a +/// namespace import (see [`crate::directive::initializer_api`]). +fn classify_initializer_api( + callee: &Expression<'_>, + string_consts: &crate::directive::StringConsts<'_>, +) -> Option { + use crate::directive::{ + INPUT_API, MODEL_API, OUTPUT_API, OUTPUT_FROM_OBSERVABLE_API, QUERY_APIS, + }; + let apis = [INPUT_API, MODEL_API, OUTPUT_API, OUTPUT_FROM_OBSERVABLE_API] + .into_iter() + .chain(QUERY_APIS) + .collect::>(); + let ((function, _), required) = + crate::directive::initializer_api(callee, Some(string_consts), &apis)?; + Some(match (function, required) { + ("input", false) => InitializerApiKind::Input, + ("input", true) => InitializerApiKind::InputRequired, + ("model", false) => InitializerApiKind::Model, + ("model", true) => InitializerApiKind::ModelRequired, + ("output", false) => InitializerApiKind::Output, + ("outputFromObservable", false) => InitializerApiKind::OutputFromObservable, + ("viewChild", false) => InitializerApiKind::ViewChild, + ("viewChild", true) => InitializerApiKind::ViewChildRequired, + ("viewChildren", false) => InitializerApiKind::ViewChildren, + ("contentChild", false) => InitializerApiKind::ContentChild, + ("contentChild", true) => InitializerApiKind::ContentChildRequired, + ("contentChildren", false) => InitializerApiKind::ContentChildren, + // `output.required()`, `viewChildren.required()`, ... + _ => return None, + }) } /// Namespace alias under which `@angular/core` is imported when JIT synthesis needs @@ -1425,6 +1374,57 @@ fn classify_initializer_api(callee: &Expression<'_>) -> Option); + +impl<'a> oxc_ast_visit::Visit<'a> for FileIdentifiers { + fn visit_identifier_reference(&mut self, it: &oxc_ast::ast::IdentifierReference<'a>) { + self.0.insert(it.name.to_string()); + } + fn visit_binding_identifier(&mut self, it: &oxc_ast::ast::BindingIdentifier<'a>) { + self.0.insert(it.name.to_string()); + } + fn visit_identifier_name(&mut self, it: &oxc_ast::ast::IdentifierName<'a>) { + self.0.insert(it.name.to_string()); + } + fn visit_label_identifier(&mut self, it: &oxc_ast::ast::LabelIdentifier<'a>) { + self.0.insert(it.name.to_string()); + } +} + +/// The namespace JIT synthesis references `@angular/core` through, and whether +/// the file already imports it, like ngc's `ImportManager`: the last +/// `import * as x from '@angular/core'` that isn't type-only is reused; +/// otherwise a new [`JIT_ANGULAR_CORE_NS`] import, renamed `i0_1`, `i0_2`, ... +/// while the file uses that identifier anywhere (an `input as i0` import, a +/// `const i0`, ...), so the added import can't redeclare it. +fn jit_angular_core_namespace(program: &oxc_ast::ast::Program<'_>) -> (String, bool) { + for stmt in program.body.iter().rev() { + let Statement::ImportDeclaration(import) = stmt else { continue }; + if import.source.value != "@angular/core" || import.import_kind.is_type() { + continue; + } + let namespace = import.specifiers.iter().flatten().find_map(|spec| match spec { + ImportDeclarationSpecifier::ImportNamespaceSpecifier(ns) => Some(ns.local.name), + _ => None, + }); + if let Some(namespace) = namespace { + return (namespace.to_string(), true); + } + } + + let mut identifiers = FileIdentifiers::default(); + oxc_ast_visit::Visit::visit_program(&mut identifiers, program); + let mut name = JIT_ANGULAR_CORE_NS.to_string(); + let mut counter = 1; + while identifiers.0.contains(&name) { + name = format!("{JIT_ANGULAR_CORE_NS}_{counter}"); + counter += 1; + } + (name, false) +} + /// Inspect a property initializer; if it matches a recognized signal initializer API, /// return the synthesized `propDecorators` entries that JIT runtime needs. /// @@ -1432,18 +1432,22 @@ pub(crate) const JIT_ANGULAR_CORE_NS: &str = "i0"; /// property (e.g. `Input`, `Output`); we skip synthesis when the user-authored decorator /// already covers the binding (matches upstream behavior — explicit decorator wins). /// -/// Synthesized decorator names are namespace-prefixed (e.g. `i0.Input`) — see -/// [`JIT_ANGULAR_CORE_NS`]. The caller is responsible for emitting the matching -/// `import * as i0 from "@angular/core"` when any synthesis occurred. +/// Synthesized decorator names are prefixed with `core_namespace` (e.g. `i0.Input`, +/// see [`jit_angular_core_namespace`]). The caller is responsible for emitting the +/// matching `import * as i0 from "@angular/core"` when any synthesis occurred. fn synthesize_signal_api_decorators( source: &str, initializer: &Expression<'_>, field_name: &str, existing: &rustc_hash::FxHashSet, + string_consts: &crate::directive::StringConsts<'_>, + core_namespace: &str, ) -> std::vec::Vec { let unwrapped = unwrap_jit_initializer(initializer); let Expression::CallExpression(call) = unwrapped else { return std::vec::Vec::new() }; - let Some(kind) = classify_initializer_api(&call.callee) else { return std::vec::Vec::new() }; + let Some(kind) = classify_initializer_api(&call.callee, string_consts) else { + return std::vec::Vec::new(); + }; match kind { InitializerApiKind::Input | InitializerApiKind::InputRequired => { @@ -1461,7 +1465,7 @@ fn synthesize_signal_api_decorators( required = required, ); std::vec::Vec::from([JitParamDecorator { - name: format!("{JIT_ANGULAR_CORE_NS}.Input"), + name: format!("{core_namespace}.Input"), args: Some(args), }]) } @@ -1476,7 +1480,7 @@ fn synthesize_signal_api_decorators( .unwrap_or_else(|| field_name.to_string()); let args = format!("\"{}\"", escape_js_string(&alias)); std::vec::Vec::from([JitParamDecorator { - name: format!("{JIT_ANGULAR_CORE_NS}.Output"), + name: format!("{core_namespace}.Output"), args: Some(args), }]) } @@ -1498,11 +1502,11 @@ fn synthesize_signal_api_decorators( let output_args = format!("\"{}Change\"", escape_js_string(&alias)); std::vec::Vec::from([ JitParamDecorator { - name: format!("{JIT_ANGULAR_CORE_NS}.Input"), + name: format!("{core_namespace}.Input"), args: Some(input_args), }, JitParamDecorator { - name: format!("{JIT_ANGULAR_CORE_NS}.Output"), + name: format!("{core_namespace}.Output"), args: Some(output_args), }, ]) @@ -1545,7 +1549,7 @@ fn synthesize_signal_api_decorators( }; let args = format!("{locator_text}, {options_text}"); std::vec::Vec::from([JitParamDecorator { - name: format!("{JIT_ANGULAR_CORE_NS}.{decorator_name}"), + name: format!("{core_namespace}.{decorator_name}"), args: Some(args), }]) } @@ -1555,8 +1559,11 @@ fn synthesize_signal_api_decorators( /// Returns `true` when any field of any JIT class has a synthesized decorator /// (signal API lowering) that references the `@angular/core` namespace. Used to /// gate the emission of `import * as i0 from "@angular/core"`. -fn jit_classes_need_angular_core_namespace(jit_classes: &[JitClassInfo]) -> bool { - let prefix = format!("{JIT_ANGULAR_CORE_NS}."); +fn jit_classes_need_angular_core_namespace( + jit_classes: &[JitClassInfo], + core_namespace: &str, +) -> bool { + let prefix = format!("{core_namespace}."); jit_classes.iter().any(|info| { info.member_decorators .iter() @@ -2067,6 +2074,9 @@ fn transform_angular_file_jit( let import_map = build_import_map(allocator, &parser_ret.program.body, options.resolved_imports.as_ref()); + // How synthesized `propDecorators` reference `@angular/core` (`i0.Input`). + let (core_namespace, core_namespace_imported) = jit_angular_core_namespace(&parser_ret.program); + // 3. Walk AST to find Angular-decorated classes let mut jit_classes: std::vec::Vec = std::vec::Vec::new(); let mut resource_counter: u32 = 0; @@ -2097,7 +2107,8 @@ fn transform_angular_file_jit( continue; }; - let Some((decorator_kind, angular_decorator)) = find_angular_decorator(class, &import_map) + let Some((decorator_kind, angular_decorator)) = + find_angular_decorator(class, &import_map, &string_consts) else { continue; }; @@ -2145,12 +2156,14 @@ fn transform_angular_file_jit( } } - // Extract constructor parameters for ctorParameters - let ctor_params = extract_jit_ctor_params(source, class); + // Extract constructor parameters for ctorParameters, and the other + // parameter decorators for the class's `__decorate`. + let (ctor_params, param_decorator_texts) = + extract_jit_ctor_params(source, class, &string_consts); // Extract Angular and non-Angular member decorators let (member_decorators, non_angular_member_decorators) = - extract_all_jit_member_decorators(source, class); + extract_all_jit_member_decorators(source, class, &string_consts, &core_namespace); jit_classes.push(JitClassInfo { class_name, @@ -2164,6 +2177,7 @@ fn transform_angular_file_jit( ctor_params, member_decorators, all_class_decorator_texts, + param_decorator_texts, non_angular_member_decorators, }); @@ -2190,10 +2204,17 @@ fn transform_angular_file_jit( // when signal-API lowering synthesized decorators that need to resolve to // `i0.Input`/`i0.Output`/etc. at runtime). let mut additional_imports = String::new(); - additional_imports.push_str("import { __decorate } from \"tslib\";\n"); - if jit_classes_need_angular_core_namespace(&jit_classes) { + // `__param` lowers a parameter decorator that isn't Angular's. + if jit_classes.iter().any(|c| !c.param_decorator_texts.is_empty()) { + additional_imports.push_str("import { __decorate, __param } from \"tslib\";\n"); + } else { + additional_imports.push_str("import { __decorate } from \"tslib\";\n"); + } + if !core_namespace_imported + && jit_classes_need_angular_core_namespace(&jit_classes, &core_namespace) + { additional_imports - .push_str(&format!("import * as {JIT_ANGULAR_CORE_NS} from \"@angular/core\";\n")); + .push_str(&format!("import * as {core_namespace} from \"@angular/core\";\n")); } for (import_name, specifier) in &resource_imports { additional_imports.push_str(&format!("import {} from \"{}\";\n", import_name, specifier)); @@ -2269,7 +2290,7 @@ fn transform_angular_file_jit( // 4b. Remove ALL member decorators and constructor param decorators { let mut decorator_spans: std::vec::Vec = std::vec::Vec::new(); - super::decorator::collect_constructor_decorator_spans(class, &mut decorator_spans); + super::decorator::collect_all_constructor_decorator_spans(class, &mut decorator_spans); super::decorator::collect_all_member_decorator_spans(class, &mut decorator_spans); for span in &decorator_spans { let mut end = span.end as usize; @@ -2355,7 +2376,13 @@ fn transform_angular_file_jit( } // Emit class-level __decorate() with ALL class decorators - let all_decorator_text = jit_info.all_class_decorator_texts.join(",\n "); + let all_decorator_text = jit_info + .all_class_decorator_texts + .iter() + .chain(&jit_info.param_decorator_texts) + .map(String::as_str) + .collect::>() + .join(",\n "); after_class.push_str(&format!( "{} = __decorate([\n {}\n], {});\n", jit_info.class_name, all_decorator_text, jit_info.class_name @@ -2562,7 +2589,8 @@ pub fn transform_angular_file( // (leaving the @Service decorator removed inconsistently) instead // of producing the intended diagnostic. if find_angular_service_decorator(class, &import_map).is_some() { - if let Some(conflict_name) = find_conflicting_angular_decorator(class, &import_map) + if let Some(conflict_name) = + find_conflicting_angular_decorator(class, &string_consts) { let class_name_for_diag = class.id.as_ref().map_or(String::new(), |id| id.name.to_string()); @@ -2670,25 +2698,44 @@ pub fn transform_angular_file( let component_id = format!("{}@{}", path, class_name); + let component_decorator = + find_component_decorator(&class.decorators, &string_consts); + // Store for HMR if enabled if options.hmr { result.template_updates.insert( component_id.clone(), compilation_result.template_js.clone(), ); + if let Some(decorator) = component_decorator { + let callee = match &decorator.expression { + Expression::CallExpression(call) => call.callee.span(), + expression => expression.span(), + }; + result.component_decorators.insert( + component_id.clone(), + source[callee.start as usize..callee.end as usize] + .to_string(), + ); + } } // Track the decorator span to remove - if let Some(span) = find_component_decorator_span(class) { - decorator_spans_to_remove.push(span); + if let Some(decorator) = component_decorator { + decorator_spans_to_remove.push(decorator.span); } // Collect constructor parameter decorators (@Optional, @Inject, etc.) collect_constructor_decorator_spans( class, + &string_consts, &mut decorator_spans_to_remove, ); // Collect member decorators (@Input, @Output, @HostBinding, etc.) - collect_member_decorator_spans(class, &mut decorator_spans_to_remove); + collect_member_decorator_spans( + class, + &string_consts, + &mut decorator_spans_to_remove, + ); // Store the ɵfac/ɵcmp definitions. // Order: ɵfac BEFORE ɵcmp (Angular convention). @@ -2702,10 +2749,19 @@ 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, Some(source)); + let has_injectable = extract_injectable_metadata_in( + allocator, + class, + Some(source), + Some(&string_consts), + ); if let Some(injectable_metadata) = &has_injectable { - if let Some(span) = find_injectable_decorator_span(class) { + if let Some(span) = find_injectable_decorator( + &class.decorators, + Some(&string_consts), + ) + .map(|d| d.span) + { decorator_spans_to_remove.push(span); } if let Some(inj_def) = generate_injectable_definition_from_decorator( @@ -2740,7 +2796,8 @@ pub fn transform_angular_file( // Add class metadata for TestBed support (after debug info, before HMR) // Only emit when enabled and not in advanced optimizations mode if options.emit_class_metadata && !options.advanced_optimizations { - if let Some(decorator) = find_component_decorator(&class.decorators) + if let Some(decorator) = + find_component_decorator(&class.decorators, &string_consts) { let emitter = JsEmitter::new(); @@ -2771,19 +2828,21 @@ pub fn transform_angular_file( Some(metadata.styles.as_slice()), Some(&string_consts), ), - ctor_parameters: build_ctor_params_metadata( + ctor_parameters: build_ctor_params_metadata_in( &allocator, class, ctor_deps_slice, &mut file_namespace_registry, &import_map, Some(source), + Some(&string_consts), ), - prop_decorators: build_prop_decorators_metadata( + prop_decorators: build_prop_decorators_metadata_in( &allocator, class, Some(source), &mut file_namespace_registry, + Some(&string_consts), ), }; @@ -2929,13 +2988,24 @@ pub fn transform_angular_file( &string_consts, ) { // Track decorator span for removal - if let Some(span) = find_directive_decorator_span(class) { + if let Some(span) = + find_directive_decorator(&class.decorators, Some(&string_consts)) + .map(|d| d.span) + { decorator_spans_to_remove.push(span); } // Collect constructor parameter decorators (@Optional, @Inject, etc.) - collect_constructor_decorator_spans(class, &mut decorator_spans_to_remove); + collect_constructor_decorator_spans( + class, + &string_consts, + &mut decorator_spans_to_remove, + ); // Collect member decorators (@Input, @Output, @HostBinding, etc.) - collect_member_decorator_spans(class, &mut decorator_spans_to_remove); + collect_member_decorator_spans( + class, + &string_consts, + &mut decorator_spans_to_remove, + ); // Resolve namespace imports for directive constructor deps. // Directives can inject services from other modules (e.g., Store from @ngrx/store), @@ -2986,10 +3056,17 @@ 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, Some(source)); + let has_injectable = extract_injectable_metadata_in( + allocator, + class, + Some(source), + Some(&string_consts), + ); if let Some(injectable_metadata) = &has_injectable { - if let Some(span) = find_injectable_decorator_span(class) { + if let Some(span) = + find_injectable_decorator(&class.decorators, Some(&string_consts)) + .map(|d| d.span) + { decorator_spans_to_remove.push(span); } if let Some(inj_def) = generate_injectable_definition_from_decorator( @@ -3019,21 +3096,22 @@ pub fn transform_angular_file( // Emit setClassMetadata for TestBed support (overrideDirective + // signal members), mirroring the @Component path. - let decls_after_class = find_directive_decorator(&class.decorators) - .map(|decorator| { - build_set_class_metadata_decls( - &allocator, - class, - &class_name, - decorator, - options, - source, - &string_consts, - &import_map, - &mut file_namespace_registry, - ) - }) - .unwrap_or_default(); + let decls_after_class = + find_directive_decorator(&class.decorators, Some(&string_consts)) + .map(|decorator| { + build_set_class_metadata_decls( + &allocator, + class, + &class_name, + decorator, + options, + source, + &string_consts, + &import_map, + &mut file_namespace_registry, + ) + }) + .unwrap_or_default(); class_positions.push(( class_name.clone(), @@ -3044,20 +3122,30 @@ pub fn transform_angular_file( class_name, (property_assignments, String::new(), decls_after_class), ); - } else if let Some(mut pipe_metadata) = - extract_pipe_metadata(allocator, class, implicit_standalone, Some(source)) - { + } else if let Some(mut pipe_metadata) = extract_pipe_metadata_in( + allocator, + class, + implicit_standalone, + Some(source), + Some(&string_consts), + ) { // Not a @Component or @Directive - check if it's a @Pipe (PRIMARY) // We need to compile @Pipe classes to generate ɵpipe and ɵfac definitions. // - ɵpipe: Pipe definition for Angular's pipe system // - ɵfac: Factory function for dependency injection (when pipe has constructor deps) // Track decorator span for removal - if let Some(span) = find_pipe_decorator_span(class) { + if let Some(span) = + find_pipe_decorator(&class.decorators, Some(&string_consts)).map(|d| d.span) + { decorator_spans_to_remove.push(span); } // Collect constructor parameter decorators (@Optional, @Inject, etc.) - collect_constructor_decorator_spans(class, &mut decorator_spans_to_remove); + collect_constructor_decorator_spans( + class, + &string_consts, + &mut decorator_spans_to_remove, + ); // Resolve namespace imports for pipe constructor deps if let Some(ref mut deps) = pipe_metadata.deps { @@ -3088,10 +3176,17 @@ 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, Some(source)); + let has_injectable = extract_injectable_metadata_in( + allocator, + class, + Some(source), + Some(&string_consts), + ); if let Some(injectable_metadata) = &has_injectable { - if let Some(span) = find_injectable_decorator_span(class) { + if let Some(span) = + find_injectable_decorator(&class.decorators, Some(&string_consts)) + .map(|d| d.span) + { decorator_spans_to_remove.push(span); } if let Some(inj_def) = generate_injectable_definition_from_decorator( @@ -3117,21 +3212,22 @@ pub fn transform_angular_file( )); // Emit setClassMetadata for TestBed support (overridePipe). - let decls_after_class = find_pipe_decorator(&class.decorators) - .map(|decorator| { - build_set_class_metadata_decls( - &allocator, - class, - &class_name, - decorator, - options, - source, - &string_consts, - &import_map, - &mut file_namespace_registry, - ) - }) - .unwrap_or_default(); + let decls_after_class = + find_pipe_decorator(&class.decorators, Some(&string_consts)) + .map(|decorator| { + build_set_class_metadata_decls( + &allocator, + class, + &class_name, + decorator, + options, + source, + &string_consts, + &import_map, + &mut file_namespace_registry, + ) + }) + .unwrap_or_default(); class_positions.push(( class_name.clone(), @@ -3143,9 +3239,12 @@ pub fn transform_angular_file( (property_assignments, String::new(), decls_after_class), ); } - } else if let Some(mut ng_module_metadata) = - extract_ng_module_metadata(allocator, class, Some(source)) - { + } else if let Some(mut ng_module_metadata) = extract_ng_module_metadata_in( + allocator, + class, + Some(source), + Some(&string_consts), + ) { // 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. // - ɵmod: NgModule definition @@ -3153,11 +3252,18 @@ pub fn transform_angular_file( // - ɵinj: Injector definition with providers and imports // Track decorator span for removal - if let Some(span) = find_ng_module_decorator_span(class) { + if let Some(span) = + find_ng_module_decorator(&class.decorators, Some(&string_consts)) + .map(|d| d.span) + { decorator_spans_to_remove.push(span); } // Collect constructor parameter decorators (@Optional, @Inject, etc.) - collect_constructor_decorator_spans(class, &mut decorator_spans_to_remove); + collect_constructor_decorator_spans( + class, + &string_consts, + &mut decorator_spans_to_remove, + ); // Resolve namespace imports for NgModule constructor deps if let Some(ref mut deps) = ng_module_metadata.deps { @@ -3190,10 +3296,17 @@ 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, Some(source)); + let has_injectable = extract_injectable_metadata_in( + allocator, + class, + Some(source), + Some(&string_consts), + ); if let Some(injectable_metadata) = &has_injectable { - if let Some(span) = find_injectable_decorator_span(class) { + if let Some(span) = + find_injectable_decorator(&class.decorators, Some(&string_consts)) + .map(|d| d.span) + { decorator_spans_to_remove.push(span); } if let Some(inj_def) = generate_injectable_definition_from_decorator( @@ -3229,7 +3342,9 @@ pub fn transform_angular_file( // Emit setClassMetadata for TestBed support (overrideModule), // appended after the NgModule's external declarations. - if let Some(decorator) = find_ng_module_decorator(&class.decorators) { + if let Some(decorator) = + find_ng_module_decorator(&class.decorators, Some(&string_consts)) + { let metadata = build_set_class_metadata_decls( &allocator, class, @@ -3304,7 +3419,11 @@ pub fn transform_angular_file( // user's constructor may still carry @Inject/@Optional/etc. // decorators that need to be stripped from the output (the // class metadata IIFE will pick them up). - collect_constructor_decorator_spans(class, &mut decorator_spans_to_remove); + collect_constructor_decorator_spans( + class, + &string_consts, + &mut decorator_spans_to_remove, + ); let type_argument_count = class.type_parameters.as_ref().map_or(0, |tp| tp.params.len() as u32); @@ -3350,20 +3469,30 @@ pub fn transform_angular_file( (property_assignments, String::new(), decls_after_class), ); } - } else if let Some(mut injectable_metadata) = - extract_injectable_metadata(allocator, class, Some(source)) - { + } else if let Some(mut injectable_metadata) = extract_injectable_metadata_in( + allocator, + class, + Some(source), + Some(&string_consts), + ) { // Standalone @Injectable (no PRIMARY decorator on the class) // We need to compile @Injectable classes to generate ɵprov and ɵfac definitions. // - ɵprov: Provider metadata for Angular's DI system // - ɵfac: Factory function to instantiate the class // Track decorator span for removal - if let Some(span) = find_injectable_decorator_span(class) { + if let Some(span) = + find_injectable_decorator(&class.decorators, Some(&string_consts)) + .map(|d| d.span) + { decorator_spans_to_remove.push(span); } // Collect constructor parameter decorators (@Optional, @Inject, etc.) - collect_constructor_decorator_spans(class, &mut decorator_spans_to_remove); + collect_constructor_decorator_spans( + class, + &string_consts, + &mut decorator_spans_to_remove, + ); // Resolve namespace imports for constructor deps. if let Some(ref mut deps) = injectable_metadata.deps { @@ -3400,21 +3529,22 @@ pub fn transform_angular_file( )); // Emit setClassMetadata for TestBed support. - let decls_after_class = find_injectable_decorator(&class.decorators) - .map(|decorator| { - build_set_class_metadata_decls( - &allocator, - class, - &class_name, - decorator, - options, - source, - &string_consts, - &import_map, - &mut file_namespace_registry, - ) - }) - .unwrap_or_default(); + let decls_after_class = + find_injectable_decorator(&class.decorators, Some(&string_consts)) + .map(|decorator| { + build_set_class_metadata_decls( + &allocator, + class, + &class_name, + decorator, + options, + source, + &string_consts, + &import_map, + &mut file_namespace_registry, + ) + }) + .unwrap_or_default(); class_positions.push(( class_name.clone(), @@ -3539,7 +3669,7 @@ pub fn transform_angular_file( // services without `@Injectable` that we route through this function. For // those, building a full symbol table just to discover there's nothing to // hoist is pure overhead. - if program_has_angular_decorated_class(&parser_ret.program) { + if program_has_angular_decorated_class(&parser_ret.program, &import_map, &string_consts) { // Semantic builder errors (redeclarations, etc.) are intentionally // dropped: the parser already captured syntax errors into // `result.diagnostics` upstream, and Semantic-level diagnostics here @@ -3553,6 +3683,7 @@ pub fn transform_angular_file( source, &hoist_semantic, &import_map, + &string_consts, )); } diff --git a/crates/oxc_angular_compiler/src/directive/decorator.rs b/crates/oxc_angular_compiler/src/directive/decorator.rs index 8a0a7ef7d..8070cdff4 100644 --- a/crates/oxc_angular_compiler/src/directive/decorator.rs +++ b/crates/oxc_angular_compiler/src/directive/decorator.rs @@ -7,9 +7,9 @@ use std::collections::HashMap; use oxc_allocator::{Allocator, Box, Vec}; use oxc_ast::ast::{ - Argument, ArrayExpressionElement, BindingPattern, Class, ClassElement, Declaration, Decorator, - Expression, MethodDefinitionKind, ObjectExpression, ObjectPropertyKind, Program, PropertyKey, - Statement, TemplateLiteral, VariableDeclarationKind, + Argument, ArrayExpressionElement, BindingPattern, CallExpression, Class, ClassElement, + Declaration, Decorator, Expression, MethodDefinitionKind, ObjectExpression, ObjectPropertyKind, + Program, PropertyKey, Statement, TemplateLiteral, VariableDeclarationKind, }; use oxc_diagnostics::OxcDiagnostic; use oxc_span::{GetSpan, Span}; @@ -24,10 +24,17 @@ use crate::factory::R3DependencyMetadata; use crate::output::ast::{OutputAstBuilder, OutputExpression, ReadVarExpr}; use crate::output::oxc_converter::convert_oxc_expression; -/// Find the @Directive decorator in a list of decorators. +/// Find the @Directive decorator in a list of decorators. With the file's +/// `consts`, only Angular's (imported from `@angular/core`, see +/// [`super::find_angular_class_decorator`]); without them (the public +/// [`find_directive_decorator_span`]), any named `Directive`. pub(crate) fn find_directive_decorator<'a>( decorators: &'a [Decorator<'a>], + consts: Option<&StringConsts<'_>>, ) -> Option<&'a Decorator<'a>> { + if let Some(consts) = consts { + return super::find_angular_class_decorator(decorators, "Directive", consts); + } decorators.iter().find(|d| match &d.expression { Expression::CallExpression(call) => is_directive_call(&call.callee), Expression::Identifier(id) => id.name == "Directive", @@ -43,8 +50,11 @@ pub(crate) fn find_directive_decorator<'a>( /// This is necessary because Angular's JIT runtime will process any remaining /// decorators and create conflicting property definitions (like `ɵfac` getters) /// that interfere with the AOT-compiled assignments. +/// +/// Without the file's imports, this matches any decorator named `Directive`; +/// the compiler only takes one imported from `@angular/core`. pub fn find_directive_decorator_span(class: &Class<'_>) -> Option { - find_directive_decorator(&class.decorators).map(|d| d.span) + find_directive_decorator(&class.decorators, None).map(|d| d.span) } /// Check if a callee expression is a call to 'Directive'. @@ -92,8 +102,8 @@ pub fn extract_directive_metadata<'a>( // Get the class name let class_name: Ident<'a> = class.id.as_ref()?.name.clone().into(); - // Find the @Directive decorator - let directive_decorator = find_directive_decorator(&class.decorators)?; + // Find Angular's @Directive decorator + let directive_decorator = find_directive_decorator(&class.decorators, Some(consts))?; // Get the decorator call arguments let call_expr = match &directive_decorator.expression { @@ -101,11 +111,6 @@ pub fn extract_directive_metadata<'a>( _ => return None, }; - // Verify it's calling 'Directive' - if !is_directive_call(&call_expr.callee) { - return None; - } - // Create builder with defaults let mut builder = R3DirectiveMetadataBuilder::new(allocator) .name(class_name.clone()) @@ -198,7 +203,8 @@ 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, source_text); + let constructor_deps = + extract_constructor_deps(allocator, class, has_superclass, source_text, consts); if let Some(deps) = constructor_deps { builder = builder.deps(deps); } @@ -228,7 +234,6 @@ pub fn extract_directive_metadata<'a>( let fields = std::mem::replace(&mut metadata.outputs, Vec::new_in(&allocator)); metadata.outputs = merge_by_class_property(io.outputs, fields, |o| o.0.as_str()); } - resolve_member_transforms(allocator, class, source_text, consts, &mut metadata.inputs); // Merge host metadata from decorator into the existing host metadata if let Some(decorator_host) = host_from_decorator { @@ -294,6 +299,7 @@ fn extract_constructor_deps<'a>( class: &'a Class<'a>, has_superclass: bool, source_text: Option<&'a str>, + consts: &StringConsts<'a>, ) -> Option>> { // Find the constructor method let constructor = class.body.body.iter().find_map(|element| { @@ -312,7 +318,7 @@ fn extract_constructor_deps<'a>( let mut deps = Vec::with_capacity_in(params.items.len(), &allocator); for param in ¶ms.items { - let dep = extract_param_dependency(allocator, param, source_text); + let dep = extract_param_dependency(allocator, param, source_text, consts); deps.push(dep); } @@ -328,11 +334,14 @@ fn extract_constructor_deps<'a>( } } -/// Extract dependency metadata from a single constructor parameter. +/// Extract dependency metadata from a single constructor parameter. Only +/// Angular's parameter decorators count, imported from `@angular/core` (see +/// [`super::angular_param_decorator`]). fn extract_param_dependency<'a>( allocator: &'a Allocator, param: &oxc_ast::ast::FormalParameter<'a>, source_text: Option<&'a str>, + consts: &StringConsts<'a>, ) -> R3DependencyMetadata<'a> { // Extract flags and @Inject token from decorators let mut optional = false; @@ -343,8 +352,8 @@ fn extract_param_dependency<'a>( let mut attribute_name: Option> = None; for decorator in ¶m.decorators { - if let Some(name) = get_decorator_name_from_expr(&decorator.expression) { - match name.as_str() { + if let Some(name) = super::angular_param_decorator(decorator, Some(consts)) { + match name { "Inject" => { // @Inject(TOKEN) - extract the token if let Expression::CallExpression(call) = &decorator.expression { @@ -406,23 +415,6 @@ fn extract_param_dependency<'a>( } } -/// Get the name of a decorator from its expression. -fn get_decorator_name_from_expr<'a>(expr: &'a Expression<'a>) -> Option> { - match expr { - // @Optional - Expression::Identifier(id) => Some(id.name.clone().into()), - // @Optional() - Expression::CallExpression(call) => { - if let Expression::Identifier(id) = &call.callee { - Some(id.name.clone().into()) - } else { - None - } - } - _ => None, - } -} - /// Extract the injection token from a parameter's type annotation. /// /// Returns a bare `ReadVar` expression with the type name. The caller @@ -510,6 +502,17 @@ impl<'a> StringConsts<'a> { self.strings.get(name) } + /// `program`'s declarations without its folded strings: enough to tell + /// where a name is imported from (see + /// [`super::angular_param_decorator`]). + pub(crate) fn declarations_of(program: &'a Program<'a>) -> Self { + Self { + strings: HashMap::default(), + program: Some(program), + scope: std::cell::OnceCell::new(), + } + } + /// The file's top-level declarations, for the partial evaluator. pub(crate) fn scope(&self) -> &FileScope<'a> { self.scope.get_or_init(|| self.program.map(FileScope::collect).unwrap_or_default()) @@ -739,18 +742,48 @@ fn scoped_transform_error( } } +/// oxc's error for a transform computed from an import (`!FLAG`, `make()`, +/// `FLAG && fn`, `Helpers.fn`): ngtsc evaluates it with the import's file, oxc +/// can't (see [`value_error`]). An imported function named directly +/// (`transform: fn`) needs no evaluating: it's assumed to be a function. +fn imported_transform_error( + subject: &str, + transform: &Prop<'_>, + span: Span, +) -> Option<(String, Span)> { + let computed = matches!( + transform.value, + Value::Reference { kind: RefKind::Import { namespace_member: false, local: None }, .. } + ); + computed.then(|| (value_error(subject, String::new, &transform.value), span)) +} + /// Whether [`transform_expression`] has no expression to emit for a transform /// it was given, because it uses the parameters of the function it's written /// in (see [`scoped_transform_error`]). fn is_out_of_scope(transform: &Prop<'_>, consts: &StringConsts<'_>) -> bool { - transform.expr.is_some() && transform.origin.is_none() && !is_named_function(transform, consts) + transform.expr.is_some() + && transform.origin.is_none() + && reference_identity(transform, consts).is_none() } -/// Whether a transform is emitted as the name of the same-file function it -/// resolves to (see [`transform_expression`]). -fn is_named_function(transform: &Prop<'_>, consts: &StringConsts<'_>) -> bool { - matches!(&transform.value, Value::Reference { name, kind: RefKind::Function(function, _) } - if consts.scope().is_top_level_function(name, function)) +/// The identifier ngtsc emits for a transform that resolves to a reference +/// (the reference's identity in this file, `getIdentityIn`), see +/// [`transform_expression`]: a same-file function's name, or the name an +/// imported function or a global (including one declared outside the file, +/// like `atob`) was first reached by, through any variables +/// (`booleanAttribute` for `const t = booleanAttribute` and `transform: t`). +fn reference_identity<'p>(transform: &'p Prop<'_>, consts: &StringConsts<'_>) -> Option<&'p str> { + match &transform.value { + Value::Reference { name, kind: RefKind::Function(function, _) } + if consts.scope().is_top_level_function(name, function) => + { + Some(name) + } + Value::Reference { kind: RefKind::Import { namespace_member: false, local }, .. } => *local, + Value::Reference { name, kind: RefKind::Global | RefKind::Ambient } => Some(name), + _ => None, + } } /// Parse `inputs:` / `outputs:` from a decorator metadata object. @@ -909,16 +942,18 @@ fn parse_input_object<'a>( let (transform_function, error) = match item.prop("transform") { Some(transform) => { let expr = transform_expression(allocator, transform, source_text, consts); - let error = - transform_error(transform, Some(position), name, class, consts.scope(), span) - .or_else(|| match transform { - Prop { expr: Some(written), .. } if is_out_of_scope(transform, consts) => { - let message = - scoped_transform_error("@Directive.inputs", name, written, consts); - Some((message, span)) - } - _ => None, - }); + let error = imported_transform_error("@Directive.inputs", transform, span) + .or_else(|| { + transform_error(transform, Some(position), name, class, consts.scope(), span) + }) + .or_else(|| match transform { + Prop { expr: Some(written), .. } if is_out_of_scope(transform, consts) => { + let message = + scoped_transform_error("@Directive.inputs", name, written, consts); + Some((message, span)) + } + _ => None, + }); (expr, error) } None => (None, None), @@ -938,7 +973,10 @@ fn parse_input_object<'a>( /// The expression ngtsc emits for a transform: a function written in place, or /// the identifier of the declaration it resolved to (`T.f` where -/// `const T = { f }` becomes `f`). For a static method that identifier isn't in +/// `const T = { f }` becomes `f`), or the name an imported function or a +/// global was first reached by (`booleanAttribute` for +/// `const transform = booleanAttribute` and `{ transform }`), see +/// [`reference_identity`]. For a static method that identifier isn't in /// scope, so the written expression is kept there, as it can be written where /// the metadata is compiled (see [`Prop::origin`]): `None` when it can't. pub(crate) fn transform_expression<'a>( @@ -947,42 +985,11 @@ pub(crate) fn transform_expression<'a>( source_text: Option<&'a str>, consts: &StringConsts<'a>, ) -> Option> { - match &transform.value { - Value::Reference { name, .. } if is_named_function(transform, consts) => { + match reference_identity(transform, consts) { + Some(name) => { Some(OutputAstBuilder::variable(allocator, Ident::from(allocator.alloc_str(name)))) } - _ => convert_oxc_expression(allocator, transform.origin?, source_text), - } -} - -/// Give `@Input({ transform })` members the transform expression ngtsc emits -/// (see [`transform_expression`]), in place of the one written. -pub(crate) fn resolve_member_transforms<'a>( - allocator: &'a Allocator, - class: &'a Class<'a>, - source_text: Option<&'a str>, - consts: &StringConsts<'a>, - inputs: &mut [R3InputMetadata<'a>], -) { - let evaluator = Evaluator::new(consts); - for element in &class.body.body { - let (key, decorators) = match element { - ClassElement::PropertyDefinition(p) => (&p.key, &p.decorators), - ClassElement::AccessorProperty(p) => (&p.key, &p.decorators), - ClassElement::MethodDefinition(m) => (&m.key, &m.decorators), - _ => continue, - }; - let Some(name) = key.static_name() else { continue }; - let options = super::property_decorators::input_decorator_options(decorators, consts); - let Some(options) = options else { continue }; - let options = evaluator.evaluate(options); - let Some(transform) = options.prop("transform") else { continue }; - let Some(input) = inputs.iter_mut().find(|i| i.class_property_name == name.as_ref()) else { - continue; - }; - if let Some(expr) = transform_expression(allocator, transform, source_text, consts) { - input.transform_function = Some(expr); - } + None => convert_oxc_expression(allocator, transform.origin?, source_text), } } @@ -1031,14 +1038,18 @@ fn upsert_input<'a>(inputs: &mut Vec<'a, R3InputMetadata<'a>>, input: R3InputMet upsert_meta(inputs, input, |i| i.class_property_name.as_str()); } -/// The `@Component` / `@Directive` decorator on `class`, its metadata object -/// (if any) and its name. +/// Angular's `@Component` / `@Directive` decorator on `class` (imported from +/// `@angular/core`, in the file `consts` was collected from), its metadata +/// object (if any) and its name. pub(crate) fn angular_decorator_config<'a>( class: &'a Class<'a>, + consts: &StringConsts<'_>, ) -> Option<(Option<&'a ObjectExpression<'a>>, &'static str)> { - let (decorator, name) = crate::component::find_component_decorator(&class.decorators) + let (decorator, name) = crate::component::find_component_decorator(&class.decorators, consts) .map(|d| (d, "Component")) - .or_else(|| find_directive_decorator(&class.decorators).map(|d| (d, "Directive")))?; + .or_else(|| { + find_directive_decorator(&class.decorators, Some(consts)).map(|d| (d, "Directive")) + })?; let config = match &decorator.expression { Expression::CallExpression(call) => match call.arguments.first() { Some(Argument::ObjectExpression(config)) => Some(&**config), @@ -1064,7 +1075,7 @@ pub fn decorator_io_errors<'a>( source_text: Option<&'a str>, consts: &StringConsts<'a>, ) -> std::vec::Vec { - let Some((config, decorator_name)) = angular_decorator_config(class) else { + let Some((config, decorator_name)) = angular_decorator_config(class, consts) else { return std::vec::Vec::new(); }; let io = config.map(|config| parse_decorator_io(allocator, config, class, source_text, consts)); @@ -1086,40 +1097,28 @@ pub fn decorator_io_errors<'a>( _ => return None, }; let name = key.static_name()?; - // `@Input({ transform })` - let options = super::property_decorators::input_decorator_options(decorators, consts); - if let Some(options) = options { - let span = options.span(); - let options = evaluator.evaluate(options); - // ngtsc reads imported options (or an imported alias or - // `required`) from their file; oxc can't, and compiling the - // input without them would be a different binding. - let imported = std::iter::once(&options) - .chain( - ["alias", "required"].iter().filter_map(|k| Some(&options.prop(k)?.value)), - ) - .find(|value| value.is_import()); - if let Some(value) = imported { - return Some((value_error("@Input", String::new, value), span)); - } - if let Some(transform) = options.prop("transform") { - let error = - transform_error(transform, None, &name, class, consts.scope(), span) - .or_else(|| match transform { - Prop { expr: Some(written), .. } - if is_out_of_scope(transform, consts) => - { - let message = - scoped_transform_error("@Input", &name, written, consts); - Some((message, span)) - } - _ => None, - }); - if error.is_some() { - return error; - } + // `@Input(...)`, as ngtsc's `tryParseInputFieldMapping` reads it. + let decorator = + super::property_decorators::member_decorator(decorators, "Input", consts); + // ngtsc rejects `@Input` on a signal input or model before reading + // the decorator. + if let (Some(decorator), Some(value)) = (decorator, value) { + let message = if is_initializer_api_call(value, consts, &[INPUT_API]) { + Some("Using @Input with a signal input is not allowed.") + } else if is_initializer_api_call(value, consts, &[MODEL_API]) { + Some("Using @Input with a model input is not allowed.") + } else { + None + }; + if let Some(message) = message { + return Some((message.to_string(), decorator.span)); } } + let error = + decorator.and_then(|d| input_decorator_error(d, &name, class, consts, &evaluator)); + if error.is_some() { + return error; + } // A signal input only collides with a metadata entry of the same name. let value = value.filter(|_| meta_inputs.contains(&name.as_ref()))?; let is_input = is_initializer_api_call(value, consts, &[INPUT_API, MODEL_API]); @@ -1133,6 +1132,34 @@ pub fn decorator_io_errors<'a>( }; let output_members = || { class.body.body.iter().find_map(|element| { + // `@Output(...)`, as ngtsc's `tryParseDecoratorOutput` reads it, on + // the members an output is compiled from. + let (decorators, value) = match element { + ClassElement::PropertyDefinition(p) => (Some(&p.decorators), p.value.as_ref()), + ClassElement::AccessorProperty(p) => (Some(&p.decorators), p.value.as_ref()), + _ => (None, None), + }; + let decorator = decorators.and_then(|decorators| { + super::property_decorators::member_decorator(decorators, "Output", consts) + }); + if let Some(error) = decorator.and_then(|d| output_decorator_error(d, &evaluator)) { + return Some(error); + } + // Then `@Output` on an `output()` or a model, like ngtsc's + // `parseOutputFields`. + if let (Some(decorator), Some(value)) = (decorator, value) { + let apis = [OUTPUT_API, OUTPUT_FROM_OBSERVABLE_API]; + let message = if is_initializer_api_call(value, consts, &apis) { + Some("Using \"@Output\" with \"output()\" is not allowed.") + } else if is_initializer_api_call(value, consts, &[MODEL_API]) { + Some("Using @Output with a model input is not allowed.") + } else { + None + }; + if let Some(message) = message { + return Some((message.to_string(), decorator.span)); + } + } let ClassElement::PropertyDefinition(prop) = element else { return None }; let (value, name) = (prop.value.as_ref()?, prop.key.static_name()?); if !meta_outputs.contains(&name.as_ref()) { @@ -1180,6 +1207,93 @@ pub fn decorator_io_errors<'a>( .collect() } +/// ngtsc's error for the `@Input(...)` decorator of the member `name` +/// (`tryParseInputFieldMapping`): more than one argument, an argument that +/// isn't `null`, a string or an object, or a `transform` it can't use. Options +/// from another module are reported as such (see [`value_error`]). +fn input_decorator_error<'a>( + decorator: &'a Decorator<'a>, + name: &str, + class: &'a Class<'a>, + consts: &StringConsts<'a>, + evaluator: &Evaluator<'_, 'a>, +) -> Option<(String, Span)> { + // ngtsc names it as it's written: `@In` for `import { Input as In }`. + let subject = format!("@{}", super::property_decorators::decorator_written_name(decorator)); + if let Some(error) = decorator_arity_error(decorator, &subject) { + return Some(error); + } + let options = super::property_decorators::decorator_argument(decorator)?; + let span = options.span(); + let options = evaluator.evaluate(options); + // ngtsc reads imported options (or an imported alias or `required`) from + // their file; oxc can't, and compiling the input without them would be a + // different binding. + let imported = std::iter::once(&options) + .chain(["alias", "required"].iter().filter_map(|k| Some(&options.prop(k)?.value))) + .find(|value| value.is_import()); + if let Some(value) = imported { + return Some((value_error(&subject, String::new, value), span)); + } + if !matches!(options, Value::Null | Value::String(_) | Value::Object(_)) { + let message = format!( + "{subject} decorator argument must resolve to a string or an object literal{}", + options.wrong_type_suffix() + ); + return Some((message, decorator.span)); + } + let transform = options.prop("transform")?; + if let Some(error) = imported_transform_error(&subject, transform, span) { + return Some(error); + } + transform_error(transform, None, name, class, consts.scope(), span).or_else( + || match transform { + Prop { expr: Some(written), .. } if is_out_of_scope(transform, consts) => { + Some((scoped_transform_error("@Input", name, written, consts), span)) + } + _ => None, + }, + ) +} + +/// ngtsc's error for a member decorator called with more than one argument, +/// named `subject` (`@Input`, or `@In` for `import { Input as In }`). +fn decorator_arity_error(decorator: &Decorator<'_>, subject: &str) -> Option<(String, Span)> { + let Expression::CallExpression(call) = &decorator.expression else { return None }; + let count = call.arguments.len(); + (count > 1).then(|| { + let message = format!("{subject} can have at most one argument, got {count} argument(s)"); + (message, decorator.span) + }) +} + +/// ngtsc's error for an `@Output(...)` decorator (`tryParseDecoratorOutput`): +/// more than one argument, or an argument that isn't a string. An argument +/// from another module is reported as such (see [`value_error`]). +fn output_decorator_error<'a>( + decorator: &'a Decorator<'a>, + evaluator: &Evaluator<'_, 'a>, +) -> Option<(String, Span)> { + // ngtsc names it `@Output` whatever it's imported as. + if let Some(error) = decorator_arity_error(decorator, "@Output") { + return Some(error); + } + let argument = super::property_decorators::decorator_argument(decorator)?; + match evaluator.evaluate(argument) { + Value::String(_) => None, + value if value.is_import() => { + Some((value_error("@Output", String::new, &value), argument.span())) + } + value => { + let message = format!( + "@Output decorator argument must resolve to a string{}", + value.wrong_type_suffix() + ); + Some((message, decorator.span)) + } + } +} + /// An initializer API: its function name and the module exporting it. pub(crate) type InitializerApi = (&'static str, &'static str); pub(crate) const INPUT_API: InitializerApi = ("input", "@angular/core"); @@ -1188,45 +1302,83 @@ pub(crate) const OUTPUT_API: InitializerApi = ("output", "@angular/core"); pub(crate) const OUTPUT_FROM_OBSERVABLE_API: InitializerApi = ("outputFromObservable", "@angular/core/rxjs-interop"); -/// Whether `value` calls one of `apis`, as ngtsc's `tryParseInitializerApi` -/// recognises them: `f()` or `f.required()` with `f` imported by name from the -/// API's module (under any alias), or `ns.f()` or `ns.f.required()` with `ns` -/// a namespace import of it, looking through `as` and parentheses. A function -/// only named like an API (a local one, or one from another module) isn't one. +/// Angular's signal query functions (ngtsc's `QUERY_INITIALIZER_FNS`). +pub(crate) const QUERY_APIS: [InitializerApi; 4] = [ + ("viewChild", "@angular/core"), + ("viewChildren", "@angular/core"), + ("contentChild", "@angular/core"), + ("contentChildren", "@angular/core"), +]; + +/// Whether `value` calls one of `apis` (see [`initializer_api_call`]). pub(crate) fn is_initializer_api_call( value: &Expression<'_>, consts: &StringConsts<'_>, apis: &[InitializerApi], ) -> bool { + initializer_api_call(value, Some(consts), apis).is_some() +} + +/// The call to one of `apis` that `value` is, looking through `as` and +/// parentheses: the API, whether it's the `.required` form, and the call. +/// See [`initializer_api`] for the callees that count. +pub(crate) fn initializer_api_call<'b, 'a>( + value: &'b Expression<'a>, + consts: Option<&StringConsts<'_>>, + apis: &[InitializerApi], +) -> Option<(InitializerApi, bool, &'b CallExpression<'a>)> { let Expression::CallExpression(call) = super::unwrap_initializer_api_expr(value) else { - return false; + return None; }; - let scope = consts.scope(); + let (api, required) = initializer_api(&call.callee, consts, apis)?; + Some((api, required, call)) +} + +/// Which of `apis` `callee` is, and whether it's its `.required` form, as +/// ngtsc's `tryParseInitializerApi` recognises them: `f` or `f.required` with +/// `f` imported by name from the API's module (under any alias), or `ns.f` or +/// `ns.f.required` with `ns` a namespace import of it. A function only named +/// like an API (a local one, one from another module, or an undeclared one) +/// isn't one. +/// +/// Without the file's imports (`consts` is `None`, for the public extraction +/// functions that aren't given them), `f` and `ns.f` are matched by name. +pub(crate) fn initializer_api( + callee: &Expression<'_>, + consts: Option<&StringConsts<'_>>, + apis: &[InitializerApi], +) -> Option<(InitializerApi, bool)> { + let scope = consts.map(StringConsts::scope); // `f`, imported by name. let named = |f: &Expression<'_>| { - let Expression::Identifier(id) = f else { return false }; - scope.import(&id.name).is_some_and(|import| { - apis.iter() - .any(|&(name, module)| import.imported == Some(name) && import.module == module) - }) + let Expression::Identifier(id) = f else { return None }; + let Some(scope) = scope else { + return apis.iter().copied().find(|&(name, _)| id.name == name); + }; + let import = scope.import(&id.name)?; + apis.iter() + .copied() + .find(|&(name, module)| import.imported == Some(name) && import.module == module) }; // `ns.f`, through a namespace import. let namespaced = |f: &Expression<'_>| { - let Expression::StaticMemberExpression(member) = f else { return false }; - let Expression::Identifier(ns) = &member.object else { return false }; - scope.import(&ns.name).is_some_and(|import| { - import.imported.is_none() - && apis - .iter() - .any(|&(name, module)| member.property.name == name && import.module == module) - }) + let Expression::StaticMemberExpression(member) = f else { return None }; + let Expression::Identifier(ns) = &member.object else { return None }; + let function = member.property.name.as_str(); + let Some(scope) = scope else { + return apis.iter().copied().find(|&(name, _)| function == name); + }; + let import = scope.import(&ns.name).filter(|import| import.imported.is_none())?; + apis.iter().copied().find(|&(name, module)| function == name && import.module == module) }; - let callee = &call.callee; - named(callee) - || namespaced(callee) - || matches!(callee, Expression::StaticMemberExpression(required) - if required.property.name == "required" - && (named(&required.object) || namespaced(&required.object))) + if let Some(api) = named(callee).or_else(|| namespaced(callee)) { + return Some((api, false)); + } + let Expression::StaticMemberExpression(required) = callee else { return None }; + if required.property.name != "required" { + return None; + } + named(&required.object).or_else(|| namespaced(&required.object)).map(|api| (api, true)) } /// Extract host metadata from a host object expression. @@ -1696,6 +1848,7 @@ mod tests { #[test] fn test_extract_directive_selector() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appHighlight]' }) class HighlightDirective {} "#; @@ -1708,6 +1861,7 @@ mod tests { #[test] fn test_extract_directive_standalone_true() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]', standalone: true @@ -1722,6 +1876,7 @@ mod tests { #[test] fn test_extract_directive_standalone_false() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]', standalone: false @@ -1736,6 +1891,7 @@ mod tests { #[test] fn test_extract_directive_standalone_defaults_to_implicit() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]' }) class TestDirective {} "#; @@ -1752,6 +1908,7 @@ mod tests { #[test] fn test_extract_directive_export_as() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]', exportAs: 'testDir' @@ -1767,6 +1924,7 @@ mod tests { #[test] fn test_extract_directive_export_as_multiple() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]', exportAs: 'foo, bar, baz' @@ -1784,6 +1942,7 @@ mod tests { #[test] fn test_extract_directive_host_property_bindings() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]', host: { @@ -1801,6 +1960,7 @@ mod tests { #[test] fn test_extract_directive_host_listeners() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]', host: { @@ -1818,6 +1978,7 @@ mod tests { #[test] fn test_extract_directive_host_static_attributes() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]', host: { @@ -1838,6 +1999,7 @@ mod tests { #[test] fn test_extract_directive_host_computed_key_identifier() { let code = r#" + import {Directive} from '@angular/core'; const ATTR = 'data-foo'; @Directive({ selector: '[d]', host: { [ATTR]: '' } }) class D {} @@ -1851,6 +2013,7 @@ mod tests { #[test] fn test_extract_directive_host_value_identifier() { let code = r#" + import {Directive} from '@angular/core'; const VAL = 'submit'; @Directive({ selector: '[d]', host: { type: VAL } }) class D {} @@ -1864,6 +2027,7 @@ mod tests { #[test] fn test_extract_directive_host_template_literal_const() { let code = r#" + import {Directive} from '@angular/core'; const ATTR = `data-foo`; @Directive({ selector: '[d]', host: { [ATTR]: '' } }) class D {} @@ -1878,6 +2042,7 @@ mod tests { fn test_extract_directive_host_unknown_identifier_dropped() { // Unresolved identifier (no matching const) is still dropped — current behavior. let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[d]', host: { [UNKNOWN]: '' } }) class D {} "#; @@ -1890,6 +2055,7 @@ mod tests { fn test_extract_directive_host_exported_const_identifier() { // `export const` (not just `const`) in the same file must also be resolved. let code = r#" + import {Directive} from '@angular/core'; export const MARKER_ATTR = 'data-marker'; @Directive({ selector: '[marker]', @@ -1906,6 +2072,7 @@ mod tests { #[test] fn test_extract_directive_host_class_attr() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]', host: { @@ -1922,6 +2089,7 @@ mod tests { #[test] fn test_extract_directive_host_style_attr() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]', host: { @@ -1941,6 +2109,7 @@ mod tests { #[test] fn test_extract_directive_host_directives_simple() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]', hostDirectives: [TooltipDirective] @@ -1956,6 +2125,7 @@ mod tests { #[test] fn test_extract_directive_host_directives_with_mappings() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]', hostDirectives: [ @@ -1981,6 +2151,7 @@ mod tests { #[test] fn test_extract_directive_host_directives_forward_ref() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]', hostDirectives: [forwardRef(() => MyDirective)] @@ -1996,6 +2167,7 @@ mod tests { #[test] fn test_extract_directive_with_inputs_from_class() { let code = r#" + import {Directive, Input, Output, HostBinding, HostListener} from '@angular/core'; @Directive({ selector: '[appTest]' }) class TestDirective { @Input() name: string; @@ -2014,6 +2186,7 @@ mod tests { #[test] fn test_extract_directive_with_outputs_from_class() { let code = r#" + import {Directive, Input, Output, HostBinding, HostListener} from '@angular/core'; @Directive({ selector: '[appTest]' }) class TestDirective { @Output() clicked = new EventEmitter(); @@ -2032,6 +2205,7 @@ mod tests { #[test] fn test_extract_directive_with_host_binding_decorator() { let code = r#" + import {Directive, Input, Output, HostBinding, HostListener} from '@angular/core'; @Directive({ selector: '[appTest]' }) class TestDirective { @HostBinding('class.active') isActive = false; @@ -2047,6 +2221,7 @@ mod tests { #[test] fn test_extract_directive_with_host_listener_decorator() { let code = r#" + import {Directive, Input, Output, HostBinding, HostListener} from '@angular/core'; @Directive({ selector: '[appTest]' }) class TestDirective { @HostListener('click') onClick() {} @@ -2062,6 +2237,7 @@ mod tests { #[test] fn test_extract_directive_merges_host_from_decorator_and_class() { let code = r#" + import {Directive, Input, Output, HostBinding, HostListener} from '@angular/core'; @Directive({ selector: '[appTest]', host: { @@ -2093,6 +2269,7 @@ mod tests { #[test] fn test_extract_directive_component_decorator_does_not_match() { let code = r#" + import {Component} from '@angular/core'; @Component({ selector: 'app-test', template: '' }) class TestComponent {} "#; @@ -2114,6 +2291,7 @@ mod tests { fn test_empty_directive_decorator() { // @Directive({}) - explicit empty config object let code = r#" + import {Directive} from '@angular/core'; @Directive({}) class TestDirective {} "#; @@ -2128,6 +2306,7 @@ mod tests { // @Directive() - no config argument at all // This is common for abstract base directive classes let code = r#" + import {Directive} from '@angular/core'; @Directive() class TestDirective {} "#; @@ -2140,6 +2319,7 @@ mod tests { #[test] fn test_exported_directive() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]' }) export class TestDirective {} "#; @@ -2151,6 +2331,7 @@ mod tests { #[test] fn test_export_default_directive() { let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]' }) export default class TestDirective {} "#; @@ -2162,6 +2343,7 @@ mod tests { #[test] fn test_namespaced_directive_decorator() { let code = r#" + import * as ng from '@angular/core'; @ng.Directive({ selector: '[appTest]' }) class TestDirective {} "#; @@ -2173,6 +2355,7 @@ mod tests { #[test] fn test_full_directive_decorator() { let code = r#" + import {Directive, Input, Output, HostBinding, HostListener} from '@angular/core'; @Directive({ selector: '[appComplete]', standalone: true, @@ -2212,6 +2395,7 @@ mod tests { // A directive that does NOT extend any base class // should have uses_inheritance = false let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appTest]' }) class TestDirective {} "#; @@ -2225,6 +2409,7 @@ mod tests { // A directive that extends a base class // should have uses_inheritance = true let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[appChild]' }) class ChildDirective extends BaseDirective {} "#; @@ -2238,6 +2423,7 @@ mod tests { // Regression test for issue #285: // `@Optional() svc: MyService | null` must resolve the token to `MyService`. let code = r#" + import {Directive, Optional} from '@angular/core'; @Directive({ selector: '[myDir]' }) class MyDirective { constructor(@Optional() private svc: MyService | null) {} @@ -2268,6 +2454,7 @@ mod tests { // The fix: Changed extract_param_token to return ReadVar(TypeName) // matching the pattern used by injectable, pipe, and ng_module extractors. let code = r#" + import {Directive} from '@angular/core'; @Directive({ selector: '[myDir]' }) class MyDirective { constructor(private store: Store, private svc: SomeService) {} diff --git a/crates/oxc_angular_compiler/src/directive/evaluator.rs b/crates/oxc_angular_compiler/src/directive/evaluator.rs index 7f95e8a6d..8fc6710a4 100644 --- a/crates/oxc_angular_compiler/src/directive/evaluator.rs +++ b/crates/oxc_angular_compiler/src/directive/evaluator.rs @@ -1144,6 +1144,10 @@ pub(crate) enum RefKind<'a> { /// another file. Import { namespace_member: bool, + /// For the binding itself, the local name it was first reached by in + /// this file (`ba` for `import { booleanAttribute as ba }`), which is + /// what ngtsc emits for it (the reference's identity in this file). + local: Option<&'a str>, }, /// An identifier with no declaration in this file that names a standard /// ECMAScript global (see [`ES_GLOBALS`]). @@ -1237,6 +1241,17 @@ impl<'a> Value<'a> { matches!(self, Value::Reference { kind: RefKind::Import { .. }, .. }) } + /// A value computed from this one, when it's an import: it stands for + /// something in another file that has no name here. + fn computed(self) -> Self { + match self { + Value::Reference { name, kind: RefKind::Import { namespace_member, .. } } => { + Value::Reference { name, kind: RefKind::Import { namespace_member, local: None } } + } + value => value, + } + } + /// Whether ngtsc's value is unknown: dynamic, or a global declared outside /// the file, which is only kept as a name so a transform can refer to it. pub(crate) fn is_dynamic(&self) -> bool { @@ -1674,7 +1689,7 @@ impl<'s, 'a> Evaluator<'s, 'a> { if let Some(e) = tpl.expressions.get(i) { match literal(self.eval(e, depth, frame)) { Value::Dynamic => return Value::Dynamic, - value if value.is_import() => return value, + value if value.is_import() => return value.computed(), value => out.push_str(&to_js_string(&value)), } } @@ -1721,6 +1736,13 @@ impl<'s, 'a> Evaluator<'s, 'a> { Some(binding) => binding.value.clone(), None => match self.slot(frame.block, name) { Some(slot) => self.stored(slot, depth), + // An import has no value declaration for + // TypeScript's `getShorthandAssignmentValueSymbol` + // to find (it returns the alias), so ngtsc + // can't evaluate `{ transform }` naming one. + None if self.consts.scope().imports.contains_key(name) => { + Value::Dynamic + } // ngtsc never resolves a shorthand to a global // it can use as a transform. None => match self.eval(&p.value, depth, frame) { @@ -1754,7 +1776,7 @@ impl<'s, 'a> Evaluator<'s, 'a> { Value::Object(inner) if self.spend(inner.len() as u32) => { props.extend(inner); } - value if value.is_import() => return value, + value if value.is_import() => return value.computed(), _ => return Value::Dynamic, } } @@ -1788,7 +1810,7 @@ impl<'s, 'a> Evaluator<'s, 'a> { match self.eval(&m.expression, depth, frame) { Value::String(key) => self.member(object, Key::Str(&key), depth), Value::Number(n) => self.member(object, Key::Num(n), depth), - key if key.is_import() => key, + key if key.is_import() => key.computed(), _ => Value::Dynamic, } } @@ -1802,7 +1824,7 @@ impl<'s, 'a> Evaluator<'s, 'a> { ) -> Value<'a> { match self.eval(&c.test, depth, frame) { test if test.is_dynamic() => Value::Dynamic, - test if test.is_import() => test, + test if test.is_import() => test.computed(), test if test.truthy() => self.eval(&c.consequent, depth, frame), _ => self.eval(&c.alternate, depth, frame), } @@ -1821,7 +1843,7 @@ impl<'s, 'a> Evaluator<'s, 'a> { } match self.eval(&u.argument, depth, frame) { value if value.is_dynamic() => Value::Dynamic, - value if value.is_import() => value, + value if value.is_import() => value.computed(), value => unary(u.operator, &value), } } @@ -1836,7 +1858,7 @@ impl<'s, 'a> Evaluator<'s, 'a> { let right = literal(self.eval(&b.right, depth, frame)); match (left, right) { (Value::Dynamic, _) | (_, Value::Dynamic) => Value::Dynamic, - (value, _) | (_, value) if value.is_import() => value, + (value, _) | (_, value) if value.is_import() => value.computed(), (left, right) => binary(b.operator, &left, &right), } } @@ -1860,7 +1882,7 @@ impl<'s, 'a> Evaluator<'s, 'a> { let right = self.eval(&l.right, depth, frame); match (left, right) { (left, right) if left.is_dynamic() || right.is_dynamic() => Value::Dynamic, - (left, _) if left.is_import() => left, + (left, _) if left.is_import() => left.computed(), (left, right) => match (l.operator, left.truthy()) { (LogicalOperator::And, true) | (LogicalOperator::Or, false) => right, _ => left, @@ -1877,7 +1899,7 @@ impl<'s, 'a> Evaluator<'s, 'a> { ) -> std::vec::Vec> { match self.eval(expr, depth, frame) { Value::Array(inner) if self.spend(inner.len() as u32) => inner, - value if value.is_import() => vec![value], + value if value.is_import() => vec![value.computed()], // ngtsc marks only this element as dynamic, not the whole array. _ => vec![Value::Dynamic], } @@ -1926,10 +1948,10 @@ impl<'s, 'a> Evaluator<'s, 'a> { if let Some(import) = scope.imports.get(name) { return match import.imported { Some(imported) => { - let name = if imported == "default" { name } else { imported }; + let declared = if imported == "default" { name } else { imported }; Value::Reference { - name: name.into(), - kind: RefKind::Import { namespace_member: false }, + name: declared.into(), + kind: RefKind::Import { namespace_member: false, local: Some(name) }, } } None => Value::Module, @@ -2246,7 +2268,7 @@ impl<'s, 'a> Evaluator<'s, 'a> { let imported = matches!(left, TSTypeName::IdentifierReference(id) if self.consts.scope().imports.contains_key(id.name.as_str())); let kind = if imported { - RefKind::Import { namespace_member: false } + RefKind::Import { namespace_member: false, local: None } } else { RefKind::Other }; @@ -2305,7 +2327,7 @@ impl<'s, 'a> Evaluator<'s, 'a> { (function, overloads) } // An imported function runs in another file. - callee if callee.is_import() => return callee, + callee if callee.is_import() => return callee.computed(), _ => return Value::Dynamic, }; // A body-less first declaration (an overload, `declare function`) can't be evaluated. @@ -2426,7 +2448,7 @@ impl<'s, 'a> Evaluator<'s, 'a> { | Value::Bool(_) | Value::Number(_) | Value::String(_) => s.push_str(&to_js_string(&arg)), - arg if arg.is_import() => return arg, + arg if arg.is_import() => return arg.computed(), _ => return Value::Dynamic, } } @@ -2471,13 +2493,13 @@ impl<'s, 'a> Evaluator<'s, 'a> { } Value::Module => Value::Reference { name: key_str(), - kind: RefKind::Import { namespace_member: true }, + kind: RefKind::Import { namespace_member: true, local: None }, }, Value::Reference { kind: RefKind::Class(class), .. } => { self.static_member(class, &key_str(), depth) } // The object is in another file, and so is its member. - object @ Value::Reference { kind: RefKind::Import { .. }, .. } => object, + object @ Value::Reference { kind: RefKind::Import { .. }, .. } => object.computed(), // Including a member of a global (`Math.round`): ngtsc only // resolves the global's own declaration. _ => Value::Dynamic, @@ -2589,12 +2611,17 @@ pub(crate) fn transform_error<'a>( } // An imported function can't be inspected from this file, so whether // it's generic or overloaded is unknown; the name clash is checked - // after those. - Value::Reference { kind: RefKind::Import { namespace_member: false }, .. } => { + // after those. Only the import itself is assumed to be a function, not + // a value computed from it (`!FLAG`, `make()`), which the caller + // reports as imported. + Value::Reference { + kind: RefKind::Import { namespace_member: false, local: Some(_) }, + .. + } => { return clash(); } // ngtsc can't name `ns.f` in the compiled file. - Value::Reference { kind: RefKind::Import { namespace_member: true }, .. } => { + Value::Reference { kind: RefKind::Import { namespace_member: true, .. }, .. } => { return clash().or_else(|| { Some((format!("Input transform function could not be referenced{suffix}"), node())) }); @@ -3371,7 +3398,7 @@ pub fn input_transform_types<'a>( None => transforms.push((name, transform)), } }; - if let Some((Some(config), _)) = super::angular_decorator_config(class) + if let Some((Some(config), _)) = super::angular_decorator_config(class, consts) && let Some(inputs) = super::decorator::config_property(config, "inputs", consts) && let Value::Array(items) = evaluator.evaluate(inputs) { diff --git a/crates/oxc_angular_compiler/src/directive/mod.rs b/crates/oxc_angular_compiler/src/directive/mod.rs index 4cd74b510..172f7222c 100644 --- a/crates/oxc_angular_compiler/src/directive/mod.rs +++ b/crates/oxc_angular_compiler/src/directive/mod.rs @@ -27,6 +27,10 @@ pub use compiler::{ create_inputs_literal, create_outputs_literal, }; pub(crate) use decorator::find_directive_decorator; +pub(crate) use decorator::{ + INPUT_API, MODEL_API, OUTPUT_API, OUTPUT_FROM_OBSERVABLE_API, QUERY_APIS, initializer_api, + initializer_api_call, +}; pub use decorator::{ StringConsts, collect_string_consts, decorator_io_errors, extract_directive_metadata, find_directive_decorator_span, @@ -34,9 +38,7 @@ pub use decorator::{ pub(crate) use decorator::{ angular_decorator_config, extract_string_value, resolve_template_literal, }; -pub(crate) use decorator::{ - merge_by_class_property, parse_decorator_io, resolve_member_transforms, -}; +pub(crate) use decorator::{merge_by_class_property, parse_decorator_io}; pub use definition::{DirectiveDefinitions, generate_directive_definitions}; pub(crate) use dts_type::quote as ts_string_literal; pub use evaluator::input_transform_types; @@ -44,14 +46,15 @@ pub use metadata::{ QueryPredicate, R3DirectiveMetadata, R3DirectiveMetadataBuilder, R3HostDirectiveMetadata, R3HostMetadata, R3InputMetadata, R3QueryMetadata, }; +pub(crate) use property_decorators::{ + angular_class_decorator, angular_core_decorator, angular_member_decorator, + angular_param_decorator, extract_host_bindings_in, extract_host_listeners_in, + parse_decorator_queries, try_parse_signal_input, try_parse_signal_model, + try_parse_signal_output, unwrap_initializer_api_expr, +}; pub use property_decorators::{ extract_class_queries, extract_content_queries, extract_host_bindings, extract_host_listeners, extract_input_metadata, extract_input_metadata_in, extract_output_metadata, - extract_output_metadata_in, extract_view_queries, -}; -pub(crate) use property_decorators::{ - extract_host_bindings_in, extract_host_listeners_in, parse_decorator_queries, - try_parse_signal_input, try_parse_signal_model, try_parse_signal_output, - unwrap_initializer_api_expr, + extract_output_metadata_in, extract_view_queries, find_angular_class_decorator, }; pub use query::{create_content_queries_function, create_view_queries_function}; diff --git a/crates/oxc_angular_compiler/src/directive/property_decorators.rs b/crates/oxc_angular_compiler/src/directive/property_decorators.rs index 1b78099d4..902ddc1ed 100644 --- a/crates/oxc_angular_compiler/src/directive/property_decorators.rs +++ b/crates/oxc_angular_compiler/src/directive/property_decorators.rs @@ -19,6 +19,7 @@ use oxc_ast::ast::{ use oxc_span::{GetSpan, Span}; use oxc_str::Ident; +use super::evaluator::{Evaluator, Value}; use super::metadata::{QueryPredicate, R3InputMetadata, R3QueryMetadata}; use crate::output::ast::OutputExpression; use crate::output::oxc_converter::convert_oxc_expression; @@ -39,32 +40,120 @@ fn is_core_namespace(consts: Option<&super::StringConsts<'_>>, name: &str) -> bo .is_some_and(|import| import.module == "@angular/core" && import.imported.is_none()) } -/// 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` in the -/// file `consts` was collected from (see [`is_core_namespace`]): `@core.Input()` -/// -/// Returns the first matching decorator. +/// Angular's member decorators, which are compiled into the definition. +const MEMBER_DECORATORS: &[&str] = &[ + "Input", + "Output", + "HostBinding", + "HostListener", + "ViewChild", + "ViewChildren", + "ContentChild", + "ContentChildren", +]; + +/// Angular's class decorators, which make a class compiled. (`@Service` is +/// matched on its own, see `find_angular_service_decorator`.) +const CLASS_DECORATORS: &[&str] = &["Component", "Directive", "Pipe", "Injectable", "NgModule"]; + +/// Which of Angular's decorators `names` `decorator` is: `@X`, `@X()`, ... +/// With the file `consts` was collected from, only one imported from +/// `@angular/core`, like ngtsc's `isAngularDecorator`: by name under any alias +/// (`@In()` for `import { Input as In }`) or through a namespace import +/// (`@core.Input()`, see [`is_core_namespace`]); another module's (even one +/// re-exporting Angular's), a local or an undeclared `X` isn't Angular's. +/// Without the file, any decorator with that name. +pub(crate) fn angular_core_decorator( + decorator: &Decorator<'_>, + consts: Option<&super::StringConsts<'_>>, + names: &[&'static str], +) -> Option<&'static str> { + let callee = match &decorator.expression { + Expression::CallExpression(call) => &call.callee, + expr => expr, + }; + let name = match callee { + Expression::Identifier(id) => match consts { + Some(consts) => { + let import = consts.scope().import(&id.name)?; + if import.module != "@angular/core" { + return None; + } + import.imported? + } + None => id.name.as_str(), + }, + Expression::StaticMemberExpression(m) => match &m.object { + Expression::Identifier(ns) if is_core_namespace(consts, &ns.name) => { + m.property.name.as_str() + } + _ => return None, + }, + _ => return None, + }; + names.iter().copied().find(|n| *n == name) +} + +/// Which of Angular's member decorators ([`MEMBER_DECORATORS`]) `decorator` +/// is (see [`angular_core_decorator`]). Without the file (the public +/// `extract_*` functions), any decorator with that name. +pub(crate) fn angular_member_decorator( + decorator: &Decorator<'_>, + consts: Option<&super::StringConsts<'_>>, +) -> Option<&'static str> { + angular_core_decorator(decorator, consts, MEMBER_DECORATORS) +} + +/// Angular's constructor parameter decorators, which are compiled into the +/// factory (`ɵfac`), `ctorParameters` and `setClassMetadata`. +const PARAM_DECORATORS: &[&str] = &["Inject", "Optional", "Self", "SkipSelf", "Host", "Attribute"]; + +/// Which of Angular's constructor parameter decorators ([`PARAM_DECORATORS`]) +/// `decorator` is (see [`angular_core_decorator`]), like ngtsc's +/// `isAngularCore` in `getConstructorDependencies` and the JIT +/// `ctorParameters` transform: `@Inj(TOKEN)` for `import { Inject as Inj }` +/// and `@core.Optional()` count, another module's or a local `@Inject` doesn't. +/// Without the file (the public `extract_*` functions), any decorator with +/// that name. +pub(crate) fn angular_param_decorator( + decorator: &Decorator<'_>, + consts: Option<&super::StringConsts<'_>>, +) -> Option<&'static str> { + angular_core_decorator(decorator, consts, PARAM_DECORATORS) +} + +/// Which of Angular's class decorators ([`CLASS_DECORATORS`]) `decorator` is, +/// in the file `consts` was collected from: one imported from `@angular/core` +/// (see [`angular_core_decorator`]), like ngtsc's `findAngularDecorator`. +pub(crate) fn angular_class_decorator( + decorator: &Decorator<'_>, + consts: &super::StringConsts<'_>, +) -> Option<&'static str> { + angular_core_decorator(decorator, Some(consts), CLASS_DECORATORS) +} + +/// Angular's class decorator `name` (`Component`, `Directive`, `Pipe`, +/// `Injectable` or `NgModule`) among `decorators`, in the file `consts` was +/// collected from. Like ngtsc's `findAngularDecorator`, only one imported from +/// `@angular/core` counts: by name under any alias (`@Cmp()` for +/// `import { Component as Cmp }`) or through a namespace import +/// (`@ng.Component()`). Returns the first match. +pub fn find_angular_class_decorator<'a>( + decorators: &'a [Decorator<'a>], + name: &str, + consts: &super::StringConsts<'_>, +) -> Option<&'a Decorator<'a>> { + decorators.iter().find(|d| angular_class_decorator(d, consts) == Some(name)) +} + +/// Find one of Angular's member decorators (`name`) in a list of decorators +/// (see [`angular_member_decorator`]). Returns the first match. fn find_decorator_by_name<'a>( decorators: &'a oxc_allocator::Vec<'a, Decorator<'a>>, name: &str, consts: Option<&super::StringConsts<'_>>, ) -> Option<&'a Decorator<'a>> { - 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(consts, &ns.name)) - } - _ => false, - }; - decorators.iter().find(|d| match &d.expression { - Expression::CallExpression(call) => is_name(&call.callee), - expr => is_name(expr), - }) + decorators.iter().find(|d| angular_member_decorator(d, consts) == Some(name)) } /// The options argument of an `@Input(...)` decorator, if any. @@ -72,12 +161,43 @@ pub(crate) fn input_decorator_options<'a>( decorators: &'a oxc_allocator::Vec<'a, Decorator<'a>>, consts: &super::StringConsts<'_>, ) -> Option<&'a Expression<'a>> { - match &find_decorator_by_name(decorators, "Input", Some(consts))?.expression { + decorator_argument(find_decorator_by_name(decorators, "Input", Some(consts))?) +} + +/// The first argument of a decorator call (`NAME` in `@Input(NAME)`), if any. +pub(crate) fn decorator_argument<'a>(decorator: &'a Decorator<'a>) -> Option<&'a Expression<'a>> { + match &decorator.expression { Expression::CallExpression(call) => call.arguments.first()?.as_expression(), _ => None, } } +/// One of Angular's member decorators (`name`: `Input`, `Output`, ...) in +/// `decorators`, as the file `consts` was collected from imports it (see +/// [`angular_member_decorator`]). +pub(crate) fn member_decorator<'a>( + decorators: &'a oxc_allocator::Vec<'a, Decorator<'a>>, + name: &str, + consts: &super::StringConsts<'_>, +) -> Option<&'a Decorator<'a>> { + find_decorator_by_name(decorators, name, Some(consts)) +} + +/// The name ngtsc gives a decorator in its messages (`Decorator.name`): the +/// identifier it's called by, so `In` for `@In()` with +/// `import { Input as In }`, and `Input` for `@core.Input()`. +pub(crate) fn decorator_written_name<'a>(decorator: &'a Decorator<'a>) -> &'a str { + let callee = match &decorator.expression { + Expression::CallExpression(call) => &call.callee, + expr => expr, + }; + match callee { + Expression::Identifier(id) => id.name.as_str(), + Expression::StaticMemberExpression(m) => m.property.name.as_str(), + _ => "", + } +} + /// Get the property key name as an Atom. /// /// Handles both identifier keys and string literal keys. @@ -110,14 +230,6 @@ fn extract_boolean_value(expr: &Expression<'_>) -> Option { } } -/// Angular's signal query functions (ngtsc's `QUERY_INITIALIZER_FNS`). -const QUERY_APIS: [super::decorator::InitializerApi; 4] = [ - ("viewChild", "@angular/core"), - ("viewChildren", "@angular/core"), - ("contentChild", "@angular/core"), - ("contentChildren", "@angular/core"), -]; - /// Try to unwrap a forwardRef call and extract the inner expression. /// /// Mirrors ngtsc's `tryUnwrapForwardRef`: `forwardRef(() => X)`, @@ -193,10 +305,53 @@ impl<'a> Default for InputConfig<'a> { /// - `@Input()` - no configuration /// - `@Input('alias')` - string alias /// - `@Input({ alias: 'name', required: true, transform: fn })` - full config +/// +/// With the file (`file`), the argument is read through the evaluator, like +/// ngtsc's `tryParseInputFieldMapping`: a string (`NAME`, `` `y` ``, +/// `'a' + 'b'`) is the alias, and an object (`OPTS`, `{...OPTS, required: true}`) +/// gives `alias` (when it's a string), `required` (when it's `true`) and +/// `transform` (the expression ngtsc emits for it, see +/// [`super::decorator::transform_expression`]). Anything else is `null` or an +/// error [`super::decorator_io_errors`] reports, and gives no options. Without +/// the file (the public `extract_*` functions), only literals are read. fn parse_input_config<'a>( allocator: &'a Allocator, decorator: &'a Decorator<'a>, source_text: Option<&'a str>, + file: Option<(&Evaluator<'_, 'a>, &super::StringConsts<'a>)>, +) -> InputConfig<'a> { + let literal = parse_literal_input_config(allocator, decorator, source_text); + let (Some((evaluator, consts)), Some(argument)) = (file, decorator_argument(decorator)) else { + return literal; + }; + let alloc = |s: &str| Ident::from(allocator.alloc_str(s)); + let options = evaluator.evaluate(argument); + match &options { + Value::String(alias) => InputConfig { alias: Some(alloc(alias)), ..Default::default() }, + Value::Object(_) => InputConfig { + alias: options.prop("alias").and_then(|p| p.value.as_str()).map(alloc), + required: matches!(options.prop("required").map(|p| &p.value), Some(Value::Bool(true))), + transform: options + .prop("transform") + .and_then(|transform| { + super::decorator::transform_expression( + allocator, + transform, + source_text, + consts, + ) + }) + .or(literal.transform), + }, + _ => InputConfig::default(), + } +} + +/// [`parse_input_config`] for the literals written in the decorator. +fn parse_literal_input_config<'a>( + allocator: &'a Allocator, + decorator: &'a Decorator<'a>, + source_text: Option<&'a str>, ) -> InputConfig<'a> { let Expression::CallExpression(call) = &decorator.expression else { return InputConfig::default(); @@ -268,78 +423,22 @@ pub(crate) struct ModelMapping<'a> { /// readonly aliased = model(undefined, { alias: 'myAlias' }); // input alias 'myAlias', output 'myAliasChange' /// ``` /// +/// Like ngtsc, only Angular's `model` counts, when `consts` holds the file's +/// imports (see [`super::decorator::initializer_api`]). +/// /// Based on Angular's `model_function.ts` in the compiler-cli. pub(crate) fn try_parse_signal_model<'a>( allocator: &'a Allocator, value: &Expression<'a>, property_name: Ident<'a>, + consts: Option<&super::StringConsts<'_>>, ) -> Option> { - // Check if the value is a call expression (unwrapping `as`/parenthesized). - let call_expr = match unwrap_initializer_api_expr(value) { - Expression::CallExpression(call) => call, - _ => return None, - }; + let (_, is_required, call_expr) = + super::decorator::initializer_api_call(value, consts, &[super::decorator::MODEL_API])?; - // Determine if this is model() or model.required() - let is_required = match &call_expr.callee { - // model() - simple identifier call - Expression::Identifier(id) if id.name == "model" => false, - // model.required() - member expression call - Expression::StaticMemberExpression(member) => { - // Check for model.required / core.model.required - if member.property.name == "required" { - if is_initializer_fn_reference(&member.object, "model") { - true - } else { - return None; - } - } else if member.property.name == "model" { - // Handle namespaced calls like `core.model()` - if let Expression::Identifier(_) = &member.object { - let output_binding = Ident::from( - allocator.alloc_str(&format!("{}Change", property_name.as_str())), - ); - return Some(ModelMapping { - input: R3InputMetadata { - class_property_name: property_name.clone(), - binding_property_name: property_name.clone(), - required: false, - is_signal: true, - transform_function: None, - }, - output: (property_name, output_binding), - }); - } - return None; - } else { - return None; - } - } - _ => return None, - }; - - // Parse options from arguments - // For model(): first arg is initial value, second arg is options - // For model.required(): first arg is options + // model(initialValue, options?) / model.required(options?) let options_arg_index = if is_required { 0 } else { 1 }; - - let mut alias: Option> = None; - - if let Some(options_arg) = call_expr.arguments.get(options_arg_index) { - if let Argument::ObjectExpression(obj) = options_arg { - for prop in &obj.properties { - if let ObjectPropertyKind::ObjectProperty(prop) = prop { - let Some(key_name) = get_property_key_name(&prop.key) else { - continue; - }; - - if key_name.as_str() == "alias" { - alias = extract_string_value(&prop.value); - } - } - } - } - } + let alias = options_alias(call_expr.arguments.get(options_arg_index)); let binding_property_name = alias.unwrap_or_else(|| property_name.clone()); // Output binding name is always `bindingPropertyName + "Change"` @@ -358,6 +457,20 @@ pub(crate) fn try_parse_signal_model<'a>( }) } +/// The string `alias` of an initializer API's options object literal. +fn options_alias<'a>(options: Option<&Argument<'a>>) -> Option> { + let Some(Argument::ObjectExpression(obj)) = options else { return None }; + let mut alias = None; + for prop in &obj.properties { + if let ObjectPropertyKind::ObjectProperty(prop) = prop + && get_property_key_name(&prop.key).is_some_and(|key| key == "alias") + { + alias = extract_string_value(&prop.value); + } + } + alias +} + /// Try to detect and parse a signal-based output from a property initializer. /// /// Signal-based outputs are created by calling `output()` or `output()` from @@ -368,58 +481,46 @@ pub(crate) fn try_parse_signal_model<'a>( /// For `outputFromObservable(observable, options?)` the options are the second argument; /// the observable expression is irrelevant for metadata extraction. /// +/// Like ngtsc, only Angular's functions count, when `consts` holds the file's +/// imports (see [`super::decorator::initializer_api`]). +/// /// Based on Angular's `output_function.ts` in the compiler-cli. pub(crate) fn try_parse_signal_output<'a>( value: &Expression<'a>, property_name: Ident<'a>, + consts: Option<&super::StringConsts<'_>>, ) -> Option<(Ident<'a>, Ident<'a>)> { - let call_expr = match unwrap_initializer_api_expr(value) { - Expression::CallExpression(call) => call, - _ => return None, - }; - - // Detect which output initializer is called and whether options are at index 0 or 1. - let is_from_observable = match &call_expr.callee { - Expression::Identifier(id) => match id.name.as_str() { - "output" => false, - "outputFromObservable" => true, - _ => return None, - }, - // Handle namespaced calls like `core.output()` or `rxjs.outputFromObservable()` - Expression::StaticMemberExpression(member) => { - if !matches!(&member.object, Expression::Identifier(_)) { - return None; - } - match member.property.name.as_str() { - "output" => false, - "outputFromObservable" => true, - _ => return None, - } - } - _ => return None, - }; + use super::decorator::{OUTPUT_API, OUTPUT_FROM_OBSERVABLE_API}; + let (api, is_required, call_expr) = super::decorator::initializer_api_call( + value, + consts, + &[OUTPUT_API, OUTPUT_FROM_OBSERVABLE_API], + )?; + // ngtsc rejects `output.required()`; it isn't an output. + if is_required { + return None; + } // output() → options at index 0; outputFromObservable(obs, options?) → options at index 1 - let options_idx = if is_from_observable { 1 } else { 0 }; - let mut alias: Option> = None; - - if let Some(Argument::ObjectExpression(obj)) = call_expr.arguments.get(options_idx) { - for prop in &obj.properties { - if let ObjectPropertyKind::ObjectProperty(prop) = prop { - let Some(key_name) = get_property_key_name(&prop.key) else { - continue; - }; - if key_name.as_str() == "alias" { - alias = extract_string_value(&prop.value); - } - } - } - } + let options_idx = if api == OUTPUT_FROM_OBSERVABLE_API { 1 } else { 0 }; + let alias = options_alias(call_expr.arguments.get(options_idx)); let binding_property_name = alias.unwrap_or_else(|| property_name.clone()); Some((property_name, binding_property_name)) } +/// Unwrap `as`/`satisfies`/parenthesized wrappers around an initializer-API call +/// expression, matching ngc's `tryParseInitializerApi` which recurses through +/// `isAsExpression` and `isParenthesizedExpression` (e.g. `x = input(0) as any`, +/// `x = (input(0))`). +pub(crate) fn unwrap_initializer_api_expr<'a, 'b>(expr: &'b Expression<'a>) -> &'b Expression<'a> { + match expr { + Expression::TSAsExpression(e) => unwrap_initializer_api_expr(&e.expression), + Expression::ParenthesizedExpression(e) => unwrap_initializer_api_expr(&e.expression), + _ => expr, + } +} + /// Try to detect and parse a signal-based input from a property initializer. /// /// Signal-based inputs are created by calling functions like `input()` or `input.required()` @@ -433,101 +534,21 @@ pub(crate) fn try_parse_signal_output<'a>( /// readonly aliasedInput = input({ alias: 'myAlias' }); /// ``` /// +/// Like ngtsc, only Angular's `input` counts, when `consts` holds the file's +/// imports (see [`super::decorator::initializer_api`]). +/// /// Based on Angular's `input_function.ts` in the compiler-cli. -/// Unwrap `as`/`satisfies`/parenthesized wrappers around an initializer-API call -/// expression, matching ngc's `tryParseInitializerApi` which recurses through -/// `isAsExpression` and `isParenthesizedExpression` (e.g. `x = input(0) as any`, -/// `x = (input(0))`). -pub(crate) fn unwrap_initializer_api_expr<'a, 'b>(expr: &'b Expression<'a>) -> &'b Expression<'a> { - match expr { - Expression::TSAsExpression(e) => unwrap_initializer_api_expr(&e.expression), - Expression::ParenthesizedExpression(e) => unwrap_initializer_api_expr(&e.expression), - _ => expr, - } -} - -/// Returns `true` when `object` is the `input`/`model`/etc. reference for a -/// namespaced call, i.e. either a bare `` identifier or `.` member -/// access. Used to recognize `.required()` and `core..required()`. -fn is_initializer_fn_reference(object: &Expression<'_>, function_name: &str) -> bool { - match object { - Expression::Identifier(id) => id.name == function_name, - Expression::StaticMemberExpression(member) => { - member.property.name == function_name - && matches!(&member.object, Expression::Identifier(_)) - } - _ => false, - } -} - pub(crate) fn try_parse_signal_input<'a>( - _allocator: &'a Allocator, value: &Expression<'a>, property_name: Ident<'a>, + consts: Option<&super::StringConsts<'_>>, ) -> Option> { - // Check if the value is a call expression (unwrapping `as`/parenthesized). - let call_expr = match unwrap_initializer_api_expr(value) { - Expression::CallExpression(call) => call, - _ => return None, - }; - - // Determine if this is input() or input.required() - let is_required = match &call_expr.callee { - // input() - simple identifier call - Expression::Identifier(id) if id.name == "input" => false, - // input.required() - member expression call - Expression::StaticMemberExpression(member) => { - // Check for input.required / core.input.required - if member.property.name == "required" { - if is_initializer_fn_reference(&member.object, "input") { - true - } else { - return None; - } - } else if member.property.name == "input" { - // Handle namespaced calls like `core.input()` - if let Expression::Identifier(_) = &member.object { - return Some(R3InputMetadata { - class_property_name: property_name.clone(), - binding_property_name: property_name, - required: false, - is_signal: true, - transform_function: None, - }); - } - return None; - } else { - return None; - } - } - _ => return None, - }; + let (_, is_required, call_expr) = + super::decorator::initializer_api_call(value, consts, &[super::decorator::INPUT_API])?; - // Parse options from arguments - // For input(): first arg is initial value, second arg is options - // For input.required(): first arg is options + // input(initialValue, options?) / input.required(options?) let options_arg_index = if is_required { 0 } else { 1 }; - - let mut alias: Option> = None; - - if let Some(options_arg) = call_expr.arguments.get(options_arg_index) { - if let Argument::ObjectExpression(obj) = options_arg { - for prop in &obj.properties { - if let ObjectPropertyKind::ObjectProperty(prop) = prop { - let Some(key_name) = get_property_key_name(&prop.key) else { - continue; - }; - - if key_name.as_str() == "alias" { - alias = extract_string_value(&prop.value); - } - // Note: Signal inputs don't support transform in the same way as decorator inputs. - // The transform is captured in the signal initializer itself. - } - } - } - } - + let alias = options_alias(call_expr.arguments.get(options_arg_index)); let binding_property_name = alias.unwrap_or_else(|| property_name.clone()); Some(R3InputMetadata { @@ -535,7 +556,8 @@ pub(crate) fn try_parse_signal_input<'a>( binding_property_name, required: is_required, is_signal: true, - transform_function: None, // Signal inputs don't capture transform metadata + // Signal inputs capture their transform in the signal initializer itself. + transform_function: None, }) } @@ -562,7 +584,12 @@ pub fn extract_input_metadata<'a>( /// [`extract_input_metadata`] for a class in the file `consts` was collected /// from, which also recognises `@core.Input()` through a namespace import of -/// `@angular/core` (see [`is_core_namespace`]). +/// `@angular/core` (see [`is_core_namespace`]). With `consts`, a signal member +/// counts only when it calls Angular's `input()` / `model()`, as ngtsc's +/// `tryParseInitializerApi` sees it (see [`super::decorator::initializer_api`]); +/// without, any function with that name does. With `consts`, an `@Input(...)` +/// argument is also evaluated like ngtsc does (`@Input(NAME)`, `@Input(OPTS)`, +/// see [`parse_input_config`]); without, only literals are read. pub fn extract_input_metadata_in<'a>( allocator: &'a Allocator, class: &'a Class<'a>, @@ -570,6 +597,8 @@ pub fn extract_input_metadata_in<'a>( consts: Option<&super::StringConsts<'a>>, ) -> Vec<'a, R3InputMetadata<'a>> { let mut inputs = Vec::new_in(&allocator); + let evaluator = consts.map(Evaluator::new); + let file = evaluator.as_ref().zip(consts); for element in &class.body.body { match element { @@ -580,7 +609,7 @@ pub fn extract_input_metadata_in<'a>( continue; }; - let config = parse_input_config(allocator, decorator, source_text); + let config = parse_input_config(allocator, decorator, source_text, file); let binding_property_name = config.alias.unwrap_or_else(|| class_property_name.clone()); @@ -598,13 +627,13 @@ pub fn extract_input_metadata_in<'a>( if let Some(property_name) = get_property_key_name(&prop.key) { // Check for model() first since it also creates an input if let Some(model_mapping) = - try_parse_signal_model(allocator, value, property_name) + try_parse_signal_model(allocator, value, property_name.clone(), consts) { inputs.push(model_mapping.input); } // Then check for input() else if let Some(signal_input) = - try_parse_signal_input(allocator, value, property_name) + try_parse_signal_input(value, property_name, consts) { inputs.push(signal_input); } @@ -622,7 +651,7 @@ pub fn extract_input_metadata_in<'a>( continue; }; - let config = parse_input_config(allocator, decorator, source_text); + let config = parse_input_config(allocator, decorator, source_text, file); let binding_property_name = config.alias.unwrap_or_else(|| class_property_name.clone()); @@ -647,7 +676,7 @@ pub fn extract_input_metadata_in<'a>( continue; }; - let config = parse_input_config(allocator, decorator, source_text); + let config = parse_input_config(allocator, decorator, source_text, file); let binding_property_name = config.alias.unwrap_or_else(|| class_property_name.clone()); @@ -689,20 +718,30 @@ impl<'a> Default for OutputConfig<'a> { /// Handles these variants: /// - `@Output()` - no configuration /// - `@Output('alias')` - string alias -fn parse_output_config<'a>(decorator: &'a Decorator<'a>) -> OutputConfig<'a> { - let Expression::CallExpression(call) = &decorator.expression else { +/// +/// With the file's `evaluator`, the argument is read through it, like ngtsc's +/// `tryParseDecoratorOutput`: a string (`NAME`, `` `y` ``, `'a' + 'b'`) is the +/// alias; anything else is an error [`super::decorator_io_errors`] reports. +/// Without it (the public `extract_*` functions), only a string literal is read. +fn parse_output_config<'a>( + allocator: &'a Allocator, + decorator: &'a Decorator<'a>, + evaluator: Option<&Evaluator<'_, 'a>>, +) -> OutputConfig<'a> { + let Some(argument) = decorator_argument(decorator) else { return OutputConfig::default(); }; - - let Some(first_arg) = call.arguments.first() else { - return OutputConfig::default(); + let alias = match evaluator { + Some(evaluator) => match evaluator.evaluate(argument) { + Value::String(alias) => Some(Ident::from(allocator.alloc_str(&alias))), + _ => None, + }, + None => match argument { + Expression::StringLiteral(lit) => Some(lit.value.clone().into()), + _ => None, + }, }; - - match first_arg { - // @Output('alias') - Argument::StringLiteral(lit) => OutputConfig { alias: Some(lit.value.clone().into()) }, - _ => OutputConfig::default(), - } + OutputConfig { alias } } /// Extract @Output metadata from all properties in a class. @@ -727,13 +766,19 @@ pub fn extract_output_metadata<'a>( /// [`extract_output_metadata`] for a class in the file `consts` was collected /// from, which also recognises `@core.Output()` through a namespace import of -/// `@angular/core` (see [`is_core_namespace`]). +/// `@angular/core` (see [`is_core_namespace`]). With `consts`, a signal member +/// counts only when it calls Angular's `output()` / `outputFromObservable()` / +/// `model()` (see [`super::decorator::initializer_api`]); without, any +/// function with that name does. With `consts`, an `@Output(...)` argument is +/// also evaluated like ngtsc does (`@Output(NAME)`, see +/// [`parse_output_config`]); without, only a string literal is read. pub fn extract_output_metadata_in<'a>( allocator: &'a Allocator, class: &'a Class<'a>, consts: Option<&super::StringConsts<'a>>, ) -> Vec<'a, (Ident<'a>, Ident<'a>)> { let mut outputs = Vec::new_in(&allocator); + let evaluator = consts.map(Evaluator::new); for element in &class.body.body { match element { @@ -745,7 +790,7 @@ pub fn extract_output_metadata_in<'a>( continue; }; - let config = parse_output_config(decorator); + let config = parse_output_config(allocator, decorator, evaluator.as_ref()); let binding_property_name = config.alias.unwrap_or_else(|| class_property_name.clone()); @@ -757,13 +802,13 @@ pub fn extract_output_metadata_in<'a>( if let Some(property_name) = get_property_key_name(&prop.key) { // Check for output() signal first if let Some(output_mapping) = - try_parse_signal_output(value, property_name.clone()) + try_parse_signal_output(value, property_name.clone(), consts) { outputs.push(output_mapping); } // Then check for model() signal which also creates an output else if let Some(model_mapping) = - try_parse_signal_model(allocator, value, property_name) + try_parse_signal_model(allocator, value, property_name, consts) { outputs.push(model_mapping.output); } @@ -781,7 +826,7 @@ pub fn extract_output_metadata_in<'a>( continue; }; - let config = parse_output_config(decorator); + let config = parse_output_config(allocator, decorator, evaluator.as_ref()); let binding_property_name = config.alias.unwrap_or_else(|| class_property_name.clone()); @@ -801,10 +846,6 @@ pub fn extract_output_metadata_in<'a>( // and Signal-based Query Detection (viewChild(), viewChildren(), contentChild(), contentChildren()) // ============================================================================ -/// Signal query function names. -/// These are imported from @angular/core and used to create signal-based queries. -const SIGNAL_QUERY_FNS: &[&str] = &["viewChild", "viewChildren", "contentChild", "contentChildren"]; - /// Parsed query decorator configuration. struct QueryConfig<'a> { /// The query predicate (type or string selectors). @@ -985,6 +1026,9 @@ impl SignalQueryType { /// readonly requiredPanel = contentChild.required('panel'); /// readonly tabs = contentChildren(TabComponent, { descendants: true }); /// ``` +/// +/// Like ngtsc, only Angular's functions count, when `consts` holds the file's +/// imports (see [`super::decorator::initializer_api`]). fn try_parse_signal_query<'a>( allocator: &'a Allocator, value: &'a Expression<'a>, @@ -992,64 +1036,13 @@ fn try_parse_signal_query<'a>( source_text: Option<&'a str>, consts: Option<&super::StringConsts<'a>>, ) -> Option<(SignalQueryType, R3QueryMetadata<'a>)> { - // Check if the value is a call expression (unwrapping `as`/parenthesized). - let call_expr = match unwrap_initializer_api_expr(value) { - Expression::CallExpression(call) => call, - _ => return None, - }; - - // Helper to get query type from function name - let get_query_type = |name: &str| -> Option { - match name { - "viewChild" => Some(SignalQueryType::ViewChild), - "viewChildren" => Some(SignalQueryType::ViewChildren), - "contentChild" => Some(SignalQueryType::ContentChild), - "contentChildren" => Some(SignalQueryType::ContentChildren), - _ => None, - } - }; - - // Check if the callee is one of the signal query functions - // Handles three patterns: - // 1. Direct call: viewChild(), viewChildren(), contentChild(), contentChildren() - // 2. Required call: viewChild.required(), contentChild.required() - // 3. Namespaced call: core.viewChild(), core.viewChild.required() - let query_type = match &call_expr.callee { - // Pattern 1: Direct call - viewChild(), viewChildren(), etc. - Expression::Identifier(id) => get_query_type(id.name.as_str())?, - Expression::StaticMemberExpression(member) => { - // Pattern 2: Required call - viewChild.required(), contentChild.required() - if member.property.name == "required" { - match &member.object { - // viewChild.required() - Expression::Identifier(id) => get_query_type(id.name.as_str())?, - // Pattern 3b: Namespaced required call - core.viewChild.required() - // member.object is `core.viewChild` (a StaticMemberExpression) - Expression::StaticMemberExpression(inner_member) => { - // inner_member.object should be an identifier (namespace) - // inner_member.property should be the query function name - if let Expression::Identifier(_) = &inner_member.object { - get_query_type(inner_member.property.name.as_str())? - } else { - return None; - } - } - _ => return None, - } - } - // Pattern 3a: Namespaced call - core.viewChild() - else if SIGNAL_QUERY_FNS.contains(&member.property.name.as_str()) { - // Must be namespace.queryFn() pattern - if let Expression::Identifier(_) = &member.object { - get_query_type(member.property.name.as_str())? - } else { - return None; - } - } else { - return None; - } - } - _ => return None, + let ((function, _), _, call_expr) = + super::decorator::initializer_api_call(value, consts, &super::decorator::QUERY_APIS)?; + let query_type = match function { + "viewChild" => SignalQueryType::ViewChild, + "viewChildren" => SignalQueryType::ViewChildren, + "contentChild" => SignalQueryType::ContentChild, + _ => SignalQueryType::ContentChildren, }; // Parse the predicate from the first argument @@ -1138,7 +1131,10 @@ pub fn extract_view_queries<'a>( } /// [`extract_view_queries`], resolving predicates that reference same-file -/// consts (`@ViewChild(SELECTOR)`) the way ngtsc's partial evaluator does. +/// consts (`@ViewChild(SELECTOR)`) the way ngtsc's partial evaluator does, and +/// recognising only Angular's `viewChild()` / `viewChildren()` (see +/// [`super::decorator::initializer_api`]); without `consts`, any function with +/// that name. pub(crate) fn extract_view_queries_in<'a>( allocator: &'a Allocator, class: &'a Class<'a>, @@ -1320,7 +1316,10 @@ pub fn extract_content_queries<'a>( } /// [`extract_content_queries`], resolving predicates that reference same-file -/// consts (`@ViewChild(SELECTOR)`) the way ngtsc's partial evaluator does. +/// consts (`@ViewChild(SELECTOR)`) the way ngtsc's partial evaluator does, and +/// recognising only Angular's `contentChild()` / `contentChildren()` (see +/// [`super::decorator::initializer_api`]); without `consts`, any function with +/// that name. pub(crate) fn extract_content_queries_in<'a>( allocator: &'a Allocator, class: &'a Class<'a>, @@ -1684,7 +1683,7 @@ pub fn extract_class_queries<'a>( ) -> (Vec<'a, R3QueryMetadata<'a>>, Vec<'a, R3QueryMetadata<'a>>) { let mut view = extract_view_queries_in(allocator, class, source_text, Some(consts)); let mut content = extract_content_queries_in(allocator, class, source_text, Some(consts)); - if let Some((Some(config), name)) = super::angular_decorator_config(class) { + if let Some((Some(config), name)) = super::angular_decorator_config(class, consts) { let queries = parse_decorator_queries(allocator, config, class, source_text, consts, name); view.extend(queries.view); content.extend(queries.content); @@ -1839,8 +1838,12 @@ pub(crate) fn parse_decorator_queries<'a>( .filter_map(|element| { let ClassElement::PropertyDefinition(prop) = element else { return None }; let name = get_property_key_name(&prop.key)?; - super::decorator::is_initializer_api_call(prop.value.as_ref()?, consts, &QUERY_APIS) - .then_some(name) + super::decorator::is_initializer_api_call( + prop.value.as_ref()?, + consts, + &super::decorator::QUERY_APIS, + ) + .then_some(name) }) .collect(); // ngtsc checks the content queries first, and reports the `new` expression. diff --git a/crates/oxc_angular_compiler/src/injectable/decorator.rs b/crates/oxc_angular_compiler/src/injectable/decorator.rs index 0ab8f4528..c20124ac7 100644 --- a/crates/oxc_angular_compiler/src/injectable/decorator.rs +++ b/crates/oxc_angular_compiler/src/injectable/decorator.rs @@ -11,6 +11,7 @@ use oxc_ast::ast::{ use oxc_span::Span; use oxc_str::Ident; +use crate::directive::StringConsts; use crate::factory::R3DependencyMetadata; use crate::output::ast::{OutputExpression, ReadVarExpr}; use crate::output::oxc_converter::convert_oxc_expression; @@ -206,14 +207,23 @@ fn convert_deps_to_r3<'a>( result } -/// Find the `@Injectable` decorator node on a class. +/// Find the `@Injectable` decorator node on a class. With the file's +/// `consts`, only Angular's (imported from `@angular/core`, see +/// [`crate::directive::find_angular_class_decorator`]); without them, any +/// `@Injectable` identifier. pub(crate) fn find_injectable_decorator<'a>( decorators: &'a [oxc_ast::ast::Decorator<'a>], + consts: Option<&StringConsts<'_>>, ) -> Option<&'a oxc_ast::ast::Decorator<'a>> { + if let Some(consts) = consts { + return crate::directive::find_angular_class_decorator(decorators, "Injectable", consts); + } decorators.iter().find(|d| is_injectable_decorator(d)) } -/// Find the span of the `@Injectable` decorator on a class. +/// Find the span of the `@Injectable` decorator on a class. Without the file's +/// imports, this matches any `@Injectable` identifier; the compiler only takes +/// one imported from `@angular/core`. pub fn find_injectable_decorator_span(class: &Class<'_>) -> Option { for decorator in &class.decorators { if is_injectable_decorator(decorator) { @@ -224,16 +234,32 @@ pub fn find_injectable_decorator_span(class: &Class<'_>) -> Option { } /// Extract injectable metadata from a class decorated with `@Injectable`. +/// +/// Without the file's imports, any `@Injectable` identifier counts, and so +/// does any constructor parameter decorator named `Inject`, `Optional`, ...; +/// [`extract_injectable_metadata_in`] only counts Angular's, like the compiler. pub fn extract_injectable_metadata<'a>( allocator: &'a Allocator, class: &'a Class<'a>, source_text: Option<&'a str>, +) -> Option> { + extract_injectable_metadata_in(allocator, class, source_text, None) +} + +/// [`extract_injectable_metadata`] for a class in the file `consts` was +/// collected from: only Angular's `@Injectable` (imported from +/// `@angular/core`) counts. +pub(crate) fn extract_injectable_metadata_in<'a>( + allocator: &'a Allocator, + class: &'a Class<'a>, + source_text: Option<&'a str>, + consts: Option<&StringConsts<'_>>, ) -> Option> { let class_name: Ident<'a> = class.id.as_ref()?.name.clone().into(); let class_span = class.span; // Find the @Injectable decorator - let decorator = class.decorators.iter().find(|d| is_injectable_decorator(d))?; + let decorator = find_injectable_decorator(&class.decorators, consts)?; // Get the decorator call expression let call_expr = match &decorator.expression { @@ -251,7 +277,7 @@ pub fn extract_injectable_metadata<'a>( use_factory: None, use_value: None, use_existing: None, - deps: extract_constructor_deps(allocator, class, source_text), + deps: extract_constructor_deps(allocator, class, source_text, consts), }); } @@ -277,7 +303,7 @@ pub fn extract_injectable_metadata<'a>( let use_existing = extract_use_existing(allocator, config_obj, source_text); // Extract constructor dependencies - let deps = extract_constructor_deps(allocator, class, source_text); + let deps = extract_constructor_deps(allocator, class, source_text, consts); Some(InjectableMetadata { class_name, @@ -526,6 +552,7 @@ pub fn extract_constructor_deps<'a>( allocator: &'a Allocator, class: &'a Class<'a>, source_text: Option<&'a str>, + consts: Option<&StringConsts<'_>>, ) -> Option>> { // Find the constructor method let constructor = class.body.body.iter().find_map(|element| { @@ -542,18 +569,21 @@ pub fn extract_constructor_deps<'a>( let mut deps = Vec::with_capacity_in(params.items.len(), &allocator); for param in ¶ms.items { - let dep = extract_param_dependency(allocator, param, source_text); + let dep = extract_param_dependency(allocator, param, source_text, consts); deps.push(dep); } Some(deps) } -/// Extract dependency metadata from a single constructor parameter. +/// Extract dependency metadata from a single constructor parameter. With the +/// file's `consts`, only Angular's parameter decorators count, imported from +/// `@angular/core` (see [`crate::directive::angular_param_decorator`]). fn extract_param_dependency<'a>( allocator: &'a Allocator, param: &oxc_ast::ast::FormalParameter<'a>, source_text: Option<&'a str>, + consts: Option<&StringConsts<'_>>, ) -> R3DependencyMetadata<'a> { // Extract flags and @Inject token from decorators let mut optional = false; @@ -564,8 +594,8 @@ fn extract_param_dependency<'a>( let mut attribute_name: Option> = None; for decorator in ¶m.decorators { - if let Some(name) = get_decorator_name(&decorator.expression) { - match name.as_str() { + if let Some(name) = crate::directive::angular_param_decorator(decorator, consts) { + match name { "Inject" => { // @Inject(TOKEN) - extract the token if let Expression::CallExpression(call) = &decorator.expression { @@ -627,23 +657,6 @@ fn extract_param_dependency<'a>( } } -/// Get the name of a decorator from its expression. -fn get_decorator_name<'a>(expr: &'a Expression<'a>) -> Option> { - match expr { - // @Optional - Expression::Identifier(id) => Some(id.name.clone().into()), - // @Optional() - Expression::CallExpression(call) => { - if let Expression::Identifier(id) = &call.callee { - Some(id.name.clone().into()) - } else { - None - } - } - _ => None, - } -} - /// Extract the injection token from a parameter's type annotation. fn extract_param_token<'a>( allocator: &'a Allocator, diff --git a/crates/oxc_angular_compiler/src/injectable/mod.rs b/crates/oxc_angular_compiler/src/injectable/mod.rs index 7223ed2a7..cc37195db 100644 --- a/crates/oxc_angular_compiler/src/injectable/mod.rs +++ b/crates/oxc_angular_compiler/src/injectable/mod.rs @@ -17,11 +17,11 @@ mod definition; mod metadata; pub use compiler::{InjectableCompileResult, compile_injectable, compile_injectable_from_metadata}; -pub(crate) use decorator::find_injectable_decorator; pub use decorator::{ DependencyMetadata, InjectableMetadata, ProvidedInValue, UseClassMetadata, UseExistingMetadata, UseFactoryMetadata, extract_injectable_metadata, find_injectable_decorator_span, }; +pub(crate) use decorator::{extract_injectable_metadata_in, find_injectable_decorator}; pub use definition::{ InjectableDefinition, generate_injectable_definition, generate_injectable_definition_from_decorator, diff --git a/crates/oxc_angular_compiler/src/lib.rs b/crates/oxc_angular_compiler/src/lib.rs index 4e9375622..e8b13c627 100644 --- a/crates/oxc_angular_compiler/src/lib.rs +++ b/crates/oxc_angular_compiler/src/lib.rs @@ -87,7 +87,8 @@ pub use styles::{encapsulate_style, shim_css_text}; // Re-export pipe types pub use pipe::{ PipeCompileResult, PipeMetadata, R3DependencyMetadata, R3PipeMetadata, R3PipeMetadataBuilder, - compile_pipe, compile_pipe_from_metadata, extract_pipe_metadata, find_pipe_decorator_span, + compile_pipe, compile_pipe_from_metadata, extract_pipe_metadata, extract_pipe_metadata_in, + find_pipe_decorator_span, }; // Re-export factory types @@ -114,8 +115,8 @@ pub use directive::{ compile_directive_from_metadata, extract_class_queries, extract_content_queries, extract_directive_metadata, extract_host_bindings, extract_host_listeners, extract_input_metadata, extract_input_metadata_in, extract_output_metadata, - extract_output_metadata_in, extract_view_queries, find_directive_decorator_span, - generate_directive_definitions, + extract_output_metadata_in, extract_view_queries, find_angular_class_decorator, + find_directive_decorator_span, generate_directive_definitions, }; // Re-export injectable types @@ -154,9 +155,9 @@ pub use class_debug_info::{R3ClassDebugInfo, compile_class_debug_info}; // Re-export class metadata types pub use class_metadata::{ R3ClassMetadata, R3DeferPerComponentDependency, build_ctor_params_metadata, - build_decorator_metadata_array, build_prop_decorators_metadata, compile_class_metadata, - compile_component_class_metadata, compile_component_metadata_async_resolver, - compile_opaque_async_class_metadata, + build_ctor_params_metadata_in, build_decorator_metadata_array, build_prop_decorators_metadata, + build_prop_decorators_metadata_in, compile_class_metadata, compile_component_class_metadata, + compile_component_metadata_async_resolver, compile_opaque_async_class_metadata, }; // Re-export dts types diff --git a/crates/oxc_angular_compiler/src/ng_module/decorator.rs b/crates/oxc_angular_compiler/src/ng_module/decorator.rs index 0586413ff..2db813dfb 100644 --- a/crates/oxc_angular_compiler/src/ng_module/decorator.rs +++ b/crates/oxc_angular_compiler/src/ng_module/decorator.rs @@ -11,6 +11,7 @@ use oxc_ast::ast::{ use oxc_span::Span; use oxc_str::Ident; +use crate::directive::StringConsts; use crate::factory::R3DependencyMetadata; use crate::output::ast::{OutputExpression, ReadVarExpr}; use crate::output::oxc_converter::convert_oxc_expression; @@ -178,17 +179,33 @@ impl<'a> NgModuleMetadata<'a> { /// }) /// export class AppModule {} /// ``` +/// +/// Without the file's imports, any decorator named `NgModule` counts, and so +/// does any constructor parameter decorator named `Optional`, `Self`, ...; +/// [`extract_ng_module_metadata_in`] only counts Angular's, like the compiler. pub fn extract_ng_module_metadata<'a>( allocator: &'a Allocator, class: &'a Class<'a>, source_text: Option<&'a str>, +) -> Option> { + extract_ng_module_metadata_in(allocator, class, source_text, None) +} + +/// [`extract_ng_module_metadata`] for a class in the file `consts` was +/// collected from: only Angular's `@NgModule` (imported from `@angular/core`, +/// see [`crate::directive::find_angular_class_decorator`]) counts. +pub(crate) fn extract_ng_module_metadata_in<'a>( + allocator: &'a Allocator, + class: &'a Class<'a>, + source_text: Option<&'a str>, + consts: Option<&StringConsts<'_>>, ) -> Option> { // Get the class name let class_name: Ident<'a> = class.id.as_ref()?.name.clone().into(); let class_span = class.span; // Find the @NgModule decorator - let ng_module_decorator = find_ng_module_decorator(&class.decorators)?; + let ng_module_decorator = find_ng_module_decorator(&class.decorators, consts)?; // Get the decorator call arguments let call_expr = match &ng_module_decorator.expression { @@ -196,11 +213,6 @@ pub fn extract_ng_module_metadata<'a>( _ => return None, }; - // Verify it's calling 'NgModule' - if !is_ng_module_call(&call_expr.callee) { - return None; - } - // Get the first argument (the config object) let config_arg = call_expr.arguments.first()?; let config_obj = match config_arg { @@ -274,15 +286,21 @@ pub fn extract_ng_module_metadata<'a>( } // Extract constructor dependencies - metadata.deps = extract_constructor_deps(allocator, class); + metadata.deps = extract_constructor_deps(allocator, class, consts); Some(metadata) } -/// Find the @NgModule decorator in a list of decorators. +/// Find the @NgModule decorator in a list of decorators. With the file's +/// `consts`, only Angular's (imported from `@angular/core`); without them, any +/// named `NgModule`. pub(crate) fn find_ng_module_decorator<'a>( decorators: &'a [Decorator<'a>], + consts: Option<&StringConsts<'_>>, ) -> Option<&'a Decorator<'a>> { + if let Some(consts) = consts { + return crate::directive::find_angular_class_decorator(decorators, "NgModule", consts); + } decorators.iter().find(|d| match &d.expression { Expression::CallExpression(call) => is_ng_module_call(&call.callee), Expression::Identifier(id) => id.name == "NgModule", @@ -293,9 +311,11 @@ pub(crate) fn find_ng_module_decorator<'a>( /// Find the span of the @NgModule decorator on a class. /// /// Returns the span including any leading whitespace/newlines that should be removed -/// along with the decorator. +/// along with the decorator. Without the file's imports, this matches any +/// decorator named `NgModule`; the compiler only takes one imported from +/// `@angular/core`. pub fn find_ng_module_decorator_span(class: &Class<'_>) -> Option { - find_ng_module_decorator(&class.decorators).map(|d| d.span) + find_ng_module_decorator(&class.decorators, None).map(|d| d.span) } /// Check if a callee expression is a call to 'NgModule'. @@ -417,6 +437,7 @@ fn extract_identifier_array<'a>( pub fn extract_constructor_deps<'a>( allocator: &'a Allocator, class: &'a Class<'a>, + consts: Option<&StringConsts<'_>>, ) -> Option>> { // Find the constructor method let constructor = class.body.body.iter().find_map(|element| { @@ -433,17 +454,20 @@ pub fn extract_constructor_deps<'a>( let mut deps = Vec::with_capacity_in(params.items.len(), &allocator); for param in ¶ms.items { - let dep = extract_param_dependency(allocator, param); + let dep = extract_param_dependency(allocator, param, consts); deps.push(dep); } Some(deps) } -/// Extract dependency metadata from a single constructor parameter. +/// Extract dependency metadata from a single constructor parameter. With the +/// file's `consts`, only Angular's parameter decorators count, imported from +/// `@angular/core` (see [`crate::directive::angular_param_decorator`]). fn extract_param_dependency<'a>( allocator: &'a Allocator, param: &oxc_ast::ast::FormalParameter<'a>, + consts: Option<&StringConsts<'_>>, ) -> R3DependencyMetadata<'a> { // Extract flags from decorators let mut optional = false; @@ -453,8 +477,8 @@ fn extract_param_dependency<'a>( let mut attribute_name: Option> = None; for decorator in ¶m.decorators { - if let Some(name) = get_decorator_name(&decorator.expression) { - match name.as_str() { + if let Some(name) = crate::directive::angular_param_decorator(decorator, consts) { + match name { "Optional" => optional = true, "SkipSelf" => skip_self = true, "Self" => self_ = true, @@ -505,23 +529,6 @@ fn extract_param_dependency<'a>( } } -/// Get the name of a decorator from its expression. -fn get_decorator_name<'a>(expr: &'a Expression<'a>) -> Option> { - match expr { - // @Optional - Expression::Identifier(id) => Some(id.name.clone().into()), - // @Optional() - Expression::CallExpression(call) => { - if let Expression::Identifier(id) = &call.callee { - Some(id.name.clone().into()) - } else { - None - } - } - _ => None, - } -} - /// Extract the injection token from a parameter's type annotation. fn extract_param_token<'a>( allocator: &'a Allocator, diff --git a/crates/oxc_angular_compiler/src/ng_module/mod.rs b/crates/oxc_angular_compiler/src/ng_module/mod.rs index 8cea9511d..940f378af 100644 --- a/crates/oxc_angular_compiler/src/ng_module/mod.rs +++ b/crates/oxc_angular_compiler/src/ng_module/mod.rs @@ -17,8 +17,8 @@ mod definition; mod metadata; pub use compiler::{NgModuleCompileResult, compile_ng_module, compile_ng_module_from_metadata}; -pub(crate) use decorator::find_ng_module_decorator; pub use decorator::{NgModuleMetadata, extract_ng_module_metadata, find_ng_module_decorator_span}; +pub(crate) use decorator::{extract_ng_module_metadata_in, find_ng_module_decorator}; pub use definition::{ FullNgModuleDefinition, NgModuleDefinition, emit_full_ng_module_definition, emit_ng_module_definition, generate_full_ng_module_definition, generate_ng_module_definition, diff --git a/crates/oxc_angular_compiler/src/pipe/decorator.rs b/crates/oxc_angular_compiler/src/pipe/decorator.rs index 9bbebba97..21f229bff 100644 --- a/crates/oxc_angular_compiler/src/pipe/decorator.rs +++ b/crates/oxc_angular_compiler/src/pipe/decorator.rs @@ -12,6 +12,7 @@ use oxc_span::Span; use oxc_str::Ident; use super::metadata::R3PipeMetadata; +use crate::directive::StringConsts; use crate::factory::R3DependencyMetadata; use crate::output::ast::{OutputExpression, ReadVarExpr}; use crate::output::oxc_converter::convert_oxc_expression; @@ -105,18 +106,36 @@ impl<'a> PipeMetadata<'a> { /// }) /// export class MyPipe implements PipeTransform {} /// ``` +/// +/// Without the file's imports, any decorator named `Pipe` counts, and so does +/// any constructor parameter decorator named `Inject`, `Optional`, ...: +/// [`extract_pipe_metadata_in`] takes the file's [`StringConsts`] and only +/// counts Angular's (imported from `@angular/core`), like the compiler. pub fn extract_pipe_metadata<'a>( + allocator: &'a Allocator, + class: &'a Class<'a>, + implicit_standalone: bool, + source_text: Option<&'a str>, +) -> Option> { + extract_pipe_metadata_in(allocator, class, implicit_standalone, source_text, None) +} + +/// [`extract_pipe_metadata`] for a class in the file `consts` was collected +/// from: only Angular's `@Pipe` (imported from `@angular/core`, see +/// [`crate::directive::find_angular_class_decorator`]) counts. +pub fn extract_pipe_metadata_in<'a>( allocator: &'a Allocator, class: &'a Class<'a>, implicit_standalone: bool, _source_text: Option<&'a str>, + consts: Option<&StringConsts<'_>>, ) -> Option> { // Get the class name let class_name: Ident<'a> = class.id.as_ref()?.name.clone().into(); let class_span = class.span; // Find the @Pipe decorator - let pipe_decorator = find_pipe_decorator(&class.decorators)?; + let pipe_decorator = find_pipe_decorator(&class.decorators, consts)?; // Get the decorator call arguments let call_expr = match &pipe_decorator.expression { @@ -124,11 +143,6 @@ pub fn extract_pipe_metadata<'a>( _ => return None, }; - // Verify it's calling 'Pipe' - if !is_pipe_call(&call_expr.callee) { - return None; - } - // Get the first argument (the config object) let config_arg = call_expr.arguments.first()?; let config_obj = match config_arg { @@ -168,15 +182,21 @@ pub fn extract_pipe_metadata<'a>( } // Extract constructor dependencies for factory generation - metadata.deps = extract_constructor_deps(allocator, class); + metadata.deps = extract_constructor_deps(allocator, class, consts); Some(metadata) } -/// Find the @Pipe decorator in a list of decorators. +/// Find the @Pipe decorator in a list of decorators. With the file's +/// `consts`, only Angular's (imported from `@angular/core`); without them, any +/// named `Pipe`. pub(crate) fn find_pipe_decorator<'a>( decorators: &'a [Decorator<'a>], + consts: Option<&StringConsts<'_>>, ) -> Option<&'a Decorator<'a>> { + if let Some(consts) = consts { + return crate::directive::find_angular_class_decorator(decorators, "Pipe", consts); + } decorators.iter().find(|d| match &d.expression { Expression::CallExpression(call) => is_pipe_call(&call.callee), Expression::Identifier(id) => id.name == "Pipe", @@ -187,9 +207,11 @@ pub(crate) fn find_pipe_decorator<'a>( /// Find the span of the @Pipe decorator on a class. /// /// Returns the span including any leading whitespace/newlines that should be removed -/// along with the decorator. +/// along with the decorator. Without the file's imports, this matches any +/// decorator named `Pipe`; the compiler only takes one imported from +/// `@angular/core`. pub fn find_pipe_decorator_span(class: &Class<'_>) -> Option { - find_pipe_decorator(&class.decorators).map(|d| d.span) + find_pipe_decorator(&class.decorators, None).map(|d| d.span) } /// Check if a callee expression is a call to 'Pipe'. @@ -239,6 +261,7 @@ fn extract_boolean_value(expr: &Expression<'_>) -> Option { fn extract_constructor_deps<'a>( allocator: &'a Allocator, class: &'a Class<'a>, + consts: Option<&StringConsts<'_>>, ) -> Option>> { // Find the constructor method let constructor = class.body.body.iter().find_map(|element| { @@ -255,17 +278,20 @@ fn extract_constructor_deps<'a>( let mut deps = Vec::with_capacity_in(params.items.len(), &allocator); for param in ¶ms.items { - let dep = extract_param_dependency(allocator, param); + let dep = extract_param_dependency(allocator, param, consts); deps.push(dep); } Some(deps) } -/// Extract dependency metadata from a single constructor parameter. +/// Extract dependency metadata from a single constructor parameter. With the +/// file's `consts`, only Angular's parameter decorators count, imported from +/// `@angular/core` (see [`crate::directive::angular_param_decorator`]). fn extract_param_dependency<'a>( allocator: &'a Allocator, param: &oxc_ast::ast::FormalParameter<'a>, + consts: Option<&StringConsts<'_>>, ) -> R3DependencyMetadata<'a> { // Extract flags and @Inject token from decorators let mut optional = false; @@ -276,8 +302,8 @@ fn extract_param_dependency<'a>( let mut attribute_name: Option> = None; for decorator in ¶m.decorators { - if let Some(name) = get_decorator_name(&decorator.expression) { - match name.as_str() { + if let Some(name) = crate::directive::angular_param_decorator(decorator, consts) { + match name { "Inject" => { // @Inject(TOKEN) - extract the token if let Expression::CallExpression(call) = &decorator.expression { @@ -338,23 +364,6 @@ fn extract_param_dependency<'a>( } } -/// Get the name of a decorator from its expression. -fn get_decorator_name<'a>(expr: &'a Expression<'a>) -> Option> { - match expr { - // @Optional - Expression::Identifier(id) => Some(id.name.clone().into()), - // @Optional() - Expression::CallExpression(call) => { - if let Expression::Identifier(id) = &call.callee { - Some(id.name.clone().into()) - } else { - None - } - } - _ => None, - } -} - /// Extract the injection token from a parameter's type annotation. fn extract_param_token<'a>( allocator: &'a Allocator, diff --git a/crates/oxc_angular_compiler/src/pipe/mod.rs b/crates/oxc_angular_compiler/src/pipe/mod.rs index 4f91fc7d9..f625450df 100644 --- a/crates/oxc_angular_compiler/src/pipe/mod.rs +++ b/crates/oxc_angular_compiler/src/pipe/mod.rs @@ -17,7 +17,9 @@ mod metadata; pub use compiler::{PipeCompileResult, compile_pipe, compile_pipe_from_metadata}; pub(crate) use decorator::find_pipe_decorator; -pub use decorator::{PipeMetadata, extract_pipe_metadata, find_pipe_decorator_span}; +pub use decorator::{ + PipeMetadata, extract_pipe_metadata, extract_pipe_metadata_in, find_pipe_decorator_span, +}; pub use definition::{ FullPipeDefinition, PipeDefinition, generate_full_pipe_definition_from_decorator, generate_pipe_definition, generate_pipe_definition_from_decorator, diff --git a/crates/oxc_angular_compiler/tests/class_decorator_identity_test.rs b/crates/oxc_angular_compiler/tests/class_decorator_identity_test.rs new file mode 100644 index 000000000..8baf43301 --- /dev/null +++ b/crates/oxc_angular_compiler/tests/class_decorator_identity_test.rs @@ -0,0 +1,287 @@ +//! Like ngtsc, a class decorator (`@Component`, `@Directive`, `@Pipe`, +//! `@Injectable`, `@NgModule`) is Angular's only when it's imported from +//! `@angular/core`: by name under any alias (`import {Component as Cmp}`), or +//! through a namespace import (`@ng.Component()`). Another module's, a local or +//! an undeclared one is left on the class, which isn't compiled. One imported +//! through another module that re-exports Angular's (`import {Component} from +//! './shared'`) is left alone too: oxc doesn't follow re-exports. +//! +//! `fixtures/class_decorators_ngtsc.json` is a snapshot of ngtsc's output +//! (`@angular/compiler-cli` 22.1.7) for class `A` in each probe's `test.ts` +//! (the other files are what it imports): its diagnostics, the definitions it +//! gets (`ɵcmp`, `ɵfac`, ...), the decorators left on it and its members, and +//! the decorator types listed in `setClassMetadata`. Cases oxc deliberately +//! handles differently carry a `skip` reason. + +use oxc_allocator::Allocator; +use oxc_angular_compiler::{TransformOptions, TransformResult, transform_angular_file}; +use serde_json::Value; + +const FIXTURES: &str = include_str!("fixtures/class_decorators_ngtsc.json"); + +fn transform(source: &str, options: &TransformOptions) -> TransformResult { + let allocator = Allocator::default(); + transform_angular_file(&allocator, "test.ts", source, Some(options), None) +} + +/// The `static ɵ...` definitions in `js`, sorted. +fn definitions(js: &str) -> Vec { + let mut defs: Vec = js + .match_indices("static ɵ") + .map(|(at, _)| { + let rest = &js[at + "static ".len()..]; + rest[..rest.find(|c: char| !c.is_alphanumeric() && c != 'ɵ').unwrap()].to_string() + }) + .collect(); + defs.sort(); + defs.dedup(); + defs +} + +/// The decorators (`@X` / `@ns.X`) left in `js`, outside strings and comments, +/// sorted. +fn decorators_left(js: &str) -> Vec { + let mut left = Vec::new(); + for (at, _) in js.match_indices('@') { + let before = js[..at].chars().next_back(); + if before.is_some_and(|c| c.is_alphanumeric() || "_*/'\"`".contains(c)) { + continue; + } + let name: String = js[at + 1..] + .chars() + .take_while(|c| c.is_alphanumeric() || *c == '_' || *c == '.') + .collect(); + if name.starts_with(|c: char| c.is_ascii_alphabetic()) { + left.push(name); + } + } + left.sort(); + left +} + +/// The end of the bracketed text opening at `open`, skipping strings. +fn closing(text: &str, open: usize) -> usize { + let bytes = text.as_bytes(); + let (mut depth, mut i) = (0, open); + while i < bytes.len() { + match bytes[i] { + q @ (b'\'' | b'"' | b'`') => { + i += 1; + while bytes[i] != q { + i += if bytes[i] == b'\\' { 2 } else { 1 }; + } + } + b'[' | b'{' | b'(' => depth += 1, + b']' | b'}' | b')' => { + depth -= 1; + if depth == 0 { + return i; + } + } + _ => {} + } + i += 1; + } + text.len() +} + +/// The decorator types `setClassMetadata(A, [...])` lists in `js`, if it's there. +fn class_metadata(js: &str) -> Option> { + let at = js.find("setClassMetadata(A,")?; + let open = at + js[at..].find('[')?; + let decorators = &js[open..=closing(js, open)]; + let (mut types, mut depth) = (Vec::new(), 0); + let mut i = 0; + while i < decorators.len() { + let rest = &decorators[i..]; + match rest.as_bytes()[0] { + q @ (b'\'' | b'"' | b'`') => { + i += 1; + while decorators.as_bytes()[i] != q { + i += if decorators.as_bytes()[i] == b'\\' { 2 } else { 1 }; + } + } + b'[' | b'{' | b'(' => depth += 1, + b']' | b'}' | b')' => depth -= 1, + _ if depth == 2 && rest.starts_with("type:") => { + let ty = rest["type:".len()..].trim_start(); + let end = ty.find(|c: char| !c.is_alphanumeric() && c != '_' && c != '.').unwrap(); + types.push(ty[..end].to_string()); + } + _ => {} + } + i += 1; + } + Some(types) +} + +fn strings(value: &Value) -> Vec { + value.as_array().unwrap().iter().map(|v| v.as_str().unwrap().to_string()).collect() +} + +#[test] +fn class_decorators_match_ngtsc() { + let fixtures: Value = serde_json::from_str(FIXTURES).unwrap(); + let mut failures = Vec::new(); + let mut compared = 0; + for fixture in fixtures["fixtures"].as_array().unwrap() { + if fixture.get("skip").is_some() { + continue; + } + compared += 1; + let name = fixture["name"].as_str().unwrap(); + let source = fixture["files"]["test.ts"].as_str().unwrap(); + let result = transform(source, &TransformOptions::default()); + let mut check = |what: &str, ngtsc: String, oxc: String| { + if ngtsc != oxc { + failures.push(format!("{name}\n {what}\n ngtsc: {ngtsc}\n oxc: {oxc}")); + } + }; + + let expected = strings(&fixture["diagnostics"]); + let actual: Vec = result + .diagnostics + .iter() + .filter(|d| d.severity == oxc_diagnostics::Severity::Error) + .map(|d| d.message.to_string()) + .collect(); + check("diagnostics", format!("{expected:?}"), format!("{actual:?}")); + // ngtsc emits nothing for a class it rejects. + if !expected.is_empty() { + continue; + } + check( + "definitions", + format!("{:?}", strings(&fixture["definitions"])), + format!("{:?}", definitions(&result.code)), + ); + check( + "decorators left", + format!("{:?}", strings(&fixture["decoratorsLeft"])), + format!("{:?}", decorators_left(&result.code)), + ); + // oxc lists only the decorator it compiles the class for (ngtsc lists + // all of Angular's: `[Injectable, Component]`), in the same form. + let ngtsc = fixture["classMetadata"].as_array().map(|_| strings(&fixture["classMetadata"])); + let oxc = class_metadata(&result.code); + let listed = match (&ngtsc, &oxc) { + (Some(ngtsc), Some(oxc)) => !oxc.is_empty() && oxc.iter().all(|t| ngtsc.contains(t)), + (None, None) => true, + _ => false, + }; + if !listed { + check("setClassMetadata", format!("{ngtsc:?}"), format!("{oxc:?}")); + } + } + assert!( + failures.is_empty(), + "{} mismatches with ngtsc:\n\n{}", + failures.len(), + failures.join("\n\n") + ); + assert_eq!(compared, 40, "fixtures compared"); +} + +/// ngtsc compiles an aliased `@Injectable` (`import {Injectable as Inj}`) but +/// gives it no factory: its `needsFactory` compares the decorator's written +/// name with `'Injectable'`, so `ɵprov` references a missing `A.ɵfac` (the +/// skipped `injectableAlias` / `aliasNotDecoratorName` probes). oxc gives it +/// the factory, as for `@Injectable`. +#[test] +fn aliased_injectable_gets_a_factory() { + for source in [ + "import {Injectable as Inj} from '@angular/core';\n@Inj({providedIn: 'root'})\nexport class A {}\n", + "import {Injectable as Component} from '@angular/core';\n@Component({providedIn: 'root'})\nexport class A {}\n", + ] { + let result = transform(source, &TransformOptions::default()); + assert!(result.diagnostics.is_empty(), "{:?}", result.diagnostics); + assert_eq!(definitions(&result.code), ["ɵfac", "ɵprov"], "{}", result.code); + assert!(decorators_left(&result.code).is_empty(), "{}", result.code); + } +} + +/// In JIT mode, too, only Angular's class decorators are rewritten for the +/// runtime compiler; another library's `@Component` is left as written. +#[test] +fn jit_rewrites_only_angular_class_decorators() { + let options = TransformOptions { jit: true, ..TransformOptions::default() }; + let aliased = transform( + "import {Component as Cmp} from '@angular/core';\n@Cmp({selector: 'a-c', templateUrl: './a.html'})\nexport class A {}\n", + &options, + ); + assert!(aliased.code.contains("__decorate([Cmp({"), "{}", aliased.code); + assert!(!aliased.code.contains("templateUrl"), "{}", aliased.code); + assert!(decorators_left(&aliased.code).is_empty(), "{}", aliased.code); + + let foreign = "import {Component} from './foreign';\n@Component({selector: 'a-c', templateUrl: './a.html'})\nexport class A {}\n"; + let result = transform(foreign, &options); + assert_eq!(result.code, foreign); +} + +/// A declaration an Angular decorator references after the class is hoisted +/// above it, so the definition doesn't read it in its TDZ. Another library's +/// `@Component` isn't compiled, so the file's statements keep their order. +#[test] +fn only_angular_class_decorators_hoist_their_references() { + let source = |import: &str, decorator: &str| { + format!( + "{import}\n@{decorator}({{selector: 'a-c', template: '', providers: [{{provide: TOKEN, useValue: 1}}]}})\nexport class A {{}}\nconst TOKEN = 'tok';\n" + ) + }; + let hoisted = |code: &str| code.find("const TOKEN").unwrap() < code.find("class A").unwrap(); + + let aliased = transform( + &source("import {Component as Cmp} from '@angular/core';", "Cmp"), + &TransformOptions::default(), + ); + assert!(hoisted(&aliased.code), "{}", aliased.code); + + let foreign = transform( + &source("import {Component} from './foreign';", "Component"), + &TransformOptions::default(), + ); + assert!(!hoisted(&foreign.code), "{}", foreign.code); +} + +/// `@Service` is Angular's under any alias too (`@NgService()` for +/// `import {Service as NgService}`), like ngtsc's `findAngularDecorator`: the +/// declaration its metadata references after the class is hoisted above it, +/// as for `@Service`, so the compiled class doesn't read it in its TDZ. +/// Another library's `Service` under the same alias isn't compiled, so the +/// file's statements keep their order. +#[test] +fn aliased_service_hoists_its_references() { + let source = |import: &str| { + format!( + "{import}\n@NgService({{factory: FACTORY}})\nexport class A {{}}\nconst FACTORY = () => new A();\n" + ) + }; + let hoisted = |code: &str| code.find("const FACTORY").unwrap() < code.find("class A").unwrap(); + + let aliased = transform( + &source("import {Service as NgService} from '@angular/core';"), + &TransformOptions::default(), + ); + assert!(aliased.code.contains("static ɵprov"), "{}", aliased.code); + assert!(hoisted(&aliased.code), "{}", aliased.code); + + let foreign = transform( + &source("import {Service as NgService} from './di';"), + &TransformOptions::default(), + ); + assert!(!foreign.code.contains("static ɵprov"), "{}", foreign.code); + assert!(!hoisted(&foreign.code), "{}", foreign.code); +} + +/// With HMR on, each compiled component's `@Component` is reported as written, +/// so a build tool can find the decorator the compiler took. Another library's +/// `@Component` isn't compiled, so it isn't reported. +#[test] +fn hmr_reports_component_decorators_as_written() { + let source = "import * as ng from '@angular/core';\nimport {Component as Cmp} from '@angular/core';\nimport {Component} from './foreign';\n@Cmp({selector: 'a-c', template: ''})\nexport class A {}\n@ng . Component({selector: 'b-c', template: ''})\nexport class B {}\n@Component({selector: 'c-c', template: ''})\nexport class C {}\n"; + let result = transform(source, &TransformOptions { hmr: true, ..TransformOptions::default() }); + let mut reported: Vec<(&str, &str)> = + result.component_decorators.iter().map(|(k, v)| (k.as_str(), v.as_str())).collect(); + reported.sort_unstable(); + assert_eq!(reported, [("test.ts@A", "Cmp"), ("test.ts@B", "ng . Component")]); +} diff --git a/crates/oxc_angular_compiler/tests/class_metadata_resources_test.rs b/crates/oxc_angular_compiler/tests/class_metadata_resources_test.rs index 521ecd862..59d5051f6 100644 --- a/crates/oxc_angular_compiler/tests/class_metadata_resources_test.rs +++ b/crates/oxc_angular_compiler/tests/class_metadata_resources_test.rs @@ -881,3 +881,38 @@ export class StyledComponent {} "Error should carry the code and the resource. Got: {rendered}" ); } + +/// An aliased `@Component` (`import { Component as Cmp }`) is compiled, but +/// like ngtsc (`transformDecoratorResources` checks `dec.name`, the name it's +/// written with) its `setClassMetadata` keeps `templateUrl` / `styleUrls`. A +/// namespaced `@ng.Component` is named `Component`, so its resources are +/// inlined. Checked against `@angular/compiler-cli` 22.1.7. +#[test] +fn only_a_decorator_written_as_component_gets_its_resources_inlined() { + let resources = || ResolvedResources { + templates: HashMap::from([("a.html".to_string(), "hi".to_string())]), + styles: HashMap::from([("a.css".to_string(), vec!["b{color:red}".to_string()])]), + }; + let source = |import: &str, decorator: &str| { + format!( + "{import}\n@{decorator}({{selector: 'a-c', templateUrl: 'a.html', styleUrls: ['a.css']}})\nexport class A {{}}\n" + ) + }; + + let aliased = extract_metadata_args(&run_with_resources( + &source("import { Component as Cmp } from '@angular/core';", "Cmp"), + resources(), + )); + assert!(aliased.contains("type:Cmp"), "{aliased}"); + assert!(aliased.contains("templateUrl:\"a.html\""), "{aliased}"); + assert!(aliased.contains("styleUrls:[\"a.css\"]"), "{aliased}"); + + let namespaced = extract_metadata_args(&run_with_resources( + &source("import * as ng from '@angular/core';", "ng.Component"), + resources(), + )); + assert!(namespaced.contains("type:ng.Component"), "{namespaced}"); + assert!(namespaced.contains("template:\"hi\""), "{namespaced}"); + assert!(namespaced.contains("styles:[\"b{color:red}\"]"), "{namespaced}"); + assert!(!namespaced.contains("templateUrl"), "{namespaced}"); +} diff --git a/crates/oxc_angular_compiler/tests/ctor_param_decorator_identity_test.rs b/crates/oxc_angular_compiler/tests/ctor_param_decorator_identity_test.rs new file mode 100644 index 000000000..0ff1c11ec --- /dev/null +++ b/crates/oxc_angular_compiler/tests/ctor_param_decorator_identity_test.rs @@ -0,0 +1,262 @@ +//! Like ngtsc, a constructor parameter decorator (`@Inject`, `@Optional`, +//! `@Self`, `@SkipSelf`, `@Host`, `@Attribute`) is Angular's only when it's +//! imported from `@angular/core`: by name under any alias +//! (`import {Inject as Inj}`), or through a namespace import (`@ng.Inject()`). +//! Angular's are compiled into the factory, `setClassMetadata` and JIT +//! `ctorParameters`, and removed. Another module's or a local one with the same +//! name is ignored by all three and left on the parameter (JIT lowers it as +//! `__param(index, decorator)` in the class's `__decorate`, as TypeScript does). +//! +//! `fixtures/ctor_param_decorators_ngtsc.json` is a snapshot of +//! `@angular/compiler-cli` 22.1.7 for class `A` in each probe's `test.ts` (the +//! other files are what it imports): its diagnostics, the arguments of its +//! factory, the `ctorParameters` of `setClassMetadata`, the parameter +//! decorators left (as the `__param(...)` TypeScript lowers them to), and the +//! same two for Angular's JIT transform (`constructorParametersDownlevelTransform`). +//! Text is compared without whitespace, with `'` as `"` and without trailing +//! commas. Cases oxc deliberately handles differently carry a `skip` reason. + +use oxc_allocator::Allocator; +use oxc_angular_compiler::{TransformOptions, TransformResult, transform_angular_file}; +use serde_json::Value; + +const FIXTURES: &str = include_str!("fixtures/ctor_param_decorators_ngtsc.json"); + +fn transform(source: &str, options: &TransformOptions) -> TransformResult { + let allocator = Allocator::default(); + transform_angular_file(&allocator, "test.ts", source, Some(options), None) +} + +/// `text` without whitespace, with `'` as `"`, without trailing commas and +/// without ngtsc's `/* @ts-ignore */`. +fn normalize(text: &str) -> String { + let text: String = text + .replace("/* @ts-ignore */", "") + .chars() + .filter(|c| !c.is_whitespace()) + .map(|c| if c == '\'' { '"' } else { c }) + .collect(); + let mut out = String::with_capacity(text.len()); + let chars: Vec = text.chars().collect(); + for (i, &c) in chars.iter().enumerate() { + if c == ',' && chars.get(i + 1).is_some_and(|n| matches!(n, ']' | '}' | ')')) { + continue; + } + out.push(c); + } + out +} + +/// The bracketed text opening at byte `open` of `text`, skipping strings. +fn bracketed(text: &str, open: usize) -> &str { + let bytes = text.as_bytes(); + let (mut depth, mut i) = (0, open); + while i < bytes.len() { + match bytes[i] { + q @ (b'\'' | b'"' | b'`') => { + i += 1; + while bytes[i] != q { + i += if bytes[i] == b'\\' { 2 } else { 1 }; + } + } + b'[' | b'{' | b'(' => depth += 1, + b']' | b'}' | b')' => { + depth -= 1; + if depth == 0 { + return &text[open..=i]; + } + } + _ => {} + } + i += 1; + } + &text[open..] +} + +/// The arguments `A`'s factory constructs it with. oxc reads an `@Inject` +/// token imported from another file through its namespace import (`i1.TOKEN`), +/// where ngtsc names the import (`TOKEN`); both are the same value. +fn factory(js: &str) -> Option { + const NEW: &str = "(__ngFactoryType__ || A)"; + let at = js.find(&format!("{NEW}("))? + NEW.len(); + Some(normalize(bracketed(js, at)).replace("i1.TOKEN", "TOKEN")) +} + +/// The `ctorParameters` array of `setClassMetadata(A, ...)`. +fn ctor_parameters(js: &str) -> Option { + let at = js.find("setClassMetadata(A")?; + let arrow = at + js[at..].find("() =>")?; + let open = arrow + js[arrow..].find('[')?; + Some(normalize(bracketed(js, open))) +} + +/// The `ctorParameters` array of JIT output. +fn jit_ctor_parameters(js: &str) -> Option { + let at = js.find("ctorParameters = () =>")?; + let open = at + js[at..].find('[')?; + Some(normalize(bracketed(js, open))) +} + +/// The `__param(index, decorator)` entries in `js`, sorted. +fn lowered_param_decorators(js: &str) -> Vec { + let mut found: Vec = js + .match_indices("__param(") + .map(|(at, _)| normalize(&format!("__param{}", bracketed(js, at + "__param".len())))) + .collect(); + found.sort(); + found +} + +/// The decorators left on `A`'s constructor parameters in AOT output, as the +/// `__param(index, decorator)` TypeScript lowers them to, sorted. +fn param_decorators_left(js: &str) -> Vec { + let Some(at) = js.find("constructor(") else { return Vec::new() }; + let params = bracketed(js, at + "constructor".len()); + let params = ¶ms[1..params.len() - 1]; + // Split at top-level commas. + let (mut list, mut depth, mut start) = (Vec::new(), 0, 0); + for (i, c) in params.char_indices() { + match c { + '(' | '[' | '{' => depth += 1, + ')' | ']' | '}' => depth -= 1, + ',' if depth == 0 => { + list.push(¶ms[start..i]); + start = i + 1; + } + _ => {} + } + } + list.push(¶ms[start..]); + + let mut left = Vec::new(); + for (index, param) in list.iter().enumerate() { + for (at, _) in param.match_indices('@') { + let name_end = param[at + 1..] + .find(|c: char| !c.is_alphanumeric() && c != '_' && c != '.') + .map_or(param.len(), |end| at + 1 + end); + let mut decorator = param[at + 1..name_end].to_string(); + if param[name_end..].starts_with('(') { + decorator.push_str(bracketed(param, name_end)); + } + left.push(normalize(&format!("__param({index}, {decorator})"))); + } + } + left.sort(); + left +} + +fn strings(value: &Value) -> Vec { + value.as_array().unwrap().iter().map(|v| v.as_str().unwrap().to_string()).collect() +} + +fn optional(value: &Value) -> Option { + value.as_str().map(str::to_string) +} + +#[test] +fn ctor_param_decorators_match_ngtsc() { + let fixtures: Value = serde_json::from_str(FIXTURES).unwrap(); + let mut failures = Vec::new(); + let mut compared = 0; + for fixture in fixtures["fixtures"].as_array().unwrap() { + if fixture.get("skip").is_some() { + continue; + } + compared += 1; + let name = fixture["name"].as_str().unwrap(); + let source = fixture["files"]["test.ts"].as_str().unwrap(); + let mut check = |what: &str, ngtsc: String, oxc: String| { + if ngtsc != oxc { + failures.push(format!("{name}\n {what}\n ngtsc: {ngtsc}\n oxc: {oxc}")); + } + }; + + let aot = transform( + source, + &TransformOptions { emit_class_metadata: true, ..TransformOptions::default() }, + ); + let errors: Vec = aot + .diagnostics + .iter() + .filter(|d| d.severity == oxc_diagnostics::Severity::Error) + .map(|d| d.message.to_string()) + .collect(); + check( + "diagnostics", + format!("{:?}", strings(&fixture["diagnostics"])), + format!("{errors:?}"), + ); + check( + "factory", + format!("{:?}", optional(&fixture["factory"])), + format!("{:?}", factory(&aot.code)), + ); + check( + "setClassMetadata ctorParameters", + format!("{:?}", optional(&fixture["ctorParameters"])), + format!("{:?}", ctor_parameters(&aot.code)), + ); + check( + "parameter decorators left", + format!("{:?}", strings(&fixture["paramDecoratorsLeft"])), + format!("{:?}", param_decorators_left(&aot.code)), + ); + + let jit = transform(source, &TransformOptions { jit: true, ..TransformOptions::default() }); + check( + "JIT ctorParameters", + format!("{:?}", optional(&fixture["jitCtorParameters"])), + format!("{:?}", jit_ctor_parameters(&jit.code)), + ); + check( + "JIT __param", + format!("{:?}", strings(&fixture["jitParamDecorators"])), + format!("{:?}", lowered_param_decorators(&jit.code)), + ); + } + assert!( + failures.is_empty(), + "{} mismatches with ngtsc:\n\n{}", + failures.len(), + failures.join("\n\n") + ); + assert_eq!(compared, 29, "fixtures compared"); +} + +/// A parameter decorator left in the output keeps its import: another module's +/// `@Inject` / `@Optional` (and the `@Inject` argument) aren't Angular's, so +/// they aren't removed, and neither is what they need at runtime. +#[test] +fn foreign_param_decorators_keep_their_imports() { + let source = "import {Component} from '@angular/core';\nimport {Inject, Optional} from './foreign';\nimport {Dep, TOKEN} from './dep';\n@Component({selector: 'a-c', template: ''})\nexport class A {\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep) {}\n}\n"; + let result = transform(source, &TransformOptions::default()); + assert!(result.code.contains("import {Inject, Optional} from './foreign';"), "{}", result.code); + assert!(result.code.contains("TOKEN } from"), "{}", result.code); + assert!( + result.code.contains("constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep)"), + "{}", + result.code + ); +} + +/// JIT output imports `__param` from tslib only when it lowers a parameter +/// decorator with it. +#[test] +fn jit_imports_param_helper_only_when_used() { + let options = TransformOptions { jit: true, ..TransformOptions::default() }; + let angular = transform( + "import {Component, Optional} from '@angular/core';\n@Component({selector: 'a-c', template: ''})\nexport class A {\n constructor(@Optional() a: Dep) {}\n}\n", + &options, + ); + assert!(angular.code.contains("import { __decorate } from \"tslib\";"), "{}", angular.code); + + let foreign = transform( + "import {Component} from '@angular/core';\nimport {Optional} from './foreign';\n@Component({selector: 'a-c', template: ''})\nexport class A {\n constructor(@Optional() a: Dep) {}\n}\n", + &options, + ); + assert!( + foreign.code.contains("import { __decorate, __param } from \"tslib\";"), + "{}", + foreign.code + ); +} diff --git a/crates/oxc_angular_compiler/tests/decorator_metadata_ngtsc_test.rs b/crates/oxc_angular_compiler/tests/decorator_metadata_ngtsc_test.rs index 91101319e..52999089b 100644 --- a/crates/oxc_angular_compiler/tests/decorator_metadata_ngtsc_test.rs +++ b/crates/oxc_angular_compiler/tests/decorator_metadata_ngtsc_test.rs @@ -307,7 +307,7 @@ fn decorator_metadata_matches_ngtsc() { failures.len(), failures.join("\n\n") ); - assert_eq!(compared, 794, "fixtures compared"); + assert_eq!(compared, 1042, "fixtures compared"); } fn transform(source: &str) -> TransformResult { @@ -383,6 +383,70 @@ export class Dir {} let result = transform(source); assert!(errors(&result, source).is_empty(), "{:?}", errors(&result, source)); assert!(strip(&result.code).contains(r#"inputs:{x:[2,"x","x",fn]}"#), "{}", result.code); + + // A value computed from an import (`!FLAG`, a call, a member, a condition + // or a deciding `&&` / `||`) isn't the import: ngtsc evaluates it with + // the other file (`!FLAG` is a boolean, ngtsc 22.1.7: "must be a + // function"; `fn || a` is `fn`), oxc can't, so it's reported, in + // `inputs:` and in `@Input`. An import the expression only passes through + // (`0 || fn`) is that import, as above. + let cases = [ + ("!FLAG", "FLAG"), + ("fn()", "fn"), + ("fn.f", "fn"), + ("FLAG ? a : a", "FLAG"), + ("fn || a", "fn"), + ("FLAG && a", "FLAG"), + ]; + for (transform_expr, name) in cases { + for (meta, member, subject, span) in [ + ( + format!("inputs: [{{name: 'x', transform: {transform_expr}}}]"), + "x: any;".to_string(), + "@Directive.inputs", + format!("[{{name: 'x', transform: {transform_expr}}}]"), + ), + ( + String::new(), + format!("@Input({{transform: {transform_expr}}}) x: any;"), + "@Input", + format!("{{transform: {transform_expr}}}"), + ), + ] { + let source = format!( + "import {{Directive, Input}} from '@angular/core'; +import {{fn, FLAG}} from './shared'; +export function a(v: string) {{ return 1; }} +@Directive({{selector: '[d]', {meta}}}) +export class Dir {{ + {member} +}} +" + ); + let message = format!( + "{subject} depends on '{name}', which is imported from another module. \ + OXC compiles one file at a time and cannot evaluate values from other files." + ); + assert_eq!(errors(&transform(&source), &source), vec![(message, span)], "{source}"); + } + } + for transform_expr in ["0 || fn", "a && fn", "1 ? fn : a"] { + let source = format!( + "import {{Directive, Input}} from '@angular/core'; +import {{fn}} from './shared'; +export function a(v: string) {{ return 1; }} +@Directive({{selector: '[d]', inputs: [{{name: 'x', transform: {transform_expr}}}]}}) +export class Dir {{ + x: any; + @Input({{transform: {transform_expr}}}) y: any; +}} +" + ); + let result = transform(&source); + assert!(errors(&result, &source).is_empty(), "{:?}", errors(&result, &source)); + let code = strip(&result.code); + assert!(code.contains(r#"inputs:{x:[2,"x","x",fn],y:[2,"y","y",fn]}"#), "{code}"); + } } /// The same for an `@Input(...)` argument: ngtsc 22.1.7 reads these from @@ -439,15 +503,52 @@ export class Dir { assert!(strip(&result.code).contains(r#"inputs:{x:[2,"y","x",fn]}"#), "{}", result.code); } +/// The same for an `@Output(...)` alias: ngtsc 22.1.7 reads it from `./shared` +/// (with `export const NAME = 'y'`, it compiles the output as `y`). oxc can't, +/// so it reports it instead of compiling the output under its property name. +#[test] +fn output_decorator_alias_imported_from_another_module_is_reported() { + let cases = [ + ("@Output(NAME) e = new EventEmitter();", "NAME", "NAME"), + ("@Output((NAME)) e = new EventEmitter();", "NAME", "(NAME)"), + ("@Output(ns.NAME) e = new EventEmitter();", "NAME", "ns.NAME"), + ("@Output(LOCAL) e = new EventEmitter();", "NAME", "LOCAL"), + ("@Output(`${NAME}`) e = new EventEmitter();", "NAME", "`${NAME}`"), + ("@Output(NAME) accessor e = new EventEmitter();", "NAME", "NAME"), + ]; + for (member, name, span) in cases { + let source = format!( + "import {{Directive, EventEmitter, Output}} from '@angular/core'; +import {{NAME}} from './shared'; +import * as ns from './shared'; +const LOCAL = NAME; +@Directive({{selector: '[d]'}}) +export class Dir {{ + {member} +}} +" + ); + let message = format!( + "@Output depends on '{name}', which is imported from another module. \ + OXC compiles one file at a time and cannot evaluate values from other files." + ); + assert_eq!( + errors(&transform(&source), &source), + vec![(message, span.to_string())], + "{member}" + ); + } +} + /// A transform returned by a function the metadata calls is emitted where the /// directive is compiled, outside that function. A parameter becomes the -/// argument it was passed (the snapshot's `scope-*` probes); anything else that -/// uses the function's parameters would mean something else there, or nothing. -/// ngtsc 22.1.7 emits `(v) => v + name` as written (`name` is then -/// `window.name`) and `booleanAttribute` for `o.t` (the name the argument was -/// first given); oxc reports these instead. (`this` and `arguments` there -/// aren't analyzable, so ngtsc rejects those itself: `scope-this`, -/// `scope-arguments`.) +/// argument it was passed (the snapshot's `scope-*` probes), and a reference +/// read through one the name it was first given (`booleanAttribute` for `o.t`, +/// `probe: shorthandRef-helperParamMember`); anything else that uses the +/// function's parameters would mean something else there, or nothing. ngtsc +/// 22.1.7 emits `(v) => v + name` as written (`name` is then `window.name`); +/// oxc reports these instead. (`this` and `arguments` there aren't analyzable, +/// so ngtsc rejects those itself: `scope-this`, `scope-arguments`.) #[test] fn transforms_using_the_parameters_of_a_called_function_are_reported() { let cases = [ @@ -457,12 +558,6 @@ fn transforms_using_the_parameters_of_a_called_function_are_reported() { "@Directive.inputs", "make('x')", ), - ( - "function make(o: any) { return [{ name: 'x', transform: o.t }]; }", - "inputs: make({ t: booleanAttribute })", - "@Directive.inputs", - "make({ t: booleanAttribute })", - ), ( "function opts(n: string) { return { transform: (v: string) => v + n }; }", "", @@ -644,9 +739,8 @@ export class Dir { a: any; b: any; c: any; } /// an error, since `input` here isn't Angular's. The snapshot's /// `initializerApi-*` probes cover the ones ngtsc rejects, and the outputs. /// -/// oxc still compiles such a member as a signal input (it recognises signal -/// members by name), where ngtsc makes it a plain one: that's why these compare -/// only the diagnostics. +/// Such a member is a plain property, so `value` is the metadata's plain input +/// (the snapshot's `signal-*Listed` probes compare the same with ngtsc). #[test] fn only_angular_initializer_apis_collide_with_metadata() { let cases = [ @@ -665,15 +759,16 @@ export class Dir {{ value = {initializer}; }} ); let result = transform(&source); assert_eq!(errors(&result, &source), vec![], "{import} {initializer}"); + let code = strip(&result.code); + assert!(code.contains(r#"inputs:{value:"value"}"#), "{import} {initializer}\n{code}"); } } /// The same for a signal query also declared in `queries:`: ngtsc 22.1.7 /// reports it only for Angular's `viewChild()`, `contentChild()`, ... (the /// snapshot's `initializerApi-viewChild*` probes), and compiles these, whose -/// functions aren't Angular's. oxc still compiles such a member as a signal -/// query too (it recognises them by name), so these compare only the -/// diagnostics. +/// functions aren't Angular's: the member is a plain property, and only the +/// decorator's query is compiled. #[test] fn only_angular_signal_queries_collide_with_decorator_queries() { let cases = [ @@ -695,6 +790,11 @@ export class Dir {{ foo = {initializer}; }} ); let result = transform(&source); assert_eq!(errors(&result, &source), vec![], "{declaration} {initializer}"); + assert!( + !result.code.contains("QuerySignal"), + "{declaration} {initializer}\n{}", + result.code + ); } } @@ -1149,3 +1249,31 @@ export class Cmp {{ ); } } + +/// Like ngtsc, a member decorator is Angular's only when it's imported from +/// `@angular/core`, under any alias or through a namespace import. Only those +/// are compiled (the snapshot's `memberDecorator-*` probes), removed from the +/// class and listed in `setClassMetadata`. Another module's `@Input` is left on +/// the class and its options aren't checked, as ngtsc 22.1.7 does. +#[test] +fn only_angular_member_decorators_are_compiled_and_removed() { + let source = "import {Directive, Input as In} from '@angular/core'; +import * as core from '@angular/core'; +import {Input, HostListener} from './other'; +@Directive({selector: '[d]'}) +export class Dir { + @In() a: any; + @core.Output() b: any; + @Input({transform: 5}) c: any; + @HostListener('click') d() {} +} +"; + let result = transform(source); + assert!(errors(&result, source).is_empty(), "{:?}", errors(&result, source)); + let code = strip(&result.code); + assert!(!code.contains("@In()") && !code.contains("@core.Output()"), "{}", result.code); + assert!(code.contains("@Input({transform:5})c:any;"), "{}", result.code); + assert!(code.contains("@HostListener('click')d(){}"), "{}", result.code); + assert!(code.contains(r#"inputs:{a:"a"},outputs:{b:"b"}}"#), "{}", result.code); + assert!(code.contains("{a:[{type:In}],b:[{type:core.Output}]}"), "{}", result.code); +} diff --git a/crates/oxc_angular_compiler/tests/fixtures/class_decorators_ngtsc.json b/crates/oxc_angular_compiler/tests/fixtures/class_decorators_ngtsc.json new file mode 100644 index 000000000..2b3aee150 --- /dev/null +++ b/crates/oxc_angular_compiler/tests/fixtures/class_decorators_ngtsc.json @@ -0,0 +1,520 @@ +{ + "compilerCli": "22.1.7", + "fixtures": [ + { + "name": "named", + "origin": "probe", + "files": { + "test.ts": "import {Component} from '@angular/core';\n@Component({selector: 'a-c', template: ''})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": ["ɵcmp", "ɵfac"], + "decoratorsLeft": [], + "classMetadata": ["Component"] + }, + { + "name": "alias", + "origin": "probe", + "files": { + "test.ts": "import {Component as Cmp} from '@angular/core';\n@Cmp({selector: 'a-c', template: ''})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": ["ɵcmp", "ɵfac"], + "decoratorsLeft": [], + "classMetadata": ["Cmp"] + }, + { + "name": "namespace", + "origin": "probe", + "files": { + "test.ts": "import * as ng from '@angular/core';\n@ng.Component({selector: 'a-c', template: ''})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": ["ɵcmp", "ɵfac"], + "decoratorsLeft": [], + "classMetadata": ["ng.Component"] + }, + { + "name": "importType", + "origin": "probe", + "files": { + "test.ts": "import type {Component} from '@angular/core';\n@Component({selector: 'a-c', template: ''})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": ["ɵcmp", "ɵfac"], + "decoratorsLeft": [], + "classMetadata": ["Component"] + }, + { + "name": "typeSpecifier", + "origin": "probe", + "files": { + "test.ts": "import {type Component} from '@angular/core';\n@Component({selector: 'a-c', template: ''})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": ["ɵcmp", "ɵfac"], + "decoratorsLeft": [], + "classMetadata": ["Component"] + }, + { + "name": "foreignPkg", + "origin": "probe", + "files": { + "test.ts": "import {Component} from 'foreign-lib';\n@Component({selector: 'a-c', template: ''})\nexport class A {}\n", + "node_modules/foreign-lib/package.json": "{\"name\":\"foreign-lib\",\"types\":\"index.d.ts\",\"main\":\"index.js\"}", + "node_modules/foreign-lib/index.d.ts": "export declare function Component(o?: any): ClassDecorator;\nexport declare function Injectable(o?: any): ClassDecorator;\n", + "node_modules/foreign-lib/index.js": "export function Component(){return c=>c}\nexport function Injectable(){return c=>c}\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["Component"], + "classMetadata": null + }, + { + "name": "foreignRel", + "origin": "probe", + "files": { + "test.ts": "import {Component} from './foreign';\n@Component({selector: 'a-c', template: ''})\nexport class A {}\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["Component"], + "classMetadata": null + }, + { + "name": "barrel", + "origin": "probe", + "files": { + "test.ts": "import {Component} from './shared';\n@Component({selector: 'a-c', template: ''})\nexport class A {}\n", + "shared.ts": "export {Component, Directive, Injectable} from '@angular/core';\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["Component"], + "classMetadata": null + }, + { + "name": "localFn", + "origin": "probe", + "files": { + "test.ts": "function Component(o: any): ClassDecorator { return (c: any) => c; }\n@Component({selector: 'a-c', template: ''})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["Component"], + "classMetadata": null + }, + { + "name": "localConst", + "origin": "probe", + "files": { + "test.ts": "const Component = (o: any) => (c: any) => c;\n@Component({selector: 'a-c', template: ''})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["Component"], + "classMetadata": null + }, + { + "name": "undeclared", + "origin": "probe", + "files": { + "test.ts": "@Component({selector: 'a-c', template: ''})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["Component"], + "classMetadata": null + }, + { + "name": "defaultImport", + "origin": "probe", + "files": { + "test.ts": "import Component from './foreign';\n@Component({selector: 'a-c', template: ''})\nexport class A {}\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["Component"], + "classMetadata": null + }, + { + "name": "foreignNamespace", + "origin": "probe", + "files": { + "test.ts": "import * as other from './foreign';\n@other.Component({selector: 'a-c', template: ''})\nexport class A {}\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["other.Component"], + "classMetadata": null + }, + { + "name": "undeclaredNamespace", + "origin": "probe", + "files": { + "test.ts": "@ng.Component({selector: 'a-c', template: ''})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["ng.Component"], + "classMetadata": null + }, + { + "name": "ngPlusForeignFoo", + "origin": "probe", + "files": { + "test.ts": "import {Component} from '@angular/core';\nimport {Foo} from './foreign';\n@Foo()\n@Component({selector: 'a-c', template: ''})\nexport class A {}\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "diagnostics": [], + "definitions": ["ɵcmp", "ɵfac"], + "decoratorsLeft": ["Foo"], + "classMetadata": ["Component"] + }, + { + "name": "ngCmpForeignDir", + "origin": "probe", + "files": { + "test.ts": "import {Component} from '@angular/core';\nimport {Directive} from './foreign';\n@Directive({selector: '[x]'})\n@Component({selector: 'a-c', template: ''})\nexport class A {}\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "diagnostics": [], + "definitions": ["ɵcmp", "ɵfac"], + "decoratorsLeft": ["Directive"], + "classMetadata": ["Component"] + }, + { + "name": "foreignCmpNgDir", + "origin": "probe", + "files": { + "test.ts": "import {Directive} from '@angular/core';\nimport {Component} from './foreign';\n@Component({selector: 'a-c', template: ''})\n@Directive({selector: '[x]'})\nexport class A {}\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "diagnostics": [], + "definitions": ["ɵdir", "ɵfac"], + "decoratorsLeft": ["Component"], + "classMetadata": ["Directive"] + }, + { + "name": "abstractDir", + "origin": "probe", + "files": { + "test.ts": "import {Directive} from '@angular/core';\n@Directive()\nexport abstract class A {}\n" + }, + "diagnostics": [], + "definitions": ["ɵdir", "ɵfac"], + "decoratorsLeft": [], + "classMetadata": ["Directive"] + }, + { + "name": "dirAlias", + "origin": "probe", + "files": { + "test.ts": "import {Directive as D, Input} from '@angular/core';\n@D({selector: '[d]'})\nexport class A { @Input() x: any; }\n" + }, + "diagnostics": [], + "definitions": ["ɵdir", "ɵfac"], + "decoratorsLeft": [], + "classMetadata": ["D"] + }, + { + "name": "injectable", + "origin": "probe", + "files": { + "test.ts": "import {Injectable} from '@angular/core';\n@Injectable({providedIn: 'root'})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": ["ɵfac", "ɵprov"], + "decoratorsLeft": [], + "classMetadata": ["Injectable"] + }, + { + "name": "injectableAlias", + "origin": "probe", + "files": { + "test.ts": "import {Injectable as Inj} from '@angular/core';\n@Inj({providedIn: 'root'})\nexport class A {}\n" + }, + "skip": "ngtsc only gives an aliased `@Injectable` a factory when its local name is `Injectable` (`needsFactory` reads `decorator.name`), so `ɵprov` references a missing `A.ɵfac`; oxc emits the factory", + "diagnostics": [], + "definitions": ["ɵprov"], + "decoratorsLeft": [], + "classMetadata": null + }, + { + "name": "injectableNs", + "origin": "probe", + "files": { + "test.ts": "import * as ng from '@angular/core';\n@ng.Injectable({providedIn: 'root'})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": ["ɵfac", "ɵprov"], + "decoratorsLeft": [], + "classMetadata": ["ng.Injectable"] + }, + { + "name": "injectableForeign", + "origin": "probe", + "files": { + "test.ts": "import {Injectable} from 'foreign-lib';\n@Injectable({providedIn: 'root'})\nexport class A {}\n", + "node_modules/foreign-lib/package.json": "{\"name\":\"foreign-lib\",\"types\":\"index.d.ts\",\"main\":\"index.js\"}", + "node_modules/foreign-lib/index.d.ts": "export declare function Component(o?: any): ClassDecorator;\nexport declare function Injectable(o?: any): ClassDecorator;\n", + "node_modules/foreign-lib/index.js": "export function Component(){return c=>c}\nexport function Injectable(){return c=>c}\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["Injectable"], + "classMetadata": null + }, + { + "name": "injectableUndeclared", + "origin": "probe", + "files": { + "test.ts": "@Injectable({providedIn: 'root'})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["Injectable"], + "classMetadata": null + }, + { + "name": "ngModule", + "origin": "probe", + "files": { + "test.ts": "import {NgModule} from '@angular/core';\n@NgModule({})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": ["ɵfac", "ɵinj", "ɵmod"], + "decoratorsLeft": [], + "classMetadata": ["NgModule"] + }, + { + "name": "ngModuleAlias", + "origin": "probe", + "files": { + "test.ts": "import {NgModule as M} from '@angular/core';\n@M({})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": ["ɵfac", "ɵinj", "ɵmod"], + "decoratorsLeft": [], + "classMetadata": ["M"] + }, + { + "name": "ngModuleForeign", + "origin": "probe", + "files": { + "test.ts": "import {NgModule} from './foreign';\n@NgModule({})\nexport class A {}\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["NgModule"], + "classMetadata": null + }, + { + "name": "pipe", + "origin": "probe", + "files": { + "test.ts": "import {Pipe} from '@angular/core';\n@Pipe({name: 'p'})\nexport class A { transform(v: any) { return v; } }\n" + }, + "diagnostics": [], + "definitions": ["ɵfac", "ɵpipe"], + "decoratorsLeft": [], + "classMetadata": ["Pipe"] + }, + { + "name": "pipeAlias", + "origin": "probe", + "files": { + "test.ts": "import {Pipe as P} from '@angular/core';\n@P({name: 'p'})\nexport class A { transform(v: any) { return v; } }\n" + }, + "diagnostics": [], + "definitions": ["ɵfac", "ɵpipe"], + "decoratorsLeft": [], + "classMetadata": ["P"] + }, + { + "name": "pipeForeign", + "origin": "probe", + "files": { + "test.ts": "import {Pipe} from './foreign';\n@Pipe({name: 'p'})\nexport class A { transform(v: any) { return v; } }\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["Pipe"], + "classMetadata": null + }, + { + "name": "dirForeign", + "origin": "probe", + "files": { + "test.ts": "import {Directive} from './foreign';\n@Directive({selector: '[d]'})\nexport class A {}\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["Directive"], + "classMetadata": null + }, + { + "name": "cmpNgInjForeign", + "origin": "probe", + "files": { + "test.ts": "import {Component} from '@angular/core';\nimport {Injectable} from './foreign';\n@Injectable()\n@Component({selector: 'a-c', template: ''})\nexport class A {}\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "diagnostics": [], + "definitions": ["ɵcmp", "ɵfac"], + "decoratorsLeft": ["Injectable"], + "classMetadata": ["Component"] + }, + { + "name": "cmpNgInjNg", + "origin": "probe", + "files": { + "test.ts": "import {Component, Injectable} from '@angular/core';\n@Injectable()\n@Component({selector: 'a-c', template: ''})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": ["ɵcmp", "ɵfac", "ɵprov"], + "decoratorsLeft": [], + "classMetadata": ["Injectable", "Component"] + }, + { + "name": "foreignBadInputs", + "origin": "probe", + "files": { + "test.ts": "import {Component} from './foreign';\n@Component({selector: 'a-c', template: '', inputs: [5 as any]})\nexport class A {}\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["Component"], + "classMetadata": null + }, + { + "name": "ngBadInputs", + "origin": "probe", + "files": { + "test.ts": "import {Component} from '@angular/core';\n@Component({selector: 'a-c', template: '', inputs: [5 as any]})\nexport class A {}\n" + }, + "diagnostics": [ + "@Directive.inputs array can only contain strings or object literals Value is of type 'number'." + ], + "definitions": [], + "decoratorsLeft": ["Component"], + "classMetadata": null + }, + { + "name": "foreignCmpNgInput", + "origin": "probe", + "files": { + "test.ts": "import {Input} from '@angular/core';\nimport {Component} from './foreign';\n@Component({selector: 'a-c', template: ''})\nexport class A { @Input() x: any; }\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["Component", "Input"], + "classMetadata": null + }, + { + "name": "foreignCmpSignalInput", + "origin": "probe", + "files": { + "test.ts": "import {input} from '@angular/core';\nimport {Component} from './foreign';\n@Component({selector: 'a-c', template: ''})\nexport class A { x = input(0); }\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "skip": "pre-existing: oxc doesn't implement ngtsc's check for `input()` outside a @Component / @Directive class", + "diagnostics": [ + "Unsupported call to the input function. This function can only be used as the initializer of a property on a @Component or @Directive class." + ], + "definitions": [], + "decoratorsLeft": ["Component"], + "classMetadata": null + }, + { + "name": "foreignCmpBadTemplate", + "origin": "probe", + "files": { + "test.ts": "import {Component} from './foreign';\n@Component({selector: 'a-c', template: '
{{ }}'})\nexport class A {}\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["Component"], + "classMetadata": null + }, + { + "name": "bothImportsNsNg", + "origin": "probe", + "files": { + "test.ts": "import {Component} from './foreign';\nimport * as ng from '@angular/core';\n@ng.Component({selector: 'a-c', template: ''})\nexport class A {}\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "diagnostics": [], + "definitions": ["ɵcmp", "ɵfac"], + "decoratorsLeft": [], + "classMetadata": ["ng.Component"] + }, + { + "name": "defaultExportAlias", + "origin": "probe", + "files": { + "test.ts": "import {Component as Cmp} from '@angular/core';\n@Cmp({selector: 'a-c', template: ''})\nexport default class A {}\n" + }, + "diagnostics": [], + "definitions": ["ɵcmp", "ɵfac"], + "decoratorsLeft": [], + "classMetadata": ["Cmp"] + }, + { + "name": "aliasNotDecoratorName", + "origin": "probe", + "files": { + "test.ts": "import {Injectable as Component} from '@angular/core';\n@Component({providedIn: 'root'})\nexport class A {}\n" + }, + "skip": "like `injectableAlias`: ngtsc gives `@Component` (`Injectable` imported under that name) no factory; oxc compiles it as an `@Injectable` with one", + "diagnostics": [], + "definitions": ["ɵprov"], + "decoratorsLeft": [], + "classMetadata": null + }, + { + "name": "stringImportName", + "origin": "probe", + "files": { + "test.ts": "import {'Component' as Cmp} from '@angular/core';\n@Cmp({selector: 'a-c', template: ''})\nexport class A {}\n" + }, + "diagnostics": [], + "definitions": ["ɵcmp", "ɵfac"], + "decoratorsLeft": [], + "classMetadata": ["Cmp"] + }, + { + "name": "noCall", + "origin": "probe", + "files": { + "test.ts": "import {Component} from '@angular/core';\n@Component\nexport class A {}\n" + }, + "skip": "pre-existing: oxc doesn't report ngtsc's \"@Component is missing a template\" for a bare `@Component`", + "diagnostics": ["@Component is missing a template. Add either a `template` or `templateUrl`"], + "definitions": [], + "decoratorsLeft": ["Component"], + "classMetadata": null + }, + { + "name": "noCallForeign", + "origin": "probe", + "files": { + "test.ts": "import {Component} from './foreign';\n@Component\nexport class A {}\n", + "foreign.ts": "export function Component(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Directive(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Pipe(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Injectable(o?: any): ClassDecorator { return (c: any) => c; }\nexport function NgModule(o?: any): ClassDecorator { return (c: any) => c; }\nexport function Foo(o?: any): ClassDecorator { return (c: any) => c; }\nexport default function Dflt(o?: any): ClassDecorator { return (c: any) => c; }\n" + }, + "diagnostics": [], + "definitions": [], + "decoratorsLeft": ["Component"], + "classMetadata": null + } + ] +} diff --git a/crates/oxc_angular_compiler/tests/fixtures/ctor_param_decorators_ngtsc.json b/crates/oxc_angular_compiler/tests/fixtures/ctor_param_decorators_ngtsc.json new file mode 100644 index 000000000..405a8df55 --- /dev/null +++ b/crates/oxc_angular_compiler/tests/fixtures/ctor_param_decorators_ngtsc.json @@ -0,0 +1,681 @@ +{ + "compilerCli": "22.1.7", + "fixtures": [ + { + "name": "ctor-cmp-named", + "files": { + "test.ts": "import {Component, Inject, Optional, Self, SkipSelf, Host, Attribute} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@Component({selector: 'a-c', template: ''})\nexport class A {\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(TOKEN),i0.ɵɵdirectiveInject(i1.Dep,8),i0.ɵɵdirectiveInject(i1.Dep,2),i0.ɵɵdirectiveInject(i1.Dep,4),i0.ɵɵdirectiveInject(i1.Dep,1),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:Inject,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:Optional}]},{type:i1.Dep,decorators:[{type:Self}]},{type:i1.Dep,decorators:[{type:SkipSelf}]},{type:i1.Dep,decorators:[{type:Host}]},{type:i1.Dep,decorators:[{type:Attribute,args:[\"x\"]}]}]", + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:Inject,args:[TOKEN]}]},{type:Dep,decorators:[{type:Optional}]},{type:Dep,decorators:[{type:Self}]},{type:Dep,decorators:[{type:SkipSelf}]},{type:Dep,decorators:[{type:Host}]},{type:Dep,decorators:[{type:Attribute,args:[\"x\"]}]}]", + "jitParamDecorators": [] + }, + { + "name": "ctor-cmp-alias", + "files": { + "test.ts": "import {Component as Dcl, Inject as InX, Optional as OpX, Self as Slf, SkipSelf as SkS, Host as HoX, Attribute as AtX} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@Dcl({selector: 'a-c', template: ''})\nexport class A {\n constructor(@InX(TOKEN) a: Dep, @OpX() b: Dep, @Slf() c: Dep, @SkS() d: Dep, @HoX() e: Dep, @AtX('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(TOKEN),i0.ɵɵdirectiveInject(i1.Dep,8),i0.ɵɵdirectiveInject(i1.Dep,2),i0.ɵɵdirectiveInject(i1.Dep,4),i0.ɵɵdirectiveInject(i1.Dep,1),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:InX,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:OpX}]},{type:i1.Dep,decorators:[{type:Slf}]},{type:i1.Dep,decorators:[{type:SkS}]},{type:i1.Dep,decorators:[{type:HoX}]},{type:i1.Dep,decorators:[{type:AtX,args:[\"x\"]}]}]", + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:InX,args:[TOKEN]}]},{type:Dep,decorators:[{type:OpX}]},{type:Dep,decorators:[{type:Slf}]},{type:Dep,decorators:[{type:SkS}]},{type:Dep,decorators:[{type:HoX}]},{type:Dep,decorators:[{type:AtX,args:[\"x\"]}]}]", + "jitParamDecorators": [] + }, + { + "name": "ctor-cmp-ns", + "files": { + "test.ts": "import * as ng from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@ng.Component({selector: 'a-c', template: ''})\nexport class A {\n constructor(@ng.Inject(TOKEN) a: Dep, @ng.Optional() b: Dep, @ng.Self() c: Dep, @ng.SkipSelf() d: Dep, @ng.Host() e: Dep, @ng.Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(TOKEN),i0.ɵɵdirectiveInject(i1.Dep,8),i0.ɵɵdirectiveInject(i1.Dep,2),i0.ɵɵdirectiveInject(i1.Dep,4),i0.ɵɵdirectiveInject(i1.Dep,1),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:ng.Inject,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:ng.Optional}]},{type:i1.Dep,decorators:[{type:ng.Self}]},{type:i1.Dep,decorators:[{type:ng.SkipSelf}]},{type:i1.Dep,decorators:[{type:ng.Host}]},{type:i1.Dep,decorators:[{type:ng.Attribute,args:[\"x\"]}]}]", + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:ng.Inject,args:[TOKEN]}]},{type:Dep,decorators:[{type:ng.Optional}]},{type:Dep,decorators:[{type:ng.Self}]},{type:Dep,decorators:[{type:ng.SkipSelf}]},{type:Dep,decorators:[{type:ng.Host}]},{type:Dep,decorators:[{type:ng.Attribute,args:[\"x\"]}]}]", + "jitParamDecorators": [] + }, + { + "name": "ctor-cmp-foreign", + "files": { + "test.ts": "import {Component} from '@angular/core';\nimport {Inject, Optional, Self, SkipSelf, Host, Attribute} from './foreign';\nimport {Dep, TOKEN} from './dep';\n@Component({selector: 'a-c', template: ''})\nexport class A {\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]}]", + "paramDecoratorsLeft": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ], + "jitCtorParameters": "[{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep}]", + "jitParamDecorators": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ] + }, + { + "name": "ctor-cmp-local", + "files": { + "test.ts": "import {Component} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\nfunction Inject(t: any): ParameterDecorator { return () => {}; }\nfunction Optional(): ParameterDecorator { return () => {}; }\nfunction Self(): ParameterDecorator { return () => {}; }\nfunction SkipSelf(): ParameterDecorator { return () => {}; }\nfunction Host(): ParameterDecorator { return () => {}; }\nfunction Attribute(n: string): ParameterDecorator { return () => {}; }\n@Component({selector: 'a-c', template: ''})\nexport class A {\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]}]", + "paramDecoratorsLeft": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ], + "jitCtorParameters": "[{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep}]", + "jitParamDecorators": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ] + }, + { + "name": "ctor-cmp-fns", + "files": { + "test.ts": "import {Component} from '@angular/core';\nimport * as f from './foreign';\nimport {Dep, TOKEN} from './dep';\n@Component({selector: 'a-c', template: ''})\nexport class A {\n constructor(@f.Inject(TOKEN) a: Dep, @f.Optional() b: Dep, @f.Self() c: Dep, @f.SkipSelf() d: Dep, @f.Host() e: Dep, @f.Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]}]", + "paramDecoratorsLeft": [ + "__param(0,f.Inject(TOKEN))", + "__param(1,f.Optional())", + "__param(2,f.Self())", + "__param(3,f.SkipSelf())", + "__param(4,f.Host())", + "__param(5,f.Attribute(\"x\"))" + ], + "jitCtorParameters": "[{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep}]", + "jitParamDecorators": [ + "__param(0,f.Inject(TOKEN))", + "__param(1,f.Optional())", + "__param(2,f.Self())", + "__param(3,f.SkipSelf())", + "__param(4,f.Host())", + "__param(5,f.Attribute(\"x\"))" + ] + }, + { + "name": "ctor-dir-named", + "files": { + "test.ts": "import {Directive, Inject, Optional, Self, SkipSelf, Host, Attribute} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@Directive({selector: '[a]'})\nexport class A {\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(TOKEN),i0.ɵɵdirectiveInject(i1.Dep,8),i0.ɵɵdirectiveInject(i1.Dep,2),i0.ɵɵdirectiveInject(i1.Dep,4),i0.ɵɵdirectiveInject(i1.Dep,1),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:Inject,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:Optional}]},{type:i1.Dep,decorators:[{type:Self}]},{type:i1.Dep,decorators:[{type:SkipSelf}]},{type:i1.Dep,decorators:[{type:Host}]},{type:i1.Dep,decorators:[{type:Attribute,args:[\"x\"]}]}]", + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:Inject,args:[TOKEN]}]},{type:Dep,decorators:[{type:Optional}]},{type:Dep,decorators:[{type:Self}]},{type:Dep,decorators:[{type:SkipSelf}]},{type:Dep,decorators:[{type:Host}]},{type:Dep,decorators:[{type:Attribute,args:[\"x\"]}]}]", + "jitParamDecorators": [] + }, + { + "name": "ctor-dir-alias", + "files": { + "test.ts": "import {Directive as Dcl, Inject as InX, Optional as OpX, Self as Slf, SkipSelf as SkS, Host as HoX, Attribute as AtX} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@Dcl({selector: '[a]'})\nexport class A {\n constructor(@InX(TOKEN) a: Dep, @OpX() b: Dep, @Slf() c: Dep, @SkS() d: Dep, @HoX() e: Dep, @AtX('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(TOKEN),i0.ɵɵdirectiveInject(i1.Dep,8),i0.ɵɵdirectiveInject(i1.Dep,2),i0.ɵɵdirectiveInject(i1.Dep,4),i0.ɵɵdirectiveInject(i1.Dep,1),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:InX,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:OpX}]},{type:i1.Dep,decorators:[{type:Slf}]},{type:i1.Dep,decorators:[{type:SkS}]},{type:i1.Dep,decorators:[{type:HoX}]},{type:i1.Dep,decorators:[{type:AtX,args:[\"x\"]}]}]", + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:InX,args:[TOKEN]}]},{type:Dep,decorators:[{type:OpX}]},{type:Dep,decorators:[{type:Slf}]},{type:Dep,decorators:[{type:SkS}]},{type:Dep,decorators:[{type:HoX}]},{type:Dep,decorators:[{type:AtX,args:[\"x\"]}]}]", + "jitParamDecorators": [] + }, + { + "name": "ctor-dir-ns", + "files": { + "test.ts": "import * as ng from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@ng.Directive({selector: '[a]'})\nexport class A {\n constructor(@ng.Inject(TOKEN) a: Dep, @ng.Optional() b: Dep, @ng.Self() c: Dep, @ng.SkipSelf() d: Dep, @ng.Host() e: Dep, @ng.Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(TOKEN),i0.ɵɵdirectiveInject(i1.Dep,8),i0.ɵɵdirectiveInject(i1.Dep,2),i0.ɵɵdirectiveInject(i1.Dep,4),i0.ɵɵdirectiveInject(i1.Dep,1),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:ng.Inject,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:ng.Optional}]},{type:i1.Dep,decorators:[{type:ng.Self}]},{type:i1.Dep,decorators:[{type:ng.SkipSelf}]},{type:i1.Dep,decorators:[{type:ng.Host}]},{type:i1.Dep,decorators:[{type:ng.Attribute,args:[\"x\"]}]}]", + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:ng.Inject,args:[TOKEN]}]},{type:Dep,decorators:[{type:ng.Optional}]},{type:Dep,decorators:[{type:ng.Self}]},{type:Dep,decorators:[{type:ng.SkipSelf}]},{type:Dep,decorators:[{type:ng.Host}]},{type:Dep,decorators:[{type:ng.Attribute,args:[\"x\"]}]}]", + "jitParamDecorators": [] + }, + { + "name": "ctor-dir-foreign", + "files": { + "test.ts": "import {Directive} from '@angular/core';\nimport {Inject, Optional, Self, SkipSelf, Host, Attribute} from './foreign';\nimport {Dep, TOKEN} from './dep';\n@Directive({selector: '[a]'})\nexport class A {\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]}]", + "paramDecoratorsLeft": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ], + "jitCtorParameters": "[{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep}]", + "jitParamDecorators": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ] + }, + { + "name": "ctor-dir-local", + "files": { + "test.ts": "import {Directive} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\nfunction Inject(t: any): ParameterDecorator { return () => {}; }\nfunction Optional(): ParameterDecorator { return () => {}; }\nfunction Self(): ParameterDecorator { return () => {}; }\nfunction SkipSelf(): ParameterDecorator { return () => {}; }\nfunction Host(): ParameterDecorator { return () => {}; }\nfunction Attribute(n: string): ParameterDecorator { return () => {}; }\n@Directive({selector: '[a]'})\nexport class A {\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]}]", + "paramDecoratorsLeft": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ], + "jitCtorParameters": "[{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep}]", + "jitParamDecorators": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ] + }, + { + "name": "ctor-dir-fns", + "files": { + "test.ts": "import {Directive} from '@angular/core';\nimport * as f from './foreign';\nimport {Dep, TOKEN} from './dep';\n@Directive({selector: '[a]'})\nexport class A {\n constructor(@f.Inject(TOKEN) a: Dep, @f.Optional() b: Dep, @f.Self() c: Dep, @f.SkipSelf() d: Dep, @f.Host() e: Dep, @f.Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]}]", + "paramDecoratorsLeft": [ + "__param(0,f.Inject(TOKEN))", + "__param(1,f.Optional())", + "__param(2,f.Self())", + "__param(3,f.SkipSelf())", + "__param(4,f.Host())", + "__param(5,f.Attribute(\"x\"))" + ], + "jitCtorParameters": "[{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep}]", + "jitParamDecorators": [ + "__param(0,f.Inject(TOKEN))", + "__param(1,f.Optional())", + "__param(2,f.Self())", + "__param(3,f.SkipSelf())", + "__param(4,f.Host())", + "__param(5,f.Attribute(\"x\"))" + ] + }, + { + "name": "ctor-inj-named", + "files": { + "test.ts": "import {Injectable, Inject, Optional, Self, SkipSelf, Host, Attribute} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@Injectable()\nexport class A {\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵinject(TOKEN),i0.ɵɵinject(i1.Dep,8),i0.ɵɵinject(i1.Dep,2),i0.ɵɵinject(i1.Dep,4),i0.ɵɵinject(i1.Dep,1),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:Inject,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:Optional}]},{type:i1.Dep,decorators:[{type:Self}]},{type:i1.Dep,decorators:[{type:SkipSelf}]},{type:i1.Dep,decorators:[{type:Host}]},{type:i1.Dep,decorators:[{type:Attribute,args:[\"x\"]}]}]", + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:Inject,args:[TOKEN]}]},{type:Dep,decorators:[{type:Optional}]},{type:Dep,decorators:[{type:Self}]},{type:Dep,decorators:[{type:SkipSelf}]},{type:Dep,decorators:[{type:Host}]},{type:Dep,decorators:[{type:Attribute,args:[\"x\"]}]}]", + "jitParamDecorators": [] + }, + { + "name": "ctor-inj-alias", + "files": { + "test.ts": "import {Injectable as Dcl, Inject as InX, Optional as OpX, Self as Slf, SkipSelf as SkS, Host as HoX, Attribute as AtX} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@Dcl()\nexport class A {\n constructor(@InX(TOKEN) a: Dep, @OpX() b: Dep, @Slf() c: Dep, @SkS() d: Dep, @HoX() e: Dep, @AtX('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": null, + "ctorParameters": null, + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:InX,args:[TOKEN]}]},{type:Dep,decorators:[{type:OpX}]},{type:Dep,decorators:[{type:Slf}]},{type:Dep,decorators:[{type:SkS}]},{type:Dep,decorators:[{type:HoX}]},{type:Dep,decorators:[{type:AtX,args:[\"x\"]}]}]", + "jitParamDecorators": [], + "skip": "ngtsc gives an aliased `@Injectable` no factory (`needsFactory` reads the decorator's written name), so it has no `ɵfac` to compare; oxc emits one (see `class_decorator_identity_test`)" + }, + { + "name": "ctor-inj-ns", + "files": { + "test.ts": "import * as ng from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@ng.Injectable()\nexport class A {\n constructor(@ng.Inject(TOKEN) a: Dep, @ng.Optional() b: Dep, @ng.Self() c: Dep, @ng.SkipSelf() d: Dep, @ng.Host() e: Dep, @ng.Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵinject(TOKEN),i0.ɵɵinject(i1.Dep,8),i0.ɵɵinject(i1.Dep,2),i0.ɵɵinject(i1.Dep,4),i0.ɵɵinject(i1.Dep,1),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:ng.Inject,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:ng.Optional}]},{type:i1.Dep,decorators:[{type:ng.Self}]},{type:i1.Dep,decorators:[{type:ng.SkipSelf}]},{type:i1.Dep,decorators:[{type:ng.Host}]},{type:i1.Dep,decorators:[{type:ng.Attribute,args:[\"x\"]}]}]", + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:ng.Inject,args:[TOKEN]}]},{type:Dep,decorators:[{type:ng.Optional}]},{type:Dep,decorators:[{type:ng.Self}]},{type:Dep,decorators:[{type:ng.SkipSelf}]},{type:Dep,decorators:[{type:ng.Host}]},{type:Dep,decorators:[{type:ng.Attribute,args:[\"x\"]}]}]", + "jitParamDecorators": [] + }, + { + "name": "ctor-inj-foreign", + "files": { + "test.ts": "import {Injectable} from '@angular/core';\nimport {Inject, Optional, Self, SkipSelf, Host, Attribute} from './foreign';\nimport {Dep, TOKEN} from './dep';\n@Injectable()\nexport class A {\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]}]", + "paramDecoratorsLeft": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ], + "jitCtorParameters": "[{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep}]", + "jitParamDecorators": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ] + }, + { + "name": "ctor-inj-local", + "files": { + "test.ts": "import {Injectable} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\nfunction Inject(t: any): ParameterDecorator { return () => {}; }\nfunction Optional(): ParameterDecorator { return () => {}; }\nfunction Self(): ParameterDecorator { return () => {}; }\nfunction SkipSelf(): ParameterDecorator { return () => {}; }\nfunction Host(): ParameterDecorator { return () => {}; }\nfunction Attribute(n: string): ParameterDecorator { return () => {}; }\n@Injectable()\nexport class A {\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]}]", + "paramDecoratorsLeft": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ], + "jitCtorParameters": "[{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep}]", + "jitParamDecorators": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ] + }, + { + "name": "ctor-inj-fns", + "files": { + "test.ts": "import {Injectable} from '@angular/core';\nimport * as f from './foreign';\nimport {Dep, TOKEN} from './dep';\n@Injectable()\nexport class A {\n constructor(@f.Inject(TOKEN) a: Dep, @f.Optional() b: Dep, @f.Self() c: Dep, @f.SkipSelf() d: Dep, @f.Host() e: Dep, @f.Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]}]", + "paramDecoratorsLeft": [ + "__param(0,f.Inject(TOKEN))", + "__param(1,f.Optional())", + "__param(2,f.Self())", + "__param(3,f.SkipSelf())", + "__param(4,f.Host())", + "__param(5,f.Attribute(\"x\"))" + ], + "jitCtorParameters": "[{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep}]", + "jitParamDecorators": [ + "__param(0,f.Inject(TOKEN))", + "__param(1,f.Optional())", + "__param(2,f.Self())", + "__param(3,f.SkipSelf())", + "__param(4,f.Host())", + "__param(5,f.Attribute(\"x\"))" + ] + }, + { + "name": "ctor-pipe-named", + "files": { + "test.ts": "import {Pipe, Inject, Optional, Self, SkipSelf, Host, Attribute} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@Pipe({name: 'p'})\nexport class A { transform(v: any) { return v; }\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(TOKEN,16),i0.ɵɵdirectiveInject(i1.Dep,24),i0.ɵɵdirectiveInject(i1.Dep,18),i0.ɵɵdirectiveInject(i1.Dep,20),i0.ɵɵdirectiveInject(i1.Dep,17),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:Inject,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:Optional}]},{type:i1.Dep,decorators:[{type:Self}]},{type:i1.Dep,decorators:[{type:SkipSelf}]},{type:i1.Dep,decorators:[{type:Host}]},{type:i1.Dep,decorators:[{type:Attribute,args:[\"x\"]}]}]", + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:Inject,args:[TOKEN]}]},{type:Dep,decorators:[{type:Optional}]},{type:Dep,decorators:[{type:Self}]},{type:Dep,decorators:[{type:SkipSelf}]},{type:Dep,decorators:[{type:Host}]},{type:Dep,decorators:[{type:Attribute,args:[\"x\"]}]}]", + "jitParamDecorators": [] + }, + { + "name": "ctor-pipe-alias", + "files": { + "test.ts": "import {Pipe as Dcl, Inject as InX, Optional as OpX, Self as Slf, SkipSelf as SkS, Host as HoX, Attribute as AtX} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@Dcl({name: 'p'})\nexport class A { transform(v: any) { return v; }\n constructor(@InX(TOKEN) a: Dep, @OpX() b: Dep, @Slf() c: Dep, @SkS() d: Dep, @HoX() e: Dep, @AtX('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(TOKEN,16),i0.ɵɵdirectiveInject(i1.Dep,24),i0.ɵɵdirectiveInject(i1.Dep,18),i0.ɵɵdirectiveInject(i1.Dep,20),i0.ɵɵdirectiveInject(i1.Dep,17),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:InX,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:OpX}]},{type:i1.Dep,decorators:[{type:Slf}]},{type:i1.Dep,decorators:[{type:SkS}]},{type:i1.Dep,decorators:[{type:HoX}]},{type:i1.Dep,decorators:[{type:AtX,args:[\"x\"]}]}]", + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:InX,args:[TOKEN]}]},{type:Dep,decorators:[{type:OpX}]},{type:Dep,decorators:[{type:Slf}]},{type:Dep,decorators:[{type:SkS}]},{type:Dep,decorators:[{type:HoX}]},{type:Dep,decorators:[{type:AtX,args:[\"x\"]}]}]", + "jitParamDecorators": [] + }, + { + "name": "ctor-pipe-ns", + "files": { + "test.ts": "import * as ng from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@ng.Pipe({name: 'p'})\nexport class A { transform(v: any) { return v; }\n constructor(@ng.Inject(TOKEN) a: Dep, @ng.Optional() b: Dep, @ng.Self() c: Dep, @ng.SkipSelf() d: Dep, @ng.Host() e: Dep, @ng.Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(TOKEN,16),i0.ɵɵdirectiveInject(i1.Dep,24),i0.ɵɵdirectiveInject(i1.Dep,18),i0.ɵɵdirectiveInject(i1.Dep,20),i0.ɵɵdirectiveInject(i1.Dep,17),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:ng.Inject,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:ng.Optional}]},{type:i1.Dep,decorators:[{type:ng.Self}]},{type:i1.Dep,decorators:[{type:ng.SkipSelf}]},{type:i1.Dep,decorators:[{type:ng.Host}]},{type:i1.Dep,decorators:[{type:ng.Attribute,args:[\"x\"]}]}]", + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:ng.Inject,args:[TOKEN]}]},{type:Dep,decorators:[{type:ng.Optional}]},{type:Dep,decorators:[{type:ng.Self}]},{type:Dep,decorators:[{type:ng.SkipSelf}]},{type:Dep,decorators:[{type:ng.Host}]},{type:Dep,decorators:[{type:ng.Attribute,args:[\"x\"]}]}]", + "jitParamDecorators": [] + }, + { + "name": "ctor-pipe-foreign", + "files": { + "test.ts": "import {Pipe} from '@angular/core';\nimport {Inject, Optional, Self, SkipSelf, Host, Attribute} from './foreign';\nimport {Dep, TOKEN} from './dep';\n@Pipe({name: 'p'})\nexport class A { transform(v: any) { return v; }\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(i1.Dep,16),i0.ɵɵdirectiveInject(i1.Dep,16),i0.ɵɵdirectiveInject(i1.Dep,16),i0.ɵɵdirectiveInject(i1.Dep,16),i0.ɵɵdirectiveInject(i1.Dep,16),i0.ɵɵdirectiveInject(i1.Dep,16))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]}]", + "paramDecoratorsLeft": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ], + "jitCtorParameters": "[{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep}]", + "jitParamDecorators": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ] + }, + { + "name": "ctor-pipe-local", + "files": { + "test.ts": "import {Pipe} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\nfunction Inject(t: any): ParameterDecorator { return () => {}; }\nfunction Optional(): ParameterDecorator { return () => {}; }\nfunction Self(): ParameterDecorator { return () => {}; }\nfunction SkipSelf(): ParameterDecorator { return () => {}; }\nfunction Host(): ParameterDecorator { return () => {}; }\nfunction Attribute(n: string): ParameterDecorator { return () => {}; }\n@Pipe({name: 'p'})\nexport class A { transform(v: any) { return v; }\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(i1.Dep,16),i0.ɵɵdirectiveInject(i1.Dep,16),i0.ɵɵdirectiveInject(i1.Dep,16),i0.ɵɵdirectiveInject(i1.Dep,16),i0.ɵɵdirectiveInject(i1.Dep,16),i0.ɵɵdirectiveInject(i1.Dep,16))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]}]", + "paramDecoratorsLeft": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ], + "jitCtorParameters": "[{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep}]", + "jitParamDecorators": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ] + }, + { + "name": "ctor-pipe-fns", + "files": { + "test.ts": "import {Pipe} from '@angular/core';\nimport * as f from './foreign';\nimport {Dep, TOKEN} from './dep';\n@Pipe({name: 'p'})\nexport class A { transform(v: any) { return v; }\n constructor(@f.Inject(TOKEN) a: Dep, @f.Optional() b: Dep, @f.Self() c: Dep, @f.SkipSelf() d: Dep, @f.Host() e: Dep, @f.Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(i1.Dep,16),i0.ɵɵdirectiveInject(i1.Dep,16),i0.ɵɵdirectiveInject(i1.Dep,16),i0.ɵɵdirectiveInject(i1.Dep,16),i0.ɵɵdirectiveInject(i1.Dep,16),i0.ɵɵdirectiveInject(i1.Dep,16))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]}]", + "paramDecoratorsLeft": [ + "__param(0,f.Inject(TOKEN))", + "__param(1,f.Optional())", + "__param(2,f.Self())", + "__param(3,f.SkipSelf())", + "__param(4,f.Host())", + "__param(5,f.Attribute(\"x\"))" + ], + "jitCtorParameters": "[{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep}]", + "jitParamDecorators": [ + "__param(0,f.Inject(TOKEN))", + "__param(1,f.Optional())", + "__param(2,f.Self())", + "__param(3,f.SkipSelf())", + "__param(4,f.Host())", + "__param(5,f.Attribute(\"x\"))" + ] + }, + { + "name": "ctor-mod-named", + "files": { + "test.ts": "import {NgModule, Inject, Optional, Self, SkipSelf, Host, Attribute} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@NgModule({})\nexport class A {\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵinject(TOKEN),i0.ɵɵinject(i1.Dep,8),i0.ɵɵinject(i1.Dep,2),i0.ɵɵinject(i1.Dep,4),i0.ɵɵinject(i1.Dep,1),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:Inject,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:Optional}]},{type:i1.Dep,decorators:[{type:Self}]},{type:i1.Dep,decorators:[{type:SkipSelf}]},{type:i1.Dep,decorators:[{type:Host}]},{type:i1.Dep,decorators:[{type:Attribute,args:[\"x\"]}]}]", + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:Inject,args:[TOKEN]}]},{type:Dep,decorators:[{type:Optional}]},{type:Dep,decorators:[{type:Self}]},{type:Dep,decorators:[{type:SkipSelf}]},{type:Dep,decorators:[{type:Host}]},{type:Dep,decorators:[{type:Attribute,args:[\"x\"]}]}]", + "jitParamDecorators": [], + "skip": "pre-existing: oxc's `@NgModule` factory ignores `@Inject(TOKEN)` and injects the parameter's type, whatever the decorator is called" + }, + { + "name": "ctor-mod-alias", + "files": { + "test.ts": "import {NgModule as Dcl, Inject as InX, Optional as OpX, Self as Slf, SkipSelf as SkS, Host as HoX, Attribute as AtX} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@Dcl({})\nexport class A {\n constructor(@InX(TOKEN) a: Dep, @OpX() b: Dep, @Slf() c: Dep, @SkS() d: Dep, @HoX() e: Dep, @AtX('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵinject(TOKEN),i0.ɵɵinject(i1.Dep,8),i0.ɵɵinject(i1.Dep,2),i0.ɵɵinject(i1.Dep,4),i0.ɵɵinject(i1.Dep,1),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:InX,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:OpX}]},{type:i1.Dep,decorators:[{type:Slf}]},{type:i1.Dep,decorators:[{type:SkS}]},{type:i1.Dep,decorators:[{type:HoX}]},{type:i1.Dep,decorators:[{type:AtX,args:[\"x\"]}]}]", + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:InX,args:[TOKEN]}]},{type:Dep,decorators:[{type:OpX}]},{type:Dep,decorators:[{type:Slf}]},{type:Dep,decorators:[{type:SkS}]},{type:Dep,decorators:[{type:HoX}]},{type:Dep,decorators:[{type:AtX,args:[\"x\"]}]}]", + "jitParamDecorators": [], + "skip": "pre-existing: see `ctor-mod-named`" + }, + { + "name": "ctor-mod-ns", + "files": { + "test.ts": "import * as ng from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@ng.NgModule({})\nexport class A {\n constructor(@ng.Inject(TOKEN) a: Dep, @ng.Optional() b: Dep, @ng.Self() c: Dep, @ng.SkipSelf() d: Dep, @ng.Host() e: Dep, @ng.Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵinject(TOKEN),i0.ɵɵinject(i1.Dep,8),i0.ɵɵinject(i1.Dep,2),i0.ɵɵinject(i1.Dep,4),i0.ɵɵinject(i1.Dep,1),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:ng.Inject,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:ng.Optional}]},{type:i1.Dep,decorators:[{type:ng.Self}]},{type:i1.Dep,decorators:[{type:ng.SkipSelf}]},{type:i1.Dep,decorators:[{type:ng.Host}]},{type:i1.Dep,decorators:[{type:ng.Attribute,args:[\"x\"]}]}]", + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:ng.Inject,args:[TOKEN]}]},{type:Dep,decorators:[{type:ng.Optional}]},{type:Dep,decorators:[{type:ng.Self}]},{type:Dep,decorators:[{type:ng.SkipSelf}]},{type:Dep,decorators:[{type:ng.Host}]},{type:Dep,decorators:[{type:ng.Attribute,args:[\"x\"]}]}]", + "jitParamDecorators": [], + "skip": "pre-existing: see `ctor-mod-named`" + }, + { + "name": "ctor-mod-foreign", + "files": { + "test.ts": "import {NgModule} from '@angular/core';\nimport {Inject, Optional, Self, SkipSelf, Host, Attribute} from './foreign';\nimport {Dep, TOKEN} from './dep';\n@NgModule({})\nexport class A {\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]}]", + "paramDecoratorsLeft": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ], + "jitCtorParameters": "[{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep}]", + "jitParamDecorators": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ] + }, + { + "name": "ctor-mod-local", + "files": { + "test.ts": "import {NgModule} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\nfunction Inject(t: any): ParameterDecorator { return () => {}; }\nfunction Optional(): ParameterDecorator { return () => {}; }\nfunction Self(): ParameterDecorator { return () => {}; }\nfunction SkipSelf(): ParameterDecorator { return () => {}; }\nfunction Host(): ParameterDecorator { return () => {}; }\nfunction Attribute(n: string): ParameterDecorator { return () => {}; }\n@NgModule({})\nexport class A {\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]}]", + "paramDecoratorsLeft": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ], + "jitCtorParameters": "[{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep}]", + "jitParamDecorators": [ + "__param(0,Inject(TOKEN))", + "__param(1,Optional())", + "__param(2,Self())", + "__param(3,SkipSelf())", + "__param(4,Host())", + "__param(5,Attribute(\"x\"))" + ] + }, + { + "name": "ctor-mod-fns", + "files": { + "test.ts": "import {NgModule} from '@angular/core';\nimport * as f from './foreign';\nimport {Dep, TOKEN} from './dep';\n@NgModule({})\nexport class A {\n constructor(@f.Inject(TOKEN) a: Dep, @f.Optional() b: Dep, @f.Self() c: Dep, @f.SkipSelf() d: Dep, @f.Host() e: Dep, @f.Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep),i0.ɵɵinject(i1.Dep))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[]}]", + "paramDecoratorsLeft": [ + "__param(0,f.Inject(TOKEN))", + "__param(1,f.Optional())", + "__param(2,f.Self())", + "__param(3,f.SkipSelf())", + "__param(4,f.Host())", + "__param(5,f.Attribute(\"x\"))" + ], + "jitCtorParameters": "[{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep},{type:Dep}]", + "jitParamDecorators": [ + "__param(0,f.Inject(TOKEN))", + "__param(1,f.Optional())", + "__param(2,f.Self())", + "__param(3,f.SkipSelf())", + "__param(4,f.Host())", + "__param(5,f.Attribute(\"x\"))" + ] + }, + { + "name": "ctor-cmp-mixed", + "files": { + "test.ts": "import {Component, Inject as Inj, Optional, Attribute} from '@angular/core';\nimport {Self} from './foreign';\nimport {Dep, TOKEN} from './dep';\n@Component({selector: 'a-c', template: ''})\nexport class A {\n constructor(@Inj(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @Optional() d: Dep, @Self() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(TOKEN),i0.ɵɵdirectiveInject(i1.Dep,8),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(i1.Dep,8),i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:Inj,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:Optional}]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[{type:Optional}]},{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[{type:Attribute,args:[\"x\"]}]}]", + "paramDecoratorsLeft": ["__param(2,Self())", "__param(4,Self())"], + "jitCtorParameters": "[{type:Dep,decorators:[{type:Inj,args:[TOKEN]}]},{type:Dep,decorators:[{type:Optional}]},{type:Dep},{type:Dep,decorators:[{type:Optional}]},{type:Dep},{type:Dep,decorators:[{type:Attribute,args:[\"x\"]}]}]", + "jitParamDecorators": ["__param(2,Self())", "__param(4,Self())"] + }, + { + "name": "ctor-cmp-swapped", + "files": { + "test.ts": "import {Component, Optional as Self, Self as Optional, Inject, SkipSelf as Host, Host as SkipSelf, Attribute} from '@angular/core';\nimport {Dep, TOKEN} from './dep';\n@Component({selector: 'a-c', template: ''})\nexport class A {\n constructor(@Inject(TOKEN) a: Dep, @Optional() b: Dep, @Self() c: Dep, @SkipSelf() d: Dep, @Host() e: Dep, @Attribute('x') f: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(TOKEN),i0.ɵɵdirectiveInject(i1.Dep,2),i0.ɵɵdirectiveInject(i1.Dep,8),i0.ɵɵdirectiveInject(i1.Dep,1),i0.ɵɵdirectiveInject(i1.Dep,4),i0.ɵɵinjectAttribute(\"x\"))", + "ctorParameters": "[{type:i1.Dep,decorators:[{type:Inject,args:[TOKEN]}]},{type:i1.Dep,decorators:[{type:Optional}]},{type:i1.Dep,decorators:[{type:Self}]},{type:i1.Dep,decorators:[{type:SkipSelf}]},{type:i1.Dep,decorators:[{type:Host}]},{type:i1.Dep,decorators:[{type:Attribute,args:[\"x\"]}]}]", + "paramDecoratorsLeft": [], + "jitCtorParameters": "[{type:Dep,decorators:[{type:Inject,args:[TOKEN]}]},{type:Dep,decorators:[{type:Optional}]},{type:Dep,decorators:[{type:Self}]},{type:Dep,decorators:[{type:SkipSelf}]},{type:Dep,decorators:[{type:Host}]},{type:Dep,decorators:[{type:Attribute,args:[\"x\"]}]}]", + "jitParamDecorators": [] + }, + { + "name": "ctor-cmp-other", + "files": { + "test.ts": "import {Component, Inject} from '@angular/core';\nimport {Foo} from './foreign';\nimport {Dep, TOKEN} from './dep';\n@Component({selector: 'a-c', template: ''})\nexport class A {\n constructor(@Foo() a: Dep, @Foo() @Inject(TOKEN) b: Dep, c: Dep) {}\n}\n", + "dep.ts": "export class Dep {}\nexport const TOKEN = 'tok';\n", + "foreign.ts": "export function Inject(t: any): ParameterDecorator { return () => {}; }\nexport function Optional(): ParameterDecorator { return () => {}; }\nexport function Self(): ParameterDecorator { return () => {}; }\nexport function SkipSelf(): ParameterDecorator { return () => {}; }\nexport function Host(): ParameterDecorator { return () => {}; }\nexport function Attribute(n: string): ParameterDecorator { return () => {}; }\nexport function Foo(): ParameterDecorator { return () => {}; }\n" + }, + "diagnostics": [], + "factory": "(i0.ɵɵdirectiveInject(i1.Dep),i0.ɵɵdirectiveInject(TOKEN),i0.ɵɵdirectiveInject(i1.Dep))", + "ctorParameters": "[{type:i1.Dep,decorators:[]},{type:i1.Dep,decorators:[{type:Inject,args:[TOKEN]}]},{type:i1.Dep}]", + "paramDecoratorsLeft": ["__param(0,Foo())", "__param(1,Foo())"], + "jitCtorParameters": "[{type:Dep},{type:Dep,decorators:[{type:Inject,args:[TOKEN]}]},{type:Dep}]", + "jitParamDecorators": ["__param(0,Foo())", "__param(1,Foo())"] + } + ] +} diff --git a/crates/oxc_angular_compiler/tests/fixtures/decorator_metadata_ngtsc.json b/crates/oxc_angular_compiler/tests/fixtures/decorator_metadata_ngtsc.json index 7c64a5630..06ef58f78 100644 --- a/crates/oxc_angular_compiler/tests/fixtures/decorator_metadata_ngtsc.json +++ b/crates/oxc_angular_compiler/tests/fixtures/decorator_metadata_ngtsc.json @@ -8199,1890 +8199,5208 @@ "diagnosticSpans": [] }, { - "name": "probe: queries-member-ViewChildren-emitDistinctChangesOnly-constFalse", + "name": "probe: signal-named", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {emitDistinctChangesOnly: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive, input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n value = input(0);\n req = input.required();\n al = input(0, {alias: 'a'});\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],1);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{value:[1,\"value\"],req:[1,\"req\"],al:[1,\"a\",\"al\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ViewChildren-emitDistinctChangesOnly-constTrue", + "name": "probe: signal-aliased", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {emitDistinctChangesOnly: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive, input as inp} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n value = inp(0);\n req = inp.required({alias: 'r'});\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{value:[1,\"value\"],req:[1,\"r\",\"req\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ViewChildren-emitDistinctChangesOnly-string", + "name": "probe: signal-namespace", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {emitDistinctChangesOnly: 'yes'}) q!: any;\n}\n" + "test.ts": "import * as ng from '@angular/core';\n@ng.Directive({selector: '[d]'})\nexport class Dir {\n value = ng.input(0);\n req = ng.input.required();\n m = ng.model(0);\n mr = ng.model.required();\n o = ng.output();\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{value:[1,\"value\"],req:[1,\"req\"],m:[1,\"m\"],mr:[1,\"mr\"]}", + "outputs": "{m:\"mChange\",mr:\"mrChange\",o:\"o\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } }, - "diagnostics": [ - "@ViewChildren options.emitDistinctChangesOnly must be a boolean Value is of type 'string'." - ], - "diagnosticSpans": [[314, 319]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-ViewChildren-emitDistinctChangesOnly-constString", + "name": "probe: signal-namespaceAndNamed", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = 'yes';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {emitDistinctChangesOnly: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive, input} from '@angular/core';\nimport * as ng from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n value = ng.input(0);\n other = input(1);\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{value:[1,\"value\"],other:[1,\"other\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } }, - "diagnostics": [ - "@ViewChildren options.emitDistinctChangesOnly must be a boolean Value is of type 'string'." - ], - "diagnosticSpans": [[334, 338]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-ViewChildren-emitDistinctChangesOnly-not", + "name": "probe: signal-modelAliased", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {emitDistinctChangesOnly: !0}) q!: any;\n}\n" + "test.ts": "import {Directive, model as m} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n value = m(0);\n req = m.required({alias: 'r'});\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{value:[1,\"value\"],req:[1,\"r\",\"req\"]}", + "outputs": "{value:\"valueChange\",req:\"rChange\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChildren-emitDistinctChangesOnly-constFalse", + "name": "probe: signal-outputAliased", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {emitDistinctChangesOnly: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive, output as out} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n changed = out();\n al = out({alias: 'a'});\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],0);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "outputs": "{changed:\"changed\",al:\"a\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChildren-emitDistinctChangesOnly-constTrue", + "name": "probe: signal-queriesAliased", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {emitDistinctChangesOnly: FLAG}) q!: any;\n}\n" + "test.ts": "import {Component, viewChild as vc, contentChild as cc, viewChildren as vcs, contentChildren as ccs} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Dir {\n a = vc('ref');\n b = vc.required('ref');\n c = cc('ref');\n d = vcs('ref');\n e = ccs('ref', {descendants: true});\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuerySignal(dirIndex,ctx.c,[\"ref\"],5)(dirIndex,ctx.e,[\"ref\"],5);}if(rf&2){i0.ɵɵqueryAdvance(2);}}", + "viewQuery": "{if(rf&1){i0.ɵɵviewQuerySignal(ctx.a,[\"ref\"],5)(ctx.b,[\"ref\"],5)(ctx.d,[\"ref\"],5);}if(rf&2){i0.ɵɵqueryAdvance(3);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChildren-emitDistinctChangesOnly-string", + "name": "probe: signal-queriesNamespace", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {emitDistinctChangesOnly: 'yes'}) q!: any;\n}\n" + "test.ts": "import * as ng from '@angular/core';\n@ng.Component({selector: 'c', template: '
'})\nexport class Dir {\n a = ng.viewChild('ref');\n b = ng.viewChild.required('ref');\n c = ng.contentChildren('ref');\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuerySignal(dirIndex,ctx.c,[\"ref\"],4);}if(rf&2){i0.ɵɵqueryAdvance();}}", + "viewQuery": "{if(rf&1){i0.ɵɵviewQuerySignal(ctx.a,[\"ref\"],5)(ctx.b,[\"ref\"],5);}if(rf&2){i0.ɵɵqueryAdvance(2);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } }, - "diagnostics": [ - "@ContentChildren options.emitDistinctChangesOnly must be a boolean Value is of type 'string'." - ], - "diagnosticSpans": [[317, 322]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChildren-emitDistinctChangesOnly-constString", + "name": "probe: signal-fromObservableNamed", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = 'yes';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {emitDistinctChangesOnly: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nimport {outputFromObservable} from '@angular/core/rxjs-interop';\nimport {Subject} from 'rxjs';\n@Directive({selector: '[d]'})\nexport class Dir {\n changed = outputFromObservable(new Subject());\n al = outputFromObservable(new Subject(), {alias: 'a'});\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "outputs": "{changed:\"changed\",al:\"a\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } }, - "diagnostics": [ - "@ContentChildren options.emitDistinctChangesOnly must be a boolean Value is of type 'string'." - ], - "diagnosticSpans": [[337, 341]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChildren-emitDistinctChangesOnly-not", + "name": "probe: signal-fromObservableAliased", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {emitDistinctChangesOnly: !0}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nimport {outputFromObservable as ofo} from '@angular/core/rxjs-interop';\nimport {Subject} from 'rxjs';\n@Directive({selector: '[d]'})\nexport class Dir {\n changed = ofo(new Subject());\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "outputs": "{changed:\"changed\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChildren-descendants-constFalse", + "name": "probe: signal-fromObservableNamespace", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {descendants: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nimport * as rx from '@angular/core/rxjs-interop';\nimport {Subject} from 'rxjs';\n@Directive({selector: '[d]'})\nexport class Dir {\n changed = rx.outputFromObservable(new Subject());\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "outputs": "{changed:\"changed\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChildren-descendants-constTrue", + "name": "probe: signal-asAndParens", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {descendants: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive, input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n value = (input(0)) as any;\n req = (input.required() as any);\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{value:[1,\"value\"],req:[1,\"req\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChildren-descendants-string", + "name": "probe: signal-undeclared", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {descendants: 'yes'}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n value = input(0);\n req = input.required();\n changed = output();\n m = model(0);\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } }, - "diagnostics": [ - "@ContentChildren options.descendants must be a boolean Value is of type 'string'." - ], - "diagnosticSpans": [[305, 310]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChildren-descendants-constString", + "name": "probe: signal-undeclaredListed", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = 'yes';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {descendants: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\n@Directive({selector: '[d]', inputs: ['value']})\nexport class Dir {\n value = input(0);\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{value:\"value\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } }, - "diagnostics": [ - "@ContentChildren options.descendants must be a boolean Value is of type 'string'." - ], - "diagnosticSpans": [[325, 329]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChildren-descendants-not", + "name": "probe: signal-undeclaredOutputListed", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {descendants: !0}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\n@Directive({selector: '[d]', outputs: ['changed']})\nexport class Dir {\n changed = output();\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "outputs": "{changed:\"changed\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ViewChildren-descendants-constFalse", + "name": "probe: signal-undeclaredQueries", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants: FLAG}) q!: any;\n}\n" + "test.ts": "import {Component} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Dir {\n a = viewChild('ref');\n b = contentChildren('ref');\n c = viewChild.required('ref');\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ViewChildren-descendants-constTrue", + "name": "probe: signal-localFunction", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nfunction input(v?: any, o?: any): any { return v; }\n@Directive({selector: '[d]'})\nexport class Dir {\n value = input(0);\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ViewChildren-descendants-string", + "name": "probe: signal-localFunctionListed", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants: 'yes'}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nfunction input(v?: any, o?: any): any { return v; }\n@Directive({selector: '[d]', inputs: ['value']})\nexport class Dir {\n value = input(0);\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{value:\"value\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } }, - "diagnostics": [ - "@ViewChildren options.descendants must be a boolean Value is of type 'string'." - ], - "diagnosticSpans": [[302, 307]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-ViewChildren-descendants-constString", + "name": "probe: signal-localObject", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = 'yes';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nconst local = { input: (v?: any, o?: any): any => v, output: (o?: any): any => null, model: (v?: any): any => v, viewChild: (p: any): any => null };\n@Directive({selector: '[d]'})\nexport class Dir {\n value = local.input(0);\n changed = local.output();\n m = local.model(0);\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } }, - "diagnostics": [ - "@ViewChildren options.descendants must be a boolean Value is of type 'string'." - ], - "diagnosticSpans": [[322, 326]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-ViewChildren-descendants-not", + "name": "probe: signal-localObjectListed", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants: !0}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nconst local = { input: (v?: any, o?: any): any => v, output: (o?: any): any => null, model: (v?: any): any => v, viewChild: (p: any): any => null };\n@Directive({selector: '[d]', inputs: ['value']})\nexport class Dir {\n value = local.input(0);\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{value:\"value\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChild-descendants-constFalse", + "name": "probe: signal-localObjectQuery", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {descendants: FLAG}) q!: any;\n}\n" + "test.ts": "import {Component} from '@angular/core';\nconst local = { input: (v?: any, o?: any): any => v, output: (o?: any): any => null, model: (v?: any): any => v, viewChild: (p: any): any => null };\n@Component({selector: 'c', template: '
'})\nexport class Dir {\n a = local.viewChild('ref');\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChild-descendants-constTrue", + "name": "probe: signal-otherModuleOutputFromObservable", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {descendants: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nimport {outputFromObservable} from './other';\nimport {Subject} from 'rxjs';\n@Directive({selector: '[d]'})\nexport class Dir {\n changed = outputFromObservable(new Subject());\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChild-descendants-string", + "name": "probe: signal-callParenthesized", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {descendants: 'yes'}) q!: any;\n}\n" + "test.ts": "import {Directive, input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n value = (input)(0);\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } }, - "diagnostics": [ - "@ContentChild options.descendants must be a boolean Value is of type 'string'." - ], - "diagnosticSpans": [[302, 307]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChild-descendants-constString", + "name": "probe: signal-elementAccess", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = 'yes';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {descendants: FLAG}) q!: any;\n}\n" + "test.ts": "import * as ng from '@angular/core';\n@ng.Directive({selector: '[d]'})\nexport class Dir {\n value = ng['input'](0);\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } }, - "diagnostics": [ - "@ContentChild options.descendants must be a boolean Value is of type 'string'." - ], - "diagnosticSpans": [[322, 326]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChild-descendants-not", + "name": "probe: signal-coreNamedWrongName", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {descendants: !0}) q!: any;\n}\n" + "test.ts": "import {Directive, model as input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n value = input(0);\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{value:[1,\"value\"]}", + "outputs": "{value:\"valueChange\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ViewChild-static-constFalse", + "name": "probe: signal-otherModuleNamed", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nimport {input, output, model} from './other';\n@Directive({selector: '[d]'})\nexport class Dir {\n value = input(0);\n req = input.required();\n changed = output();\n m = model(0);\n}\n", + "other.ts": "export function input(v?: any, o?: any): any { return v; }\nexport namespace input { export function required(o?: any): any { return null; } }\nexport function output(o?: any): any { return null; }\nexport function model(v?: any, o?: any): any { return v; }\nexport namespace model { export function required(o?: any): any { return null; } }\nexport function viewChild(p: any, o?: any): any { return null; }\nexport namespace viewChild { export function required(p: any, o?: any): any { return null; } }\nexport function contentChildren(p: any, o?: any): any { return null; }\nexport function outputFromObservable(s: any, o?: any): any { return null; }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ViewChild-static-constTrue", + "name": "probe: signal-otherModuleNamedListed", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nimport {input} from './other';\n@Directive({selector: '[d]', inputs: ['value']})\nexport class Dir {\n value = input(0);\n}\n", + "other.ts": "export function input(v?: any, o?: any): any { return v; }\nexport namespace input { export function required(o?: any): any { return null; } }\nexport function output(o?: any): any { return null; }\nexport function model(v?: any, o?: any): any { return v; }\nexport namespace model { export function required(o?: any): any { return null; } }\nexport function viewChild(p: any, o?: any): any { return null; }\nexport namespace viewChild { export function required(p: any, o?: any): any { return null; } }\nexport function contentChildren(p: any, o?: any): any { return null; }\nexport function outputFromObservable(s: any, o?: any): any { return null; }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{value:\"value\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ViewChild-static-string", + "name": "probe: signal-otherModuleNamespace", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: 'yes'}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nimport * as o from './other';\n@Directive({selector: '[d]'})\nexport class Dir {\n value = o.input(0);\n req = o.input.required();\n changed = o.output();\n}\n", + "other.ts": "export function input(v?: any, o?: any): any { return v; }\nexport namespace input { export function required(o?: any): any { return null; } }\nexport function output(o?: any): any { return null; }\nexport function model(v?: any, o?: any): any { return v; }\nexport namespace model { export function required(o?: any): any { return null; } }\nexport function viewChild(p: any, o?: any): any { return null; }\nexport namespace viewChild { export function required(p: any, o?: any): any { return null; } }\nexport function contentChildren(p: any, o?: any): any { return null; }\nexport function outputFromObservable(s: any, o?: any): any { return null; }\n" }, - "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], - "diagnosticSpans": [[280, 283]], - "classes": {}, - "dtsImports": [] - }, - { - "name": "probe: queries-member-ViewChild-static-constString", - "origin": "probe", - "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = 'yes';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: FLAG}) q!: any;\n}\n" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } }, - "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], - "diagnosticSpans": [[300, 303]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-ViewChild-static-not", + "name": "probe: signal-otherModuleQueries", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: !0}) q!: any;\n}\n" + "test.ts": "import {Component} from '@angular/core';\nimport {viewChild, contentChildren} from './other';\n@Component({selector: 'c', template: '
'})\nexport class Dir {\n a = viewChild('ref');\n b = viewChild.required('ref');\n c = contentChildren('ref');\n}\n", + "other.ts": "export function input(v?: any, o?: any): any { return v; }\nexport namespace input { export function required(o?: any): any { return null; } }\nexport function output(o?: any): any { return null; }\nexport function model(v?: any, o?: any): any { return v; }\nexport namespace model { export function required(o?: any): any { return null; } }\nexport function viewChild(p: any, o?: any): any { return null; }\nexport namespace viewChild { export function required(p: any, o?: any): any { return null; } }\nexport function contentChildren(p: any, o?: any): any { return null; }\nexport function outputFromObservable(s: any, o?: any): any { return null; }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChild-static-constFalse", + "name": "probe: signal-otherModuleFromObservableAliased", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {static: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nimport {outputFromObservable as ofo} from './other';\n@Directive({selector: '[d]'})\nexport class Dir {\n changed = ofo(null);\n}\n", + "other.ts": "export function input(v?: any, o?: any): any { return v; }\nexport namespace input { export function required(o?: any): any { return null; } }\nexport function output(o?: any): any { return null; }\nexport function model(v?: any, o?: any): any { return v; }\nexport namespace model { export function required(o?: any): any { return null; } }\nexport function viewChild(p: any, o?: any): any { return null; }\nexport namespace viewChild { export function required(p: any, o?: any): any { return null; } }\nexport function contentChildren(p: any, o?: any): any { return null; }\nexport function outputFromObservable(s: any, o?: any): any { return null; }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChild-static-constTrue", + "name": "probe: signal-mixed", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {static: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive, input, output} from '@angular/core';\nimport * as o from './other';\n@Directive({selector: '[d]'})\nexport class Dir {\n real = input(0);\n fake = o.input(0);\n realOut = output();\n fakeOut = o.output();\n}\n", + "other.ts": "export function input(v?: any, o?: any): any { return v; }\nexport namespace input { export function required(o?: any): any { return null; } }\nexport function output(o?: any): any { return null; }\nexport function model(v?: any, o?: any): any { return v; }\nexport namespace model { export function required(o?: any): any { return null; } }\nexport function viewChild(p: any, o?: any): any { return null; }\nexport namespace viewChild { export function required(p: any, o?: any): any { return null; } }\nexport function contentChildren(p: any, o?: any): any { return null; }\nexport function outputFromObservable(s: any, o?: any): any { return null; }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{real:[1,\"real\"]}", + "outputs": "{realOut:\"realOut\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChild-static-string", + "name": "probe: signal-defaultImport", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {static: 'yes'}) q!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nimport input from './other-default';\n@Directive({selector: '[d]'})\nexport class Dir {\n value = input(0);\n}\n", + "other-default.ts": "export default function input(v?: any): any { return v; }\n" }, - "diagnostics": ["@ContentChild options.static must be a boolean Value is of type 'string'."], - "diagnosticSpans": [[283, 286]], - "classes": {}, - "dtsImports": [] - }, - { - "name": "probe: queries-member-ContentChild-static-constString", - "origin": "probe", - "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = 'yes';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {static: FLAG}) q!: any;\n}\n" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } }, - "diagnostics": ["@ContentChild options.static must be a boolean Value is of type 'string'."], - "diagnosticSpans": [[303, 306]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-ContentChild-static-not", + "name": "probe: shorthandRef-importedConst", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {static: !0}) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst transform = booleanAttribute;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-static-number", + "name": "probe: shorthandRef-importedConstDeco", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: 1}) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst transform = booleanAttribute;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform}) x!: any;\n}\n" }, - "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'number'."], - "diagnosticSpans": [[280, 283]], - "classes": {}, - "dtsImports": [] - }, - { - "name": "probe: queries-member-static-null", - "origin": "probe", - "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: null}) q!: any;\n}\n" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" + } }, - "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'null'."], - "diagnosticSpans": [[280, 283]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-static-undefined", + "name": "probe: shorthandRef-importedConstNamed", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: undefined}) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst t = booleanAttribute;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform: t}]})\nexport class Dir {\n x!: any;\n}\n" }, - "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'undefined'."], - "diagnosticSpans": [[280, 283]], - "classes": {}, - "dtsImports": [] - }, - { - "name": "probe: queries-member-static-unknownIdent", - "origin": "probe", - "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\ndeclare const someGlobal: boolean;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: someGlobal}) q!: any;\n}\n" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" + } }, - "diagnostics": [ - "@ViewChild options.static must be a boolean Value is a reference to 'someGlobal'." - ], - "diagnosticSpans": [[315, 318]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-desc-letUninit", + "name": "probe: shorthandRef-importedConstNamedSame", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nlet FLAG;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants: FLAG}) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst transform = booleanAttribute;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform: transform}]})\nexport class Dir {\n x!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" + } }, - "diagnostics": [ - "@ViewChildren options.descendants must be a boolean Value is of type 'undefined'." - ], - "diagnosticSpans": [[312, 316]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-static-ternary", + "name": "probe: shorthandRef-importedLet", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst A = 1;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: A ? true : false}) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nlet transform = numberAttribute;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",numberAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-static-enum", + "name": "probe: shorthandRef-importedExported", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nenum E { A }\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: E.A}) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nexport const transform = booleanAttribute;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, - "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'E'."], - "diagnosticSpans": [[293, 296]], - "classes": {}, - "dtsImports": [] - }, - { - "name": "probe: queries-member-static-predTypeRef", - "origin": "probe", - "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild(ElementRef, {static: 'no'}) q!: any;\n}\n" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" + } }, - "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], - "diagnosticSpans": [[280, 290]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-optsConst", + "name": "probe: shorthandRef-importedTyped", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst OPTS = {static: true};\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', OPTS) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst transform: (v: unknown) => boolean = booleanAttribute;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" + } }, - "diagnostics": [ - "@ViewChild options must be an object literal. Find more at https://next.angular.dev/errors/NG1001" - ], - "diagnosticSpans": [[314, 318]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-optsParens", + "name": "probe: shorthandRef-importedDestructObj", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', ({static: true})) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst {transform} = {transform: booleanAttribute};\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-optsAs", + "name": "probe: shorthandRef-importedDestructArr", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: true} as any) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst [transform] = [booleanAttribute];\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-optsSpread", + "name": "probe: shorthandRef-importedAs", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst OPTS = {static: true};\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {...OPTS}) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst transform = booleanAttribute as any;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-optsSpreadThen", + "name": "probe: shorthandRef-importedParens", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst OPTS = {static: true, descendants: true};\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {...OPTS, descendants: false}) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst transform = (booleanAttribute);\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-optsString", + "name": "probe: shorthandRef-chain", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', 'str') q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst t = booleanAttribute;\nconst transform = t;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" + } }, - "diagnostics": [ - "@ViewChild options must be an object literal. Find more at https://next.angular.dev/errors/NG1001" - ], - "diagnosticSpans": [[285, 290]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-readConst", + "name": "probe: shorthandRef-chainDeco", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst R = ElementRef;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {read: R}) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst t = booleanAttribute;\nconst transform = t;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform}) x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5,R);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-readNumber", + "name": "probe: shorthandRef-localFn", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {read: 1}) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nfunction toNum(v: string) { return 1; }\nconst transform = toNum;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5,1);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",toNum]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:string;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-quotedKey", + "name": "probe: shorthandRef-localFnDeco", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {'descendants': false, \"emitDistinctChangesOnly\": false}) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nfunction toNum(v: string) { return 1; }\nconst transform = toNum;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform}) x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],0);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",toNum]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:string;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-computedKey", + "name": "probe: shorthandRef-localExportedFn", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {['descendants']: false}) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nexport function toNum(v: string) { return 1; }\nconst transform = toNum;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",toNum]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:string;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-shorthand", + "name": "probe: shorthandRef-localFnDeclShorthand", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst descendants = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants}) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nfunction transform(v: string) { return 1; }\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",transform]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:string;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-multiErr", + "name": "probe: shorthandRef-localFnDeclShorthandDeco", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants: 'a', emitDistinctChangesOnly: 'b'}) q!: any;\n @ViewChild('x', {static: 'c'}) r!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nfunction transform(v: string) { return 1; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform}) x!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",transform]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:string;" + } }, - "diagnostics": [ - "@ViewChildren options.descendants must be a boolean Value is of type 'string'." - ], - "diagnosticSpans": [[302, 305]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-setter", + "name": "probe: shorthandRef-staticMethod", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {emitDistinctChangesOnly: FLAG}) set q(v: any) {}\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nclass U { static c(v: string) { return 1; } }\nconst transform = U.c;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], - "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],1);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",c]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:string;" } }, + "dtsImports": [], + "skip": "ngtsc emits the static method's bare name (`c`), which isn't in scope where the metadata is compiled; oxc keeps `transform` (see `static_method_transform_is_not_confused_with_a_same_named_function`)" + }, + { + "name": "probe: shorthandRef-localObjMember", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst ns = {fn: (v: string) => 1};\nconst transform = ns.fn;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" + }, + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[136, 141]], + "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-options-ConstFalse", + "name": "probe: shorthandRef-localObjFnMember", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
', queries: {q: new ViewChildren('x', {emitDistinctChangesOnly: FLAG})}})\nexport class Cmp {\n q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nfunction toNum(v: string) { return 1; }\nconst ns = {fn: toNum};\nconst transform = ns.fn;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],1);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",toNum]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:string;" } }, "dtsImports": [] }, { - "name": "probe: queries-options-OptsConst", + "name": "probe: shorthandRef-localConstArrow", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst OPTS = {static: true};\n@Component({selector: 'c', template: '
', queries: {q: new ViewChild('x', OPTS)}})\nexport class Cmp {\n q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst toNum = (v: string) => 1;\nconst transform = toNum;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [ - "@ViewChild options must be an object literal. Find more at https://next.angular.dev/errors/NG1001" + "Input transform must be a function Value could not be determined statically." ], - "diagnosticSpans": [[308, 312]], + "diagnosticSpans": [[133, 138]], "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-options-StaticString", + "name": "probe: shorthandRef-importedFromOtherNamed", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {q: new ViewChild('x', {static: 'c'})}})\nexport class Cmp {\n q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nimport {toNum} from './util';\nconst transform = toNum;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n", + "util.ts": "export function toNum(v: string) { return 1; }\n" }, - "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], - "diagnosticSpans": [[274, 277]], + "diagnostics": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",toNum]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:string;" + } + }, + "dtsImports": [], + "skip": "needs declarations from another file; oxc compiles one file at a time (imported consts, types and transforms are opaque)" + }, + { + "name": "probe: shorthandRef-importedShorthandDirect", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nimport {toNum as transform} from './util';\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n", + "util.ts": "export function toNum(v: string) { return 1; }\n" + }, + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[176, 185]], "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-predicate-queriesFnExpr", + "name": "probe: shorthandRef-nsImportMember", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(function Token() {})}})\nexport class Cmp {\n item: any;\n}\n" - }, - "diagnostics": [], - "diagnosticSpans": [], - "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(functionToken(){},5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" - } + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nimport * as u from './util';\nconst transform = u.toNum;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n", + "util.ts": "export function toNum(v: string) { return 1; }\n" }, + "diagnostics": [ + "Input transform function could not be referenced Value is a reference to 'toNum'." + ], + "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-predicate-queriesArrow", + "name": "probe: shorthandRef-nsImportMemberDeco", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(() => ElementRef)}})\nexport class Cmp {\n item: any;\n}\n" - }, - "diagnostics": [], - "diagnosticSpans": [], - "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(()=>ElementRef,5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" - } + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nimport * as u from './util';\nconst transform = u.toNum;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform}) x!: any;\n}\n", + "util.ts": "export function toNum(v: string) { return 1; }\n" }, + "diagnostics": [ + "Input transform function could not be referenced Value is a reference to 'toNum'." + ], + "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-predicate-queriesCall", + "name": "probe: shorthandRef-coreNsMember", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nfunction mk(): any { return ElementRef; }\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(mk())}})\nexport class Cmp {\n item: any;\n}\n" - }, - "diagnostics": [], - "diagnosticSpans": [], - "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(mk(),5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" - } + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nimport * as core from '@angular/core';\nconst transform = core.booleanAttribute;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, + "diagnostics": [ + "Input transform function could not be referenced Value is a reference to 'booleanAttribute'." + ], + "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-predicate-queriesCallArg", + "name": "probe: shorthandRef-ternary", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\ndeclare function mk(v: any): any;\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(mk(1))}})\nexport class Cmp {\n item: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst transform = true ? booleanAttribute : numberAttribute;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(mk(1),5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: queries-predicate-queriesAs", + "name": "probe: shorthandRef-globalFn", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(ElementRef as any)}})\nexport class Cmp {\n item: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst transform = parseInt;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(ElementRef,5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",parseInt]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:string;" } }, "dtsImports": [] }, { - "name": "probe: queries-predicate-queriesMember", + "name": "probe: shorthandRef-spreadObj", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst T = {ref: ElementRef};\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(T.ref)}})\nexport class Cmp {\n item: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst transform = booleanAttribute;\nconst o = {name: 'x', transform};\n@Directive({selector: '[d]', inputs: [o]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(T.ref,5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: queries-predicate-queriesGlobal", + "name": "probe: shorthandRef-decoOpts", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(window)}})\nexport class Cmp {\n item: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nconst transform = booleanAttribute;\nconst OPTS = {transform};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(OPTS) x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(window,5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: queries-predicate-queriesTemplate", + "name": "probe: shorthandRef-aliasedImportConst", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(`x`)}})\nexport class Cmp {\n item: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nimport {booleanAttribute as ba} from '@angular/core';\nconst transform = ba;\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",ba]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: queries-predicate-queriesNumber", + "name": "probe: shorthandRef-aliasedImportNamed", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(1)}})\nexport class Cmp {\n item: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nimport {booleanAttribute as ba} from '@angular/core';\n@Directive({selector: '[d]', inputs: [{name: 'x', transform: ba}]})\nexport class Dir {\n x!: any;\n}\n" }, - "diagnostics": ["@ViewChild predicate cannot be interpreted Value is of type 'number'."], - "diagnosticSpans": [[277, 278]], - "classes": {}, - "dtsImports": [] - }, - { - "name": "probe: queries-predicate-queriesObject", - "origin": "probe", - "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild({})}})\nexport class Cmp {\n item: any;\n}\n" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",ba]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" + } }, - "diagnostics": ["@ViewChild predicate cannot be interpreted Value is of type '{}'."], - "diagnosticSpans": [[277, 279]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-predicate-queriesNew", + "name": "probe: shorthandRef-aliasedImportDeco", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(new ElementRef(null))}})\nexport class Cmp {\n item: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nimport {booleanAttribute as ba} from '@angular/core';\nconst t = ba;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: t}) x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(newElementRef(null),5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",ba]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: queries-predicate-memberFnExpr", + "name": "probe: shorthandRef-helperParamMember", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild(function Token() {}) item: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nfunction make(o: any) { return [{ name: 'x', transform: o.t }]; }\n@Directive({selector: '[d]', inputs: make({ t: booleanAttribute })})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(functionToken(){},5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: queries-predicate-memberArrow", + "name": "probe: shorthandRef-helperParamMemberAliased", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild(() => ElementRef) item: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nimport {booleanAttribute as ba} from '@angular/core';\nfunction make(o: any) { return [{ name: 'x', transform: o.t }]; }\n@Directive({selector: '[d]', inputs: make({ t: ba })})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(()=>ElementRef,5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",ba]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: queries-predicate-memberCall", + "name": "probe: shorthandRef-helperParam", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nfunction mk(): any { return ElementRef; }\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild(mk()) item: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nfunction make(t: any) { return [{ name: 'x', transform: t }]; }\nconst b = booleanAttribute;\n@Directive({selector: '[d]', inputs: make(b)})\nexport class Dir {\n x!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(mk(),5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: queries-predicate-memberNumber", + "name": "probe: shorthandRef-importCoreShorthand", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild(1 as any) item: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nimport {booleanAttribute as transform} from '@angular/core';\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, - "diagnostics": ["@ViewChild predicate cannot be interpreted Value is of type 'number'."], - "diagnosticSpans": [[280, 288]], + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[194, 203]], "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-noArgs", + "name": "probe: shorthandRef-importCoreShorthandDeco", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild() q!: any;\n}\n" + "test.ts": "import {Directive, Input, numberAttribute} from '@angular/core';\nimport {booleanAttribute as transform} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform}) x!: any;\n}\n" }, - "diagnostics": ["@ViewChild must have arguments"], - "diagnosticSpans": [[269, 290]], + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[185, 194]], "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-noCall", + "name": "probe: shorthandRef-importNsShorthand", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nimport * as transform from './util';\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n", + "util.ts": "export function toNum(v: string) { return 1; }\nexport const NAME = 'x';\nexport const ALIAS = 'y';\nexport const REQ = true;\nexport class U { static fn(v: string) { return 1; } }\n" }, - "diagnostics": ["@ViewChild must have arguments"], - "diagnosticSpans": [[269, 288]], + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[170, 179]], "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-tooMany", + "name": "probe: shorthandRef-importName", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {}, 1 as any) q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nimport {NAME as name} from './util';\n@Directive({selector: '[d]', inputs: [{name}]})\nexport class Dir {\n x!: any;\n}\n", + "util.ts": "export function toNum(v: string) { return 1; }\nexport const NAME = 'x';\nexport const ALIAS = 'y';\nexport const REQ = true;\nexport class U { static fn(v: string) { return 1; } }\n" }, - "diagnostics": ["@ViewChild has too many arguments"], - "diagnosticSpans": [[280, 283]], + "diagnostics": [ + "Value at position 0 of @Directive.inputs array must have a \"name\" property Value could not be determined statically." + ], + "diagnosticSpans": [[157, 165]], "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-noArgsSetter", + "name": "probe: shorthandRef-importAlias", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren() set q(v: any) {}\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nimport {ALIAS as alias} from './util';\n@Directive({selector: '[d]', inputs: [{name: 'x', alias}]})\nexport class Dir {\n x!: any;\n}\n", + "util.ts": "export function toNum(v: string) { return 1; }\nexport const NAME = 'x';\nexport const ALIAS = 'y';\nexport const REQ = true;\nexport class U { static fn(v: string) { return 1; } }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } }, - "diagnostics": ["@ContentChildren must have arguments"], - "diagnosticSpans": [[269, 304]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-twoMembers", + "name": "probe: shorthandRef-importRequired", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: 'a'}) a!: any;\n @ContentChild('x', {descendants: 'b'}) b!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nimport {REQ as required} from './util';\n@Directive({selector: '[d]', inputs: [{name: 'x', required}]})\nexport class Dir {\n x!: any;\n}\n", + "util.ts": "export function toNum(v: string) { return 1; }\nexport const NAME = 'x';\nexport const ALIAS = 'y';\nexport const REQ = true;\nexport class U { static fn(v: string) { return 1; } }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } }, - "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], - "diagnosticSpans": [[280, 283]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-contentFirstInOrder", + "name": "probe: shorthandRef-importAliasDeco", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {descendants: 'b'}) b!: any;\n @ViewChild('x', {static: 'a'}) a!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\nimport {ALIAS as alias} from './util';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({alias}) x!: any;\n}\n", + "util.ts": "export function toNum(v: string) { return 1; }\nexport const NAME = 'x';\nexport const ALIAS = 'y';\nexport const REQ = true;\nexport class U { static fn(v: string) { return 1; } }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } }, - "diagnostics": [ - "@ContentChild options.descendants must be a boolean Value is of type 'string'." - ], - "diagnosticSpans": [[302, 305]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-memberVsQueries", + "name": "probe: shorthandRef-globalShorthandMissing", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {q: new ViewChild('x', {static: 'c'})}})\nexport class Cmp {\n @ViewChild('x', {static: 'a'}) a!: any;\n q!: any;\n}\n" + "test.ts": "import {Directive, Input, booleanAttribute, numberAttribute} from '@angular/core';\n@Directive({selector: '[d]', inputs: [{name: 'x', transform}]})\nexport class Dir {\n x!: any;\n}\n" }, - "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], - "diagnosticSpans": [[329, 332]], + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[133, 142]], "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-inputVsMember", + "name": "probe: signal-namespaceAliasOptions", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: 'a'}) a!: any;\n @Input({transform: 1 as any}) i!: any;\n}\n" + "test.ts": "import * as ng from '@angular/core';\n@ng.Directive({selector: '[d]'})\nexport class Dir {\n a = ng.input(0, {alias: 'aa'});\n b = ng.model(0, {alias: 'bb'});\n c = ng.output({alias: 'cc'});\n d = ng.input.required({alias: 'dd'});\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{a:[1,\"aa\",\"a\"],b:[1,\"bb\",\"b\"],d:[1,\"dd\",\"d\"]}", + "outputs": "{b:\"bbChange\",c:\"cc\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } }, - "diagnostics": ["Input transform must be a function Value is of type 'number'."], - "diagnosticSpans": [[318, 339]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-outputsMetaVsMember", + "name": "probe: shorthandRef-importQuery", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', outputs: 1 as any})\nexport class Cmp {\n @ViewChild('x', {static: 'a'}) a!: any;\n}\n" + "test.ts": "import {Directive, ViewChild} from '@angular/core';\nimport {foo} from './util';\n@Directive({selector: '[d]', queries: {foo}})\nexport class Dir {\n foo: any;\n}\n", + "util.ts": "import {ViewChild} from '@angular/core';\nexport const foo = new ViewChild('x');\nexport const x = 'x';\n" }, - "diagnostics": [ - "Failed to resolve @Directive.outputs to a string array Value is of type 'number'." - ], - "diagnosticSpans": [[256, 264]], + "diagnostics": ["Decorator query metadata must be an instance of a query type"], + "diagnosticSpans": [[118, 123]], "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-forwardRefStatic", + "name": "probe: shorthandRef-importQueriesSpread", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {forwardRef} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild(forwardRef(() => ElementRef), {static: 'a'}) a!: any;\n}\n" + "test.ts": "import {Directive, ViewChild} from '@angular/core';\nimport {foo} from './util';\nconst q = {foo};\n@Directive({selector: '[d]', queries: q})\nexport class Dir {\n foo: any;\n}\n", + "util.ts": "import {ViewChild} from '@angular/core';\nexport const foo = new ViewChild('x');\nexport const x = 'x';\n" }, - "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], - "diagnosticSpans": [[339, 349]], + "diagnostics": ["Decorator queries metadata must be an object literal"], + "diagnosticSpans": [[135, 136]], "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-predicateArrayConst", + "name": "probe: memberDecorator-otherModuleTransform", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst SELS = ['a', 'b'];\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren(SELS, {emitDistinctChangesOnly: false}) a!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nimport {Input} from './foreign';\n@Directive({selector: '[d]'})\nexport class D { @Input({transform: 5}) x: any; }\n", + "foreign.ts": "export function Input(o?: any): any { return () => {}; }\nexport function Output(o?: any): any { return () => {}; }\nexport function HostBinding(o?: any): any { return () => {}; }\nexport function HostListener(e?: any, a?: any): any { return () => {}; }\nexport function ViewChild(p?: any, o?: any): any { return () => {}; }\nexport function ContentChildren(p?: any, o?: any): any { return () => {}; }\n" }, "diagnostics": [], - "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"a\",\"b\"],1);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.a=_t);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "D": { + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, - "dtsImports": [] + "dtsImports": [], + "diagnosticSpans": [] }, { - "name": "probe: queries-member-predicateBadArray", + "name": "probe: memberDecorator-aliasedFromCore", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren(['a', 1] as any) a!: any;\n}\n" + "test.ts": "import {Directive, Input as In, Output as Out, HostBinding as HB, HostListener as HL, ViewChild as VC, ContentChildren as CC, EventEmitter} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class D {\n @In() x: any;\n @In({alias: 'yy', transform: (v: string) => v}) y: any;\n @Out() changed = new EventEmitter();\n @HB('class.on') on = true;\n @HL('click', ['$event']) onClick(e: any) {}\n @VC('ref') ref: any;\n @CC('item', {descendants: true}) items: any;\n}\n" }, - "diagnostics": [ - "Failed to resolve @ViewChildren predicate at position 1 to a string Value is of type 'number'." - ], - "diagnosticSpans": [[283, 298]], - "classes": {}, - "dtsImports": [] + "diagnostics": [], + "classes": { + "D": { + "inputs": "{x:\"x\",y:[2,\"yy\",\"y\",(v)=>v]}", + "outputs": "{changed:\"changed\"}", + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"item\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.items=_t);}}", + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"ref\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.ref=_t.first);}}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_y": "staticngAcceptInputType_y:string;" + } + }, + "dtsImports": [], + "diagnosticSpans": [] }, { - "name": "probe: queries-member-optsNull", + "name": "probe: memberDecorator-otherModule", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', null) a!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nimport {Input, Output, HostBinding, HostListener, ViewChild, ContentChildren} from './foreign';\n@Directive({selector: '[d]'})\nexport class D {\n @Input() x: any;\n @Output() changed: any;\n @HostBinding('class.on') on = true;\n @HostListener('click', ['$event']) onClick(e: any) {}\n @ViewChild('ref') ref: any;\n @ContentChildren('item') items: any;\n}\n", + "foreign.ts": "export function Input(o?: any): any { return () => {}; }\nexport function Output(o?: any): any { return () => {}; }\nexport function HostBinding(o?: any): any { return () => {}; }\nexport function HostListener(e?: any, a?: any): any { return () => {}; }\nexport function ViewChild(p?: any, o?: any): any { return () => {}; }\nexport function ContentChildren(p?: any, o?: any): any { return () => {}; }\n" }, - "diagnostics": [ - "@ViewChild options must be an object literal. Find more at https://next.angular.dev/errors/NG1001" - ], - "diagnosticSpans": [[285, 289]], - "classes": {}, - "dtsImports": [] + "diagnostics": [], + "classes": { + "D": { + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [], + "diagnosticSpans": [] }, { - "name": "probe: queries-member-optsUndefinedArg", + "name": "probe: memberDecorator-undeclared", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', undefined) a!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class D {\n @Input() x: any;\n}\n" }, - "diagnostics": [ - "@ViewChild options must be an object literal. Find more at https://next.angular.dev/errors/NG1001" - ], - "diagnosticSpans": [[285, 294]], - "classes": {}, - "dtsImports": [] + "diagnostics": [], + "classes": { + "D": { + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [], + "diagnosticSpans": [] }, { - "name": "probe: queries-member-readAndStatic", + "name": "probe: memberDecorator-localFunction", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {read: ElementRef, static: FLAG}) a!: any;\n}\n" + "test.ts": "import {Directive} from '@angular/core';\nfunction Input(o?: any): any { return () => {}; }\n@Directive({selector: '[d]'})\nexport class D {\n @Input({transform: 5}) x: any;\n}\n" }, "diagnostics": [], - "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7,ElementRef);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.a=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "D": { + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, - "dtsImports": [] + "dtsImports": [], + "diagnosticSpans": [] }, { - "name": "probe: queries-member-dir", + "name": "probe: memberDecorator-coreNamespace", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Directive({selector: '[d]'})\nexport class Dir {\n @ContentChildren('x', {descendants: FLAG, emitDistinctChangesOnly: FLAG}) a!: any;\n}\n" + "test.ts": "import * as ng from '@angular/core';\n@ng.Directive({selector: '[d]'})\nexport class D { @ng.Input() x: any; @ng.Output() y: any; }\n" }, "diagnostics": [], - "diagnosticSpans": [], "classes": { - "Dir": { - "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.a=_t);}}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "D": { + "inputs": "{x:\"x\"}", + "outputs": "{y:\"y\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, - "dtsImports": [] + "dtsImports": [], + "diagnosticSpans": [] }, { - "name": "probe: queries-member-signalPlusDecorator", + "name": "probe: queries-member-ViewChildren-emitDistinctChangesOnly-constFalse", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {viewChild} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: 'a'}) a = viewChild('x');\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {emitDistinctChangesOnly: FLAG}) q!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],1);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } }, - "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], - "diagnosticSpans": [[321, 324]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: queries-member-getter", + "name": "probe: queries-member-ViewChildren-emitDistinctChangesOnly-constTrue", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: FLAG}) get a(): any { return null; }\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {emitDistinctChangesOnly: FLAG}) q!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.a=_t.first);}}", + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: queries-member-staticMember", + "name": "probe: queries-member-ViewChildren-emitDistinctChangesOnly-string", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: 'a'}) static a: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {emitDistinctChangesOnly: 'yes'}) q!: any;\n}\n" }, - "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], - "diagnosticSpans": [[280, 283]], + "diagnostics": [ + "@ViewChildren options.emitDistinctChangesOnly must be a boolean Value is of type 'string'." + ], + "diagnosticSpans": [[314, 319]], "classes": {}, "dtsImports": [] }, { - "name": "probe: transform-global-atob", + "name": "probe: queries-member-ViewChildren-emitDistinctChangesOnly-constString", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: atob}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = 'yes';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {emitDistinctChangesOnly: FLAG}) q!: any;\n}\n" + }, + "diagnostics": [ + "@ViewChildren options.emitDistinctChangesOnly must be a boolean Value is of type 'string'." + ], + "diagnosticSpans": [[334, 338]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-ViewChildren-emitDistinctChangesOnly-not", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {emitDistinctChangesOnly: !0}) q!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Dir": { - "inputs": "{x:[2,\"x\",\"x\",atob]}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: transform-global-btoa", + "name": "probe: queries-member-ContentChildren-emitDistinctChangesOnly-constFalse", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: btoa}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {emitDistinctChangesOnly: FLAG}) q!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Dir": { - "inputs": "{x:[2,\"x\",\"x\",btoa]}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "Cmp": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],0);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: transform-global-alert", + "name": "probe: queries-member-ContentChildren-emitDistinctChangesOnly-constTrue", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: alert}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {emitDistinctChangesOnly: FLAG}) q!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Dir": { - "inputs": "{x:[2,\"x\",\"x\",alert]}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "Cmp": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: transform-global-globalThis", + "name": "probe: queries-member-ContentChildren-emitDistinctChangesOnly-string", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: globalThis}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {emitDistinctChangesOnly: 'yes'}) q!: any;\n}\n" }, "diagnostics": [ - "Input transform must be a function Value could not be determined statically." + "@ContentChildren options.emitDistinctChangesOnly must be a boolean Value is of type 'string'." ], - "diagnosticSpans": [[118, 128]], + "diagnosticSpans": [[317, 322]], "classes": {}, "dtsImports": [] }, { - "name": "probe: transform-global-undefined", + "name": "probe: queries-member-ContentChildren-emitDistinctChangesOnly-constString", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: undefined}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = 'yes';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {emitDistinctChangesOnly: FLAG}) q!: any;\n}\n" }, - "diagnostics": ["Input transform must be a function Value is of type 'undefined'."], - "diagnosticSpans": [[106, 128]], + "diagnostics": [ + "@ContentChildren options.emitDistinctChangesOnly must be a boolean Value is of type 'string'." + ], + "diagnosticSpans": [[337, 341]], "classes": {}, "dtsImports": [] }, { - "name": "probe: transform-global-inputsAtob", + "name": "probe: queries-member-ContentChildren-emitDistinctChangesOnly-not", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]', inputs: [{name: 'x', transform: atob}]})\nexport class Dir {\n x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {emitDistinctChangesOnly: !0}) q!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Dir": { - "inputs": "{x:[2,\"x\",\"x\",atob]}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" - } + "Cmp": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } }, "dtsImports": [] }, { - "name": "probe: transform-global-projectFunction", + "name": "probe: queries-member-ContentChildren-descendants-constFalse", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: ambientFn}) x: any;\n}\n", - "globals.d.ts": "declare function ambientFn(v: string): number;\ndeclare var ambientVar: string;\ndeclare function ambientOverload(v: string): number;\ndeclare function ambientOverload(v: number): number;\ndeclare function ambientGeneric(v: T): T;\ndeclare namespace AmbNs { function f(v: string): number; }\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {descendants: FLAG}) q!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Dir": { - "inputs": "{x:[2,\"x\",\"x\",ambientFn]}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "Cmp": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: transform-global-projectNamespaceMember", + "name": "probe: queries-member-ContentChildren-descendants-constTrue", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: AmbNs.f}) x: any;\n}\n", - "globals.d.ts": "declare function ambientFn(v: string): number;\ndeclare var ambientVar: string;\ndeclare function ambientOverload(v: string): number;\ndeclare function ambientOverload(v: number): number;\ndeclare function ambientGeneric(v: T): T;\ndeclare namespace AmbNs { function f(v: string): number; }\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {descendants: FLAG}) q!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-member-ContentChildren-descendants-string", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {descendants: 'yes'}) q!: any;\n}\n" }, "diagnostics": [ - "Input transform must be a function Value could not be determined statically." + "@ContentChildren options.descendants must be a boolean Value is of type 'string'." ], - "diagnosticSpans": [[118, 125]], + "diagnosticSpans": [[305, 310]], "classes": {}, "dtsImports": [] }, { - "name": "probe: transform-global-declareGlobalFunction", + "name": "probe: queries-member-ContentChildren-descendants-constString", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\ndeclare global { function gg(v: string): number; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: gg}) x: any;\n}\n" - }, - "diagnostics": [], - "diagnosticSpans": [], - "classes": { - "Dir": { - "inputs": "{x:[2,\"x\",\"x\",gg]}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" - } + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = 'yes';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {descendants: FLAG}) q!: any;\n}\n" }, + "diagnostics": [ + "@ContentChildren options.descendants must be a boolean Value is of type 'string'." + ], + "diagnosticSpans": [[325, 329]], + "classes": {}, "dtsImports": [] }, { - "name": "probe: transform-global-aliasWindow", + "name": "probe: queries-member-ContentChildren-descendants-not", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({alias: window as any}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren('x', {descendants: !0}) q!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Dir": { - "inputs": "{x:\"x\"}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "Cmp": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: transform-global-aliasUndeclared", + "name": "probe: queries-member-ViewChildren-descendants-constFalse", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({alias: undeclaredName}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants: FLAG}) q!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Dir": { - "inputs": "{x:\"x\"}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: transform-global-Intl", + "name": "probe: queries-member-ViewChildren-descendants-constTrue", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: Intl}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants: FLAG}) q!: any;\n}\n" }, - "diagnostics": ["Input transform must be a function Value is a reference to 'Intl'."], - "classes": {}, - "dtsImports": [], - "skip": "ngtsc reports this at the transform's declaration in TypeScript's lib.es5.d.ts, a file oxc doesn't read (so this snapshot's generator, which keeps test.ts diagnostics, records none); oxc reports the same message on the expression" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] }, { - "name": "probe: transform-global-Reflect", + "name": "probe: queries-member-ViewChildren-descendants-string", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: Reflect}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants: 'yes'}) q!: any;\n}\n" }, - "diagnostics": ["Input transform must be a function Value is a reference to 'Reflect'."], + "diagnostics": [ + "@ViewChildren options.descendants must be a boolean Value is of type 'string'." + ], + "diagnosticSpans": [[302, 307]], "classes": {}, - "dtsImports": [], - "skip": "ngtsc reports this at the transform's declaration in TypeScript's lib.es2015.reflect.d.ts, a file oxc doesn't read (so this snapshot's generator, which keeps test.ts diagnostics, records none); oxc reports the same message on the expression" + "dtsImports": [] }, { - "name": "probe: transform-global-clashAtob", + "name": "probe: queries-member-ViewChildren-descendants-constString", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n static ngAcceptInputType_x: string;\n @Input({transform: atob}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = 'yes';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants: FLAG}) q!: any;\n}\n" }, "diagnostics": [ - "Class cannot have both a transform function on Input x and a static member called ngAcceptInputType_x" + "@ViewChildren options.descendants must be a boolean Value is of type 'string'." ], + "diagnosticSpans": [[322, 326]], "classes": {}, - "dtsImports": [], - "skip": "ngtsc reports this at the transform's declaration in TypeScript's lib.dom.d.ts, a file oxc doesn't read (so this snapshot's generator, which keeps test.ts diagnostics, records none); oxc reports the same message on the expression" + "dtsImports": [] }, { - "name": "probe: transform-global-window", + "name": "probe: queries-member-ViewChildren-descendants-not", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: window}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants: !0}) q!: any;\n}\n" }, - "diagnostics": ["Input transform must be a function Value is a reference to 'window'."], - "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] }, { - "name": "probe: transform-global-document", + "name": "probe: queries-member-ContentChild-descendants-constFalse", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: document}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {descendants: FLAG}) q!: any;\n}\n" }, - "diagnostics": ["Input transform must be a function Value is a reference to 'document'."], - "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] }, { - "name": "probe: transform-global-navigator", + "name": "probe: queries-member-ContentChild-descendants-constTrue", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: navigator}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {descendants: FLAG}) q!: any;\n}\n" }, - "diagnostics": ["Input transform must be a function Value is a reference to 'navigator'."], - "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] }, { - "name": "probe: transform-global-localStorage", + "name": "probe: queries-member-ContentChild-descendants-string", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: localStorage}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {descendants: 'yes'}) q!: any;\n}\n" }, - "diagnostics": ["Input transform must be a function Value is a reference to 'localStorage'."], + "diagnostics": [ + "@ContentChild options.descendants must be a boolean Value is of type 'string'." + ], + "diagnosticSpans": [[302, 307]], "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + "dtsImports": [] }, { - "name": "probe: transform-global-console", + "name": "probe: queries-member-ContentChild-descendants-constString", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: console}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = 'yes';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {descendants: FLAG}) q!: any;\n}\n" }, - "diagnostics": ["Input transform must be a function Value is a reference to 'console'."], + "diagnostics": [ + "@ContentChild options.descendants must be a boolean Value is of type 'string'." + ], + "diagnosticSpans": [[322, 326]], "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + "dtsImports": [] }, { - "name": "probe: transform-global-self", + "name": "probe: queries-member-ContentChild-descendants-not", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: self}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {descendants: !0}) q!: any;\n}\n" }, - "diagnostics": ["Input transform must be a function Value is a reference to 'self'."], - "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] }, { - "name": "probe: transform-global-CSS", + "name": "probe: queries-member-ViewChild-static-constFalse", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: CSS}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: FLAG}) q!: any;\n}\n" }, - "diagnostics": ["Input transform must be a function Value is a reference to 'CSS'."], - "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] }, { - "name": "probe: transform-global-WebAssembly", + "name": "probe: queries-member-ViewChild-static-constTrue", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: WebAssembly}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: FLAG}) q!: any;\n}\n" }, - "diagnostics": ["Input transform must be a function Value is a reference to 'WebAssembly'."], - "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] }, { - "name": "probe: transform-global-structuredClone", + "name": "probe: queries-member-ViewChild-static-string", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: structuredClone}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: 'yes'}) q!: any;\n}\n" }, - "diagnostics": [ - "Input transform function cannot be generic Value is a reference to 'structuredClone'." - ], + "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], + "diagnosticSpans": [[280, 283]], "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + "dtsImports": [] }, { - "name": "probe: transform-global-queueMicrotask", + "name": "probe: queries-member-ViewChild-static-constString", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: queueMicrotask}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = 'yes';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: FLAG}) q!: any;\n}\n" }, - "diagnostics": [ - "Unable to import type VoidFunction. The symbol is not exported from /Users/brooklyn/workspace/github/oxc-angular-compiler/node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.dom.d.ts." - ], + "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], + "diagnosticSpans": [[300, 303]], "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + "dtsImports": [] }, { - "name": "probe: transform-global-setTimeout", + "name": "probe: queries-member-ViewChild-static-not", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: setTimeout}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: !0}) q!: any;\n}\n" }, - "diagnostics": [ - "Unable to import type TimerHandler. The symbol is not exported from /Users/brooklyn/workspace/github/oxc-angular-compiler/node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.dom.d.ts." - ], - "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" - }, - { - "name": "probe: transform-global-fetch", - "origin": "probe", - "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: fetch}) x: any;\n}\n" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } }, - "diagnostics": [ - "Unable to import type RequestInfo. The symbol is not exported from /Users/brooklyn/workspace/github/oxc-angular-compiler/node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.dom.d.ts." - ], - "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + "dtsImports": [] }, { - "name": "probe: transform-global-projectVar", + "name": "probe: queries-member-ContentChild-static-constFalse", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: ambientVar}) x: any;\n}\n", - "globals.d.ts": "declare function ambientFn(v: string): number;\ndeclare var ambientVar: string;\ndeclare function ambientOverload(v: string): number;\ndeclare function ambientOverload(v: number): number;\ndeclare function ambientGeneric(v: T): T;\ndeclare namespace AmbNs { function f(v: string): number; }\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {static: FLAG}) q!: any;\n}\n" }, - "diagnostics": ["Input transform must be a function Value is a reference to 'ambientVar'."], - "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (a project `.d.ts`), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" - }, - { - "name": "probe: transform-global-projectOverload", - "origin": "probe", - "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: ambientOverload}) x: any;\n}\n", - "globals.d.ts": "declare function ambientFn(v: string): number;\ndeclare var ambientVar: string;\ndeclare function ambientOverload(v: string): number;\ndeclare function ambientOverload(v: number): number;\ndeclare function ambientGeneric(v: T): T;\ndeclare namespace AmbNs { function f(v: string): number; }\n" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } }, - "diagnostics": [ - "Input transform function cannot have multiple signatures Value is a reference to 'ambientOverload'." - ], - "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (a project `.d.ts`), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + "dtsImports": [] }, { - "name": "probe: transform-global-projectGeneric", + "name": "probe: queries-member-ContentChild-static-constTrue", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: ambientGeneric}) x: any;\n}\n", - "globals.d.ts": "declare function ambientFn(v: string): number;\ndeclare var ambientVar: string;\ndeclare function ambientOverload(v: string): number;\ndeclare function ambientOverload(v: number): number;\ndeclare function ambientGeneric(v: T): T;\ndeclare namespace AmbNs { function f(v: string): number; }\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {static: FLAG}) q!: any;\n}\n" }, - "diagnostics": [ - "Input transform function cannot be generic Value is a reference to 'ambientGeneric'." - ], - "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (a project `.d.ts`), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] }, { - "name": "probe: transform-global-inputsWindow", + "name": "probe: queries-member-ContentChild-static-string", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]', inputs: [{name: 'x', transform: window}]})\nexport class Dir {\n x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {static: 'yes'}) q!: any;\n}\n" }, - "diagnostics": ["Input transform must be a function Value is a reference to 'window'."], + "diagnostics": ["@ContentChild options.static must be a boolean Value is of type 'string'."], + "diagnosticSpans": [[283, 286]], "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + "dtsImports": [] }, { - "name": "probe: transform-global-declareGlobalVar", + "name": "probe: queries-member-ContentChild-static-constString", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\ndeclare global { var gv: string; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: gv}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = 'yes';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {static: FLAG}) q!: any;\n}\n" }, - "diagnostics": ["Input transform must be a function Value is of type 'undefined'."], + "diagnostics": ["@ContentChild options.static must be a boolean Value is of type 'string'."], + "diagnosticSpans": [[303, 306]], "classes": {}, - "dtsImports": [], - "skip": "a global declared outside this file (this file's `declare global`, whose names oxc doesn't track), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + "dtsImports": [] }, { - "name": "probe: transform-global-namespaceMember", + "name": "probe: queries-member-ContentChild-static-not", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\nnamespace U { export function f(v: string) { return 1; } }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: U.f}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {static: !0}) q!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } }, - "diagnostics": [ - "Input transform must be a function Value could not be determined statically." - ], - "diagnosticSpans": [[177, 180]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: transform-global-declareModule", + "name": "probe: queries-member-static-number", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\ndeclare module M { }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: M}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: 1}) q!: any;\n}\n" }, - "diagnostics": [ - "Input transform must be a function Value could not be determined statically." - ], - "diagnosticSpans": [[139, 140]], + "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'number'."], + "diagnosticSpans": [[280, 283]], "classes": {}, "dtsImports": [] }, { - "name": "probe: transform-global-undeclared", + "name": "probe: queries-member-static-null", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: undeclaredName}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: null}) q!: any;\n}\n" }, - "diagnostics": [ - "Input transform must be a function Value could not be determined statically." - ], + "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'null'."], + "diagnosticSpans": [[280, 283]], "classes": {}, - "dtsImports": [], - "skip": "a name declared nowhere, which TypeScript reports: oxc can't tell it from a global declared outside the file, so it assumes it's a function and accepts it, where ngtsc reports this" + "dtsImports": [] }, { - "name": "probe: transform-global-inputsUndeclared", + "name": "probe: queries-member-static-undefined", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]', inputs: [{name: 'x', transform: undeclaredName}]})\nexport class Dir {\n x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: undefined}) q!: any;\n}\n" }, - "diagnostics": [ - "Input transform must be a function Value could not be determined statically." - ], + "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'undefined'."], + "diagnosticSpans": [[280, 283]], "classes": {}, - "dtsImports": [], - "skip": "a name declared nowhere, which TypeScript reports: oxc can't tell it from a global declared outside the file, so it assumes it's a function and accepts it, where ngtsc reports this" + "dtsImports": [] }, { - "name": "probe: transform-global-member-window_atob", + "name": "probe: queries-member-static-unknownIdent", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: window.atob}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\ndeclare const someGlobal: boolean;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: someGlobal}) q!: any;\n}\n" }, "diagnostics": [ - "Input transform must be a function Value could not be determined statically." + "@ViewChild options.static must be a boolean Value is a reference to 'someGlobal'." ], - "diagnosticSpans": [[118, 129]], + "diagnosticSpans": [[315, 318]], "classes": {}, "dtsImports": [] }, { - "name": "probe: transform-global-member-document_createElement", + "name": "probe: queries-member-desc-letUninit", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: document.createElement}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nlet FLAG;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants: FLAG}) q!: any;\n}\n" }, "diagnostics": [ - "Input transform must be a function Value could not be determined statically." + "@ViewChildren options.descendants must be a boolean Value is of type 'undefined'." ], - "diagnosticSpans": [[118, 140]], + "diagnosticSpans": [[312, 316]], "classes": {}, "dtsImports": [] }, { - "name": "probe: transform-global-member-globalThis_atob", + "name": "probe: queries-member-static-ternary", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: globalThis.atob}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst A = 1;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: A ? true : false}) q!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } }, - "diagnostics": [ - "Input transform must be a function Value could not be determined statically." - ], - "diagnosticSpans": [[118, 133]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: transform-global-member-self_atob", + "name": "probe: queries-member-static-enum", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: self.atob}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nenum E { A }\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: E.A}) q!: any;\n}\n" }, - "diagnostics": [ - "Input transform must be a function Value could not be determined statically." - ], - "diagnosticSpans": [[118, 127]], + "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'E'."], + "diagnosticSpans": [[293, 296]], "classes": {}, "dtsImports": [] }, { - "name": "probe: transform-global-member-console_log", + "name": "probe: queries-member-static-predTypeRef", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: console.log}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild(ElementRef, {static: 'no'}) q!: any;\n}\n" }, - "diagnostics": [ - "Input transform must be a function Value could not be determined statically." - ], - "diagnosticSpans": [[118, 129]], + "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], + "diagnosticSpans": [[280, 290]], "classes": {}, "dtsImports": [] }, { - "name": "probe: transform-global-member-undeclaredName_foo", + "name": "probe: queries-member-optsConst", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: undeclaredName.foo}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst OPTS = {static: true};\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', OPTS) q!: any;\n}\n" }, "diagnostics": [ - "Input transform must be a function Value could not be determined statically." + "@ViewChild options must be an object literal. Find more at https://next.angular.dev/errors/NG1001" ], - "diagnosticSpans": [[118, 136]], + "diagnosticSpans": [[314, 318]], "classes": {}, "dtsImports": [] }, { - "name": "probe: transform-global-member-localStorage_getItem", + "name": "probe: queries-member-optsParens", "origin": "probe", "files": { - "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: localStorage.getItem}) x: any;\n}\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', ({static: true})) q!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } }, - "diagnostics": [ - "Input transform must be a function Value could not be determined statically." - ], - "diagnosticSpans": [[118, 138]], - "classes": {}, "dtsImports": [] }, { - "name": "probe: shortCircuit-metaOr", + "name": "probe: queries-member-optsAs", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [LOCAL || NAME]})\nexport class Dir {\n localAlias: any;\n}\n", - "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: true} as any) q!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Dir": { - "inputs": "{localAlias:\"localAlias\"}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-metaObjAliasOr", + "name": "probe: queries-member-optsSpread", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [{name: 'x', alias: LOCAL || NAME}]})\nexport class Dir {\n x: any;\n}\n", - "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst OPTS = {static: true};\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {...OPTS}) q!: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Dir": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-member-optsSpreadThen", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst OPTS = {static: true, descendants: true};\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {...OPTS, descendants: false}) q!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-member-optsString", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', 'str') q!: any;\n}\n" + }, + "diagnostics": [ + "@ViewChild options must be an object literal. Find more at https://next.angular.dev/errors/NG1001" + ], + "diagnosticSpans": [[285, 290]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-readConst", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst R = ElementRef;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {read: R}) q!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5,R);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-member-readNumber", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {read: 1}) q!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5,1);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-member-quotedKey", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {'descendants': false, \"emitDistinctChangesOnly\": false}) q!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],0);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-member-computedKey", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {['descendants']: false}) q!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-member-shorthand", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst descendants = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants}) q!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-member-multiErr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {descendants: 'a', emitDistinctChangesOnly: 'b'}) q!: any;\n @ViewChild('x', {static: 'c'}) r!: any;\n}\n" + }, + "diagnostics": [ + "@ViewChildren options.descendants must be a boolean Value is of type 'string'." + ], + "diagnosticSpans": [[302, 305]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-setter", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren('x', {emitDistinctChangesOnly: FLAG}) set q(v: any) {}\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],1);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-options-ConstFalse", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = false;\n@Component({selector: 'c', template: '
', queries: {q: new ViewChildren('x', {emitDistinctChangesOnly: FLAG})}})\nexport class Cmp {\n q!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],1);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.q=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-options-OptsConst", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst OPTS = {static: true};\n@Component({selector: 'c', template: '
', queries: {q: new ViewChild('x', OPTS)}})\nexport class Cmp {\n q!: any;\n}\n" + }, + "diagnostics": [ + "@ViewChild options must be an object literal. Find more at https://next.angular.dev/errors/NG1001" + ], + "diagnosticSpans": [[308, 312]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-options-StaticString", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {q: new ViewChild('x', {static: 'c'})}})\nexport class Cmp {\n q!: any;\n}\n" + }, + "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], + "diagnosticSpans": [[274, 277]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-predicate-queriesFnExpr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(function Token() {})}})\nexport class Cmp {\n item: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(functionToken(){},5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-predicate-queriesArrow", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(() => ElementRef)}})\nexport class Cmp {\n item: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(()=>ElementRef,5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-predicate-queriesCall", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nfunction mk(): any { return ElementRef; }\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(mk())}})\nexport class Cmp {\n item: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(mk(),5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-predicate-queriesCallArg", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\ndeclare function mk(v: any): any;\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(mk(1))}})\nexport class Cmp {\n item: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(mk(1),5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-predicate-queriesAs", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(ElementRef as any)}})\nexport class Cmp {\n item: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(ElementRef,5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-predicate-queriesMember", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst T = {ref: ElementRef};\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(T.ref)}})\nexport class Cmp {\n item: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(T.ref,5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-predicate-queriesGlobal", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(window)}})\nexport class Cmp {\n item: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(window,5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-predicate-queriesTemplate", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(`x`)}})\nexport class Cmp {\n item: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-predicate-queriesNumber", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(1)}})\nexport class Cmp {\n item: any;\n}\n" + }, + "diagnostics": ["@ViewChild predicate cannot be interpreted Value is of type 'number'."], + "diagnosticSpans": [[277, 278]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-predicate-queriesObject", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild({})}})\nexport class Cmp {\n item: any;\n}\n" + }, + "diagnostics": ["@ViewChild predicate cannot be interpreted Value is of type '{}'."], + "diagnosticSpans": [[277, 279]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-predicate-queriesNew", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(new ElementRef(null))}})\nexport class Cmp {\n item: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(newElementRef(null),5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-predicate-memberFnExpr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild(function Token() {}) item: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(functionToken(){},5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-predicate-memberArrow", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild(() => ElementRef) item: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(()=>ElementRef,5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-predicate-memberCall", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nfunction mk(): any { return ElementRef; }\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild(mk()) item: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(mk(),5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-predicate-memberNumber", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild(1 as any) item: any;\n}\n" + }, + "diagnostics": ["@ViewChild predicate cannot be interpreted Value is of type 'number'."], + "diagnosticSpans": [[280, 288]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-noArgs", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild() q!: any;\n}\n" + }, + "diagnostics": ["@ViewChild must have arguments"], + "diagnosticSpans": [[269, 290]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-noCall", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild q!: any;\n}\n" + }, + "diagnostics": ["@ViewChild must have arguments"], + "diagnosticSpans": [[269, 288]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-tooMany", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {}, 1 as any) q!: any;\n}\n" + }, + "diagnostics": ["@ViewChild has too many arguments"], + "diagnosticSpans": [[280, 283]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-noArgsSetter", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChildren() set q(v: any) {}\n}\n" + }, + "diagnostics": ["@ContentChildren must have arguments"], + "diagnosticSpans": [[269, 304]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-twoMembers", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: 'a'}) a!: any;\n @ContentChild('x', {descendants: 'b'}) b!: any;\n}\n" + }, + "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], + "diagnosticSpans": [[280, 283]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-contentFirstInOrder", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {descendants: 'b'}) b!: any;\n @ViewChild('x', {static: 'a'}) a!: any;\n}\n" + }, + "diagnostics": [ + "@ContentChild options.descendants must be a boolean Value is of type 'string'." + ], + "diagnosticSpans": [[302, 305]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-memberVsQueries", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', queries: {q: new ViewChild('x', {static: 'c'})}})\nexport class Cmp {\n @ViewChild('x', {static: 'a'}) a!: any;\n q!: any;\n}\n" + }, + "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], + "diagnosticSpans": [[329, 332]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-inputVsMember", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: 'a'}) a!: any;\n @Input({transform: 1 as any}) i!: any;\n}\n" + }, + "diagnostics": ["Input transform must be a function Value is of type 'number'."], + "diagnosticSpans": [[318, 339]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-outputsMetaVsMember", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
', outputs: 1 as any})\nexport class Cmp {\n @ViewChild('x', {static: 'a'}) a!: any;\n}\n" + }, + "diagnostics": [ + "Failed to resolve @Directive.outputs to a string array Value is of type 'number'." + ], + "diagnosticSpans": [[256, 264]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-forwardRefStatic", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {forwardRef} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild(forwardRef(() => ElementRef), {static: 'a'}) a!: any;\n}\n" + }, + "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], + "diagnosticSpans": [[339, 349]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-predicateArrayConst", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst SELS = ['a', 'b'];\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren(SELS, {emitDistinctChangesOnly: false}) a!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"a\",\"b\"],1);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.a=_t);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-member-predicateBadArray", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChildren(['a', 1] as any) a!: any;\n}\n" + }, + "diagnostics": [ + "Failed to resolve @ViewChildren predicate at position 1 to a string Value is of type 'number'." + ], + "diagnosticSpans": [[283, 298]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-optsNull", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', null) a!: any;\n}\n" + }, + "diagnostics": [ + "@ViewChild options must be an object literal. Find more at https://next.angular.dev/errors/NG1001" + ], + "diagnosticSpans": [[285, 289]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-optsUndefinedArg", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', undefined) a!: any;\n}\n" + }, + "diagnostics": [ + "@ViewChild options must be an object literal. Find more at https://next.angular.dev/errors/NG1001" + ], + "diagnosticSpans": [[285, 294]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-readAndStatic", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {read: ElementRef, static: FLAG}) a!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7,ElementRef);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.a=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-member-dir", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Directive({selector: '[d]'})\nexport class Dir {\n @ContentChildren('x', {descendants: FLAG, emitDistinctChangesOnly: FLAG}) a!: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.a=_t);}}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-member-signalPlusDecorator", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {viewChild} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: 'a'}) a = viewChild('x');\n}\n" + }, + "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], + "diagnosticSpans": [[321, 324]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: queries-member-getter", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst FLAG = true;\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: FLAG}) get a(): any { return null; }\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.a=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: queries-member-staticMember", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ViewChildren, ContentChild, ContentChildren, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: 'a'}) static a: any;\n}\n" + }, + "diagnostics": ["@ViewChild options.static must be a boolean Value is of type 'string'."], + "diagnosticSpans": [[280, 283]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-constString", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(NAME) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-letString", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nlet NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(NAME) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-varString", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nvar NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(NAME) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-typedConst", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME: string = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(NAME) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-template", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(`y`) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-templateExpr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst P = 'p';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(`${P}y`) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"py\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-concat", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input('a' + 'b') x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"ab\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-constObject", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst OPTS = {alias: 'y'};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(OPTS) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-constObjectRequired", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst OPTS = {alias: 'y', required: true};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(OPTS) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-constObjectTransform", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst OPTS = {alias: 'y', transform: booleanAttribute};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(OPTS) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"y\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-spread", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst OPTS = {alias: 'y'};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({...OPTS}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-spreadRequired", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst OPTS = {alias: 'y'};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({...OPTS, required: true}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-spreadOverride", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst OPTS = {alias: 'y', required: true};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({...OPTS, alias: 'z'}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"z\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-parens", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input((NAME)) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-asConst", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y' as const;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(NAME) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-asAny", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(NAME as any) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-nonNull", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(NAME!) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-nestedConst", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst A = 'y';\nconst B = A;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(B) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-objectMember", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst N = {x: 'y'};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(N.x) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-arrayElement", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst N = ['a', 'y'];\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(N[1]) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-aliasConst", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({alias: NAME}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-aliasTemplate", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({alias: `y`}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-requiredConst", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst R = true;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({required: R}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-enumString", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nenum E { A = 'y' }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(E.A) x: any;\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is of type 'E'." + ], + "diagnosticSpans": [[169, 180]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-enumNumber", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nenum E { A }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(E.A) x: any;\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is of type 'E'." + ], + "diagnosticSpans": [[163, 174]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-constEnumString", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst enum E { A = 'y' }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(E.A) x: any;\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is of type 'E'." + ], + "diagnosticSpans": [[175, 186]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-number", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(1 as any) x: any;\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is of type 'number'." + ], + "diagnosticSpans": [[150, 166]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-bool", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(true as any) x: any;\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is of type 'boolean'." + ], + "diagnosticSpans": [[150, 169]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-null", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(null) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-undefined", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(undefined) x: any;\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is of type 'undefined'." + ], + "diagnosticSpans": [[150, 167]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-array", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(['y'] as any) x: any;\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is of type '[string]'." + ], + "diagnosticSpans": [[150, 170]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-constNumber", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst N = 1;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(N as any) x: any;\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is of type 'number'." + ], + "diagnosticSpans": [[163, 179]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-declared", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\ndeclare const N: string;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(N) x: any;\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is a reference to 'N'." + ], + "diagnosticSpans": [[175, 184]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-call", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nfunction f(): string { return 'y'; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(f()) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-twoArgs", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n // @ts-ignore\n @Input('a', 'b') x: any;\n}\n" + }, + "diagnostics": ["@Input can have at most one argument, got 2 argument(s)"], + "diagnosticSpans": [[166, 182]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-twoArgsConst", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n // @ts-ignore\n @Input(NAME, NAME) x: any;\n}\n" + }, + "diagnostics": ["@Input can have at most one argument, got 2 argument(s)"], + "diagnosticSpans": [[184, 202]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-aliasNumber", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({alias: 1 as any}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-aliasConstNumber", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst OPTS = {alias: 1};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(OPTS as any) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-requiredString", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({required: 'yes' as any}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-setter", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(NAME) set x(v: any) {}\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-setterObject", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst OPTS = {alias: 'y', required: true};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(OPTS) set x(v: any) {}\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-accessor", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(NAME) accessor x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-component", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y';\n@Component({selector: 'c', template: ''})\nexport class Cmp {\n @Input(NAME) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-componentObject", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst OPTS = {alias: 'y', required: true};\n@Component({selector: 'c', template: ''})\nexport class Cmp {\n @Input(OPTS) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "inputs": "{x:[0,\"y\",\"x\"]}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-numberThenOutput", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(1 as any) x: any;\n @Output(2 as any) e = new EventEmitter();\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is of type 'number'." + ], + "diagnosticSpans": [[150, 166]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-badAndMetaInputs", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]', inputs: [1 as any]})\nexport class Dir {\n @Input(1 as any) x: any;\n}\n" + }, + "diagnostics": [ + "@Directive.inputs array can only contain strings or object literals Value is of type 'number'." + ], + "diagnosticSpans": [[136, 146]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-constString", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(NAME) e = new EventEmitter();\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "outputs": "{e:\"y\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-letString", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nlet NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(NAME) e = new EventEmitter();\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "outputs": "{e:\"y\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-template", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(`y`) e = new EventEmitter();\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "outputs": "{e:\"y\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-templateExpr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst P = 'p';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(`${P}y`) e = new EventEmitter();\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "outputs": "{e:\"py\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-concat", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output('a' + 'b') e = new EventEmitter();\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "outputs": "{e:\"ab\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-parens", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output((NAME)) e = new EventEmitter();\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "outputs": "{e:\"y\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-asConst", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y' as const;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(NAME) e = new EventEmitter();\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "outputs": "{e:\"y\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-nestedConst", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst A = 'y';\nconst B = A;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(B) e = new EventEmitter();\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "outputs": "{e:\"y\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-enumString", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nenum E { A = 'y' }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(E.A) e = new EventEmitter();\n}\n" + }, + "diagnostics": ["@Output decorator argument must resolve to a string Value is of type 'E'."], + "diagnosticSpans": [[169, 181]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-enumNumber", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nenum E { A }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(E.A as any) e = new EventEmitter();\n}\n" + }, + "diagnostics": ["@Output decorator argument must resolve to a string Value is of type 'E'."], + "diagnosticSpans": [[163, 182]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-number", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(1 as any) e = new EventEmitter();\n}\n" + }, + "diagnostics": [ + "@Output decorator argument must resolve to a string Value is of type 'number'." + ], + "diagnosticSpans": [[150, 167]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-null", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(null) e = new EventEmitter();\n}\n" + }, + "diagnostics": [ + "@Output decorator argument must resolve to a string Value is of type 'null'." + ], + "diagnosticSpans": [[150, 163]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-undefined", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(undefined) e = new EventEmitter();\n}\n" + }, + "diagnostics": [ + "@Output decorator argument must resolve to a string Value is of type 'undefined'." + ], + "diagnosticSpans": [[150, 168]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-object", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output({alias: 'y'} as any) e = new EventEmitter();\n}\n" + }, + "diagnostics": [ + "@Output decorator argument must resolve to a string Value is of type '{ alias: string }'." + ], + "diagnosticSpans": [[150, 178]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-constObject", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst OPTS = {alias: 'y'};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(OPTS as any) e = new EventEmitter();\n}\n" + }, + "diagnostics": [ + "@Output decorator argument must resolve to a string Value is of type '{ alias: string }'." + ], + "diagnosticSpans": [[177, 197]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-declared", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\ndeclare const N: string;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(N) e = new EventEmitter();\n}\n" + }, + "diagnostics": [ + "@Output decorator argument must resolve to a string Value is a reference to 'N'." + ], + "diagnosticSpans": [[175, 185]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-twoArgs", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n // @ts-ignore\n @Output('a', 'b') e = new EventEmitter();\n}\n" + }, + "diagnostics": ["@Output can have at most one argument, got 2 argument(s)"], + "diagnosticSpans": [[166, 183]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-noArg", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output() e = new EventEmitter();\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "outputs": "{e:\"e\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-component", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y';\n@Component({selector: 'c', template: ''})\nexport class Cmp {\n @Output(NAME) e = new EventEmitter();\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "outputs": "{e:\"y\"}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-accessor", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(NAME) accessor e = new EventEmitter();\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "outputs": "{e:\"y\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-badAndMetaOutputs", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]', outputs: [1 as any]})\nexport class Dir {\n @Output(1 as any) e = new EventEmitter();\n}\n" + }, + "diagnostics": [ + "Failed to resolve outputs at position 0 to a string Value is of type 'number'." + ], + "diagnosticSpans": [[137, 147]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-aliasedTwoArgs", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input as In} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n // @ts-ignore\n @In('a', 'b') x: any;\n}\n" + }, + "diagnostics": ["@In can have at most one argument, got 2 argument(s)"], + "diagnosticSpans": [[121, 134]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-aliasedNumber", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input as In} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @In(1 as any) x: any;\n}\n" + }, + "diagnostics": [ + "@In decorator argument must resolve to a string or an object literal Value is of type 'number'." + ], + "diagnosticSpans": [[105, 118]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-nsTwoArgs", + "origin": "probe", + "files": { + "test.ts": "import {Directive} from '@angular/core';\nimport * as core from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n // @ts-ignore\n @core.Input('a', 'b') x: any;\n}\n" + }, + "diagnostics": ["@Input can have at most one argument, got 2 argument(s)"], + "diagnosticSpans": [[147, 168]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-nsNumber", + "origin": "probe", + "files": { + "test.ts": "import {Directive} from '@angular/core';\nimport * as core from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @core.Input(1 as any) x: any;\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is of type 'number'." + ], + "diagnosticSpans": [[131, 152]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-aliasedNumber", + "origin": "probe", + "files": { + "test.ts": "import {Directive, EventEmitter, Output as Out} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Out(1 as any) e = new EventEmitter();\n}\n" + }, + "diagnostics": [ + "@Output decorator argument must resolve to a string Value is of type 'number'." + ], + "diagnosticSpans": [[121, 135]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-aliasedTwoArgs", + "origin": "probe", + "files": { + "test.ts": "import {Directive, EventEmitter, Output as Out} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n // @ts-ignore\n @Out('a', 'b') e = new EventEmitter();\n}\n" + }, + "diagnostics": ["@Output can have at most one argument, got 2 argument(s)"], + "diagnosticSpans": [[137, 151]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-letUninit", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nlet NAME;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(NAME) x: any;\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is of type 'undefined'." + ], + "diagnosticSpans": [[149, 161]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-aliasLetUninit", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nlet NAME;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({alias: NAME}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-constArrowTransform", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst OPTS = {transform: (v: string) => 1};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(OPTS) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",(v)=>1]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:string;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-constFnTransform", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nexport function f(v: string) { return 1; }\nconst OPTS = {alias: 'y', transform: f};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(OPTS) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"y\",\"x\",f]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:string;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-constBadTransform", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst OPTS = {transform: 1};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(OPTS) x: any;\n}\n" + }, + "diagnostics": ["Input transform must be a function Value is of type 'number'."], + "diagnosticSpans": [[175, 179]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-twoArgsAndBadFirst", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n // @ts-ignore\n @Input(1, 2) x: any;\n}\n" + }, + "diagnostics": ["@Input can have at most one argument, got 2 argument(s)"], + "diagnosticSpans": [[155, 167]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-emptyObject", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-emptyString", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input('') x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-constEmpty", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = '';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(NAME) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-ternary", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst C = false;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(C ? 'a' : 'b') x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"b\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-orDefault", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst N = '';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(N || 'z') x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"z\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-transformOnlyConst", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst OPTS = {transform: booleanAttribute};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(OPTS) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-transformConstAlias", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({alias: NAME, transform: booleanAttribute}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"y\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-staticNumber", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(1 as any) static x: any;\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is of type 'number'." + ], + "diagnosticSpans": [[139, 155]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-out-staticNumber", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(1 as any) static e = new EventEmitter();\n}\n" + }, + "diagnostics": [ + "@Output decorator argument must resolve to a string Value is of type 'number'." + ], + "diagnosticSpans": [[139, 156]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: memberArg-in-twoInputsSecondBad", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter, booleanAttribute} from '@angular/core';\nconst NAME = 'y';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(NAME) a: any;\n @Input(1 as any) b: any;\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is of type 'number'." + ], + "diagnosticSpans": [[180, 196]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: transform-global-atob", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: atob}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",atob]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: transform-global-btoa", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: btoa}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",btoa]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: transform-global-alert", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: alert}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",alert]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: transform-global-globalThis", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: globalThis}) x: any;\n}\n" + }, + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[118, 128]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: transform-global-undefined", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: undefined}) x: any;\n}\n" + }, + "diagnostics": ["Input transform must be a function Value is of type 'undefined'."], + "diagnosticSpans": [[106, 128]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: transform-global-inputsAtob", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]', inputs: [{name: 'x', transform: atob}]})\nexport class Dir {\n x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",atob]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: transform-global-projectFunction", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: ambientFn}) x: any;\n}\n", + "globals.d.ts": "declare function ambientFn(v: string): number;\ndeclare var ambientVar: string;\ndeclare function ambientOverload(v: string): number;\ndeclare function ambientOverload(v: number): number;\ndeclare function ambientGeneric(v: T): T;\ndeclare namespace AmbNs { function f(v: string): number; }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",ambientFn]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: transform-global-projectNamespaceMember", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: AmbNs.f}) x: any;\n}\n", + "globals.d.ts": "declare function ambientFn(v: string): number;\ndeclare var ambientVar: string;\ndeclare function ambientOverload(v: string): number;\ndeclare function ambientOverload(v: number): number;\ndeclare function ambientGeneric(v: T): T;\ndeclare namespace AmbNs { function f(v: string): number; }\n" + }, + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[118, 125]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: transform-global-declareGlobalFunction", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\ndeclare global { function gg(v: string): number; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: gg}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",gg]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: transform-global-aliasWindow", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({alias: window as any}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: transform-global-aliasUndeclared", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({alias: undeclaredName}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: transform-global-Intl", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: Intl}) x: any;\n}\n" + }, + "diagnostics": ["Input transform must be a function Value is a reference to 'Intl'."], + "classes": {}, + "dtsImports": [], + "skip": "ngtsc reports this at the transform's declaration in TypeScript's lib.es5.d.ts, a file oxc doesn't read (so this snapshot's generator, which keeps test.ts diagnostics, records none); oxc reports the same message on the expression" + }, + { + "name": "probe: transform-global-Reflect", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: Reflect}) x: any;\n}\n" + }, + "diagnostics": ["Input transform must be a function Value is a reference to 'Reflect'."], + "classes": {}, + "dtsImports": [], + "skip": "ngtsc reports this at the transform's declaration in TypeScript's lib.es2015.reflect.d.ts, a file oxc doesn't read (so this snapshot's generator, which keeps test.ts diagnostics, records none); oxc reports the same message on the expression" + }, + { + "name": "probe: transform-global-clashAtob", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n static ngAcceptInputType_x: string;\n @Input({transform: atob}) x: any;\n}\n" + }, + "diagnostics": [ + "Class cannot have both a transform function on Input x and a static member called ngAcceptInputType_x" + ], + "classes": {}, + "dtsImports": [], + "skip": "ngtsc reports this at the transform's declaration in TypeScript's lib.dom.d.ts, a file oxc doesn't read (so this snapshot's generator, which keeps test.ts diagnostics, records none); oxc reports the same message on the expression" + }, + { + "name": "probe: transform-global-window", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: window}) x: any;\n}\n" + }, + "diagnostics": ["Input transform must be a function Value is a reference to 'window'."], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-document", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: document}) x: any;\n}\n" + }, + "diagnostics": ["Input transform must be a function Value is a reference to 'document'."], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-navigator", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: navigator}) x: any;\n}\n" + }, + "diagnostics": ["Input transform must be a function Value is a reference to 'navigator'."], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-localStorage", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: localStorage}) x: any;\n}\n" + }, + "diagnostics": ["Input transform must be a function Value is a reference to 'localStorage'."], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-console", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: console}) x: any;\n}\n" + }, + "diagnostics": ["Input transform must be a function Value is a reference to 'console'."], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-self", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: self}) x: any;\n}\n" + }, + "diagnostics": ["Input transform must be a function Value is a reference to 'self'."], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-CSS", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: CSS}) x: any;\n}\n" + }, + "diagnostics": ["Input transform must be a function Value is a reference to 'CSS'."], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-WebAssembly", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: WebAssembly}) x: any;\n}\n" + }, + "diagnostics": ["Input transform must be a function Value is a reference to 'WebAssembly'."], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-structuredClone", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: structuredClone}) x: any;\n}\n" + }, + "diagnostics": [ + "Input transform function cannot be generic Value is a reference to 'structuredClone'." + ], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-queueMicrotask", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: queueMicrotask}) x: any;\n}\n" + }, + "diagnostics": [ + "Unable to import type VoidFunction. The symbol is not exported from /Users/brooklyn/workspace/github/oxc-angular-compiler/node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.dom.d.ts." + ], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-setTimeout", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: setTimeout}) x: any;\n}\n" + }, + "diagnostics": [ + "Unable to import type TimerHandler. The symbol is not exported from /Users/brooklyn/workspace/github/oxc-angular-compiler/node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.dom.d.ts." + ], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-fetch", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: fetch}) x: any;\n}\n" + }, + "diagnostics": [ + "Unable to import type RequestInfo. The symbol is not exported from /Users/brooklyn/workspace/github/oxc-angular-compiler/node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/lib.dom.d.ts." + ], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-projectVar", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: ambientVar}) x: any;\n}\n", + "globals.d.ts": "declare function ambientFn(v: string): number;\ndeclare var ambientVar: string;\ndeclare function ambientOverload(v: string): number;\ndeclare function ambientOverload(v: number): number;\ndeclare function ambientGeneric(v: T): T;\ndeclare namespace AmbNs { function f(v: string): number; }\n" + }, + "diagnostics": ["Input transform must be a function Value is a reference to 'ambientVar'."], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (a project `.d.ts`), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-projectOverload", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: ambientOverload}) x: any;\n}\n", + "globals.d.ts": "declare function ambientFn(v: string): number;\ndeclare var ambientVar: string;\ndeclare function ambientOverload(v: string): number;\ndeclare function ambientOverload(v: number): number;\ndeclare function ambientGeneric(v: T): T;\ndeclare namespace AmbNs { function f(v: string): number; }\n" + }, + "diagnostics": [ + "Input transform function cannot have multiple signatures Value is a reference to 'ambientOverload'." + ], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (a project `.d.ts`), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-projectGeneric", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: ambientGeneric}) x: any;\n}\n", + "globals.d.ts": "declare function ambientFn(v: string): number;\ndeclare var ambientVar: string;\ndeclare function ambientOverload(v: string): number;\ndeclare function ambientOverload(v: number): number;\ndeclare function ambientGeneric(v: T): T;\ndeclare namespace AmbNs { function f(v: string): number; }\n" + }, + "diagnostics": [ + "Input transform function cannot be generic Value is a reference to 'ambientGeneric'." + ], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (a project `.d.ts`), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-inputsWindow", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]', inputs: [{name: 'x', transform: window}]})\nexport class Dir {\n x: any;\n}\n" + }, + "diagnostics": ["Input transform must be a function Value is a reference to 'window'."], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (TypeScript's DOM lib), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-declareGlobalVar", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\ndeclare global { var gv: string; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: gv}) x: any;\n}\n" + }, + "diagnostics": ["Input transform must be a function Value is of type 'undefined'."], + "classes": {}, + "dtsImports": [], + "skip": "a global declared outside this file (this file's `declare global`, whose names oxc doesn't track), which oxc doesn't read: oxc assumes it's a function and accepts it, as it does an imported transform (only the `ngAcceptInputType_` clash is checked), where ngtsc reads the declaration and reports this" + }, + { + "name": "probe: transform-global-namespaceMember", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\nnamespace U { export function f(v: string) { return 1; } }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: U.f}) x: any;\n}\n" + }, + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[177, 180]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: transform-global-declareModule", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\ndeclare module M { }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: M}) x: any;\n}\n" + }, + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[139, 140]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: transform-global-undeclared", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: undeclaredName}) x: any;\n}\n" + }, + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "classes": {}, + "dtsImports": [], + "skip": "a name declared nowhere, which TypeScript reports: oxc can't tell it from a global declared outside the file, so it assumes it's a function and accepts it, where ngtsc reports this" + }, + { + "name": "probe: transform-global-inputsUndeclared", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]', inputs: [{name: 'x', transform: undeclaredName}]})\nexport class Dir {\n x: any;\n}\n" + }, + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "classes": {}, + "dtsImports": [], + "skip": "a name declared nowhere, which TypeScript reports: oxc can't tell it from a global declared outside the file, so it assumes it's a function and accepts it, where ngtsc reports this" + }, + { + "name": "probe: transform-global-member-window_atob", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: window.atob}) x: any;\n}\n" + }, + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[118, 129]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: transform-global-member-document_createElement", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: document.createElement}) x: any;\n}\n" + }, + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[118, 140]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: transform-global-member-globalThis_atob", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: globalThis.atob}) x: any;\n}\n" + }, + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[118, 133]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: transform-global-member-self_atob", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: self.atob}) x: any;\n}\n" + }, + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[118, 127]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: transform-global-member-console_log", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: console.log}) x: any;\n}\n" + }, + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[118, 129]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: transform-global-member-undeclaredName_foo", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: undeclaredName.foo}) x: any;\n}\n" + }, + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[118, 136]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: transform-global-member-localStorage_getItem", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: localStorage.getItem}) x: any;\n}\n" + }, + "diagnostics": [ + "Input transform must be a function Value could not be determined statically." + ], + "diagnosticSpans": [[118, 138]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: shorthandRef-globalConst", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\nconst t = atob;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: t}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",atob]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shorthandRef-globalTernary", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\nconst A = 1;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: A ? atob : btoa}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",atob]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shorthandRef-globalHelper", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\nfunction pick(f: (v: string) => string) { return f; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: pick(atob)}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",atob]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shorthandRef-globalObject", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\nconst T = {f: atob};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: T.f}) x: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",atob]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shorthandRef-globalCondition", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: window ? atob : btoa}) x: any;\n}\n" + }, + "diagnostics": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",atob]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [], + "skip": "a global declared outside the file is dynamic in oxc wherever its value is used (here in a condition or `||`), since oxc can't tell it from a name declared nowhere, so the transform isn't a function; ngtsc reads its declaration and picks `atob`" + }, + { + "name": "probe: shorthandRef-globalOr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: atob || btoa}) x: any;\n}\n" + }, + "diagnostics": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",atob]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [], + "skip": "a global declared outside the file is dynamic in oxc wherever its value is used (here in a condition or `||`), since oxc can't tell it from a name declared nowhere, so the transform isn't a function; ngtsc reads its declaration and picks `atob`" + }, + { + "name": "probe: shorthandRef-globalNot", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: (!atob ? btoa : atob)}) x: any;\n}\n" + }, + "diagnostics": [], + "classes": { + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",atob]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [], + "skip": "a global declared outside the file is dynamic in oxc wherever its value is used (here in a condition or `||`), since oxc can't tell it from a name declared nowhere, so the transform isn't a function; ngtsc reads its declaration and picks `atob`" + }, + { + "name": "probe: signalDecorator-in-direct", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() value = input(0);\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[176, 184]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-alias", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\nimport {input as inp} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() value = inp(0);\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[220, 228]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-ns", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\nimport * as ng from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() value = ng.input(0);\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[213, 221]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-nsDecorator", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\nimport * as ng from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @ng.Input() value = ng.input(0);\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[213, 224]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-required", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() value = input.required();\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[176, 184]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-nsRequired", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\nimport * as ng from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() value = ng.input.required();\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[213, 221]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-model", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() value = model(0);\n}\n" + }, + "diagnostics": ["Using @Input with a model input is not allowed."], + "diagnosticSpans": [[176, 184]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-modelRequired", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() value = model.required();\n}\n" + }, + "diagnostics": ["Using @Input with a model input is not allowed."], + "diagnosticSpans": [[176, 184]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-local", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, model, output, ViewChild, viewChild} from '@angular/core';\nfunction input(v: number) { return v; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() value = input(0);\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{value:\"value\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-undeclared", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() value = input(0);\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{value:\"value\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-aliasArg", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input('a') value = input(0);\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[176, 187]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-badArg", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(1 as any) value = input(0);\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[176, 192]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-twoArgs", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input('a', 'b' as any) value = input(0);\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[176, 199]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-afterBad", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(1 as any) a: any;\n @Input() value = input(0);\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is of type 'number'." + ], + "diagnosticSpans": [[176, 192]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-beforeBad", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() value = input(0);\n @Input(1 as any) a: any;\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[176, 184]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-meta", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]', inputs: ['value']})\nexport class Dir {\n @Input() value = input(0);\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[195, 203]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-metaOther", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]', inputs: ['other']})\nexport class Dir {\n @Input() value = input(0);\n other = input(0);\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[195, 203]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-metaBad", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]', inputs: [1 as any]})\nexport class Dir {\n @Input() value = input(0);\n}\n" + }, + "diagnostics": [ + "@Directive.inputs array can only contain strings or object literals Value is of type 'number'." + ], + "diagnosticSpans": [[162, 172]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-static", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() static value = input(0);\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[176, 184]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-component", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Component({selector: 'c', template: ''})\nexport class Dir {\n @Input() value = input(0);\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[188, 196]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-transform", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: (v: string) => 1}) value = input(0);\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[176, 213]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-andOutput", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() @Output() value = model(0);\n}\n" + }, + "diagnostics": ["Using @Input with a model input is not allowed."], + "diagnosticSpans": [[176, 184]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-outputOnInputMember", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() a = output();\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{a:\"a\"}", + "outputs": "{a:\"a\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-out-direct", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output() e = output();\n}\n" + }, + "diagnostics": ["Using \"@Output\" with \"output()\" is not allowed."], + "diagnosticSpans": [[176, 185]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-out-alias", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\nimport {output as out} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output() e = out();\n}\n" + }, + "diagnostics": ["Using \"@Output\" with \"output()\" is not allowed."], + "diagnosticSpans": [[221, 230]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-out-ns", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\nimport * as ng from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output() e = ng.output();\n}\n" + }, + "diagnostics": ["Using \"@Output\" with \"output()\" is not allowed."], + "diagnosticSpans": [[213, 222]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-out-fromObs", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\nimport {outputFromObservable} from '@angular/core/rxjs-interop';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output() e = outputFromObservable(new EventEmitter());\n}\n" + }, + "diagnostics": ["Using \"@Output\" with \"output()\" is not allowed."], + "diagnosticSpans": [[241, 250]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-out-model", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output() e = model(0);\n}\n" + }, + "diagnostics": ["Using @Output with a model input is not allowed."], + "diagnosticSpans": [[176, 185]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-out-modelRequired", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output() e = model.required();\n}\n" + }, + "diagnostics": ["Using @Output with a model input is not allowed."], + "diagnosticSpans": [[176, 185]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-out-badArg", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(1 as any) e = output();\n}\n" + }, + "diagnostics": [ + "@Output decorator argument must resolve to a string Value is of type 'number'." + ], + "diagnosticSpans": [[176, 193]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-out-afterBad", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(1 as any) a = new EventEmitter();\n @Output() e = output();\n}\n" + }, + "diagnostics": [ + "@Output decorator argument must resolve to a string Value is of type 'number'." + ], + "diagnosticSpans": [[176, 193]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-out-meta", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]', outputs: ['e']})\nexport class Dir {\n @Output() e = output();\n}\n" + }, + "diagnostics": ["Using \"@Output\" with \"output()\" is not allowed."], + "diagnosticSpans": [[192, 201]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-out-inputFirst", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output() e = output();\n @Input() value = input(0);\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[202, 210]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-out-static", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output() static e = output();\n}\n" + }, + "diagnostics": ["Using \"@Output\" with \"output()\" is not allowed."], + "diagnosticSpans": [[176, 185]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-out-inputOnOutput", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, output, ViewChild, viewChild} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output() e = input(0);\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{e:[1,\"e\"]}", + "outputs": "{e:\"e\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-foreign", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, model, output, ViewChild, viewChild} from '@angular/core';\nimport {input} from './other';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() value = input(0);\n}\n", + "other.ts": "export function input(v: number) { return v; }\nexport function model(v: number) { return v; }\nexport function output() { return 1; }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{value:\"value\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-foreignModel", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, output, ViewChild, viewChild} from '@angular/core';\nimport {model} from './other';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() value = model(0);\n}\n", + "other.ts": "export function input(v: number) { return v; }\nexport function model(v: number) { return v; }\nexport function output() { return 1; }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{value:\"value\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-out-foreign", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, EventEmitter, input, model, ViewChild, viewChild} from '@angular/core';\nimport {output} from './other';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output() e = output();\n}\n", + "other.ts": "export function input(v: number) { return v; }\nexport function model(v: number) { return v; }\nexport function output() { return 1; }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "outputs": "{e:\"e\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-accessor", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, input, model, output} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() accessor value = input(0);\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[129, 137]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-out-accessor", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, input, model, output} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output() accessor e = output();\n}\n" + }, + "diagnostics": ["Using \"@Output\" with \"output()\" is not allowed."], + "diagnosticSpans": [[129, 138]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-setter", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, input, model, output} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() set value(v: any) {}\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{value:\"value\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-parens", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, input, model, output} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() value = (input(0));\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[129, 137]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: signalDecorator-in-asAny", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, input, model, output} from '@angular/core';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input() value = input(0) as any;\n}\n" + }, + "diagnostics": ["Using @Input with a signal input is not allowed."], + "diagnosticSpans": [[129, 137]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-metaOr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [LOCAL || NAME]})\nexport class Dir {\n localAlias: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{localAlias:\"localAlias\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-metaObjAliasOr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [{name: 'x', alias: LOCAL || NAME}]})\nexport class Dir {\n x: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { "inputs": "{x:[0,\"localAlias\",\"x\"]}", "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } @@ -10090,355 +13408,695 @@ "dtsImports": [] }, { - "name": "probe: shortCircuit-metaObjAliasAnd", + "name": "probe: shortCircuit-metaObjAliasAnd", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [{name: 'x', alias: (EMPTY_LOCAL && NAME) || 'z'}]})\nexport class Dir {\n x: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"z\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-metaCond", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [true ? 'x' : NAME]})\nexport class Dir {\n x: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-metaArrayOr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: ['x'] || NAME})\nexport class Dir {\n x: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-metaOutOr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', outputs: [LOCAL || NAME]})\nexport class Dir {\n localAlias = new EventEmitter();\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "outputs": "{localAlias:\"localAlias\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-metaOutCond", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', outputs: [1 ? LOCAL : NAME]})\nexport class Dir {\n localAlias = new EventEmitter();\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "outputs": "{localAlias:\"localAlias\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-metaObjRequiredOr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [{name: 'x', required: true || FLAG}]})\nexport class Dir {\n x: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-metaObjRequiredAnd", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [{name: 'x', required: 0 && FLAG}]})\nexport class Dir {\n x: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-metaSpreadOr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [...(['x'] || NAME)]})\nexport class Dir {\n x: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-metaNestedOr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [(EMPTY_LOCAL || 0) || (LOCAL && 'x') || NAME]})\nexport class Dir {\n x: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-metaDynamicUnselected", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: ['x' || window.name]})\nexport class Dir {\n x: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [ + "@Directive.inputs array can only contain strings or object literals Value could not be determined statically." + ], + "diagnosticSpans": [[310, 330]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-metaCondImportUnselected", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: 0 ? NAME : ['x']})\nexport class Dir {\n x: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:\"x\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-qMetaOr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild('x' || NAME)}})\nexport class Cmp {\n item: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-qMetaCond", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(true ? 'x' : NAME)}})\nexport class Cmp {\n item: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-qMetaStaticOr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild('x', {static: true || FLAG})}})\nexport class Cmp {\n item: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-qMetaReadOr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild('x', {read: 0 || Token})}})\nexport class Cmp {\n item: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5,0||Token);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-qMetaPredOrToken", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(0 || Token)}})\nexport class Cmp {\n item: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(0||Token,5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-qMemberOr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x' || NAME) item: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-qMemberCond", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild(false ? NAME : 'x') item: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-qMemberStaticOr", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: true || FLAG}) item: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-qMemberStaticCond", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: 1 ? true : FLAG}) item: any;\n}\n", + "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Cmp": { + "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: shortCircuit-qMemberDescOr", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [{name: 'x', alias: (EMPTY_LOCAL && NAME) || 'z'}]})\nexport class Dir {\n x: any;\n}\n", + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {descendants: false && FLAG}) item: any;\n}\n", "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Dir": { - "inputs": "{x:[0,\"z\",\"x\"]}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "Cmp": { + "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", + "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-metaCond", + "name": "probe: helperTransform-inputOpts", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [true ? 'x' : NAME]})\nexport class Dir {\n x: any;\n}\n", - "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nfunction opts(o: {t: typeof booleanAttribute}) { return {transform: o.t}; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(opts({t: booleanAttribute})) x: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { "Dir": { - "inputs": "{x:\"x\"}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-metaArrayOr", + "name": "probe: helperTransform-metaMake", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: ['x'] || NAME})\nexport class Dir {\n x: any;\n}\n", - "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nfunction make(o: {t: any}) { return {name: 'x', transform: o.t}; }\n@Directive({selector: '[d]', inputs: [make({t: booleanAttribute})]})\nexport class Dir {\n x: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { "Dir": { - "inputs": "{x:\"x\"}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-metaOutOr", + "name": "probe: helperTransform-inputOptsAliased", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', outputs: [LOCAL || NAME]})\nexport class Dir {\n localAlias = new EventEmitter();\n}\n", - "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {booleanAttribute as ba} from '@angular/core';\nfunction opts(o: {t: typeof booleanAttribute}) { return {transform: o.t}; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(opts({t: ba})) x: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { "Dir": { - "outputs": "{localAlias:\"localAlias\"}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "inputs": "{x:[2,\"x\",\"x\",ba]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-metaOutCond", + "name": "probe: helperTransform-inputOptsLocal", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', outputs: [1 ? LOCAL : NAME]})\nexport class Dir {\n localAlias = new EventEmitter();\n}\n", - "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nfunction opts(o: {t: typeof booleanAttribute}) { return {transform: o.t}; }\nfunction local(v: string) { return true; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(opts({t: local})) x: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { "Dir": { - "outputs": "{localAlias:\"localAlias\"}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "inputs": "{x:[2,\"x\",\"x\",local]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:string;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-metaObjRequiredOr", + "name": "probe: helperTransform-inputOptsSpread", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [{name: 'x', required: true || FLAG}]})\nexport class Dir {\n x: any;\n}\n", - "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nfunction opts(o: {t: typeof booleanAttribute}) { return {transform: o.t}; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({...opts({t: booleanAttribute}), alias: 'a'}) x: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { "Dir": { - "inputs": "{x:\"x\"}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "inputs": "{x:[2,\"a\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-metaObjRequiredAnd", + "name": "probe: helperTransform-inputOptsConst", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [{name: 'x', required: 0 && FLAG}]})\nexport class Dir {\n x: any;\n}\n", - "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nfunction opts(o: {t: typeof booleanAttribute}) { return {transform: o.t}; }\nconst O = opts({t: booleanAttribute});\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(O) x: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { "Dir": { - "inputs": "{x:\"x\"}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-metaSpreadOr", + "name": "probe: helperTransform-inputDirectMember", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [...(['x'] || NAME)]})\nexport class Dir {\n x: any;\n}\n", - "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nconst T = {t: booleanAttribute};\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: T.t}) x: any;\n}\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { "Dir": { - "inputs": "{x:\"x\"}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-metaNestedOr", + "name": "probe: shortCircuit-inOrLocal", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: [(EMPTY_LOCAL || 0) || (LOCAL && 'x') || NAME]})\nexport class Dir {\n x: any;\n}\n", + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(LOCAL || NAME) x: any;\n}\n", "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { "Dir": { - "inputs": "{x:\"x\"}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "inputs": "{x:[0,\"localAlias\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-metaDynamicUnselected", + "name": "probe: shortCircuit-inAndFalsy", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: ['x' || window.name]})\nexport class Dir {\n x: any;\n}\n", + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input((EMPTY_LOCAL && NAME) || 'z') x: any;\n}\n", "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" }, - "diagnostics": [ - "@Directive.inputs array can only contain strings or object literals Value could not be determined statically." - ], - "diagnosticSpans": [[310, 330]], - "classes": {}, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{x:[0,\"z\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, "dtsImports": [] }, { - "name": "probe: shortCircuit-metaCondImportUnselected", + "name": "probe: shortCircuit-inCondTrue", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]', inputs: 0 ? NAME : ['x']})\nexport class Dir {\n x: any;\n}\n", + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(true ? LOCAL : NAME) x: any;\n}\n", "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { "Dir": { - "inputs": "{x:\"x\"}", - "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + "inputs": "{x:[0,\"localAlias\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-qMetaOr", + "name": "probe: shortCircuit-inCondFalse", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild('x' || NAME)}})\nexport class Cmp {\n item: any;\n}\n", + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(false ? NAME : LOCAL) x: any;\n}\n", "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[0,\"localAlias\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-qMetaCond", + "name": "probe: shortCircuit-inCoalesce", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(true ? 'x' : NAME)}})\nexport class Cmp {\n item: any;\n}\n", + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(LOCAL ?? NAME) x: any;\n}\n", "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" }, - "diagnostics": [], - "diagnosticSpans": [], - "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" - } - }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value could not be determined statically." + ], + "diagnosticSpans": [[324, 345]], + "classes": {}, "dtsImports": [] }, { - "name": "probe: shortCircuit-qMetaStaticOr", + "name": "probe: shortCircuit-inObjAliasOr", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild('x', {static: true || FLAG})}})\nexport class Cmp {\n item: any;\n}\n", + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({alias: LOCAL || NAME}) x: any;\n}\n", "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[0,\"localAlias\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-qMetaReadOr", + "name": "probe: shortCircuit-inObjRequiredOr", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild('x', {read: 0 || Token})}})\nexport class Cmp {\n item: any;\n}\n", + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({alias: 'a', required: true || FLAG}) x: any;\n}\n", "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5,0||Token);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[0,\"a\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-qMetaPredOrToken", + "name": "probe: shortCircuit-inObjRequiredCond", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
', queries: {item: new ViewChild(0 || Token)}})\nexport class Cmp {\n item: any;\n}\n", + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({alias: 'a', required: 1 ? true : FLAG}) x: any;\n}\n", "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery(0||Token,5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[0,\"a\",\"x\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-qMemberOr", + "name": "probe: shortCircuit-inTransformOr", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x' || NAME) item: any;\n}\n", + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: 0 || booleanAttribute}) x: any;\n}\n", "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",booleanAttribute]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:unknown;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-qMemberCond", + "name": "probe: shortCircuit-inTransformOrLocalFirst", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild(false ? NAME : 'x') item: any;\n}\n", + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\nfunction local(v: string) { return 1; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: local || toBool}) x: any;\n}\n", "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],5);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",local]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:string;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-qMemberStaticOr", + "name": "probe: shortCircuit-inTransformCondLocal", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: true || FLAG}) item: any;\n}\n", + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\nfunction local(v: string) { return 1; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({transform: false ? toBool : local}) x: any;\n}\n", "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "inputs": "{x:[2,\"x\",\"x\",local]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;", + "dts:ngAcceptInputType_x": "staticngAcceptInputType_x:string;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-qMemberStaticCond", + "name": "probe: shortCircuit-outOr", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ViewChild('x', {static: 1 ? true : FLAG}) item: any;\n}\n", + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(LOCAL || NAME) e = new EventEmitter();\n}\n", "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "viewQuery": "{if(rf&1){i0.ɵɵviewQuery([\"x\"],7);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "outputs": "{e:\"localAlias\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] }, { - "name": "probe: shortCircuit-qMemberDescOr", + "name": "probe: shortCircuit-outCond", "origin": "probe", "files": { - "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Component({selector: 'c', template: '
'})\nexport class Cmp {\n @ContentChild('x', {descendants: false && FLAG}) item: any;\n}\n", + "test.ts": "import {Directive, Component, Input, Output, ViewChild, ContentChild, ElementRef, booleanAttribute, EventEmitter} from '@angular/core';\nimport {NAME, FLAG, TRUE_FLAG, Token, EMPTY, toBool} from './other';\nconst LOCAL: string = 'localAlias';\nconst EMPTY_LOCAL: string = '';\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(true ? LOCAL : NAME) e = new EventEmitter();\n}\n", "other.ts": "import {booleanAttribute} from '@angular/core';\nexport const NAME = 'y';\nexport const EMPTY = '';\nexport const FLAG = false;\nexport const TRUE_FLAG = true;\nexport class Token {}\nexport function toBool(v: string) { return !!v; }\nexport interface Other { a: string }\n" }, "diagnostics": [], "diagnosticSpans": [], "classes": { - "Cmp": { - "contentQueries": "{if(rf&1){i0.ɵɵcontentQuery(dirIndex,[\"x\"],4);}if(rf&2){let_t;i0.ɵɵqueryRefresh(_t=i0.ɵɵloadQuery())&&(ctx.item=_t.first);}}", - "dts:ɵcmp": "staticɵcmp:i0.ɵɵComponentDeclaration;" + "Dir": { + "outputs": "{e:\"localAlias\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" } }, "dtsImports": [] @@ -12515,6 +16173,125 @@ } }, "dtsImports": [] + }, + { + "name": "probe: r6-ns-member-input-alias-typeof", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, ContentChild, ViewChild} from '@angular/core';\nnamespace NS { export const AL = 'why'; }\ndeclare const S: typeof NS.AL;\n@Directive({selector: '[d]', })\nexport class Dir {\n @Input(S) a: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{a:[0,\"why\",\"a\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: r6-m504-output-typeof", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter} from '@angular/core';\nnamespace NS { export const AL = 'changed'; }\ndeclare const S: typeof NS.AL;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(S) a = new EventEmitter();\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "outputs": "{a:\"changed\"}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: r6-m504-input-options-typeof", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter} from '@angular/core';\nnamespace NS { export const AL = 'why'; export const REQ = true; }\ndeclare const S: typeof NS.AL;\ndeclare const R: typeof NS.REQ;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input({alias: S, required: R}) a: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{a:[0,\"why\",\"a\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: r6-m504-input-plain", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter} from '@angular/core';\nnamespace NS { export const AL = 'why'; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(NS.AL) a: any;\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value could not be determined statically." + ], + "diagnosticSpans": [[163, 176]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: r6-m504-output-plain", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter} from '@angular/core';\nnamespace NS { export const AL = 'why'; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Output(NS.AL) a = new EventEmitter();\n}\n" + }, + "diagnostics": [ + "@Output decorator argument must resolve to a string Value could not be determined statically." + ], + "diagnosticSpans": [[163, 177]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: r6-m504-input-alias-import", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter} from '@angular/core';\nnamespace NS { export const AL = 'why'; }\nimport S = NS.AL;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(S) a: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{a:[0,\"why\",\"a\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] + }, + { + "name": "probe: r6-m504-input-namespace", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter} from '@angular/core';\nnamespace NS { export const AL = 'why'; }\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(NS) a: any;\n}\n" + }, + "diagnostics": [ + "@Input decorator argument must resolve to a string or an object literal Value is a reference to 'NS'." + ], + "diagnosticSpans": [[163, 173]], + "classes": {}, + "dtsImports": [] + }, + { + "name": "probe: r6-m504-input-options-object-typeof", + "origin": "probe", + "files": { + "test.ts": "import {Directive, Input, Output, EventEmitter} from '@angular/core';\nnamespace NS { export const OPTS = {alias: 'why'}; }\ndeclare const O: typeof NS.OPTS;\n@Directive({selector: '[d]'})\nexport class Dir {\n @Input(O) a: any;\n}\n" + }, + "diagnostics": [], + "diagnosticSpans": [], + "classes": { + "Dir": { + "inputs": "{a:[0,\"why\",\"a\"]}", + "dts:ɵdir": "staticɵdir:i0.ɵɵDirectiveDeclaration;" + } + }, + "dtsImports": [] } ] } diff --git a/crates/oxc_angular_compiler/tests/jit_signal_initializer_test.rs b/crates/oxc_angular_compiler/tests/jit_signal_initializer_test.rs index 8bb4913ce..3f20f49f5 100644 --- a/crates/oxc_angular_compiler/tests/jit_signal_initializer_test.rs +++ b/crates/oxc_angular_compiler/tests/jit_signal_initializer_test.rs @@ -224,3 +224,113 @@ fn explicit_view_child_decorator_blocks_query_synthesis() { "synthesis must skip when explicit query decorator coexists. Got:\n{out}" ); } + +#[test] +fn only_angular_initializer_apis_are_lowered() { + // ngtsc's JIT transform recognises the APIs by their `@angular/core` import + // (`tryParseInitializerApi`): an alias counts; a function from another + // module, a local one or an undeclared one doesn't. The synthesized + // decorators reuse the file's `@angular/core` namespace import, like ngtsc. + let out = compile_jit( + "import { Component, input as inp } from '@angular/core';\n\ + import * as ng from '@angular/core';\n\ + import { output } from './other';\n\ + function model(v?: any): any { return v; }\n\ + @Component({ selector: 'c', template: '', standalone: true })\n\ + export class C {\n a = inp(0);\n b = output();\n c = model(0);\n d = viewChild('x');\n e = ng.contentChild.required('y');\n}\n", + ); + let props = &out[out.find("propDecorators").expect("propDecorators missing")..]; + let props: String = props.chars().filter(|c| !c.is_whitespace()).collect(); + assert!( + props.starts_with( + r#"propDecorators={a:[{type:ng.Input,args:[{isSignal:true,alias:"a",required:false,transform:undefined}]}],e:[{type:ng.ContentChild,args:["y",{isSignal:true}]}]};"# + ), + "Got:\n{out}" + ); +} + +#[test] +fn synthesized_decorators_use_a_free_angular_core_namespace() { + // ngtsc's `ImportManager` reuses the file's own `import * as x from + // '@angular/core'`, and otherwise names the new import `i0_1`, `i0_2`, ... + // when the file already has an `i0` identifier: an aliased signal API + // (`input as i0`), a variable, ... A second `i0` binding would be a + // SyntaxError. + let alias = compile_jit(&component("value = i0(0);", "input as i0")); + assert!(alias.contains("import * as i0_1 from \"@angular/core\";"), "{alias}"); + assert!(alias.contains("type: i0_1.Input"), "{alias}"); + assert!(!alias.contains("import * as i0 from"), "{alias}"); + + let variable = compile_jit(&format!( + "const i0 = 1;\nconst i0_1 = 2;\n{}", + component("value = input(i0 + i0_1);", "input") + )); + assert!(variable.contains("import * as i0_2 from \"@angular/core\";"), "{variable}"); + assert!(variable.contains("type: i0_2.Input"), "{variable}"); + + let reused = compile_jit(&format!( + "import * as i0 from '@angular/core';\n{}", + component("value = input(0);", "input") + )); + assert_eq!(reused.matches("from \"@angular/core\"").count(), 2, "{reused}"); + assert!(reused.contains("type: i0.Input"), "{reused}"); +} + +#[test] +fn member_decorators_are_lowered_only_when_imported_from_angular_core() { + // Like ngtsc's JIT transform: `@angular/core`'s decorators (under an alias + // or a namespace too) go to `propDecorators`, as written; another module's + // `@Input` stays a decorator applied with `__decorate`. + let out = compile_jit( + "import { Component, Input as In } from '@angular/core';\n\ + import * as core from '@angular/core';\n\ + import { Input } from './other';\n\ + @Component({ selector: 'c', template: '', standalone: true })\n\ + export class C {\n @In() a: any;\n @core.Output() b: any;\n @Input() c: any;\n}\n", + ); + let compact: String = out.chars().filter(|c| !c.is_whitespace()).collect(); + assert!( + compact.contains("propDecorators={a:[{type:In}],b:[{type:core.Output}]};"), + "Got:\n{out}" + ); + assert!(compact.contains(r#"__decorate([Input()],C.prototype,"c",void0);"#), "Got:\n{out}"); +} + +#[test] +fn member_decorators_named_like_angular_ones_count_only_from_angular_core() { + // Like ngtsc's JIT transform, where a member decorator comes from decides, + // not its name: another module's `@Inject` / `@Component` / `@ns.Optional` + // stay decorators applied with `__decorate` (they used to be dropped). + let out = compile_jit( + "import { Component } from '@angular/core';\n\ + import { Inject, Component as Cmp } from './di';\n\ + import * as di from './di';\n\ + @Component({ selector: 'c', template: '', standalone: true })\n\ + export class C {\n @Inject(X) a: any;\n @Cmp({}) b: any;\n @di.Optional() c: any;\n}\n", + ); + let compact: String = out.chars().filter(|c| !c.is_whitespace()).collect(); + assert!(compact.contains(r#"__decorate([Inject(X)],C.prototype,"a",void0);"#), "Got:\n{out}"); + assert!(compact.contains(r#"__decorate([Cmp({})],C.prototype,"b",void0);"#), "Got:\n{out}"); + assert!( + compact.contains(r#"__decorate([di.Optional()],C.prototype,"c",void0);"#), + "Got:\n{out}" + ); + + // `@angular/core`'s `Inject` under an alias is treated like `@Inject`. + let aliased = compile_jit( + "import { Component, Inject as Inj } from '@angular/core';\n\ + @Component({ selector: 'c', template: '', standalone: true })\n\ + export class C {\n @Inj(X) a: any;\n}\n", + ); + let plain = compile_jit( + "import { Component, Inject } from '@angular/core';\n\ + @Component({ selector: 'c', template: '', standalone: true })\n\ + export class C {\n @Inject(X) a: any;\n}\n", + ); + assert!(!aliased.contains("Inj(X)"), "Got:\n{aliased}"); + assert_eq!( + aliased.replace("Inject as Inj", "Inject"), + plain, + "aliased:\n{aliased}\nplain:\n{plain}" + ); +} diff --git a/crates/oxc_angular_compiler/tests/signal_member_jit_metadata_test.rs b/crates/oxc_angular_compiler/tests/signal_member_jit_metadata_test.rs index c2f8afc54..2790e693b 100644 --- a/crates/oxc_angular_compiler/tests/signal_member_jit_metadata_test.rs +++ b/crates/oxc_angular_compiler/tests/signal_member_jit_metadata_test.rs @@ -418,3 +418,27 @@ fn namespaced_required_signal_model_detected() { assert!(md.contains("Output"), "core.model.required output not detected:\n{md}"); assert!(md.contains("required:true"), "required flag missing for core.model.required:\n{md}"); } + +/// Like ngtsc, only Angular's `input()`, `viewChild()`, ... get a synthetic +/// decorator: imported from `@angular/core` by name (under any alias) or +/// through a namespace import. ngtsc 22.1.7 emits exactly +/// `{ a: [{ type: i0.Input, args: [{ isSignal: true, alias: "a", required: false }] }], +/// d: [{ type: i0.ViewChild, args: ['x', { isSignal: true }] }] }` for this +/// class: `output` comes from another module and `model` is a local function. +#[test] +fn only_angular_initializer_apis_get_synthetic_decorators() { + let md = metadata_region(&compile( + "import { Component, input as inp, viewChild } from '@angular/core';\n\ + import { output } from './other';\n\ + function model(v?: any): any { return v; }\n\ + @Component({ selector: 'c', template: 'x' })\n\ + export class C {\n a = inp(0);\n b = output();\n c = model(0);\n d = viewChild('x');\n}\n", + )); + let md: String = md.chars().filter(|c| !c.is_whitespace()).collect(); + assert!( + md.contains( + r#"{a:[{type:i0.Input,args:[{isSignal:true,alias:"a",required:false}]}],d:[{type:i0.ViewChild,args:["x",{isSignal:true}]}]}"# + ), + "{md}" + ); +} diff --git a/napi/angular-compiler/index.d.ts b/napi/angular-compiler/index.d.ts index f36a7c73a..a9a6471d5 100644 --- a/napi/angular-compiler/index.d.ts +++ b/napi/angular-compiler/index.d.ts @@ -907,6 +907,11 @@ export interface TransformResult { templateUpdates: Map /** Style updates for HMR (component_id → styles). */ styleUpdates: Map + /** + * For HMR: each compiled component's `@Component` decorator callee as + * written (component_id → `Component`, `Cmp`, `ng.Component`, ...). + */ + componentDecorators: Record /** Compilation errors. */ errors: Array /** Compilation warnings. */ diff --git a/napi/angular-compiler/src/lib.rs b/napi/angular-compiler/src/lib.rs index c4601f87f..1528c7e26 100644 --- a/napi/angular-compiler/src/lib.rs +++ b/napi/angular-compiler/src/lib.rs @@ -22,9 +22,9 @@ use oxc_angular_compiler::{ AngularVersion as RustAngularVersion, ChangeDetectionStrategy as RustChangeDetectionStrategy, HostMetadataInput as RustHostMetadataInput, TransformOptions as RustTransformOptions, ViewEncapsulation as RustViewEncapsulation, - build_ctor_params_metadata as core_build_ctor_params_metadata, + build_ctor_params_metadata_in as core_build_ctor_params_metadata, build_decorator_metadata_array as core_build_decorator_metadata_array, - build_prop_decorators_metadata as core_build_prop_decorators_metadata, + build_prop_decorators_metadata_in as core_build_prop_decorators_metadata, compile_template_for_hmr, compile_template_to_js_with_options, encapsulate_style as rust_encapsulate_style, generate_hmr_update_module_from_js, generate_style_update_module, @@ -355,6 +355,11 @@ pub struct TransformResult { #[napi(ts_type = "Map")] pub style_updates: HashMap>, + /// For HMR: each compiled component's `@Component` decorator callee as + /// written (component_id → `Component`, `Cmp`, `ng.Component`, ...). + #[napi(ts_type = "Record")] + pub component_decorators: HashMap, + /// Compilation errors. pub errors: Vec, @@ -1145,6 +1150,7 @@ impl Task for TransformAngularFileTask { dependencies: result.dependencies, template_updates: result.template_updates, style_updates: result.style_updates, + component_decorators: result.component_decorators, errors, warnings: vec![], dts_declarations: result @@ -1251,7 +1257,7 @@ pub fn extract_pipe_metadata_sync( pipe_name: String, implicit_standalone: Option, ) -> Option { - use oxc_angular_compiler::extract_pipe_metadata; + use oxc_angular_compiler::{collect_string_consts, extract_pipe_metadata_in}; use oxc_ast::ast::{Declaration, ExportDefaultDeclarationKind, Statement}; use oxc_parser::Parser; use oxc_span::SourceType; @@ -1262,6 +1268,8 @@ pub fn extract_pipe_metadata_sync( let parser_ret = Parser::new(&allocator, &source, source_type).parse(); let program = &parser_ret.program; + // Only Angular's `@Pipe` (imported from `@angular/core`) counts, as when compiling. + let string_consts = collect_string_consts(&allocator, program); // Find the pipe class by name for stmt in &program.body { @@ -1288,9 +1296,13 @@ pub fn extract_pipe_metadata_sync( } // Extract metadata from @Pipe decorator - if let Some(metadata) = - extract_pipe_metadata(&allocator, class, implicit_standalone, Some(&source)) - { + if let Some(metadata) = extract_pipe_metadata_in( + &allocator, + class, + implicit_standalone, + Some(&source), + Some(&string_consts), + ) { return Some(ExtractedPipeMetadata { class_name: metadata.class_name.to_string(), span_start: metadata.class_span.start, @@ -1328,7 +1340,9 @@ pub fn compile_pipe_sync( implicit_standalone: Option, ) -> PipeCompileResult { use oxc_angular_compiler::output::emitter::JsEmitter; - use oxc_angular_compiler::{R3PipeMetadataBuilder, compile_pipe, extract_pipe_metadata}; + use oxc_angular_compiler::{ + R3PipeMetadataBuilder, collect_string_consts, compile_pipe, extract_pipe_metadata_in, + }; use oxc_ast::ast::{Declaration, ExportDefaultDeclarationKind, Statement}; use oxc_parser::Parser; use oxc_span::SourceType; @@ -1340,6 +1354,8 @@ pub fn compile_pipe_sync( let parser_ret = Parser::new(&allocator, &source, source_type).parse(); let program = &parser_ret.program; + // Only Angular's `@Pipe` (imported from `@angular/core`) counts, as when compiling. + let string_consts = collect_string_consts(&allocator, program); // Find the pipe class by name for stmt in &program.body { @@ -1366,9 +1382,13 @@ pub fn compile_pipe_sync( } // Extract metadata from @Pipe decorator - if let Some(metadata) = - extract_pipe_metadata(&allocator, class, implicit_standalone, Some(&source)) - { + if let Some(metadata) = extract_pipe_metadata_in( + &allocator, + class, + implicit_standalone, + Some(&source), + Some(&string_consts), + ) { // Create type expression for the pipe class use oxc_allocator::Box; use oxc_angular_compiler::output::ast::{OutputExpression, ReadVarExpr}; @@ -2032,8 +2052,12 @@ pub fn compile_class_metadata_sync( }; }; - // Find the target decorator on the class - let target_decorator = find_angular_decorator(&class.decorators, &decorator_type); + // Find the target decorator on the class. Like the compiler, only Angular's + // counts (imported from `@angular/core`), and signal members get a synthetic + // decorator only when they call Angular's `input()`, `viewChild()`, ... + let string_consts = oxc_angular_compiler::collect_string_consts(&allocator, program); + let target_decorator = + find_angular_decorator(&class.decorators, &decorator_type, &string_consts); let Some(decorator) = target_decorator else { return ClassMetadataNapiCompileResult { @@ -2074,14 +2098,16 @@ pub fn compile_class_metadata_sync( &mut namespace_registry, &empty_import_map, Some(&source), + Some(&string_consts), ); - // Build property decorators metadata + // Build property decorators metadata. let prop_decorators_expr = core_build_prop_decorators_metadata( &allocator, class, Some(&source), &mut namespace_registry, + Some(&string_consts), ); // Create R3ClassMetadata @@ -2143,23 +2169,34 @@ pub fn compile_class_metadata( AsyncTask::new(CompileClassMetadataTask { source, file_path, class_name, decorator_type }) } -/// Find an Angular decorator by name on a class. +/// Find the Angular decorator `decorator_name` on a class. `Component`, +/// `Directive`, `Pipe`, `Injectable` and `NgModule` count only when imported from +/// `@angular/core` (under any alias, or through a namespace import), like the +/// compiler; any other name (`Service`) is matched by name. fn find_angular_decorator<'a>( decorators: &'a [oxc_ast::ast::Decorator<'a>], decorator_name: &str, + string_consts: &oxc_angular_compiler::StringConsts<'_>, ) -> Option<&'a oxc_ast::ast::Decorator<'a>> { + if matches!(decorator_name, "Component" | "Directive" | "Pipe" | "Injectable" | "NgModule") { + return oxc_angular_compiler::find_angular_class_decorator( + decorators, + decorator_name, + string_consts, + ); + } decorators.iter().find(|d| { match &d.expression { oxc_ast::ast::Expression::CallExpression(call) => match &call.callee { - // Direct call: @Component() + // Direct call: @Service() oxc_ast::ast::Expression::Identifier(id) => id.name == decorator_name, - // Namespaced: @ng.Component() or @core.Component() + // Namespaced: @ng.Service() or @core.Service() oxc_ast::ast::Expression::StaticMemberExpression(member) => { member.property.name == decorator_name } _ => false, }, - // Decorator without call: @Component (rare, usually invalid) + // Decorator without call: @Service (rare, usually invalid) oxc_ast::ast::Expression::Identifier(id) => id.name == decorator_name, _ => false, } diff --git a/napi/angular-compiler/test/decorator-fields.test.ts b/napi/angular-compiler/test/decorator-fields.test.ts index d17baef83..37532ec0d 100644 --- a/napi/angular-compiler/test/decorator-fields.test.ts +++ b/napi/angular-compiler/test/decorator-fields.test.ts @@ -233,6 +233,32 @@ describe('decorator-fields utils', () => { const out = locateComponentDecorators(src) expect(out.map((d) => d.className)).toEqual(['A', 'B']) }) + + // The compiler reports how it found each component's decorator spelled; + // the scan looks for exactly those spellings, whole. + it('finds the decorator spellings it is given, and only those', () => { + const src = ` + @Cmp({ template: 'a' }) + class A {} + @ng.Component({ template: 'b' }) + class B {} + @Component({ template: 'c' }) + class C {} + @CmpX({ template: 'd' }) + class D {} + @Cmp.x({ template: 'e' }) + class E {} + @ng . Component ({ template: 'f' }) + class F {} + ` + const out = locateComponentDecorators(src, ['Cmp', 'ng.Component', 'ng . Component']) + expect(out.map((d) => [d.className, d.decorator])).toEqual([ + ['A', 'Cmp'], + ['B', 'ng.Component'], + ['F', 'ng . Component'], + ]) + expect(locateComponentDecorators(src).map((d) => d.className)).toEqual(['C']) + }) }) describe('locateStylesInArgs', () => { diff --git a/napi/angular-compiler/test/extract-component-metadata.test.ts b/napi/angular-compiler/test/extract-component-metadata.test.ts index 741167030..4226accc7 100644 --- a/napi/angular-compiler/test/extract-component-metadata.test.ts +++ b/napi/angular-compiler/test/extract-component-metadata.test.ts @@ -70,3 +70,102 @@ export class X { expect(component.host?.listeners ?? []).toEqual([]) }) }) + +// Like ngtsc, a signal member counts only when it calls Angular's own `input()`, +// `output()`, `model()`, `viewChild()`, ...: imported from `@angular/core` by +// name (under any alias) or through a namespace import. +describe('extractComponentMetadataSync signal members', () => { + it('reads only the ones that call Angular APIs', () => { + const [component] = extractComponentMetadataSync( + ` +import { Component, input as inp, output } from '@angular/core'; +import * as ng from '@angular/core'; +import { model, viewChild } from './other'; +function contentChild(x: string): any { return null; } + +@Component({ selector: 'app-x', template: '
' }) +export class X { + a = inp(0); + b = ng.model.required(); + c = output(); + d = model(0); + e = viewChild('ref'); + f = contentChild('ref'); + g = ng.viewChild('ref'); +} +`, + 'x.component.ts', + ) + expect(component.inputs?.map((i) => [i.classPropertyName, i.isSignal])).toEqual([ + ['a', true], + ['b', true], + ]) + expect(component.outputs?.map((o) => o.classPropertyName)).toEqual(['b', 'c']) + expect(component.viewQueries?.map((q) => q.propertyName)).toEqual(['g']) + expect(component.queries ?? []).toEqual([]) + }) +}) + +// Like ngtsc, an `@Input(...)` / `@Output(...)` argument is evaluated, so a +// same-file const gives the alias, `required` flag and transform. +describe('extractComponentMetadataSync member decorator arguments', () => { + it('reads aliases and options through same-file consts', () => { + const [component] = extractComponentMetadataSync( + ` +import { Component, EventEmitter, Input, Output, booleanAttribute } from '@angular/core'; +const NAME = 'y'; +const OPTS = { alias: \`z\`, required: true, transform: booleanAttribute }; +const t = booleanAttribute; + +@Component({ selector: 'app-x', template: '' }) +export class X { + @Input(NAME) a: any; + @Input(OPTS) b: any; + @Input({ ...OPTS, alias: 'w', transform: t }) c: any; + @Output('e' + NAME) d = new EventEmitter(); +} +`, + 'x.component.ts', + ) + expect( + component.inputs?.map((i) => [ + i.classPropertyName, + i.bindingPropertyName, + i.required, + i.transform ?? null, + ]), + ).toEqual([ + ['a', 'y', false, null], + ['b', 'z', true, 'booleanAttribute'], + ['c', 'w', true, 'booleanAttribute'], + ]) + expect(component.outputs?.map((o) => [o.classPropertyName, o.bindingPropertyName])).toEqual([ + ['d', 'ey'], + ]) + }) +}) + +// Like the compiler (and ngtsc), only Angular's `@Component` counts: imported +// from `@angular/core` under any name or through a namespace import. +describe('extractComponentMetadataSync class decorators', () => { + it("lists the classes Angular's @Component decorates, not another library's", () => { + const components = extractComponentMetadataSync( + ` +import { Component as Cmp } from '@angular/core'; +import * as ng from '@angular/core'; +import { Component } from './widgets'; + +@Cmp({ selector: 'app-a', template: '' }) +export class A {} + +@ng.Component({ selector: 'app-b', template: '' }) +export class B {} + +@Component({ selector: 'x-widget', template: '' }) +export class Widget {} +`, + 'x.component.ts', + ) + expect(components.map((c) => c.className)).toEqual(['A', 'B']) + }) +}) diff --git a/napi/angular-compiler/test/hmr-hot-update.test.ts b/napi/angular-compiler/test/hmr-hot-update.test.ts index cc00e8aff..0887c51cb 100644 --- a/napi/angular-compiler/test/hmr-hot-update.test.ts +++ b/napi/angular-compiler/test/hmr-hot-update.test.ts @@ -569,6 +569,137 @@ describe('pendingHmrUpdates race condition', () => { expect(fullReload, 'expected a full-reload event').toBeDefined() }) + it("triggers full reload when only another library's @Component template changes", async () => { + const plugin = getAngularPlugin() + const mockServer = await setupPluginWithServer(plugin) + + // `Widget`'s `@Component` comes from another library, so it isn't + // compiled: its template isn't one HMR can replace. + const mixedPath = join(appDir, 'mixed.component.ts') + const source = ` + import * as ng from '@angular/core'; + import { Component } from './widgets'; + @ng.Component({ selector: 'app-first', template: '
First
' }) + export class FirstComponent {} + @Component({ selector: 'x-widget', template: '
Widget
' }) + export class Widget {} + ` + writeFileSync(mixedPath, source) + + if (!plugin.transform || typeof plugin.transform === 'function') { + throw new Error('Expected plugin transform handler') + } + await plugin.transform.handler.call( + { error() {}, warn() {}, addWatchFile() {} } as any, + source, + mixedPath, + ) + + writeFileSync(mixedPath, source.replace('
Widget
', '
Widget Edited
')) + + const ctx = createMockHmrContext(mixedPath, [{ id: mixedPath }], mockServer) + await callHandleHotUpdate(plugin, ctx) + + const componentUpdates = mockServer._wsMessages.filter( + (m: any) => m.event === 'angular:component-update', + ) + expect(componentUpdates).toHaveLength(0) + const fullReload = mockServer._wsMessages.find((m: any) => m.type === 'full-reload') + expect(fullReload, 'expected a full-reload event').toBeDefined() + }) + + // The compiler takes a `@Component` imported from `@angular/core` under any + // name, so an inline template or styles edit to one HMRs whatever the + // decorator is spelled as. + it.each([ + ['a named import', `import { Component } from '@angular/core';`, 'Component'], + ['an aliased import', `import { Component as Cmp } from '@angular/core';`, 'Cmp'], + ['a namespace import', `import * as ng from '@angular/core';`, 'ng.Component'], + ['a spaced namespace import', `import * as ng from '@angular/core';`, 'ng . Component'], + ])( + 'dispatches HMR for an inline template / styles edit of a component decorated through %s', + async (_, importLine, decorator) => { + const plugin = getAngularPlugin() + const mockServer = await setupPluginWithServer(plugin) + + const path = join(appDir, `spelled-${decorator.replace(/\W/g, '')}.component.ts`) + const source = ` + ${importLine} + @${decorator}({ selector: 'app-first', template: '
First
', styles: ['.a{}'] }) + export class FirstComponent {} + ` + writeFileSync(path, source) + + if (!plugin.transform || typeof plugin.transform === 'function') { + throw new Error('Expected plugin transform handler') + } + await plugin.transform.handler.call( + { error() {}, warn() {}, addWatchFile() {} } as any, + source, + path, + ) + + writeFileSync( + path, + source.replace('
First
', '
Edited
').replace('.a{}', '.b{}'), + ) + const ctx = createMockHmrContext(path, [{ id: path }], mockServer) + await callHandleHotUpdate(plugin, ctx) + + const componentIds = mockServer._wsMessages + .filter((m: any) => m.event === 'angular:component-update') + .map((m: any) => decodeURIComponent(m.data.id)) + expect(componentIds).toEqual([`${path}@FirstComponent`]) + const fullReload = mockServer._wsMessages.find((m: any) => m.type === 'full-reload') + expect(fullReload, 'expected no full-reload for inline template change').toBeUndefined() + }, + ) + + // Only the decorator the compiler took is emptied: another library's + // `@Component` on the same class keeps its template, so editing it reloads. + it.each([ + ['the compiled decorator', '
Angular
', 'hmr'], + ["another library's decorator on the same class", '
Widget
', 'reload'], + ])('edit to the template of %s', async (_, edited, expected) => { + const plugin = getAngularPlugin() + const mockServer = await setupPluginWithServer(plugin) + + const path = join(appDir, `two-decorators-${expected}.component.ts`) + const source = ` + import * as ng from '@angular/core'; + import { Component } from './widgets'; + @Component({ selector: 'x-widget', template: '
Widget
' }) + @ng.Component({ selector: 'app-first', template: '
Angular
' }) + export class FirstComponent {} + ` + writeFileSync(path, source) + + if (!plugin.transform || typeof plugin.transform === 'function') { + throw new Error('Expected plugin transform handler') + } + await plugin.transform.handler.call( + { error() {}, warn() {}, addWatchFile() {} } as any, + source, + path, + ) + + writeFileSync(path, source.replace(edited, '
Edited
')) + const ctx = createMockHmrContext(path, [{ id: path }], mockServer) + await callHandleHotUpdate(plugin, ctx) + + const updates = mockServer._wsMessages.filter( + (m: any) => m.event === 'angular:component-update', + ) + const fullReload = mockServer._wsMessages.find((m: any) => m.type === 'full-reload') + if (expected === 'hmr') { + expect(updates).toHaveLength(1) + expect(fullReload).toBeUndefined() + } else { + expect(updates).toHaveLength(0) + expect(fullReload).toBeDefined() + } + }) + it('consumes pending entry and dispatches angular:invalidate on compile error', async () => { const plugin = getAngularPlugin() const mockServer = await setupPluginWithServer(plugin) diff --git a/napi/angular-compiler/test/plugin.test.ts b/napi/angular-compiler/test/plugin.test.ts index 9f85de9e8..07328d3f7 100644 --- a/napi/angular-compiler/test/plugin.test.ts +++ b/napi/angular-compiler/test/plugin.test.ts @@ -198,3 +198,45 @@ describe('@oxc-angular/vite auto component style minification', () => { expect(code).toContain('.container[_ngcontent-%COMP%] { color: red; background: transparent; }') }) }) + +describe('@oxc-angular/vite decorator pre-check', () => { + // The compiler takes a class decorator imported from `@angular/core` under + // any name, so the quick text check that skips files without Angular + // decorators must not skip `@Cmp(...)` or `@ng.Component(...)`. + async function transform(source: string): Promise { + const plugin = getAngularPlugin() + if (!plugin.transform || typeof plugin.transform === 'function') { + throw new Error('Expected plugin transform handler') + } + const result = await plugin.transform.handler.call( + { + error(message: string) { + throw new Error(message) + }, + warn() {}, + } as any, + source, + 'app.component.ts', + ) + return result && typeof result === 'object' && 'code' in result + ? (result.code as string) + : undefined + } + + it.each([ + [ + 'an aliased import', + `import { Component as Cmp } from '@angular/core'; +@Cmp({ selector: 'app-root', template: '' }) +export class AppComponent {}`, + ], + [ + 'a namespace import', + `import * as ng from '@angular/core'; +@ng.Component({ selector: 'app-root', template: '' }) +export class AppComponent {}`, + ], + ])('compiles a component decorated through %s', async (_, source) => { + expect(await transform(source)).toContain('ɵɵdefineComponent') + }) +}) diff --git a/napi/angular-compiler/vite-plugin/index.ts b/napi/angular-compiler/vite-plugin/index.ts index 0acf9bb74..cb34c4e0f 100644 --- a/napi/angular-compiler/vite-plugin/index.ts +++ b/napi/angular-compiler/vite-plugin/index.ts @@ -267,6 +267,12 @@ export function angular(options: PluginOptions = {}): Plugin[] { // emits per-component HMR updates and we mirror that here. const componentsByFile = new Map>() + // For each component .ts file, each compiled component's `@Component` + // decorator as the compiler reports it (class name → `Component`, `Cmp`, + // `ng.Component`, ...), so the template/styles strip below finds exactly the + // decorators the compiler took, under whatever name they're imported. + const componentDecoratorsByFile = new Map>() + // Reverse mapping: resource file path → component file path. Single-valued: // the last transform to reference a resource owns the slot. Multiple // components in the SAME file are covered by `dispatchAllComponentsInFile`; @@ -794,7 +800,8 @@ export function angular(options: PluginOptions = {}): Plugin[] { const cachedStripped = componentMetadataCache.get(resolvedId) return ( cachedStripped !== undefined && - cachedStripped === stripComponentMetadata(source) + cachedStripped === + stripComponentMetadata(source, componentDecoratorsByFile.get(resolvedId)) ) } const styles: string[] | null = @@ -875,14 +882,19 @@ export function angular(options: PluginOptions = {}): Plugin[] { } // Quick check for Angular decorators - avoids parsing files without them - // OXC handles @Component, @Directive, @NgModule, @Injectable, @Pipe, @Service + // OXC handles @Component, @Directive, @NgModule, @Injectable, @Pipe, @Service. + // The compiler also takes one imported from `@angular/core` under another + // name (`@Cmp(...)`, `@ng.Component(...)`); such a file still names + // `@angular/core` and the decorator. const hasAngularDecorator = code.includes('@Component') || code.includes('@Directive') || code.includes('@NgModule') || code.includes('@Injectable') || code.includes('@Pipe') || - code.includes('@Service') + code.includes('@Service') || + (code.includes('@angular/core') && + /\b(?:Component|Directive|NgModule|Injectable|Pipe|Service)\b/.test(code)) if (!hasAngularDecorator) { return } @@ -1022,10 +1034,14 @@ export function angular(options: PluginOptions = {}): Plugin[] { dependencies, ) const classNamesInFile = new Set() + const decoratorsInFile = new Map() for (const componentId of templateUpdateKeys) { const atIdx = componentId.indexOf('@') if (atIdx === -1) continue - classNamesInFile.add(componentId.slice(atIdx + 1)) + const className = componentId.slice(atIdx + 1) + classNamesInFile.add(className) + const decorator = result.componentDecorators[componentId] + if (decorator !== undefined) decoratorsInFile.set(className, decorator) } // Prune pending updates for components that USED to be in this // file but no longer are (e.g. a class was renamed or removed). @@ -1043,13 +1059,14 @@ export function angular(options: PluginOptions = {}): Plugin[] { } componentsByFile.set(actualId, classNamesInFile) + componentDecoratorsByFile.set(actualId, decoratorsInFile) for (const className of classNamesInFile) { debugHmr('registered: %s -> %s', actualId, className) } // Cache the metadata-stripped (whole-file) source for cheaply // diffing whether anything besides template/styles changed. - componentMetadataCache.set(actualId, stripComponentMetadata(code)) + componentMetadataCache.set(actualId, stripComponentMetadata(code, decoratorsInFile)) } return { @@ -1267,6 +1284,7 @@ export function angular(options: PluginOptions = {}): Plugin[] { // file (e.g. an external template change just invalidated the .ts // module via the graph), the resource branch has it covered. const fileClassNames = componentsByFile.get(ctx.file)! + const fileDecorators = componentDecoratorsByFile.get(ctx.file) let alreadyPending = false for (const className of fileClassNames) { if (pendingHmrUpdates.has(`${ctx.file}@${className}`)) { @@ -1296,7 +1314,7 @@ export function angular(options: PluginOptions = {}): Plugin[] { } catch { newContent = '' } - const newStripped = stripComponentMetadata(newContent) + const newStripped = stripComponentMetadata(newContent, fileDecorators) if (newStripped === cachedStripped) { debugHmr('inline template/styles-only change, dispatching HMR for %s', ctx.file) componentMetadataCache.set(ctx.file, newStripped) @@ -1455,14 +1473,25 @@ export function angular(options: PluginOptions = {}): Plugin[] { } /** - * Empty the `template:` and `styles:` field values of *every* `@Component(...)` - * in the source, returning the result. Used to detect "only template/styles - * changed somewhere in the file" — if the stripped form of the old and new - * source is byte-identical, the diff is contained within those fields and we - * can dispatch HMR (one event per component in the file) instead of a full - * reload. + * Empty the `template:` and `styles:` field values of each component's + * `@Component(...)` in `components` (class name → the decorator as the compiler + * reported it: `Component`, `Cmp`, `ng.Component`), returning the result. Used + * to detect "only template/styles changed somewhere in the file" — if the + * stripped form of the old and new source is byte-identical, the diff is + * contained within those fields and we can dispatch HMR (one event per + * component in the file) instead of a full reload. + * + * Only the decorator the compiler took is emptied, found by the spelling it + * reported, so this never decides on its own which decorators are Angular's. + * Another library's `@Component`, or another decorator of the same class, is + * left whole, so a change to it reloads the page. The new source is stripped + * with the old spellings: a change that could alter them (an import) is outside + * the emptied fields, so it already makes the two forms differ. */ -function stripComponentMetadata(code: string): string { +function stripComponentMetadata( + code: string, + components: ReadonlyMap | undefined, +): string { // Enumerate decorators ONCE (O(N) walk of source) and look up each one's // template + styles range directly from its argsRange. Calling the // className-based locators per decorator would re-enumerate inside each, @@ -1470,9 +1499,11 @@ function stripComponentMetadata(code: string): string { // // Splice from highest start → lowest so earlier offsets stay valid as we // mutate the string from the end backwards. - const decorators = locateComponentDecorators(code) + if (!components || components.size === 0) return code + const decorators = locateComponentDecorators(code, components.values()) const ranges: Array<[number, number]> = [] for (const d of decorators) { + if (components.get(d.className) !== d.decorator) continue const tpl = locateTemplateInArgs(code, d.argsRange) if (tpl) ranges.push(tpl) const styles = locateStylesInArgs(code, d.argsRange) diff --git a/napi/angular-compiler/vite-plugin/utils/decorator-fields.ts b/napi/angular-compiler/vite-plugin/utils/decorator-fields.ts index 20a2bf641..9f818a302 100644 --- a/napi/angular-compiler/vite-plugin/utils/decorator-fields.ts +++ b/napi/angular-compiler/vite-plugin/utils/decorator-fields.ts @@ -21,9 +21,12 @@ * distinguish `/` as a division operator from `/` as a regex-literal * opener without a real JS lexer. Regex literals inside @Component * args don't appear in real Angular code, so this is accepted. - * - **Aliased decorator imports**: `@core.Component(...)` or - * `import { Component as C }` followed by `@C({...})`. Only the - * literal `@Component` form is recognized. + * - **Which decorators are Angular's**: this scan does not read imports. + * It finds a decorator by the spelling the caller passes, which the + * compiler reports for each component it compiled (`Component`, `Cmp` + * for `import { Component as Cmp }`, `ng.Component`). A spelling with + * whitespace or a comment between `@` and the name, or type arguments + * before `(`, is not found, so its fields are never emptied. * - **Parenthesized decorator expressions** like `@(Component as any)(...)` * — uncommon and not supported. * - **Computed property keys** (`{ ['styles']: [...] }`) can't be resolved @@ -71,8 +74,8 @@ const ASCII_WORD_RE = /[A-Za-z0-9_$]/ const IDENT_START_RE = /[\p{L}_$]/u const IDENT_CONT_RE = /[\p{L}\p{N}_$]/u -/** The only decorator form recognized — see the module docstring. */ -const DECORATOR_NAME = 'Component' +/** The decorator spelling looked for when the caller names none. */ +const DEFAULT_DECORATOR_NAMES: readonly string[] = ['Component'] /** Opener chars accepted as the value of `styles:` — `string | string[]`. */ const STYLES_OPENERS = '\'"`[' @@ -476,11 +479,14 @@ export interface ComponentDecorator { argsRange: [number, number] /** The class name declared after this decorator. */ className: string + /** The decorator as spelled before its `(`: `Component`, `Cmp`, `ng.Component`. */ + decorator: string } /** * Enumerate every `@Component(...)` decorator in `code`, pairing each with - * the class declared immediately after it. Decorators that don't pair to a + * the class declared immediately after it. `decoratorNames` are the spellings + * to look for (`Cmp` finds `@Cmp(...)`); by default only `@Component(...)`. Decorators that don't pair to a * class (dangling, malformed, anonymous) are skipped — the caller sees only * well-formed component declarations. * @@ -493,14 +499,18 @@ export interface ComponentDecorator { * * See the module-level docstring for a full list of known limitations. */ -export function locateComponentDecorators(code: string): ComponentDecorator[] { +export function locateComponentDecorators( + code: string, + decoratorNames: Iterable = DEFAULT_DECORATOR_NAMES, +): ComponentDecorator[] { + const names = [...new Set(decoratorNames)].filter((name) => name.length > 0) // Pass 1: find every `@Component(...)` and bound its args list. The walk // skips comments and string/template literals, so a commented-out or // quoted `@Component(` is not mistaken for a real decorator. That matters // beyond ignoring it: a phantom occurrence between a real decorator and // its class would bound the real one's class-name scan in pass 2, leaving // the class paired with the phantom's metadata. - type Found = { decoratorStart: number; openParen: number; closeParen: number } + type Found = { decoratorStart: number; openParen: number; closeParen: number; decorator: string } const found: Found[] = [] let i = 0 while (i < code.length) { @@ -515,16 +525,12 @@ export function locateComponentDecorators(code: string): ComponentDecorator[] { i = close === -1 ? code.length : close + 1 continue } - if (ch === '@' && code.startsWith(DECORATOR_NAME, i + 1)) { - let j = i + 1 + DECORATOR_NAME.length - while (j < code.length && WS_RE.test(code[j])) j++ - if (code[j] === '(') { - const closeParen = findClosingDelim(code, j) - if (closeParen !== -1) { - found.push({ decoratorStart: i, openParen: j, closeParen }) - i = closeParen + 1 - continue - } + if (ch === '@') { + const match = matchDecoratorCall(code, i + 1, names) + if (match) { + found.push({ decoratorStart: i, ...match }) + i = match.closeParen + 1 + continue } } i++ @@ -535,16 +541,38 @@ export function locateComponentDecorators(code: string): ComponentDecorator[] { // decorator's class-name scan from claiming a sibling's class. const out: ComponentDecorator[] = [] for (let i = 0; i < found.length; i++) { - const { openParen, closeParen } = found[i] + const { openParen, closeParen, decorator } = found[i] const scanEnd = i + 1 < found.length ? found[i + 1].decoratorStart : code.length const className = findClassName(code, closeParen + 1, scanEnd) if (className !== null) { - out.push({ argsRange: [openParen, closeParen], className }) + out.push({ argsRange: [openParen, closeParen], className, decorator }) } } return out } +/** + * If one of `names` followed by optional whitespace and a balanced `(...)` + * starts at `start` (just past an `@`), return which name and the `(` / `)` + * offsets. A name only matches whole: `Cmp` is not found in `@CmpX(` or + * `@Cmp.x(`, since what follows it must be `(`. + */ +function matchDecoratorCall( + code: string, + start: number, + names: readonly string[], +): { decorator: string; openParen: number; closeParen: number } | null { + for (const name of names) { + if (!code.startsWith(name, start)) continue + let j = start + name.length + while (j < code.length && WS_RE.test(code[j])) j++ + if (code[j] !== '(') continue + const closeParen = findClosingDelim(code, j) + if (closeParen !== -1) return { decorator: name, openParen: j, closeParen } + } + return null +} + /** * Find the first `class IDENT` whose `class` keyword appears in * `[start, end)`. Returns the identifier (Unicode-aware), or null if no