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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import {
BindingPipe,
Conditional,
Interpolation,
KeyedRead,
LiteralArray,
LiteralMap,
MatchSource,
ParseTemplateOptions,
PropertyRead,
SafeKeyedRead,
SafePropertyRead,
TmplAstBoundAttribute,
TmplAstBoundText,
Expand Down Expand Up @@ -878,6 +880,73 @@ runInEachFileSystem(() => {
).toEqual('data');
});

it('safe property reads with optional chaining', () => {
const fileName = absoluteFrom('/main.ts');
const templateString = `<div [inputA]="route?.data?.['icon']"></div>`;
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 = `<div [inputA]="arr[route?.data]"></div>`;
const {templateTypeChecker, program} = setup(
[
{
fileName,
templates: {'Cmp': templateString},
source: `
interface Route {
data: string;
}
export class Cmp {
route?: Route;
arr: Record<string, string> = {};
}
`,
},
],
{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 KeyedRead;
const dataRead = ast.key as SafePropertyRead;
const dataSymbol = templateTypeChecker.getSymbolOfNode(dataRead, cmp)!;
assertExpressionSymbol(dataSymbol);
expect(
program
.getTypeChecker()
.symbolToString(templateTypeChecker.getTsSymbolOfSymbol(dataSymbol)!),
).toEqual('data');
});

it('ternary expressions', () => {
const nodes = getAstElements(templateTypeChecker, cmp);

Expand Down