refactor(compiler-cli): Always wrap RHS of TCB writes in parens (#39768)

There were two issues with the current TCB:

1. The logic for only wrapping the right hand side of the property write
if it was not already a parenthesized expression was incorrect. A
parenthesized expression could still have a trailing comment, and if
that were the case, that span comment would still be ambiguous, as explained
by the comment in the code before `wrapForTypeChecker`.
2. The right hand side of keyed writes was not wrapped in parens at all

PR Close #39768
This commit is contained in:
Andrew Scott 2020-11-25 15:18:05 -08:00 committed by Misko Hevery
parent 786295dfbd
commit c69e67c9cb
2 changed files with 5 additions and 7 deletions

View File

@ -8,6 +8,7 @@
import {AST, AstVisitor, ASTWithSource, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, NonNullAssert, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, ThisReceiver, Unary} from '@angular/compiler'; import {AST, AstVisitor, ASTWithSource, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, NonNullAssert, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, ThisReceiver, Unary} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {TypeCheckingConfig} from '../api'; import {TypeCheckingConfig} from '../api';
import {addParseSpanInfo, wrapForDiagnostics, wrapForTypeChecker} from './diagnostics'; import {addParseSpanInfo, wrapForDiagnostics, wrapForTypeChecker} from './diagnostics';
@ -164,7 +165,7 @@ class AstTranslator implements AstVisitor {
const left = ts.createElementAccess(receiver, this.translate(ast.key)); const left = ts.createElementAccess(receiver, this.translate(ast.key));
// TODO(joost): annotate `left` with the span of the element access, which is not currently // TODO(joost): annotate `left` with the span of the element access, which is not currently
// available on `ast`. // available on `ast`.
const right = this.translate(ast.value); const right = wrapForTypeChecker(this.translate(ast.value));
const node = wrapForDiagnostics(ts.createBinary(left, ts.SyntaxKind.EqualsToken, right)); const node = wrapForDiagnostics(ts.createBinary(left, ts.SyntaxKind.EqualsToken, right));
addParseSpanInfo(node, ast.sourceSpan); addParseSpanInfo(node, ast.sourceSpan);
return node; return node;
@ -255,14 +256,11 @@ class AstTranslator implements AstVisitor {
// ^ nameSpan // ^ nameSpan
const leftWithPath = wrapForDiagnostics(left); const leftWithPath = wrapForDiagnostics(left);
addParseSpanInfo(leftWithPath, ast.sourceSpan); addParseSpanInfo(leftWithPath, ast.sourceSpan);
let right = this.translate(ast.value);
// The right needs to be wrapped in parens as well or we cannot accurately match its // The right needs to be wrapped in parens as well or we cannot accurately match its
// span to just the RHS. For example, the span in `e = $event /*0,10*/` is ambiguous. // span to just the RHS. For example, the span in `e = $event /*0,10*/` is ambiguous.
// It could refer to either the whole binary expression or just the RHS. // It could refer to either the whole binary expression or just the RHS.
// We should instead generate `e = ($event /*0,10*/)` so we know the span 0,10 matches RHS. // We should instead generate `e = ($event /*0,10*/)` so we know the span 0,10 matches RHS.
if (!ts.isParenthesizedExpression(right)) { const right = wrapForTypeChecker(this.translate(ast.value));
right = wrapForTypeChecker(right);
}
const node = const node =
wrapForDiagnostics(ts.createBinary(leftWithPath, ts.SyntaxKind.EqualsToken, right)); wrapForDiagnostics(ts.createBinary(leftWithPath, ts.SyntaxKind.EqualsToken, right));
addParseSpanInfo(node, ast.sourceSpan); addParseSpanInfo(node, ast.sourceSpan);

View File

@ -91,7 +91,7 @@ describe('type check blocks diagnostics', () => {
const TEMPLATE = `<div (click)='a.b.c = d'></div>`; const TEMPLATE = `<div (click)='a.b.c = d'></div>`;
expect(tcbWithSpans(TEMPLATE)) expect(tcbWithSpans(TEMPLATE))
.toContain( .toContain(
'(((((((ctx).a /*14,15*/) /*14,15*/).b /*16,17*/) /*14,17*/).c /*18,19*/) /*14,23*/ = ((ctx).d /*22,23*/) /*22,23*/) /*14,23*/'); '(((((((ctx).a /*14,15*/) /*14,15*/).b /*16,17*/) /*14,17*/).c /*18,19*/) /*14,23*/ = (((ctx).d /*22,23*/) /*22,23*/)) /*14,23*/');
}); });
it('should $event property writes', () => { it('should $event property writes', () => {
@ -110,7 +110,7 @@ describe('type check blocks diagnostics', () => {
const TEMPLATE = `<div (click)="a[b] = c"></div>`; const TEMPLATE = `<div (click)="a[b] = c"></div>`;
expect(tcbWithSpans(TEMPLATE)) expect(tcbWithSpans(TEMPLATE))
.toContain( .toContain(
'((((ctx).a /*14,15*/) /*14,15*/)[((ctx).b /*16,17*/) /*16,17*/] = ((ctx).c /*21,22*/) /*21,22*/) /*14,22*/'); '((((ctx).a /*14,15*/) /*14,15*/)[((ctx).b /*16,17*/) /*16,17*/] = (((ctx).c /*21,22*/) /*21,22*/)) /*14,22*/');
}); });
it('should annotate safe property access', () => { it('should annotate safe property access', () => {