diff --git a/crates/oxc_angular_compiler/src/class_metadata/builders.rs b/crates/oxc_angular_compiler/src/class_metadata/builders.rs index d0d8d3f43..d0b8bbc94 100644 --- a/crates/oxc_angular_compiler/src/class_metadata/builders.rs +++ b/crates/oxc_angular_compiler/src/class_metadata/builders.rs @@ -5,9 +5,9 @@ use oxc_allocator::{Allocator, Box, Vec as AllocVec}; use oxc_ast::ast::{ - BindingPattern, Class, ClassElement, Declaration, Decorator, Expression, FormalParameter, - MethodDefinitionKind, ObjectPropertyKind, PropertyKey, Statement, TSNamespaceDeclarationBody, - TSType, TSTypeName, VariableDeclaration, + BindingPattern, ChainElement, Class, ClassElement, Declaration, Decorator, Expression, + FormalParameter, MethodDefinitionKind, ObjectPropertyKind, PropertyKey, Statement, + TSNamespaceDeclarationBody, TSType, TSTypeName, VariableDeclaration, }; use oxc_span::GetSpan; use oxc_str::Ident; @@ -790,6 +790,10 @@ fn build_signal_query_decorator<'a>( let string_or_call = match expr { Expression::StringLiteral(_) | Expression::CallExpression(_) => true, Expression::TemplateLiteral(template) => template.expressions.is_empty(), + // `a?.b()` is a call too, as `ts.isCallExpression` sees it + Expression::ChainExpression(chain) => { + matches!(chain.expression, ChainElement::CallExpression(_)) + } _ => false, }; if string_or_call { diff --git a/crates/oxc_angular_compiler/tests/signal_query_metadata_forward_ref_test.rs b/crates/oxc_angular_compiler/tests/signal_query_metadata_forward_ref_test.rs index f370fe708..7a71f6341 100644 --- a/crates/oxc_angular_compiler/tests/signal_query_metadata_forward_ref_test.rs +++ b/crates/oxc_angular_compiler/tests/signal_query_metadata_forward_ref_test.rs @@ -79,3 +79,27 @@ export class FParent { assert_contains(&code, "legacy:[{type:ContentChildren,args:[FPane]}]"); assert_contains(&code, r#"strings:[{type:ViewChildren,args:["a,b"]}]"#); } + +#[test] +fn call_predicates_stay_as_written() { + // ngtsc leaves a call as written, an optional call (`a?.b()`) included, since + // `ts.isCallExpression` is true for it. + let code = compile( + r" +import { Component, viewChild } from '@angular/core'; +const tokens: any = { get: () => 'ref' }; +@Component({ selector: 'g-cmp', template: '' }) +export class GCmp { + plain = viewChild(tokens.get()); + optional = viewChild(tokens?.get()); + optionalCall = viewChild(tokens.get?.()); +} +", + ); + assert_contains(&code, "plain:[{type:i0.ViewChild,args:[tokens.get(),{isSignal:true}]}]"); + assert_contains(&code, "optional:[{type:i0.ViewChild,args:[tokens?.get(),{isSignal:true}]}]"); + assert_contains( + &code, + "optionalCall:[{type:i0.ViewChild,args:[tokens.get?.(),{isSignal:true}]}]", + ); +}