mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
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
This commit is contained in:
parent
07cb321f00
commit
fd5a04927e
2 changed files with 34 additions and 4 deletions
|
|
@ -1082,8 +1082,13 @@ class _ParseAST {
|
|||
if (this.consumeOptionalCharacter(chars.$LPAREN)) {
|
||||
this.rparensExpected++;
|
||||
const result = this.parsePipe();
|
||||
if (!this.consumeOptionalCharacter(chars.$RPAREN)) {
|
||||
this.error('Missing closing parentheses');
|
||||
// Calling into `error` above will attempt to recover up until the next closing paren.
|
||||
// If that's the case, consume it so we can partially recover the expression.
|
||||
this.consumeOptionalCharacter(chars.$RPAREN);
|
||||
}
|
||||
this.rparensExpected--;
|
||||
this.expectCharacter(chars.$RPAREN);
|
||||
return new ParenthesizedExpression(this.span(start), this.sourceSpan(start), result);
|
||||
} else if (this.next.isKeywordNull()) {
|
||||
this.advance();
|
||||
|
|
|
|||
|
|
@ -658,6 +658,19 @@ describe('parser', () => {
|
|||
it('should report a missing expected token', () => {
|
||||
expectActionError('a(b', 'Missing expected ) at the end of the expression [a(b]');
|
||||
});
|
||||
|
||||
it('should report a single error for an `as` expression inside a parenthesized expression', () => {
|
||||
expectActionError(
|
||||
`foo(($event.target as HTMLElement).value)`,
|
||||
'Missing closing parentheses at column 20',
|
||||
1,
|
||||
);
|
||||
expectActionError(
|
||||
`foo(((($event.target as HTMLElement))).value)`,
|
||||
'Missing closing parentheses at column 22',
|
||||
1,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseBinding', () => {
|
||||
|
|
@ -1373,6 +1386,12 @@ describe('parser', () => {
|
|||
it('should be able to recover from a missing selector', () => recover('a.'));
|
||||
it('should be able to recover from a missing selector in a array literal', () =>
|
||||
recover('[[a.], b, c]'));
|
||||
|
||||
it('should recover from parenthesized `as` expressions', () => {
|
||||
recover('foo(($event.target as HTMLElement).value)', 'foo(($event.target).value)');
|
||||
recover('foo(((($event.target as HTMLElement))).value)', 'foo(((($event.target))).value)');
|
||||
recover('foo(((bar as HTMLElement) as Something).value)', 'foo(((bar)).value)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('offsets', () => {
|
||||
|
|
@ -1468,7 +1487,13 @@ function checkAction(exp: string, expected?: string) {
|
|||
validate(ast);
|
||||
}
|
||||
|
||||
function expectError(ast: {errors: ParserError[]}, message: string) {
|
||||
function expectError(ast: {errors: ParserError[]}, message: string, errorCount?: number) {
|
||||
if (errorCount != null) {
|
||||
expect(ast.errors.length).toBe(errorCount);
|
||||
} else {
|
||||
expect(ast.errors.length).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
for (const error of ast.errors) {
|
||||
if (error.message.indexOf(message) >= 0) {
|
||||
return;
|
||||
|
|
@ -1480,8 +1505,8 @@ function expectError(ast: {errors: ParserError[]}, message: string) {
|
|||
);
|
||||
}
|
||||
|
||||
function expectActionError(text: string, message: string) {
|
||||
expectError(validate(parseAction(text)), message);
|
||||
function expectActionError(text: string, message: string, errorCount?: number) {
|
||||
expectError(validate(parseAction(text)), message, errorCount);
|
||||
}
|
||||
|
||||
function expectBindingError(text: string, message: string) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue