From 3e102f0b84f8b53e35efb8b91ecdfbb93ad276fc Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Thu, 22 May 2025 05:50:56 +0200 Subject: [PATCH] fix(compiler): lexer support for template literals in object literals (#61601) This commit fixes a shortcoming of the lexer with template literals fixes #61572 PR Close #61601 --- packages/compiler/src/expression_parser/lexer.ts | 12 +++++------- .../compiler/test/expression_parser/lexer_spec.ts | 14 ++++++++++++++ .../compiler/test/expression_parser/parser_spec.ts | 7 +++++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/compiler/src/expression_parser/lexer.ts b/packages/compiler/src/expression_parser/lexer.ts index 924feee9935..e88fa3f2460 100644 --- a/packages/compiler/src/expression_parser/lexer.ts +++ b/packages/compiler/src/expression_parser/lexer.ts @@ -212,8 +212,7 @@ class _Scanner { private readonly length: number; private peek = 0; private index = -1; - private literalInterpolationDepth = 0; - private braceDepth = 0; + private braceStack: ('interpolation' | 'expression')[] = []; constructor(private readonly input: string) { this.length = input.length; @@ -341,7 +340,7 @@ class _Scanner { } private scanOpenBrace(start: number, code: number): Token { - this.braceDepth++; + this.braceStack.push('expression'); this.advance(); return newCharacterToken(start, this.index, code); } @@ -349,13 +348,12 @@ class _Scanner { private scanCloseBrace(start: number, code: number): Token { this.advance(); - if (this.braceDepth === 0 && this.literalInterpolationDepth > 0) { - this.literalInterpolationDepth--; + const currentBrace = this.braceStack.pop(); + if (currentBrace === 'interpolation') { this.tokens.push(newOperatorToken(start, this.index, '}')); return this.scanTemplateLiteralPart(this.index); } - this.braceDepth--; return newCharacterToken(start, this.index, code); } @@ -512,7 +510,7 @@ class _Scanner { // @ts-expect-error if (this.peek === chars.$LBRACE) { - this.literalInterpolationDepth++; + this.braceStack.push('interpolation'); this.tokens.push( new StringToken( start, diff --git a/packages/compiler/test/expression_parser/lexer_spec.ts b/packages/compiler/test/expression_parser/lexer_spec.ts index 0697f1d4239..3a0742f8af7 100644 --- a/packages/compiler/test/expression_parser/lexer_spec.ts +++ b/packages/compiler/test/expression_parser/lexer_spec.ts @@ -588,6 +588,20 @@ describe('lexer', () => { expectStringToken(tokens[8], 29, 33, '!!!', StringTokenKind.TemplateLiteralEnd); }); + it('should tokenize a template literal in an literal object value', () => { + const tokens: Token[] = lex('{foo: `${name}`}'); + expect(tokens.length).toBe(9); + expectCharacterToken(tokens[0], 0, 1, '{'); + expectIdentifierToken(tokens[1], 1, 4, 'foo'); + expectCharacterToken(tokens[2], 4, 5, ':'); + expectStringToken(tokens[3], 6, 7, '', StringTokenKind.TemplateLiteralPart); + expectOperatorToken(tokens[4], 7, 9, '${'); + expectIdentifierToken(tokens[5], 9, 13, 'name'); + expectOperatorToken(tokens[6], 13, 14, '}'); + expectStringToken(tokens[7], 14, 15, '', StringTokenKind.TemplateLiteralEnd); + expectCharacterToken(tokens[8], 15, 16, '}'); + }); + it('should produce an error if a template literal is not terminated', () => { expectErrorToken( lex('`hello')[0], diff --git a/packages/compiler/test/expression_parser/parser_spec.ts b/packages/compiler/test/expression_parser/parser_spec.ts index 12ca2fd89f5..1833b5d95d8 100644 --- a/packages/compiler/test/expression_parser/parser_spec.ts +++ b/packages/compiler/test/expression_parser/parser_spec.ts @@ -453,6 +453,13 @@ describe('parser', () => { checkBinding('`hello ${(name | capitalize)}!!!`', '`hello ${((name | capitalize))}!!!`'); }); + it('should parse template literals in objects literals', () => { + checkBinding('{"a": `${name}`}'); + checkBinding('{"a": `hello ${name}!`}'); + checkBinding('{"a": `hello ${`hello ${`hello`}`}!`}'); + checkBinding('{"a": `hello ${{"b": `hello`}}`}'); + }); + it('should report error if interpolation is empty', () => { expectBindingError( '`hello ${}`',