fix(compiler): recover from an incomplete open tag at the end of a file (#41054)

The compiler's parsing code has logic to recover from incomplete open
tags (i.e. `<div`) but the recovery logic does not handle when the
incomplete tag is terminated by an EOF. This commit updates the logic to
allow for the EOF character to be interpreted as the end of the tag open
so that the parser can continue processing. It will then fail to find
the end tag and recover by marking the open tag as incomplete.

Part of https://github.com/angular/vscode-ng-language-service/issues/1140

PR Close #41054
This commit is contained in:
Andrew Scott 2021-03-01 12:41:40 -08:00 committed by Zach Arend
parent ffbacbbc98
commit 736b1f9fd4
2 changed files with 10 additions and 2 deletions

View File

@ -523,7 +523,7 @@ class _Tokenizer {
tagName = openTagToken.parts[1]; tagName = openTagToken.parts[1];
this._attemptCharCodeUntilFn(isNotWhitespace); this._attemptCharCodeUntilFn(isNotWhitespace);
while (this._cursor.peek() !== chars.$SLASH && this._cursor.peek() !== chars.$GT && while (this._cursor.peek() !== chars.$SLASH && this._cursor.peek() !== chars.$GT &&
this._cursor.peek() !== chars.$LT) { this._cursor.peek() !== chars.$LT && this._cursor.peek() !== chars.$EOF) {
this._consumeAttributeName(); this._consumeAttributeName();
this._attemptCharCodeUntilFn(isNotWhitespace); this._attemptCharCodeUntilFn(isNotWhitespace);
if (this._attemptCharCode(chars.$EQ)) { if (this._attemptCharCode(chars.$EQ)) {
@ -774,7 +774,8 @@ function isNotWhitespace(code: number): boolean {
function isNameEnd(code: number): boolean { function isNameEnd(code: number): boolean {
return chars.isWhitespace(code) || code === chars.$GT || code === chars.$LT || return chars.isWhitespace(code) || code === chars.$GT || code === chars.$LT ||
code === chars.$SLASH || code === chars.$SQ || code === chars.$DQ || code === chars.$EQ; code === chars.$SLASH || code === chars.$SQ || code === chars.$DQ || code === chars.$EQ ||
code === chars.$EOF;
} }
function isPrefixEnd(code: number): boolean { function isPrefixEnd(code: number): boolean {

View File

@ -234,6 +234,13 @@ import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../src/parse_u
}); });
describe('tags', () => { describe('tags', () => {
it('terminated with EOF', () => {
expect(tokenizeAndHumanizeSourceSpans('<div')).toEqual([
[lex.TokenType.INCOMPLETE_TAG_OPEN, '<div'],
[lex.TokenType.EOF, ''],
]);
});
it('after tag name', () => { it('after tag name', () => {
expect(tokenizeAndHumanizeSourceSpans('<div<span><div</span>')).toEqual([ expect(tokenizeAndHumanizeSourceSpans('<div<span><div</span>')).toEqual([
[lex.TokenType.INCOMPLETE_TAG_OPEN, '<div'], [lex.TokenType.INCOMPLETE_TAG_OPEN, '<div'],