mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(compiler): Generate correct expression for optional chaning.
Optional chaining was generating expressions with included an extra pair of parenthesis which changed the semantics of the expression and threw an unexpected error from the optional chain non nullable extended diagnostic.
fixes #70085
(cherry picked from commit d7b03f5523)
This commit is contained in:
committed by
Alon Mishne
parent
4f7e9987fa
commit
76dff307b4
@@ -1508,6 +1508,12 @@ describe('type check blocks', () => {
|
||||
expect(block).toContain('(((((this).a))![0] as any)');
|
||||
expect(block).toContain('((((((this).a)).optionalMethod))!() as any)');
|
||||
});
|
||||
|
||||
it('should produce correct correct ts expression', () => {
|
||||
const TEMPLATE = `{{ one?.two.three }}`;
|
||||
const block = tcb(TEMPLATE, DIRECTIVES);
|
||||
expect(block).toContain('(((((this).one))?.two.three))');
|
||||
});
|
||||
});
|
||||
|
||||
describe('config.strictSafeNavigationTypes (View Engine bug emulation)', () => {
|
||||
|
||||
+4
-1
@@ -836,11 +836,14 @@ runInEachFileSystem(() => {
|
||||
.declarations![0] as ts.PropertyDeclaration
|
||||
).parent.name!.getText(),
|
||||
).toEqual('Car');
|
||||
|
||||
// Even if engine is string, TS will returned the type returned by the expression,
|
||||
// which is string | undefined because of the safe navigation operator.
|
||||
expect(
|
||||
program
|
||||
.getTypeChecker()
|
||||
.typeToString(templateTypeChecker.getTypeOfSymbol(keyedReadSymbol)!),
|
||||
).toEqual('string');
|
||||
).toEqual('string | undefined');
|
||||
});
|
||||
|
||||
it('safe property reads with as any (failure case)', () => {
|
||||
|
||||
@@ -143,7 +143,10 @@ class TcbExprTranslator implements AstVisitor {
|
||||
}
|
||||
|
||||
visitKeyedRead(ast: KeyedRead): TcbExpr {
|
||||
const receiver = this.translate(ast.receiver).wrapForTypeChecker();
|
||||
const receiver = this.translate(ast.receiver);
|
||||
if (!this.isStrictSafeNavigationChain(ast.receiver)) {
|
||||
receiver.wrapForTypeChecker();
|
||||
}
|
||||
const key = this.translate(ast.key);
|
||||
return new TcbExpr(`${receiver.print()}[${key.print()}]`).addParseSpanInfo(ast.sourceSpan);
|
||||
}
|
||||
@@ -234,7 +237,10 @@ class TcbExprTranslator implements AstVisitor {
|
||||
}
|
||||
|
||||
visitPropertyRead(ast: PropertyRead): TcbExpr {
|
||||
const receiver = this.translate(ast.receiver).wrapForTypeChecker();
|
||||
const receiver = this.translate(ast.receiver);
|
||||
if (!this.isStrictSafeNavigationChain(ast.receiver)) {
|
||||
receiver.wrapForTypeChecker();
|
||||
}
|
||||
return new TcbExpr(`${receiver.print()}.${ast.name}`)
|
||||
.addParseSpanInfo(ast.nameSpan)
|
||||
.wrapForTypeChecker()
|
||||
@@ -284,7 +290,10 @@ class TcbExprTranslator implements AstVisitor {
|
||||
if (resolved !== null) {
|
||||
expr = resolved;
|
||||
} else {
|
||||
const propertyReceiver = this.translate(receiver.receiver).wrapForTypeChecker();
|
||||
const propertyReceiver = this.translate(receiver.receiver);
|
||||
if (!this.isStrictSafeNavigationChain(receiver.receiver)) {
|
||||
propertyReceiver.wrapForTypeChecker();
|
||||
}
|
||||
expr = new TcbExpr(`${propertyReceiver.print()}.${receiver.name}`).addParseSpanInfo(
|
||||
receiver.nameSpan,
|
||||
);
|
||||
@@ -414,6 +423,13 @@ class TcbExprTranslator implements AstVisitor {
|
||||
private escapeTemplateLiteral(value: string) {
|
||||
return value.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\${/g, '$\\{');
|
||||
}
|
||||
|
||||
private isStrictSafeNavigationChain(ast: AST): boolean {
|
||||
return (
|
||||
this.config.strictSafeNavigationTypes &&
|
||||
(ast instanceof SafePropertyRead || ast instanceof SafeKeyedRead || ast instanceof SafeCall)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VeSafeLhsInferenceBugDetector implements AstVisitor {
|
||||
|
||||
Reference in New Issue
Block a user