diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts index 9d2f2819c96..113e34e6795 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts @@ -658,11 +658,7 @@ export class SymbolBuilder { const expressionTarget = this.boundTarget.getExpressionTarget(expression); if (expressionTarget !== null) { return this.getSymbol(expressionTarget) as - | VariableSymbol - | ReferenceSymbol - | ExpressionSymbol - | LetDeclarationSymbol - | null; + VariableSymbol | ReferenceSymbol | ExpressionSymbol | LetDeclarationSymbol | null; } let withSpan = expression.sourceSpan; @@ -714,7 +710,7 @@ export class SymbolBuilder { node.parent !== undefined && (ts.isParenthesizedExpression(node.parent) || ts.isNonNullExpression(node.parent) || - isAccessExpression(node.parent)) + (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node)) ) { node = node.parent; } diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts index fa008e36e5b..e33fcc315c7 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts @@ -13,11 +13,13 @@ import { BindingPipe, Conditional, Interpolation, + KeyedRead, LiteralArray, LiteralMap, MatchSource, ParseTemplateOptions, PropertyRead, + SafeKeyedRead, SafePropertyRead, TmplAstBoundAttribute, TmplAstBoundText, @@ -878,6 +880,73 @@ runInEachFileSystem(() => { ).toEqual('data'); }); + it('safe property reads with optional chaining', () => { + const fileName = absoluteFrom('/main.ts'); + const templateString = `
`; + const {templateTypeChecker, program} = setup( + [ + { + fileName, + templates: {'Cmp': templateString}, + source: ` + interface Route { + data?: { icon: string; }; + } + export class Cmp { route?: Route; } + `, + }, + ], + {strictSafeNavigationTypes: true}, + ); + const sf = getSourceFileOrError(program, fileName); + const cmp = getClass(sf, 'Cmp'); + const nodes = getAstElements(templateTypeChecker, cmp); + const ast = (nodes[0].inputs[0].value as ASTWithSource).ast as SafeKeyedRead; + const dataRead = ast.receiver as SafePropertyRead; + const dataSymbol = templateTypeChecker.getSymbolOfNode(dataRead, cmp)!; + assertExpressionSymbol(dataSymbol); + expect( + program + .getTypeChecker() + .symbolToString(templateTypeChecker.getTsSymbolOfSymbol(dataSymbol)!), + ).toEqual('data'); + }); + + it('safe property reads used in element access argument', () => { + const fileName = absoluteFrom('/main.ts'); + const templateString = ``; + const {templateTypeChecker, program} = setup( + [ + { + fileName, + templates: {'Cmp': templateString}, + source: ` + interface Route { + data: string; + } + export class Cmp { + route?: Route; + arr: Record