fix(core): fix used templates are not deleted (#60459)

After running the control flow migration, used templates are not deleted.

PR Close #60459
This commit is contained in:
aparzi
2025-03-24 01:26:36 +01:00
committed by Alex Rickabaugh
parent d95d352a8e
commit cf5c61c849
2 changed files with 73 additions and 12 deletions
@@ -385,10 +385,6 @@ export function calculateNesting(
}
}
function escapeRegExp(val: string) {
return val.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
/**
* determines if a given template string contains line breaks
*/
@@ -425,20 +421,48 @@ export function getTemplates(template: string): Map<string, Template> {
const visitor = new TemplateCollector();
visitAll(visitor, parsed.tree.rootNodes);
// count usages of each ng-template
for (let [key, tmpl] of visitor.templates) {
const escapeKey = escapeRegExp(key.slice(1));
const regex = new RegExp(`[^a-zA-Z0-9-<(\']${escapeKey}\\W`, 'gm');
const matches = template.match(regex);
tmpl.count = matches?.length ?? 0;
tmpl.count = countTemplateUsage(parsed.tree.rootNodes, key);
tmpl.generateContents(template);
}
return visitor.templates;
}
return new Map<string, Template>();
}
function countTemplateUsage(nodes: any[], templateName: string): number {
let count = 0;
let isReferencedInTemplateOutlet = false;
for (const node of nodes) {
if (node.attrs) {
for (const attr of node.attrs) {
if (attr.name === '*ngTemplateOutlet' && attr.value === templateName.slice(1)) {
isReferencedInTemplateOutlet = true;
break;
}
if (attr.name.trim() === templateName) {
count++;
}
}
}
if (node.children) {
if (node.name === 'for') {
for (const child of node.children) {
if (child.value?.includes(templateName.slice(1))) {
count++;
}
}
}
count += countTemplateUsage(node.children, templateName);
}
}
return isReferencedInTemplateOutlet ? count + 2 : count;
}
export function updateTemplates(
template: string,
templates: Map<string, Template>,
@@ -497,8 +521,12 @@ export function processNgTemplates(
} else {
template = template.replace(replaceRegex, t.children);
}
// the +1 accounts for the t.count's counting of the original template
if (t.count === matches.length + 1 && safeToRemove) {
const dist = matches.filter(
(obj, index, self) => index === self.findIndex((t) => t.input === obj.input),
);
if ((t.count === dist.length || t.count - matches.length === 1) && safeToRemove) {
const refsInComponentFile = getViewChildOrViewChildrenNames(sourceFile);
if (refsInComponentFile?.length > 0) {
const templateRefs = getTemplateReferences(template);
@@ -6932,5 +6932,38 @@ describe('control flow migration', () => {
const content = tree.readContent('/comp.ts');
expect(content).not.toContain('<ng-template #contentTemplate>');
});
it('should remove ng-template reference when use in if-else block', async () => {
writeFile(
'/comp.ts',
`
import {Component} from '@angular/core';
@Component({
templateUrl: './comp.html'
})
class Comp {
}
`,
);
writeFile(
'/comp.html',
[
`<div>`,
`<div *ngIf="param; else loading">`,
`<div>content</div>`,
`</div>`,
`<ng-template #loading>`,
`<div>loading</div>`,
`</ng-template>`,
`</div>`,
].join('\n'),
);
await runMigration();
const content = tree.readContent('/comp.html');
expect(content).not.toContain('<ng-template #loading>');
});
});
});