Skip to content

Commit 5104a89

Browse files
thePunderWomandylhunn
authored andcommitted
fix(migrations): handle nested ng-template replacement safely in CF migration (#53368)
When there are ng-templates nested inside other ng-templates, the replacement and removal of the templates gets disrupted. Re-processing the templates in the file along the way resolves this issue. fixes: #53362 PR Close #53368
1 parent 708131d commit 5104a89

File tree

2 files changed

+55
-1
lines changed

2 files changed

+55
-1
lines changed

packages/core/schematics/ng-generate/control-flow-migration/util.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ export function getTemplates(template: string): Map<string, Template> {
284284
// count usages of each ng-template
285285
for (let [key, tmpl] of visitor.templates) {
286286
const escapeKey = escapeRegExp(key.slice(1));
287-
const regex = new RegExp(`[^a-zA-Z0-9-<\']${escapeKey}\\W`, 'gm');
287+
const regex = new RegExp(`[^a-zA-Z0-9-<(\']${escapeKey}\\W`, 'gm');
288288
const matches = template.match(regex);
289289
tmpl.count = matches?.length ?? 0;
290290
tmpl.generateContents(template);
@@ -295,6 +295,15 @@ export function getTemplates(template: string): Map<string, Template> {
295295
return new Map<string, Template>();
296296
}
297297

298+
export function updateTemplates(
299+
template: string, templates: Map<string, Template>): Map<string, Template> {
300+
const updatedTemplates = getTemplates(template);
301+
for (let [key, tmpl] of updatedTemplates) {
302+
templates.set(key, tmpl);
303+
}
304+
return templates;
305+
}
306+
298307
function wrapIntoI18nContainer(i18nAttr: Attribute, content: string) {
299308
const {start, middle, end} = generatei18nContainer(i18nAttr, content);
300309
return `${start}${middle}${end}`;
@@ -341,6 +350,9 @@ export function processNgTemplates(template: string): {migrated: string, err: Er
341350
if (t.count === matches.length + 1 && safeToRemove) {
342351
template = template.replace(t.contents, '');
343352
}
353+
// templates may have changed structure from nested replaced templates
354+
// so we need to reprocess them before the next loop.
355+
updateTemplates(template, templates);
344356
}
345357
}
346358
return {migrated: template, err: undefined};

packages/core/schematics/test/control_flow_migration_spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3970,6 +3970,48 @@ describe('control flow migration', () => {
39703970
`}`,
39713971
].join('\n'));
39723972
});
3973+
3974+
it('should migrate nested template usage correctly', async () => {
3975+
writeFile('/comp.ts', `
3976+
import {Component} from '@angular/core';
3977+
import {NgIf} from '@angular/common';
3978+
3979+
@Component({
3980+
templateUrl: './comp.html'
3981+
})
3982+
class Comp {
3983+
show = false;
3984+
}
3985+
`);
3986+
3987+
writeFile('/comp.html', [
3988+
`<ng-container *ngIf="!(condition$ | async); else template">`,
3989+
` Hello!`,
3990+
`</ng-container>`,
3991+
`<ng-template #bar>Bar</ng-template>`,
3992+
`<ng-template #foo>Foo</ng-template>`,
3993+
`<ng-template #template>`,
3994+
` <ng-container`,
3995+
` *ngIf="(foo$ | async) === true; then foo; else bar"`,
3996+
` ></ng-container>`,
3997+
`</ng-template>`,
3998+
].join('\n'));
3999+
4000+
await runMigration();
4001+
const content = tree.readContent('/comp.html');
4002+
4003+
expect(content).toBe([
4004+
`@if (!(condition$ | async)) {`,
4005+
` Hello!`,
4006+
`} @else {`,
4007+
` @if ((foo$ | async) === true) {`,
4008+
` Foo`,
4009+
` } @else {`,
4010+
` Bar`,
4011+
` }`,
4012+
`}\n`,
4013+
].join('\n'));
4014+
});
39734015
});
39744016

39754017
describe('formatting', () => {

0 commit comments

Comments
 (0)