diff --git a/packages/compiler/src/output/output_ast.ts b/packages/compiler/src/output/output_ast.ts index c7332111c2..d74bff4389 100644 --- a/packages/compiler/src/output/output_ast.ts +++ b/packages/compiler/src/output/output_ast.ts @@ -545,7 +545,7 @@ export class LocalizedString extends Expression { * @param meta The metadata to serialize * @param messagePart The first part of the tagged string */ - serializeI18nHead(): {cooked: string, raw: string} { + serializeI18nHead(): CookedRawString { const MEANING_SEPARATOR = '|'; const ID_SEPARATOR = '@@'; const LEGACY_ID_INDICATOR = '␟'; @@ -562,7 +562,8 @@ export class LocalizedString extends Expression { metaBlock = `${metaBlock}${LEGACY_ID_INDICATOR}${legacyId}`; }); } - return createCookedRawString(metaBlock, this.messageParts[0].text); + return createCookedRawString( + metaBlock, this.messageParts[0].text, this.getMessagePartSourceSpan(0)); } getMessagePartSourceSpan(i: number): ParseSourceSpan|null { @@ -581,13 +582,24 @@ export class LocalizedString extends Expression { * @param placeholderName The placeholder name to serialize * @param messagePart The following message string after this placeholder */ - serializeI18nTemplatePart(partIndex: number): {cooked: string, raw: string} { + serializeI18nTemplatePart(partIndex: number): CookedRawString { const placeholderName = this.placeHolderNames[partIndex - 1].text; const messagePart = this.messageParts[partIndex]; - return createCookedRawString(placeholderName, messagePart.text); + return createCookedRawString( + placeholderName, messagePart.text, this.getMessagePartSourceSpan(partIndex)); } } +/** + * A structure to hold the cooked and raw strings of a template literal element, along with its + * source-span range. + */ +export interface CookedRawString { + cooked: string; + raw: string; + range: ParseSourceSpan|null; +} + const escapeSlashes = (str: string): string => str.replace(/\\/g, '\\\\'); const escapeStartingColon = (str: string): string => str.replace(/^:/, '\\:'); const escapeColons = (str: string): string => str.replace(/:/g, '\\:'); @@ -608,17 +620,20 @@ const escapeForMessagePart = (str: string): string => * @param metaBlock Any metadata that should be prepended to the string * @param messagePart The message part of the string */ -function createCookedRawString(metaBlock: string, messagePart: string) { +function createCookedRawString( + metaBlock: string, messagePart: string, range: ParseSourceSpan|null): CookedRawString { if (metaBlock === '') { return { cooked: messagePart, - raw: escapeForMessagePart(escapeStartingColon(escapeSlashes(messagePart))) + raw: escapeForMessagePart(escapeStartingColon(escapeSlashes(messagePart))), + range, }; } else { return { cooked: `:${metaBlock}:${messagePart}`, raw: escapeForMessagePart( - `:${escapeColons(escapeSlashes(metaBlock))}:${escapeSlashes(messagePart)}`) + `:${escapeColons(escapeSlashes(metaBlock))}:${escapeSlashes(messagePart)}`), + range, }; } } diff --git a/packages/compiler/test/render3/view/i18n_spec.ts b/packages/compiler/test/render3/view/i18n_spec.ts index 50d534c64e..2ed1dba0f7 100644 --- a/packages/compiler/test/render3/view/i18n_spec.ts +++ b/packages/compiler/test/render3/view/i18n_spec.ts @@ -223,76 +223,107 @@ describe('Utils', () => { it('serializeI18nHead()', () => { expect(o.localizedString(meta(), [literal('')], [], []).serializeI18nHead()) - .toEqual({cooked: '', raw: ''}); + .toEqual({cooked: '', raw: '', range: jasmine.any(ParseSourceSpan)}); expect(o.localizedString(meta('', '', 'desc'), [literal('')], [], []).serializeI18nHead()) - .toEqual({cooked: ':desc:', raw: ':desc:'}); + .toEqual({cooked: ':desc:', raw: ':desc:', range: jasmine.any(ParseSourceSpan)}); expect(o.localizedString(meta('id', '', 'desc'), [literal('')], [], []).serializeI18nHead()) - .toEqual({cooked: ':desc@@id:', raw: ':desc@@id:'}); + .toEqual({cooked: ':desc@@id:', raw: ':desc@@id:', range: jasmine.any(ParseSourceSpan)}); expect( o.localizedString(meta('', 'meaning', 'desc'), [literal('')], [], []).serializeI18nHead()) - .toEqual({cooked: ':meaning|desc:', raw: ':meaning|desc:'}); + .toEqual({ + cooked: ':meaning|desc:', + raw: ':meaning|desc:', + range: jasmine.any(ParseSourceSpan) + }); expect(o.localizedString(meta('id', 'meaning', 'desc'), [literal('')], [], []) .serializeI18nHead()) - .toEqual({cooked: ':meaning|desc@@id:', raw: ':meaning|desc@@id:'}); + .toEqual({ + cooked: ':meaning|desc@@id:', + raw: ':meaning|desc@@id:', + range: jasmine.any(ParseSourceSpan) + }); expect(o.localizedString(meta('id', '', ''), [literal('')], [], []).serializeI18nHead()) - .toEqual({cooked: ':@@id:', raw: ':@@id:'}); + .toEqual({cooked: ':@@id:', raw: ':@@id:', range: jasmine.any(ParseSourceSpan)}); // Escaping colons (block markers) expect(o.localizedString(meta('id:sub_id', 'meaning', 'desc'), [literal('')], [], []) .serializeI18nHead()) - .toEqual({cooked: ':meaning|desc@@id:sub_id:', raw: ':meaning|desc@@id\\:sub_id:'}); + .toEqual({ + cooked: ':meaning|desc@@id:sub_id:', + raw: ':meaning|desc@@id\\:sub_id:', + range: jasmine.any(ParseSourceSpan) + }); expect(o.localizedString(meta('id', 'meaning:sub_meaning', 'desc'), [literal('')], [], []) .serializeI18nHead()) - .toEqual( - {cooked: ':meaning:sub_meaning|desc@@id:', raw: ':meaning\\:sub_meaning|desc@@id:'}); + .toEqual({ + cooked: ':meaning:sub_meaning|desc@@id:', + raw: ':meaning\\:sub_meaning|desc@@id:', + range: jasmine.any(ParseSourceSpan) + }); expect(o.localizedString(meta('id', 'meaning', 'desc:sub_desc'), [literal('')], [], []) .serializeI18nHead()) - .toEqual({cooked: ':meaning|desc:sub_desc@@id:', raw: ':meaning|desc\\:sub_desc@@id:'}); + .toEqual({ + cooked: ':meaning|desc:sub_desc@@id:', + raw: ':meaning|desc\\:sub_desc@@id:', + range: jasmine.any(ParseSourceSpan) + }); expect(o.localizedString(meta('id', 'meaning', 'desc'), [literal('message source')], [], []) .serializeI18nHead()) .toEqual({ cooked: ':meaning|desc@@id:message source', - raw: ':meaning|desc@@id:message source' + raw: ':meaning|desc@@id:message source', + range: jasmine.any(ParseSourceSpan) }); expect(o.localizedString(meta('id', 'meaning', 'desc'), [literal(':message source')], [], []) .serializeI18nHead()) .toEqual({ cooked: ':meaning|desc@@id::message source', - raw: ':meaning|desc@@id::message source' + raw: ':meaning|desc@@id::message source', + range: jasmine.any(ParseSourceSpan) }); expect(o.localizedString(meta('', '', ''), [literal('message source')], [], []) .serializeI18nHead()) - .toEqual({cooked: 'message source', raw: 'message source'}); + .toEqual({ + cooked: 'message source', + raw: 'message source', + range: jasmine.any(ParseSourceSpan) + }); expect(o.localizedString(meta('', '', ''), [literal(':message source')], [], []) .serializeI18nHead()) - .toEqual({cooked: ':message source', raw: '\\:message source'}); + .toEqual({ + cooked: ':message source', + raw: '\\:message source', + range: jasmine.any(ParseSourceSpan) + }); }); it('serializeI18nPlaceholderBlock()', () => { expect(o.localizedString(meta('', '', ''), [literal(''), literal('')], [literal('')], []) .serializeI18nTemplatePart(1)) - .toEqual({cooked: '', raw: ''}); + .toEqual({cooked: '', raw: '', range: jasmine.any(ParseSourceSpan)}); expect(o.localizedString( meta('', '', ''), [literal(''), literal('')], [new o.LiteralPiece('abc', {} as any)], []) .serializeI18nTemplatePart(1)) - .toEqual({cooked: ':abc:', raw: ':abc:'}); + .toEqual({cooked: ':abc:', raw: ':abc:', range: jasmine.any(ParseSourceSpan)}); expect( o.localizedString(meta('', '', ''), [literal(''), literal('message')], [literal('')], []) .serializeI18nTemplatePart(1)) - .toEqual({cooked: 'message', raw: 'message'}); + .toEqual({cooked: 'message', raw: 'message', range: jasmine.any(ParseSourceSpan)}); expect(o.localizedString( meta('', '', ''), [literal(''), literal('message')], [literal('abc')], []) .serializeI18nTemplatePart(1)) - .toEqual({cooked: ':abc:message', raw: ':abc:message'}); + .toEqual( + {cooked: ':abc:message', raw: ':abc:message', range: jasmine.any(ParseSourceSpan)}); expect( o.localizedString(meta('', '', ''), [literal(''), literal(':message')], [literal('')], []) .serializeI18nTemplatePart(1)) - .toEqual({cooked: ':message', raw: '\\:message'}); + .toEqual({cooked: ':message', raw: '\\:message', range: jasmine.any(ParseSourceSpan)}); expect(o.localizedString( meta('', '', ''), [literal(''), literal(':message')], [literal('abc')], []) .serializeI18nTemplatePart(1)) - .toEqual({cooked: ':abc::message', raw: ':abc::message'}); + .toEqual( + {cooked: ':abc::message', raw: ':abc::message', range: jasmine.any(ParseSourceSpan)}); }); function meta(customId?: string, meaning?: string, description?: string): I18nMeta { @@ -563,4 +594,4 @@ function literal(text: string, span: any = jasmine.any(ParseSourceSpan)): o.Lite function placeholder(name: string, span: any = jasmine.any(ParseSourceSpan)): o.PlaceholderPiece { return new o.PlaceholderPiece(name, span); -} +} \ No newline at end of file