Skip to content

Commit 8ecb1ba

Browse files
crisbetokirjs
authored andcommitted
fix(compiler): recover invalid parenthesized expressions (#61815)
When the expression parser consumes tokens inside a parenthesized expression, it looks for valid tokens until it hits and invalid one or a closing paren. If it finds an invalid token, it reports and error and tries to recover until it finds a closing paren. The problem is that in such cases, it would produce the `ParenthesizedExpression` and continue parsing **from** from the closing paren which would then produce more errors that add noise to the output and result in an incorrect representation of the user's code. E.g. `foo((event.target as HTMLElement).value)` would be recovered to `foo((event.target)).value` instead of `foo((event.target).value)`. These changes resolve the issue by skipping over the closing paren at the recovery point. Fixes #61792. PR Close #61815
1 parent f180f3d commit 8ecb1ba

File tree

2 files changed

+34
-4
lines changed

2 files changed

+34
-4
lines changed

packages/compiler/src/expression_parser/parser.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1040,8 +1040,13 @@ class _ParseAST {
10401040
if (this.consumeOptionalCharacter(chars.$LPAREN)) {
10411041
this.rparensExpected++;
10421042
const result = this.parsePipe();
1043+
if (!this.consumeOptionalCharacter(chars.$RPAREN)) {
1044+
this.error('Missing closing parentheses');
1045+
// Calling into `error` above will attempt to recover up until the next closing paren.
1046+
// If that's the case, consume it so we can partially recover the expression.
1047+
this.consumeOptionalCharacter(chars.$RPAREN);
1048+
}
10431049
this.rparensExpected--;
1044-
this.expectCharacter(chars.$RPAREN);
10451050
return new ParenthesizedExpression(this.span(start), this.sourceSpan(start), result);
10461051
} else if (this.next.isKeywordNull()) {
10471052
this.advance();

packages/compiler/test/expression_parser/parser_spec.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,19 @@ describe('parser', () => {
658658
it('should report a missing expected token', () => {
659659
expectActionError('a(b', 'Missing expected ) at the end of the expression [a(b]');
660660
});
661+
662+
it('should report a single error for an `as` expression inside a parenthesized expression', () => {
663+
expectActionError(
664+
`foo(($event.target as HTMLElement).value)`,
665+
'Missing closing parentheses at column 20',
666+
1,
667+
);
668+
expectActionError(
669+
`foo(((($event.target as HTMLElement))).value)`,
670+
'Missing closing parentheses at column 22',
671+
1,
672+
);
673+
});
661674
});
662675

663676
describe('parseBinding', () => {
@@ -1355,6 +1368,12 @@ describe('parser', () => {
13551368
it('should be able to recover from a missing selector', () => recover('a.'));
13561369
it('should be able to recover from a missing selector in a array literal', () =>
13571370
recover('[[a.], b, c]'));
1371+
1372+
it('should recover from parenthesized `as` expressions', () => {
1373+
recover('foo(($event.target as HTMLElement).value)', 'foo(($event.target).value)');
1374+
recover('foo(((($event.target as HTMLElement))).value)', 'foo(((($event.target))).value)');
1375+
recover('foo(((bar as HTMLElement) as Something).value)', 'foo(((bar)).value)');
1376+
});
13581377
});
13591378

13601379
describe('offsets', () => {
@@ -1445,7 +1464,13 @@ function checkAction(exp: string, expected?: string) {
14451464
validate(ast);
14461465
}
14471466

1448-
function expectError(ast: {errors: ParserError[]}, message: string) {
1467+
function expectError(ast: {errors: ParserError[]}, message: string, errorCount?: number) {
1468+
if (errorCount != null) {
1469+
expect(ast.errors.length).toBe(errorCount);
1470+
} else {
1471+
expect(ast.errors.length).toBeGreaterThan(0);
1472+
}
1473+
14491474
for (const error of ast.errors) {
14501475
if (error.message.indexOf(message) >= 0) {
14511476
return;
@@ -1457,8 +1482,8 @@ function expectError(ast: {errors: ParserError[]}, message: string) {
14571482
);
14581483
}
14591484

1460-
function expectActionError(text: string, message: string) {
1461-
expectError(validate(parseAction(text)), message);
1485+
function expectActionError(text: string, message: string, errorCount?: number) {
1486+
expectError(validate(parseAction(text)), message, errorCount);
14621487
}
14631488

14641489
function expectBindingError(text: string, message: string) {

0 commit comments

Comments
 (0)