refactor(compiler): use a named type for cooked/raw string objects (#38775)

Using an interface makes the code cleaner and more readable.
This change also adds the `range` property to the type to be used
for source-mapping.

PR Close #38775
This commit is contained in:
Pete Bacon Darwin 2020-09-21 12:45:19 +01:00 committed by Misko Hevery
parent b0a43872a8
commit 6158dc16b4
2 changed files with 74 additions and 28 deletions

View File

@ -545,7 +545,7 @@ export class LocalizedString extends Expression {
* @param meta The metadata to serialize * @param meta The metadata to serialize
* @param messagePart The first part of the tagged string * @param messagePart The first part of the tagged string
*/ */
serializeI18nHead(): {cooked: string, raw: string} { serializeI18nHead(): CookedRawString {
const MEANING_SEPARATOR = '|'; const MEANING_SEPARATOR = '|';
const ID_SEPARATOR = '@@'; const ID_SEPARATOR = '@@';
const LEGACY_ID_INDICATOR = '␟'; const LEGACY_ID_INDICATOR = '␟';
@ -562,7 +562,8 @@ export class LocalizedString extends Expression {
metaBlock = `${metaBlock}${LEGACY_ID_INDICATOR}${legacyId}`; 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 { getMessagePartSourceSpan(i: number): ParseSourceSpan|null {
@ -581,13 +582,24 @@ export class LocalizedString extends Expression {
* @param placeholderName The placeholder name to serialize * @param placeholderName The placeholder name to serialize
* @param messagePart The following message string after this placeholder * @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 placeholderName = this.placeHolderNames[partIndex - 1].text;
const messagePart = this.messageParts[partIndex]; 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 escapeSlashes = (str: string): string => str.replace(/\\/g, '\\\\');
const escapeStartingColon = (str: string): string => str.replace(/^:/, '\\:'); const escapeStartingColon = (str: string): string => str.replace(/^:/, '\\:');
const escapeColons = (str: string): string => str.replace(/:/g, '\\:'); 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 metaBlock Any metadata that should be prepended to the string
* @param messagePart The message part of 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 === '') { if (metaBlock === '') {
return { return {
cooked: messagePart, cooked: messagePart,
raw: escapeForMessagePart(escapeStartingColon(escapeSlashes(messagePart))) raw: escapeForMessagePart(escapeStartingColon(escapeSlashes(messagePart))),
range,
}; };
} else { } else {
return { return {
cooked: `:${metaBlock}:${messagePart}`, cooked: `:${metaBlock}:${messagePart}`,
raw: escapeForMessagePart( raw: escapeForMessagePart(
`:${escapeColons(escapeSlashes(metaBlock))}:${escapeSlashes(messagePart)}`) `:${escapeColons(escapeSlashes(metaBlock))}:${escapeSlashes(messagePart)}`),
range,
}; };
} }
} }

View File

@ -223,76 +223,107 @@ describe('Utils', () => {
it('serializeI18nHead()', () => { it('serializeI18nHead()', () => {
expect(o.localizedString(meta(), [literal('')], [], []).serializeI18nHead()) expect(o.localizedString(meta(), [literal('')], [], []).serializeI18nHead())
.toEqual({cooked: '', raw: ''}); .toEqual({cooked: '', raw: '', range: jasmine.any(ParseSourceSpan)});
expect(o.localizedString(meta('', '', 'desc'), [literal('')], [], []).serializeI18nHead()) 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()) 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( expect(
o.localizedString(meta('', 'meaning', 'desc'), [literal('')], [], []).serializeI18nHead()) 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('')], [], []) expect(o.localizedString(meta('id', 'meaning', 'desc'), [literal('')], [], [])
.serializeI18nHead()) .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()) 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) // Escaping colons (block markers)
expect(o.localizedString(meta('id:sub_id', 'meaning', 'desc'), [literal('')], [], []) expect(o.localizedString(meta('id:sub_id', 'meaning', 'desc'), [literal('')], [], [])
.serializeI18nHead()) .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('')], [], []) expect(o.localizedString(meta('id', 'meaning:sub_meaning', 'desc'), [literal('')], [], [])
.serializeI18nHead()) .serializeI18nHead())
.toEqual( .toEqual({
{cooked: ':meaning:sub_meaning|desc@@id:', raw: ':meaning\\:sub_meaning|desc@@id:'}); 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('')], [], []) expect(o.localizedString(meta('id', 'meaning', 'desc:sub_desc'), [literal('')], [], [])
.serializeI18nHead()) .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')], [], []) expect(o.localizedString(meta('id', 'meaning', 'desc'), [literal('message source')], [], [])
.serializeI18nHead()) .serializeI18nHead())
.toEqual({ .toEqual({
cooked: ':meaning|desc@@id:message source', 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')], [], []) expect(o.localizedString(meta('id', 'meaning', 'desc'), [literal(':message source')], [], [])
.serializeI18nHead()) .serializeI18nHead())
.toEqual({ .toEqual({
cooked: ':meaning|desc@@id::message source', 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')], [], []) expect(o.localizedString(meta('', '', ''), [literal('message source')], [], [])
.serializeI18nHead()) .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')], [], []) expect(o.localizedString(meta('', '', ''), [literal(':message source')], [], [])
.serializeI18nHead()) .serializeI18nHead())
.toEqual({cooked: ':message source', raw: '\\:message source'}); .toEqual({
cooked: ':message source',
raw: '\\:message source',
range: jasmine.any(ParseSourceSpan)
});
}); });
it('serializeI18nPlaceholderBlock()', () => { it('serializeI18nPlaceholderBlock()', () => {
expect(o.localizedString(meta('', '', ''), [literal(''), literal('')], [literal('')], []) expect(o.localizedString(meta('', '', ''), [literal(''), literal('')], [literal('')], [])
.serializeI18nTemplatePart(1)) .serializeI18nTemplatePart(1))
.toEqual({cooked: '', raw: ''}); .toEqual({cooked: '', raw: '', range: jasmine.any(ParseSourceSpan)});
expect(o.localizedString( expect(o.localizedString(
meta('', '', ''), [literal(''), literal('')], meta('', '', ''), [literal(''), literal('')],
[new o.LiteralPiece('abc', {} as any)], []) [new o.LiteralPiece('abc', {} as any)], [])
.serializeI18nTemplatePart(1)) .serializeI18nTemplatePart(1))
.toEqual({cooked: ':abc:', raw: ':abc:'}); .toEqual({cooked: ':abc:', raw: ':abc:', range: jasmine.any(ParseSourceSpan)});
expect( expect(
o.localizedString(meta('', '', ''), [literal(''), literal('message')], [literal('')], []) o.localizedString(meta('', '', ''), [literal(''), literal('message')], [literal('')], [])
.serializeI18nTemplatePart(1)) .serializeI18nTemplatePart(1))
.toEqual({cooked: 'message', raw: 'message'}); .toEqual({cooked: 'message', raw: 'message', range: jasmine.any(ParseSourceSpan)});
expect(o.localizedString( expect(o.localizedString(
meta('', '', ''), [literal(''), literal('message')], [literal('abc')], []) meta('', '', ''), [literal(''), literal('message')], [literal('abc')], [])
.serializeI18nTemplatePart(1)) .serializeI18nTemplatePart(1))
.toEqual({cooked: ':abc:message', raw: ':abc:message'}); .toEqual(
{cooked: ':abc:message', raw: ':abc:message', range: jasmine.any(ParseSourceSpan)});
expect( expect(
o.localizedString(meta('', '', ''), [literal(''), literal(':message')], [literal('')], []) o.localizedString(meta('', '', ''), [literal(''), literal(':message')], [literal('')], [])
.serializeI18nTemplatePart(1)) .serializeI18nTemplatePart(1))
.toEqual({cooked: ':message', raw: '\\:message'}); .toEqual({cooked: ':message', raw: '\\:message', range: jasmine.any(ParseSourceSpan)});
expect(o.localizedString( expect(o.localizedString(
meta('', '', ''), [literal(''), literal(':message')], [literal('abc')], []) meta('', '', ''), [literal(''), literal(':message')], [literal('abc')], [])
.serializeI18nTemplatePart(1)) .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 { 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 { function placeholder(name: string, span: any = jasmine.any(ParseSourceSpan)): o.PlaceholderPiece {
return new o.PlaceholderPiece(name, span); return new o.PlaceholderPiece(name, span);
} }