Skip to content

Commit f51ab32

Browse files
crisbetokirjs
authored andcommitted
fix(compiler): recover template literals with broken expressions (#64150)
Fixes two issues that were preventing template literals from being recovered properly if one of the interpolated expressions is broken: 1. We weren't updating the expected brace counter when an interpolation starts which in turn was throwing off the recovery logic in `skip`. 2. When producing tokens for template literals, we were treating the closing brace as an operator whereas other places treat it as a character. Even after fixing the first issue, this was preventing the recovery logic from working correctly. Fixes #63940. PR Close #64150
1 parent 77b6305 commit f51ab32

File tree

4 files changed

+28
-28
lines changed

4 files changed

+28
-28
lines changed

packages/compiler/src/expression_parser/lexer.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,6 @@ export class Token {
146146
return this.isOperator('${');
147147
}
148148

149-
isTemplateLiteralInterpolationEnd(): boolean {
150-
return this.isOperator('}');
151-
}
152-
153149
toString(): string | null {
154150
switch (this.type) {
155151
case TokenType.Character:
@@ -356,7 +352,7 @@ class _Scanner {
356352

357353
const currentBrace = this.braceStack.pop();
358354
if (currentBrace === 'interpolation') {
359-
this.tokens.push(newOperatorToken(start, this.index, '}'));
355+
this.tokens.push(newCharacterToken(start, this.index, chars.$RBRACE));
360356
return this.scanTemplateLiteralPart(this.index);
361357
}
362358

packages/compiler/src/expression_parser/parser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1604,12 +1604,14 @@ class _ParseAST {
16041604
}
16051605
} else if (token.isTemplateLiteralInterpolationStart()) {
16061606
this.advance();
1607+
this.rbracesExpected++;
16071608
const expression = this.parsePipe();
16081609
if (expression instanceof EmptyExpr) {
16091610
this.error('Template literal interpolation cannot be empty');
16101611
} else {
16111612
expressions.push(expression);
16121613
}
1614+
this.rbracesExpected--;
16131615
} else {
16141616
this.advance();
16151617
}

packages/compiler/test/expression_parser/lexer_spec.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ describe('lexer', () => {
456456
expectStringToken(tokens[0], 0, 7, 'hello ', StringTokenKind.TemplateLiteralPart);
457457
expectOperatorToken(tokens[1], 7, 9, '${');
458458
expectIdentifierToken(tokens[2], 9, 13, 'name');
459-
expectOperatorToken(tokens[3], 13, 14, '}');
459+
expectCharacterToken(tokens[3], 13, 14, '}');
460460
expectStringToken(tokens[4], 14, 15, '', StringTokenKind.TemplateLiteralEnd);
461461
});
462462

@@ -466,7 +466,7 @@ describe('lexer', () => {
466466
expectStringToken(tokens[0], 0, 1, '', StringTokenKind.TemplateLiteralPart);
467467
expectOperatorToken(tokens[1], 1, 3, '${');
468468
expectIdentifierToken(tokens[2], 3, 7, 'name');
469-
expectOperatorToken(tokens[3], 7, 8, '}');
469+
expectCharacterToken(tokens[3], 7, 8, '}');
470470
expectStringToken(tokens[4], 8, 17, ' Johnson', StringTokenKind.TemplateLiteralEnd);
471471
});
472472

@@ -476,7 +476,7 @@ describe('lexer', () => {
476476
expectStringToken(tokens[0], 0, 4, 'foo', StringTokenKind.TemplateLiteralPart);
477477
expectOperatorToken(tokens[1], 4, 6, '${');
478478
expectIdentifierToken(tokens[2], 6, 9, 'bar');
479-
expectOperatorToken(tokens[3], 9, 10, '}');
479+
expectCharacterToken(tokens[3], 9, 10, '}');
480480
expectStringToken(tokens[4], 10, 14, 'baz', StringTokenKind.TemplateLiteralEnd);
481481
});
482482

@@ -505,15 +505,15 @@ describe('lexer', () => {
505505
expectStringToken(tokens[0], 0, 1, '', StringTokenKind.TemplateLiteralPart);
506506
expectOperatorToken(tokens[1], 1, 3, '${');
507507
expectIdentifierToken(tokens[2], 3, 4, 'a');
508-
expectOperatorToken(tokens[3], 4, 5, '}');
508+
expectCharacterToken(tokens[3], 4, 5, '}');
509509
expectStringToken(tokens[4], 5, 8, ' - ', StringTokenKind.TemplateLiteralPart);
510510
expectOperatorToken(tokens[5], 8, 10, '${');
511511
expectIdentifierToken(tokens[6], 10, 11, 'b');
512-
expectOperatorToken(tokens[7], 11, 12, '}');
512+
expectCharacterToken(tokens[7], 11, 12, '}');
513513
expectStringToken(tokens[8], 12, 15, ' - ', StringTokenKind.TemplateLiteralPart);
514514
expectOperatorToken(tokens[9], 15, 17, '${');
515515
expectIdentifierToken(tokens[10], 17, 18, 'c');
516-
expectOperatorToken(tokens[11], 18, 19, '}');
516+
expectCharacterToken(tokens[11], 18, 19, '}');
517517
});
518518

519519
it('should tokenize template literal with an object literal inside the interpolation', () => {
@@ -526,7 +526,7 @@ describe('lexer', () => {
526526
expectCharacterToken(tokens[4], 9, 10, ':');
527527
expectKeywordToken(tokens[5], 11, 15, 'true');
528528
expectCharacterToken(tokens[6], 15, 16, '}');
529-
expectOperatorToken(tokens[7], 16, 17, '}');
529+
expectCharacterToken(tokens[7], 16, 17, '}');
530530
expectStringToken(tokens[8], 17, 22, ' baz', StringTokenKind.TemplateLiteralEnd);
531531
});
532532

@@ -540,11 +540,11 @@ describe('lexer', () => {
540540
expectStringToken(tokens[4], 16, 17, '', StringTokenKind.TemplateLiteralPart);
541541
expectOperatorToken(tokens[5], 17, 19, '${');
542542
expectIdentifierToken(tokens[6], 19, 20, 'a');
543-
expectOperatorToken(tokens[7], 20, 21, '}');
543+
expectCharacterToken(tokens[7], 20, 21, '}');
544544
expectStringToken(tokens[8], 21, 26, ' - b', StringTokenKind.TemplateLiteralEnd);
545-
expectOperatorToken(tokens[9], 26, 27, '}');
545+
expectCharacterToken(tokens[9], 26, 27, '}');
546546
expectStringToken(tokens[10], 27, 28, '', StringTokenKind.TemplateLiteralEnd);
547-
expectOperatorToken(tokens[11], 28, 29, '}');
547+
expectCharacterToken(tokens[11], 28, 29, '}');
548548
expectStringToken(tokens[12], 29, 34, ' baz', StringTokenKind.TemplateLiteralEnd);
549549
});
550550

@@ -554,12 +554,12 @@ describe('lexer', () => {
554554
expectStringToken(tokens[0], 0, 7, 'hello ', StringTokenKind.TemplateLiteralPart);
555555
expectOperatorToken(tokens[1], 7, 9, '${');
556556
expectIdentifierToken(tokens[2], 9, 13, 'name');
557-
expectOperatorToken(tokens[3], 13, 14, '}');
557+
expectCharacterToken(tokens[3], 13, 14, '}');
558558
expectStringToken(tokens[4], 14, 15, '', StringTokenKind.TemplateLiteralEnd);
559559
expectStringToken(tokens[5], 15, 20, 'see ', StringTokenKind.TemplateLiteralPart);
560560
expectOperatorToken(tokens[6], 20, 22, '${');
561561
expectIdentifierToken(tokens[7], 22, 26, 'name');
562-
expectOperatorToken(tokens[8], 26, 27, '}');
562+
expectCharacterToken(tokens[8], 26, 27, '}');
563563
expectStringToken(tokens[9], 27, 34, ' later', StringTokenKind.TemplateLiteralEnd);
564564
});
565565

@@ -569,7 +569,7 @@ describe('lexer', () => {
569569
expectStringToken(tokens[0], 0, 7, 'hello ', StringTokenKind.TemplateLiteralPart);
570570
expectOperatorToken(tokens[1], 7, 9, '${');
571571
expectIdentifierToken(tokens[2], 9, 13, 'name');
572-
expectOperatorToken(tokens[3], 13, 14, '}');
572+
expectCharacterToken(tokens[3], 13, 14, '}');
573573
expectStringToken(tokens[4], 14, 15, '', StringTokenKind.TemplateLiteralEnd);
574574
expectOperatorToken(tokens[5], 16, 17, '+');
575575
expectNumberToken(tokens[6], 18, 21, 123);
@@ -583,7 +583,7 @@ describe('lexer', () => {
583583
expectIdentifierToken(tokens[2], 9, 13, 'name');
584584
expectOperatorToken(tokens[3], 14, 15, '|');
585585
expectIdentifierToken(tokens[4], 16, 26, 'capitalize');
586-
expectOperatorToken(tokens[5], 26, 27, '}');
586+
expectCharacterToken(tokens[5], 26, 27, '}');
587587
expectStringToken(tokens[6], 27, 31, '!!!', StringTokenKind.TemplateLiteralEnd);
588588
});
589589

@@ -597,7 +597,7 @@ describe('lexer', () => {
597597
expectOperatorToken(tokens[4], 15, 16, '|');
598598
expectIdentifierToken(tokens[5], 17, 27, 'capitalize');
599599
expectCharacterToken(tokens[6], 27, 28, ')');
600-
expectOperatorToken(tokens[7], 28, 29, '}');
600+
expectCharacterToken(tokens[7], 28, 29, '}');
601601
expectStringToken(tokens[8], 29, 33, '!!!', StringTokenKind.TemplateLiteralEnd);
602602
});
603603

@@ -610,7 +610,7 @@ describe('lexer', () => {
610610
expectStringToken(tokens[3], 6, 7, '', StringTokenKind.TemplateLiteralPart);
611611
expectOperatorToken(tokens[4], 7, 9, '${');
612612
expectIdentifierToken(tokens[5], 9, 13, 'name');
613-
expectOperatorToken(tokens[6], 13, 14, '}');
613+
expectCharacterToken(tokens[6], 13, 14, '}');
614614
expectStringToken(tokens[7], 14, 15, '', StringTokenKind.TemplateLiteralEnd);
615615
expectCharacterToken(tokens[8], 15, 16, '}');
616616
});
@@ -630,7 +630,7 @@ describe('lexer', () => {
630630
expectStringToken(tokens[0], 0, 7, 'hello ', StringTokenKind.TemplateLiteralPart);
631631
expectOperatorToken(tokens[1], 7, 9, '${');
632632
expectIdentifierToken(tokens[2], 9, 13, 'name');
633-
expectOperatorToken(tokens[3], 13, 14, '}');
633+
expectCharacterToken(tokens[3], 13, 14, '}');
634634
expectErrorToken(
635635
tokens[4],
636636
15,
@@ -669,7 +669,7 @@ describe('lexer', () => {
669669
expectOperatorToken(tokens[2], 10, 12, '${');
670670
expectIdentifierToken(tokens[3], 12, 15, 'tag');
671671
expectStringToken(tokens[4], 15, 22, 'world', StringTokenKind.TemplateLiteralEnd);
672-
expectOperatorToken(tokens[5], 22, 23, '}');
672+
expectCharacterToken(tokens[5], 22, 23, '}');
673673
expectStringToken(tokens[6], 23, 24, '', StringTokenKind.TemplateLiteralEnd);
674674
});
675675
});

packages/compiler/test/expression_parser/parser_spec.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import {
1818
TemplateBinding,
1919
VariableBinding,
2020
BindingPipeType,
21-
Binary,
2221
} from '../../src/expression_parser/ast';
2322
import {ParseError} from '../../src/parse_util';
2423
import {Lexer} from '../../src/expression_parser/lexer';
@@ -481,10 +480,7 @@ describe('parser', () => {
481480
});
482481

483482
it('should report error if interpolation is empty', () => {
484-
expectBindingError(
485-
'`hello ${}`',
486-
'Template literal interpolation cannot be empty at the end of the expression',
487-
);
483+
expectBindingError('`hello ${}`', 'Template literal interpolation cannot be empty');
488484
});
489485

490486
it('should parse tagged template literals with no interpolations', () => {
@@ -1449,6 +1445,12 @@ describe('parser', () => {
14491445
recover('foo(((($event.target as HTMLElement))).value)', 'foo(((($event.target))).value)');
14501446
recover('foo(((bar as HTMLElement) as Something).value)', 'foo(((bar)).value)');
14511447
});
1448+
1449+
it('should be able to recover from a broken expression in a template literal', () => {
1450+
recover('`before ${expr.}`', '`before ${expr.}`');
1451+
recover('`${expr.} after`', '`${expr.} after`');
1452+
recover('`before ${expr.} after`', '`before ${expr.} after`');
1453+
});
14521454
});
14531455

14541456
describe('offsets', () => {

0 commit comments

Comments
 (0)