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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy