From 3f73dfa151dade9ab45599de71e20cbf283092b6 Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Fri, 11 Jan 2019 17:23:51 -0800 Subject: [PATCH] fix(ivy): sanitize external i18n ids before generating const names (#28522) Prior to this change there was no i18n id sanitization before we output goog.getMsg calls. Due to the fact that message ids are used as a part of const names, some characters were bcausing issues while executing generated code. This commit adds sanitization to i18n ids used to generate i18n-related consts. PR Close #28522 --- .../compliance/r3_view_compiler_i18n_spec.ts | 53 +++++++++++++------ .../compiler/src/render3/view/template.ts | 2 +- 2 files changed, 39 insertions(+), 16 deletions(-) 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 1bfce76af2..e9da4eb0c5 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 @@ -129,23 +129,25 @@ const verify = (input: string, output: string, extra: any = {}): void => { ({i18nUseExternalIds, ...(extra.compilerOptions || {})}); // invoke with file-based prefix translation names - let result = compile(files, angularFiles, opts(false)); - maybePrint(result.source, extra.verbose); - expect(verifyPlaceholdersIntegrity(result.source)).toBe(true); - expectEmit(result.source, output, 'Incorrect template'); - - if (extra.skipIdBasedCheck) return; + if (!extra.skipPathBasedCheck) { + const result = compile(files, angularFiles, opts(false)); + maybePrint(result.source, extra.verbose); + expect(verifyPlaceholdersIntegrity(result.source)).toBe(true); + expectEmit(result.source, output, 'Incorrect template'); + } // invoke with translation names based on external ids - result = compile(files, angularFiles, opts(true)); - maybePrint(result.source, extra.verbose); - const interpolationConfig = extra.inputArgs && extra.inputArgs.interpolation ? - InterpolationConfig.fromArray(extra.inputArgs.interpolation) : - undefined; - expect(verifyTranslationIds(input, result.source, extra.exceptions, interpolationConfig)) - .toBe(true); - expect(verifyPlaceholdersIntegrity(result.source)).toBe(true); - expectEmit(result.source, output, 'Incorrect template'); + if (!extra.skipIdBasedCheck) { + const result = compile(files, angularFiles, opts(true)); + maybePrint(result.source, extra.verbose); + const interpolationConfig = extra.inputArgs && extra.inputArgs.interpolation ? + InterpolationConfig.fromArray(extra.inputArgs.interpolation) : + undefined; + expect(verifyTranslationIds(input, result.source, extra.exceptions, interpolationConfig)) + .toBe(true); + expect(verifyPlaceholdersIntegrity(result.source)).toBe(true); + expectEmit(result.source, output, 'Incorrect template'); + } }; describe('i18n support in the view compiler', () => { @@ -589,6 +591,27 @@ describe('i18n support in the view compiler', () => { verify(input, output); }); + + it('should sanitize ids and generate proper const names', () => { + const input = ` +
+ Some content +
+ `; + + const output = String.raw ` + const MSG_EXTERNAL_ID_WITH_INVALID_CHARS$$APP_SPEC_TS_0 = goog.getMsg("Element title"); + const $_c1$ = ["title", MSG_EXTERNAL_ID_WITH_INVALID_CHARS$$APP_SPEC_TS_0]; + const MSG_EXTERNAL_ID_WITH_INVALID_CHARS_2$$APP_SPEC_TS_2 = goog.getMsg(" Some content "); + … + `; + + const exceptions = { + 'ID.WITH.INVALID.CHARS': 'Verify const name generation only', + 'ID.WITH.INVALID.CHARS.2': 'Verify const name generation only' + }; + verify(input, output, {exceptions, skipPathBasedCheck: true}); + }); }); describe('nested nodes', () => { diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 2e399f6916..71876720da 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -362,7 +362,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver if (this.i18nUseExternalIds) { const prefix = getTranslationConstPrefix(`EXTERNAL_`); const uniqueSuffix = this.constantPool.uniqueName(suffix); - name = `${prefix}${messageId}$$${uniqueSuffix}`; + name = `${prefix}${sanitizeIdentifier(messageId)}$$${uniqueSuffix}`; } else { const prefix = getTranslationConstPrefix(suffix); name = this.constantPool.uniqueName(prefix);