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
This commit is contained in:
Pete Bacon Darwin 2020-05-12 08:20:02 +01:00 committed by Kara Erickson
parent b0e362f75b
commit 4e1b5e43fa
2 changed files with 36 additions and 3 deletions

View File

@ -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<ts.ScriptTarget, ts.ScriptTarget.JSON> {
const target = context.getCompilerOptions().target || ts.ScriptTarget.ES2015;
return target !== ts.ScriptTarget.JSON ? target : ts.ScriptTarget.ES2015;
}
function getFileOverviewComment(statements: ts.NodeArray<ts.Statement>): FileOverviewMeta|null {
if (statements.length > 0) {
const host = statements[0];

View File

@ -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 = `
<div i18n="meaning:A|descA@@idA">Content A</div>
`;
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);