Skip to content

Commit 159be56

Browse files
crisbetokirjs
authored andcommitted
fix(compiler): recover template literals with broken expressions (angular#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 angular#63940. PR Close angular#64150
1 parent 31bc9e4 commit 159be56

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
@@ -156,10 +156,6 @@ export class Token {
156156
return this.isOperator('${');
157157
}
158158

159-
isTemplateLiteralInterpolationEnd(): boolean {
160-
return this.isOperator('}');
161-
}
162-
163159
toString(): string | null {
164160
switch (this.type) {
165161
case TokenType.Character:
@@ -378,7 +374,7 @@ class _Scanner {
378374

379375
const currentBrace = this.braceStack.pop();
380376
if (currentBrace === 'interpolation') {
381-
this.tokens.push(newOperatorToken(start, this.index, '}'));
377+
this.tokens.push(newCharacterToken(start, this.index, chars.$RBRACE));
382378
return this.scanTemplateLiteralPart(this.index);
383379
}
384380

packages/compiler/src/expression_parser/parser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1590,12 +1590,14 @@ class _ParseAST {
15901590
}
15911591
} else if (token.isTemplateLiteralInterpolationStart()) {
15921592
this.advance();
1593+
this.rbracesExpected++;
15931594
const expression = this.parsePipe();
15941595
if (expression instanceof EmptyExpr) {
15951596
this.error('Template literal interpolation cannot be empty');
15961597
} else {
15971598
expressions.push(expression);
15981599
}
1600+
this.rbracesExpected--;
15991601
} else {
16001602
this.advance();
16011603
}

packages/compiler/test/expression_parser/lexer_spec.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ describe('lexer', () => {
468468
expectStringToken(tokens[0], 0, 7, 'hello ', StringTokenKind.TemplateLiteralPart);
469469
expectOperatorToken(tokens[1], 7, 9, '${');
470470
expectIdentifierToken(tokens[2], 9, 13, 'name');
471-
expectOperatorToken(tokens[3], 13, 14, '}');
471+
expectCharacterToken(tokens[3], 13, 14, '}');
472472
expectStringToken(tokens[4], 14, 15, '', StringTokenKind.TemplateLiteralEnd);
473473
});
474474

@@ -478,7 +478,7 @@ describe('lexer', () => {
478478
expectStringToken(tokens[0], 0, 1, '', StringTokenKind.TemplateLiteralPart);
479479
expectOperatorToken(tokens[1], 1, 3, '${');
480480
expectIdentifierToken(tokens[2], 3, 7, 'name');
481-
expectOperatorToken(tokens[3], 7, 8, '}');
481+
expectCharacterToken(tokens[3], 7, 8, '}');
482482
expectStringToken(tokens[4], 8, 17, ' Johnson', StringTokenKind.TemplateLiteralEnd);
483483
});
484484

@@ -488,7 +488,7 @@ describe('lexer', () => {
488488
expectStringToken(tokens[0], 0, 4, 'foo', StringTokenKind.TemplateLiteralPart);
489489
expectOperatorToken(tokens[1], 4, 6, '${');
490490
expectIdentifierToken(tokens[2], 6, 9, 'bar');
491-
expectOperatorToken(tokens[3], 9, 10, '}');
491+
expectCharacterToken(tokens[3], 9, 10, '}');
492492
expectStringToken(tokens[4], 10, 14, 'baz', StringTokenKind.TemplateLiteralEnd);
493493
});
494494

@@ -517,15 +517,15 @@ describe('lexer', () => {
517517
expectStringToken(tokens[0], 0, 1, '', StringTokenKind.TemplateLiteralPart);
518518
expectOperatorToken(tokens[1], 1, 3, '${');
519519
expectIdentifierToken(tokens[2], 3, 4, 'a');
520-
expectOperatorToken(tokens[3], 4, 5, '}');
520+
expectCharacterToken(tokens[3], 4, 5, '}');
521521
expectStringToken(tokens[4], 5, 8, ' - ', StringTokenKind.TemplateLiteralPart);
522522
expectOperatorToken(tokens[5], 8, 10, '${');
523523
expectIdentifierToken(tokens[6], 10, 11, 'b');
524-
expectOperatorToken(tokens[7], 11, 12, '}');
524+
expectCharacterToken(tokens[7], 11, 12, '}');
525525
expectStringToken(tokens[8], 12, 15, ' - ', StringTokenKind.TemplateLiteralPart);
526526
expectOperatorToken(tokens[9], 15, 17, '${');
527527
expectIdentifierToken(tokens[10], 17, 18, 'c');
528-
expectOperatorToken(tokens[11], 18, 19, '}');
528+
expectCharacterToken(tokens[11], 18, 19, '}');
529529
});
530530

531531
it('should tokenize template literal with an object literal inside the interpolation', () => {
@@ -538,7 +538,7 @@ describe('lexer', () => {
538538
expectCharacterToken(tokens[4], 9, 10, ':');
539539
expectKeywordToken(tokens[5], 11, 15, 'true');
540540
expectCharacterToken(tokens[6], 15, 16, '}');
541-
expectOperatorToken(tokens[7], 16, 17, '}');
541+
expectCharacterToken(tokens[7], 16, 17, '}');
542542
expectStringToken(tokens[8], 17, 22, ' baz', StringTokenKind.TemplateLiteralEnd);
543543
});
544544

@@ -552,11 +552,11 @@ describe('lexer', () => {
552552
expectStringToken(tokens[4], 16, 17, '', StringTokenKind.TemplateLiteralPart);
553553
expectOperatorToken(tokens[5], 17, 19, '${');
554554
expectIdentifierToken(tokens[6], 19, 20, 'a');
555-
expectOperatorToken(tokens[7], 20, 21, '}');
555+
expectCharacterToken(tokens[7], 20, 21, '}');
556556
expectStringToken(tokens[8], 21, 26, ' - b', StringTokenKind.TemplateLiteralEnd);
557-
expectOperatorToken(tokens[9], 26, 27, '}');
557+
expectCharacterToken(tokens[9], 26, 27, '}');
558558
expectStringToken(tokens[10], 27, 28, '', StringTokenKind.TemplateLiteralEnd);
559-
expectOperatorToken(tokens[11], 28, 29, '}');
559+
expectCharacterToken(tokens[11], 28, 29, '}');
560560
expectStringToken(tokens[12], 29, 34, ' baz', StringTokenKind.TemplateLiteralEnd);
561561
});
562562

@@ -566,12 +566,12 @@ describe('lexer', () => {
566566
expectStringToken(tokens[0], 0, 7, 'hello ', StringTokenKind.TemplateLiteralPart);
567567
expectOperatorToken(tokens[1], 7, 9, '${');
568568
expectIdentifierToken(tokens[2], 9, 13, 'name');
569-
expectOperatorToken(tokens[3], 13, 14, '}');
569+
expectCharacterToken(tokens[3], 13, 14, '}');
570570
expectStringToken(tokens[4], 14, 15, '', StringTokenKind.TemplateLiteralEnd);
571571
expectStringToken(tokens[5], 15, 20, 'see ', StringTokenKind.TemplateLiteralPart);
572572
expectOperatorToken(tokens[6], 20, 22, '${');
573573
expectIdentifierToken(tokens[7], 22, 26, 'name');
574-
expectOperatorToken(tokens[8], 26, 27, '}');
574+
expectCharacterToken(tokens[8], 26, 27, '}');
575575
expectStringToken(tokens[9], 27, 34, ' later', StringTokenKind.TemplateLiteralEnd);
576576
});
577577

@@ -581,7 +581,7 @@ describe('lexer', () => {
581581
expectStringToken(tokens[0], 0, 7, 'hello ', StringTokenKind.TemplateLiteralPart);
582582
expectOperatorToken(tokens[1], 7, 9, '${');
583583
expectIdentifierToken(tokens[2], 9, 13, 'name');
584-
expectOperatorToken(tokens[3], 13, 14, '}');
584+
expectCharacterToken(tokens[3], 13, 14, '}');
585585
expectStringToken(tokens[4], 14, 15, '', StringTokenKind.TemplateLiteralEnd);
586586
expectOperatorToken(tokens[5], 16, 17, '+');
587587
expectNumberToken(tokens[6], 18, 21, 123);
@@ -595,7 +595,7 @@ describe('lexer', () => {
595595
expectIdentifierToken(tokens[2], 9, 13, 'name');
596596
expectOperatorToken(tokens[3], 14, 15, '|');
597597
expectIdentifierToken(tokens[4], 16, 26, 'capitalize');
598-
expectOperatorToken(tokens[5], 26, 27, '}');
598+
expectCharacterToken(tokens[5], 26, 27, '}');
599599
expectStringToken(tokens[6], 27, 31, '!!!', StringTokenKind.TemplateLiteralEnd);
600600
});
601601

@@ -609,7 +609,7 @@ describe('lexer', () => {
609609
expectOperatorToken(tokens[4], 15, 16, '|');
610610
expectIdentifierToken(tokens[5], 17, 27, 'capitalize');
611611
expectCharacterToken(tokens[6], 27, 28, ')');
612-
expectOperatorToken(tokens[7], 28, 29, '}');
612+
expectCharacterToken(tokens[7], 28, 29, '}');
613613
expectStringToken(tokens[8], 29, 33, '!!!', StringTokenKind.TemplateLiteralEnd);
614614
});
615615

@@ -622,7 +622,7 @@ describe('lexer', () => {
622622
expectStringToken(tokens[3], 6, 7, '', StringTokenKind.TemplateLiteralPart);
623623
expectOperatorToken(tokens[4], 7, 9, '${');
624624
expectIdentifierToken(tokens[5], 9, 13, 'name');
625-
expectOperatorToken(tokens[6], 13, 14, '}');
625+
expectCharacterToken(tokens[6], 13, 14, '}');
626626
expectStringToken(tokens[7], 14, 15, '', StringTokenKind.TemplateLiteralEnd);
627627
expectCharacterToken(tokens[8], 15, 16, '}');
628628
});
@@ -642,7 +642,7 @@ describe('lexer', () => {
642642
expectStringToken(tokens[0], 0, 7, 'hello ', StringTokenKind.TemplateLiteralPart);
643643
expectOperatorToken(tokens[1], 7, 9, '${');
644644
expectIdentifierToken(tokens[2], 9, 13, 'name');
645-
expectOperatorToken(tokens[3], 13, 14, '}');
645+
expectCharacterToken(tokens[3], 13, 14, '}');
646646
expectErrorToken(
647647
tokens[4],
648648
15,
@@ -681,7 +681,7 @@ describe('lexer', () => {
681681
expectOperatorToken(tokens[2], 10, 12, '${');
682682
expectIdentifierToken(tokens[3], 12, 15, 'tag');
683683
expectStringToken(tokens[4], 15, 22, 'world', StringTokenKind.TemplateLiteralEnd);
684-
expectOperatorToken(tokens[5], 22, 23, '}');
684+
expectCharacterToken(tokens[5], 22, 23, '}');
685685
expectStringToken(tokens[6], 23, 24, '', StringTokenKind.TemplateLiteralEnd);
686686
});
687687
});

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', () => {
@@ -1484,6 +1480,12 @@ describe('parser', () => {
14841480
recover('foo(((($event.target as HTMLElement))).value)', 'foo(((($event.target))).value)');
14851481
recover('foo(((bar as HTMLElement) as Something).value)', 'foo(((bar)).value)');
14861482
});
1483+
1484+
it('should be able to recover from a broken expression in a template literal', () => {
1485+
recover('`before ${expr.}`', '`before ${expr.}`');
1486+
recover('`${expr.} after`', '`${expr.} after`');
1487+
recover('`before ${expr.} after`', '`before ${expr.} after`');
1488+
});
14871489
});
14881490

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

0 commit comments

Comments
 (0)