fix(compiler-cli): compute source-mappings for localized strings (#38747)

Previously, localized strings had very limited or incorrect source-mapping
information available.

Now the i18n AST nodes and related output AST nodes include source-span
information about message-parts and placeholders - including closing tag
placeholders.

This information is then used when generating the final localized string
ASTs to ensure that the correct source-mapping is rendered.

See #38588 (comment)

PR Close #38747
This commit is contained in:
Pete Bacon Darwin
2020-08-31 16:27:44 +01:00
committed by atscott
parent 6b0dba48b1
commit b4eb016e56
7 changed files with 217 additions and 113 deletions

View File

@ -24,7 +24,7 @@ runInEachFileSystem((os) => {
beforeEach(() => {
env = NgtscTestEnvironment.setup(testFiles);
env.tsconfig();
env.tsconfig({sourceMap: true, target: 'es2015', enableI18nLegacyMessageIdFormat: false});
});
describe('Inline templates', () => {
@ -360,6 +360,90 @@ runInEachFileSystem((os) => {
});
});
describe('$localize', () => {
it('should create simple i18n message source-mapping', () => {
const mappings = compileAndMap(`<div i18n>Hello, World!</div>`);
expect(mappings).toContain({
source: '<div i18n>',
generated: 'i0.ɵɵelementStart(0, "div")',
sourceUrl: '../test.ts',
});
expect(mappings).toContain({
source: 'Hello, World!',
generated: '`Hello, World!`',
sourceUrl: '../test.ts',
});
});
it('should create placeholder source-mappings', () => {
const mappings = compileAndMap(`<div i18n>Hello, {{name}}!</div>`);
expect(mappings).toContain({
source: '<div i18n>',
generated: 'i0.ɵɵelementStart(0, "div")',
sourceUrl: '../test.ts',
});
expect(mappings).toContain({
source: '</div>',
generated: 'i0.ɵɵelementEnd()',
sourceUrl: '../test.ts',
});
expect(mappings).toContain({
source: 'Hello, ',
generated: '`Hello, ${',
sourceUrl: '../test.ts',
});
expect(mappings).toContain({
source: '{{name}}',
generated: '"\\uFFFD0\\uFFFD"',
sourceUrl: '../test.ts',
});
expect(mappings).toContain({
source: '!',
generated: '}:INTERPOLATION:!`',
sourceUrl: '../test.ts',
});
});
it('should create tag (container) placeholder source-mappings', () => {
const mappings = compileAndMap(`<div i18n>Hello, <b>World</b>!</div>`);
expect(mappings).toContain({
source: '<div i18n>',
generated: 'i0.ɵɵelementStart(0, "div")',
sourceUrl: '../test.ts',
});
expect(mappings).toContain({
source: '</div>',
generated: 'i0.ɵɵelementEnd()',
sourceUrl: '../test.ts',
});
expect(mappings).toContain({
source: 'Hello, ',
generated: '`Hello, ${',
sourceUrl: '../test.ts',
});
expect(mappings).toContain({
source: '<b>',
generated: '"\\uFFFD#2\\uFFFD"',
sourceUrl: '../test.ts',
});
expect(mappings).toContain({
source: 'World',
generated: '}:START_BOLD_TEXT:World${',
sourceUrl: '../test.ts',
});
expect(mappings).toContain({
source: '</b>',
generated: '"\\uFFFD/#2\\uFFFD"',
sourceUrl: '../test.ts',
});
expect(mappings).toContain({
source: '!',
generated: '}:CLOSE_BOLD_TEXT:!`',
sourceUrl: '../test.ts',
});
});
});
it('should create (simple string) inline template source-mapping', () => {
const mappings = compileAndMap('<div>this is a test</div><div>{{ 1 + 2 }}</div>');
@ -520,7 +604,6 @@ runInEachFileSystem((os) => {
function compileAndMap(template: string, templateUrl: string|null = null) {
const templateConfig = templateUrl ? `templateUrl: '${templateUrl}'` :
('template: `' + template.replace(/`/g, '\\`') + '`');
env.tsconfig({sourceMap: true});
env.write('test.ts', `
import {Component} from '@angular/core';