@@ -33,6 +33,12 @@ const endMarkerRegex = new RegExp(endMarker, 'gm');
3333const startI18nMarkerRegex = new RegExp ( startI18nMarker , 'gm' ) ;
3434const endI18nMarkerRegex = new RegExp ( endI18nMarker , 'gm' ) ;
3535const replaceMarkerRegex = new RegExp ( `${ startMarker } |${ endMarker } ` , 'gm' ) ;
36+ const PRIORITY_WEIGHT_TEMPLATE_REFERENCE_BY_OUTLET = 2 ;
37+
38+ interface TemplateUsageResult {
39+ isReferencedInTemplateOutlet : boolean ;
40+ totalCount : number ;
41+ }
3642
3743/**
3844 * Analyzes a source file to find file that need to be migrated and the text ranges within them.
@@ -430,39 +436,6 @@ export function getTemplates(template: string): Map<string, Template> {
430436 return new Map < string , Template > ( ) ;
431437}
432438
433- function countTemplateUsage ( nodes : any [ ] , templateName : string ) : number {
434- let count = 0 ;
435- let isReferencedInTemplateOutlet = false ;
436-
437- for ( const node of nodes ) {
438- if ( node . attrs ) {
439- for ( const attr of node . attrs ) {
440- if ( attr . name === '*ngTemplateOutlet' && attr . value === templateName . slice ( 1 ) ) {
441- isReferencedInTemplateOutlet = true ;
442- break ;
443- }
444-
445- if ( attr . name . trim ( ) === templateName ) {
446- count ++ ;
447- }
448- }
449- }
450-
451- if ( node . children ) {
452- if ( node . name === 'for' ) {
453- for ( const child of node . children ) {
454- if ( child . value ?. includes ( templateName . slice ( 1 ) ) ) {
455- count ++ ;
456- }
457- }
458- }
459- count += countTemplateUsage ( node . children , templateName ) ;
460- }
461- }
462-
463- return isReferencedInTemplateOutlet ? count + 2 : count ;
464- }
465-
466439export function updateTemplates (
467440 template : string ,
468441 templates : Map < string , Template > ,
@@ -499,9 +472,13 @@ export function processNgTemplates(
499472 const templates = getTemplates ( template ) ;
500473
501474 // swap placeholders and remove
502- for ( const [ name , t ] of templates ) {
503- const replaceRegex = new RegExp ( getPlaceholder ( name . slice ( 1 ) ) , 'g' ) ;
504- const forRegex = new RegExp ( getPlaceholder ( name . slice ( 1 ) , PlaceholderKind . Alternate ) , 'g' ) ;
475+ for ( const [ nameWithHash , t ] of templates ) {
476+ const name = nameWithHash . slice ( 1 ) ;
477+ const replaceRegex = new RegExp ( getPlaceholder ( name ) , 'g' ) ;
478+ const forRegex = new RegExp (
479+ getPlaceholder ( nameWithHash . slice ( 1 ) , PlaceholderKind . Alternate ) ,
480+ 'g' ,
481+ ) ;
505482 const forMatches = [ ...template . matchAll ( forRegex ) ] ;
506483 const matches = [ ...forMatches , ...template . matchAll ( replaceRegex ) ] ;
507484 let safeToRemove = true ;
@@ -526,7 +503,15 @@ export function processNgTemplates(
526503 ( obj , index , self ) => index === self . findIndex ( ( t ) => t . input === obj . input ) ,
527504 ) ;
528505
529- if ( ( t . count === dist . length || t . count - matches . length === 1 ) && safeToRemove ) {
506+ // Check if template is used by ngTemplateOutlet in addition to control flow
507+ const hasTemplateOutletUsage = checkForTemplateOutletUsage ( template , nameWithHash . slice ( 1 ) ) ;
508+
509+ // Only remove template if it's safe to do so AND not used by ngTemplateOutlet
510+ if (
511+ ( t . count === dist . length || t . count - matches . length === 1 ) &&
512+ safeToRemove &&
513+ ! hasTemplateOutletUsage
514+ ) {
530515 const refsInComponentFile = getViewChildOrViewChildrenNames ( sourceFile ) ;
531516 if ( refsInComponentFile ?. length > 0 ) {
532517 const templateRefs = getTemplateReferences ( template ) ;
@@ -555,6 +540,71 @@ export function processNgTemplates(
555540 }
556541}
557542
543+ function analyzeTemplateUsage ( nodes : any [ ] , templateName : string ) : TemplateUsageResult {
544+ let count = 0 ;
545+ let isReferencedInTemplateOutlet = false ;
546+ const templateNameWithHash = `#${ templateName } ` ;
547+
548+ function traverseNodes ( nodeList : any [ ] ) : void {
549+ for ( const node of nodeList ) {
550+ if ( node . attrs ) {
551+ for ( const attr of node . attrs ) {
552+ if (
553+ ( attr . name === '*ngTemplateOutlet' || attr . name === '[ngTemplateOutlet]' ) &&
554+ attr . value === templateName
555+ ) {
556+ isReferencedInTemplateOutlet = true ;
557+ }
558+
559+ if ( attr . name . trim ( ) === templateNameWithHash ) {
560+ count ++ ;
561+ }
562+ }
563+ }
564+
565+ if ( node . children ) {
566+ if ( node . name === 'for' ) {
567+ for ( const child of node . children ) {
568+ if ( child . value ?. includes ( templateName ) ) {
569+ count ++ ;
570+ }
571+ }
572+ }
573+
574+ traverseNodes ( node . children ) ;
575+ }
576+ }
577+ }
578+
579+ traverseNodes ( nodes ) ;
580+
581+ return {
582+ isReferencedInTemplateOutlet,
583+ totalCount : isReferencedInTemplateOutlet
584+ ? count + PRIORITY_WEIGHT_TEMPLATE_REFERENCE_BY_OUTLET
585+ : count ,
586+ } ;
587+ }
588+
589+ /**
590+ * Checks if a template is used by ngTemplateOutlet directive
591+ */
592+ function checkForTemplateOutletUsage ( template : string , templateName : string ) : boolean {
593+ const parsed = parseTemplate ( template ) ;
594+ if ( parsed . tree === undefined ) {
595+ return false ;
596+ }
597+
598+ const result = analyzeTemplateUsage ( parsed . tree . rootNodes , templateName ) ;
599+ return result . isReferencedInTemplateOutlet ;
600+ }
601+
602+ function countTemplateUsage ( nodes : any [ ] , templateNameWithHash : string ) : number {
603+ const templateName = templateNameWithHash . slice ( 1 ) ;
604+ const result = analyzeTemplateUsage ( nodes , templateName ) ;
605+ return result . totalCount ;
606+ }
607+
558608function getViewChildOrViewChildrenNames ( sourceFile : ts . SourceFile ) : Array < string > {
559609 const names : Array < string > = [ ] ;
560610
@@ -585,12 +635,12 @@ function getTemplateReferences(template: string): string[] {
585635 return [ ] ;
586636 }
587637
588- const references : string [ ] = [ ] ;
638+ const templateNameRefWithoutHash : string [ ] = [ ] ;
589639
590640 function visitNodes ( nodes : any ) {
591641 for ( const node of nodes ) {
592642 if ( node ?. name === 'ng-template' ) {
593- references . push ( ...node . attrs ?. map ( ( ref : any ) => ref ?. name ?. slice ( 1 ) ) ) ;
643+ templateNameRefWithoutHash . push ( ...node . attrs ?. map ( ( ref : any ) => ref ?. name ?. slice ( 1 ) ) ) ;
594644 }
595645 if ( node . children ) {
596646 visitNodes ( node . children ) ;
@@ -599,7 +649,7 @@ function getTemplateReferences(template: string): string[] {
599649 }
600650
601651 visitNodes ( parsed . tree . rootNodes ) ;
602- return references ;
652+ return templateNameRefWithoutHash ;
603653}
604654
605655function replaceRemainingPlaceholders ( template : string ) : string {
0 commit comments