fix(compiler): normalize line endings in ICU expansions (#36741)
The html parser already normalizes line endings (converting `\r\n` to `\n`) for most text in templates but it was missing the expressions of ICU expansions. In ViewEngine backticked literal strings, used to define inline templates, were already normalized by the TypeScript parser. In Ivy we are parsing the raw text of the source file directly so the line endings need to be manually normalized. This change ensures that inline templates have the line endings of ICU expression normalized correctly, which matches the ViewEngine. In ViewEngine external templates, defined in HTML files, the behavior was different, since TypeScript was not normalizing the line endings. Specifically, ICU expansion "expressions" are not being normalized. This is a problem because it means that i18n message ids can be different on different machines that are setup with different line ending handling, or if the developer moves a template from inline to external or vice versa. The goal is always to normalize line endings, whether inline or external. But this would be a breaking change since it would change i18n message ids that have been previously computed. Therefore this commit aligns the ivy template parsing to have the same "buggy" behavior for external templates. There is now a compiler option `i18nNormalizeLineEndingsInICUs`, which if set to `true` will ensure the correct non-buggy behavior. For the time being this option defaults to `false` to ensure backward compatibility while allowing opt-in to the desired behavior. This option's default will be flipped in a future breaking change release. Further, when this option is set to `false`, any ICU expression tokens, which have not been normalized, are added to the `ParseResult` from the `HtmlParser.parse()` method. In the future, this collection of tokens could be used to diagnose and encourage developers to migrate their i18n message ids. See FW-2106. Closes #36725 PR Close #36741
This commit is contained in:

committed by
Andrew Kushnir

parent
7bc5bcde34
commit
70dd27ffd8
@ -124,6 +124,7 @@ const escapeTemplate = (template: string) =>
|
||||
|
||||
const getAppFilesWithTemplate = (template: string, args: any = {}) => ({
|
||||
app: {
|
||||
'spec.template.html': template,
|
||||
'spec.ts': `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@ -131,8 +132,9 @@ const getAppFilesWithTemplate = (template: string, args: any = {}) => ({
|
||||
selector: 'my-component',
|
||||
${args.preserveWhitespaces ? 'preserveWhitespaces: true,' : ''}
|
||||
${args.interpolation ? 'interpolation: ' + JSON.stringify(args.interpolation) + ', ' : ''}
|
||||
template: \`${escapeTemplate(template)}\`
|
||||
})
|
||||
${
|
||||
args.templateUrl ? `templateUrl: 'spec.template.html'` :
|
||||
`template: \`${escapeTemplate(template)}\``})
|
||||
export class MyComponent {}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
@ -3693,6 +3695,72 @@ describe('i18n support in the template compiler', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('line ending normalization', () => {
|
||||
[true, false].forEach(
|
||||
templateUrl => describe(templateUrl ? '[templateUrl]' : '[inline template]', () => {
|
||||
[true, false, undefined].forEach(
|
||||
i18nNormalizeLineEndingsInICUs => describe(
|
||||
`{i18nNormalizeLineEndingsInICUs: ${i18nNormalizeLineEndingsInICUs}}`, () => {
|
||||
it('should normalize line endings in templates', () => {
|
||||
const input =
|
||||
`<div title="abc\r\ndef" i18n-title i18n>\r\nSome Message\r\n{\r\n value,\r\n select,\r\n =0 {\r\n zero\r\n }\r\n}</div>`;
|
||||
|
||||
const output = String.raw`
|
||||
$I18N_0$ = $localize \`abc
|
||||
def\`;
|
||||
…
|
||||
$I18N_4$ = $localize \`{VAR_SELECT, select, =0 {zero
|
||||
}}\`
|
||||
…
|
||||
$I18N_3$ = $localize \`
|
||||
Some Message
|
||||
$` + String.raw`{$I18N_4$}:ICU:\`;
|
||||
`;
|
||||
|
||||
verify(input, output, {
|
||||
inputArgs: {templateUrl},
|
||||
compilerOptions: {i18nNormalizeLineEndingsInICUs}
|
||||
});
|
||||
});
|
||||
|
||||
it('should compute the correct message id for messages', () => {
|
||||
const input =
|
||||
`<div title="abc\r\ndef" i18n-title i18n>\r\nSome Message\r\n{\r\n value,\r\n select,\r\n =0 {\r\n zero\r\n }\r\n}</div>`;
|
||||
|
||||
// The ids generated by the compiler are different if the template is external
|
||||
// and we are not explicitly normalizing the line endings.
|
||||
const ICU_EXPRESSION_ID =
|
||||
templateUrl && i18nNormalizeLineEndingsInICUs !== true ?
|
||||
`␟70a685282be2d956e4db234fa3d985970672faa0` :
|
||||
`␟b5fe162f4e47ab5b3e534491d30b715e0dff0f52`;
|
||||
const ICU_ID = templateUrl && i18nNormalizeLineEndingsInICUs !== true ?
|
||||
`␟6a55b51b9bcf8f84b1b868c585ae09949668a72b` :
|
||||
`␟e31c7bc4db2f2e56dc40f005958055a02fd43a2e`;
|
||||
|
||||
const output =
|
||||
String.raw`
|
||||
$I18N_0$ = $localize \`:␟4f9ce2c66b187afd9898b25f6336d1eb2be8b5dc␟7326958852138509669:abc
|
||||
def\`;
|
||||
…
|
||||
$I18N_4$ = $localize \`:${
|
||||
ICU_EXPRESSION_ID}␟4863953183043480207:{VAR_SELECT, select, =0 {zero
|
||||
}}\`
|
||||
…
|
||||
$I18N_3$ = $localize \`:${ICU_ID}␟2773178924738647105:
|
||||
Some Message
|
||||
$` + String.raw`{$I18N_4$}:ICU:\`;
|
||||
`;
|
||||
|
||||
verify(input, output, {
|
||||
inputArgs: {templateUrl},
|
||||
compilerOptions:
|
||||
{i18nNormalizeLineEndingsInICUs, enableI18nLegacyMessageIdFormat: true}
|
||||
});
|
||||
});
|
||||
}));
|
||||
}));
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
const verifyNestedSectionsError = (errorThrown: any, expectedErrorText: string) => {
|
||||
expect(errorThrown.ngParseErrors.length).toBe(1);
|
||||
|
Reference in New Issue
Block a user