From a13a266e19a38b26fe616aa500e05ac917376caa Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sat, 5 Apr 2025 07:13:44 +0200 Subject: [PATCH] fix(compiler): error if rawText isn't estimated correctly (#60529) The `TemplateLiteralElementExpr` has some logic where it tries to estimate the `rawText` if one isn't provided by looking at the node's source span. The problem with this approach is that we have some long-standing issues with our expression AST parser (see https://github.com/angular/angular/pull/60267#discussion_r1986402524) where it might not produce accurate spans if escape sequences are involved. This in turn can lead to unrecoverable errors, because TypeScript will throw an error if the raw string doesn't match the cooked one when constructing a TypeScript AST node. These changes remove the logic that depends on the source span and relies purely on the secondary fallback that inserts escaped characters manually. It's also worth noting that the `rawText` doesn't seem to matter much at this point, because the main usage of it is when downlevelling template literals to ES5 which we no longer support. Fixes #60528. PR Close #60529 --- .../value_composition/GOLDEN_PARTIAL.js | 2 ++ .../value_composition/template_literals.js | 5 ++++- .../value_composition/template_literals.ts | 1 + packages/compiler/src/output/output_ast.ts | 13 ++++++------- packages/core/test/application_ref_spec.ts | 1 + 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/value_composition/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/value_composition/GOLDEN_PARTIAL.js index b5d140447ee9..36ad3c1fd755 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/value_composition/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/value_composition/GOLDEN_PARTIAL.js @@ -750,6 +750,7 @@ MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-
No interpolations: {{ \`hello world \` }}
With interpolations: {{ \`hello \${name}, it is currently \${timeOfDay}!\` }}

With pipe: {{\`hello \${name}\` | uppercase}}

+

@let insideLet = \`Hello \${name}\`; Inside let: {{insideLet}}

`, isInline: true, dependencies: [{ kind: "pipe", type: UppercasePipe, name: "uppercase" }] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{ type: Component, @@ -759,6 +760,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE
No interpolations: {{ \`hello world \` }}
With interpolations: {{ \`hello \${name}, it is currently \${timeOfDay}!\` }}

With pipe: {{\`hello \${name}\` | uppercase}}

+

@let insideLet = \`Hello \${name}\`; Inside let: {{insideLet}}

`, imports: [UppercasePipe], }] diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/value_composition/template_literals.js b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/value_composition/template_literals.js index 5e4c1454552f..7bd7a1116af6 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/value_composition/template_literals.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/value_composition/template_literals.js @@ -4,5 +4,8 @@ if (rf & 2) { $r3$.ɵɵadvance(2); $r3$.ɵɵtextInterpolate1("With interpolations: ", `hello ${ctx.name}, it is currently ${ctx.timeOfDay}!`, ""); $r3$.ɵɵadvance(2); - $r3$.ɵɵtextInterpolate1("With pipe: ", $r3$.ɵɵpipeBind1(6, 3, `hello ${ctx.name}`), ""); + $r3$.ɵɵtextInterpolate1("With pipe: ", $r3$.ɵɵpipeBind1(6, 4, `hello ${ctx.name}`), ""); + const $insideLet_r1$ = `Hello ${ctx.name}`; + $r3$.ɵɵadvance(4); + $r3$.ɵɵtextInterpolate1(" Inside let: ", $insideLet_r1$, ""); } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/value_composition/template_literals.ts b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/value_composition/template_literals.ts index 70ef4e7044ee..8b0a3b3a179c 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/value_composition/template_literals.ts +++ b/packages/compiler-cli/test/compliance/test_cases/r3_compiler_compliance/components_and_directives/value_composition/template_literals.ts @@ -13,6 +13,7 @@ export class UppercasePipe {
No interpolations: {{ \`hello world \` }}
With interpolations: {{ \`hello \${name}, it is currently \${timeOfDay}!\` }}

With pipe: {{\`hello \${name}\` | uppercase}}

+

@let insideLet = \`Hello \${name}\`; Inside let: {{insideLet}}

`, imports: [UppercasePipe], }) diff --git a/packages/compiler/src/output/output_ast.ts b/packages/compiler/src/output/output_ast.ts index 49bbcc6f57a2..66dcb4d1174e 100644 --- a/packages/compiler/src/output/output_ast.ts +++ b/packages/compiler/src/output/output_ast.ts @@ -683,23 +683,22 @@ export class TemplateLiteralExpr extends Expression { } } export class TemplateLiteralElementExpr extends Expression { - rawText: string; + readonly rawText: string; constructor( - public text: string, + readonly text: string, sourceSpan?: ParseSourceSpan | null, rawText?: string, ) { super(STRING_TYPE, sourceSpan); - // If `rawText` is not provided, try to extract the raw string from its - // associated `sourceSpan`. If that is also not available, "fake" the raw - // string instead by escaping the following control sequences: + // If `rawText` is not provided, "fake" the raw string by escaping the following sequences: // - "\" would otherwise indicate that the next character is a control character. // - "`" and "${" are template string control sequences that would otherwise prematurely // indicate the end of the template literal element. - this.rawText = - rawText ?? sourceSpan?.toString() ?? escapeForTemplateLiteral(escapeSlashes(text)); + // Note that we can't rely on the `sourceSpan` here, because it may be incorrect (see + // https://github.com/angular/angular/pull/60267#discussion_r1986402524). + this.rawText = rawText ?? escapeForTemplateLiteral(escapeSlashes(text)); } override visitExpression(visitor: ExpressionVisitor, context: any) { diff --git a/packages/core/test/application_ref_spec.ts b/packages/core/test/application_ref_spec.ts index 8180bf411af8..90e7be3e53e1 100644 --- a/packages/core/test/application_ref_spec.ts +++ b/packages/core/test/application_ref_spec.ts @@ -27,6 +27,7 @@ import { Type, ViewChild, ViewContainerRef, + Injector, } from '../src/core'; import {ErrorHandler} from '../src/error_handler'; import {ComponentRef} from '../src/linker/component_factory';