From 4e1b5e43fa1ea6652188d8d05404374629f50a37 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Tue, 12 May 2020 08:20:02 +0100 Subject: [PATCH] fix(compiler-cli): compute the correct target output for `$localize` messages (#36989) In some versions of TypeScript, the transformation of synthetic `$localize` tagged template literals is broken. See https://github.com/microsoft/TypeScript/issues/38485 We now compute what the expected final output target of the compilation will be so that we can generate ES5 compliant `$localize` calls instead of relying upon TS to do the downleveling for us. This is a workaround for the TS compiler bug, which could be removed when this is fixed. But since it only affects ES5 targeted compilations, which is now not the norm, it has limited impact on the majority of Angular projects. So this fix can probably be left in indefinitely. PR Close #36989 --- .../src/ngtsc/transform/src/transform.ts | 20 +++++++++++++++++-- .../compliance/r3_view_compiler_i18n_spec.ts | 19 +++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts index 58704a1369..3c92295a6d 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/transform.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/transform.ts @@ -233,8 +233,8 @@ function transformIvySourceFile( // Generate the constant statements first, as they may involve adding additional imports // to the ImportManager. const constants = constantPool.statements.map( - stmt => - translateStatement(stmt, importManager, defaultImportRecorder, ts.ScriptTarget.ES2015)); + stmt => translateStatement( + stmt, importManager, defaultImportRecorder, getLocalizeCompileTarget(context))); // Preserve @fileoverview comments required by Closure, since the location might change as a // result of adding extra imports and constant pool statements. @@ -250,6 +250,22 @@ function transformIvySourceFile( return sf; } +/** + * Compute the correct target output for `$localize` messages generated by Angular + * + * In some versions of TypeScript, the transformation of synthetic `$localize` tagged template + * literals is broken. See https://github.com/microsoft/TypeScript/issues/38485 + * + * Here we compute what the expected final output target of the compilation will + * be so that we can generate ES5 compliant `$localize` calls instead of relying upon TS to do the + * downleveling for us. + */ +function getLocalizeCompileTarget(context: ts.TransformationContext): + Exclude { + const target = context.getCompilerOptions().target || ts.ScriptTarget.ES2015; + return target !== ts.ScriptTarget.JSON ? target : ts.ScriptTarget.ES2015; +} + function getFileOverviewComment(statements: ts.NodeArray): FileOverviewMeta|null { if (statements.length > 0) { const host = statements[0]; diff --git a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts index cfb73846a3..d7cd299ada 100644 --- a/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts +++ b/packages/compiler-cli/test/compliance/r3_view_compiler_i18n_spec.ts @@ -5,9 +5,9 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ - import {AttributeMarker} from '@angular/compiler/src/core'; import {setup} from '@angular/compiler/test/aot/test_util'; +import * as ts from 'typescript'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../../../compiler/src/compiler'; import {decimalDigest} from '../../../compiler/src/i18n/digest'; @@ -3761,6 +3761,23 @@ $` + String.raw`{$I18N_4$}:ICU:\`; })); }); + describe('es5 support', () => { + it('should generate ES5 compliant localized messages if the target is ES5', () => { + const input = ` +
Content A
+ `; + + const output = String.raw` + var $I18N_0$; + … + $I18N_0$ = $localize(…__makeTemplateObject([":meaning:A|descA@@idA:Content A"], [":meaning\\:A|descA@@idA:Content A"])…); + `; + + verify( + input, output, {skipIdBasedCheck: true, compilerOptions: {target: ts.ScriptTarget.ES5}}); + }); + }); + describe('errors', () => { const verifyNestedSectionsError = (errorThrown: any, expectedErrorText: string) => { expect(errorThrown.ngParseErrors.length).toBe(1);