Skip to content

Commit 949dec2

Browse files
crisbetoatscott
authored andcommitted
fix(migrations): avoid conflicts with some greek letters in control flow migration (#55113)
The control flow migration was using a couple of Greek letters as placeholders. This ended up conflicting with templates authored in Greek. These changes use a more obscure placeholder to make conflicts less likely. It also moves the placeholder generation to a centralized function so it's easier to make changes if we decide to update the pattern again. Fixes #55085. PR Close #55113
1 parent fc03413 commit 949dec2

File tree

4 files changed

+57
-15
lines changed

4 files changed

+57
-15
lines changed

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import {visitAll} from '@angular/compiler';
1010

1111
import {ElementCollector, ElementToMigrate, endMarker, MigrateError, Result, startMarker} from './types';
12-
import {calculateNesting, getMainBlock, getOriginals, hasLineBreaks, parseTemplate, reduceNestingOffset} from './util';
12+
import {calculateNesting, getMainBlock, getOriginals, getPlaceholder, hasLineBreaks, parseTemplate, PlaceholderKind, reduceNestingOffset} from './util';
1313

1414
export const ngfor = '*ngFor';
1515
export const nakedngfor = 'ngFor';
@@ -112,9 +112,8 @@ function migrateStandardNgFor(etm: ElementToMigrate, tmpl: string, offset: numbe
112112
}
113113
// template
114114
if (part.startsWith('template:')) {
115-
// this generates a special template placeholder just for this use case
116-
// which has a φ at the end instead of the standard δ in other placeholders
117-
tmplPlaceholder = ${part.split(':')[1].trim()}φ`;
115+
// use an alternate placeholder here to avoid conflicts
116+
tmplPlaceholder = getPlaceholder(part.split(':')[1].trim(), PlaceholderKind.Alternate);
118117
}
119118
// aliases
120119
// declared with `let myIndex = index`

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import {visitAll} from '@angular/compiler';
1010

1111
import {ElementCollector, ElementToMigrate, endMarker, MigrateError, Result, startMarker} from './types';
12-
import {calculateNesting, getMainBlock, getOriginals, hasLineBreaks, parseTemplate, reduceNestingOffset} from './util';
12+
import {calculateNesting, getMainBlock, getOriginals, getPlaceholder, hasLineBreaks, parseTemplate, PlaceholderKind, reduceNestingOffset} from './util';
1313

1414
export const ngif = '*ngIf';
1515
export const boundngif = '[ngIf]';
@@ -133,7 +133,7 @@ function buildStandardIfElseBlock(
133133
.replace(' as ', '; as ')
134134
// replace 'let' with 'as' whatever spaces are between ; and 'let'
135135
.replace(/;\s*let/g, '; as');
136-
const elsePlaceholder = ${etm.getTemplateName(elseString)}δ`;
136+
const elsePlaceholder = getPlaceholder(etm.getTemplateName(elseString));
137137
return buildIfElseBlock(etm, tmpl, condition, elsePlaceholder, offset);
138138
}
139139

@@ -153,9 +153,9 @@ function buildBoundIfElseBlock(etm: ElementToMigrate, tmpl: string, offset: numb
153153
} else if (aliases.length === 1) {
154154
condition += `; as ${aliases[0]}`;
155155
}
156-
const elsePlaceholder = ${etm.elseAttr!.value.trim()}δ`;
156+
const elsePlaceholder = getPlaceholder(etm.elseAttr!.value.trim());
157157
if (etm.thenAttr !== undefined) {
158-
const thenPlaceholder = ${etm.thenAttr!.value.trim()}δ`;
158+
const thenPlaceholder = getPlaceholder(etm.thenAttr!.value.trim());
159159
return buildIfThenElseBlock(etm, tmpl, condition, thenPlaceholder, elsePlaceholder, offset);
160160
}
161161
return buildIfElseBlock(etm, tmpl, condition, elsePlaceholder, offset);
@@ -194,8 +194,8 @@ function buildStandardIfThenElseBlock(
194194
.replace(' as ', '; as ')
195195
// replace 'let' with 'as' whatever spaces are between ; and 'let'
196196
.replace(/;\s*let/g, '; as');
197-
const thenPlaceholder = ${etm.getTemplateName(thenString, elseString)}δ`;
198-
const elsePlaceholder = ${etm.getTemplateName(elseString)}δ`;
197+
const thenPlaceholder = getPlaceholder(etm.getTemplateName(thenString, elseString));
198+
const elsePlaceholder = getPlaceholder(etm.getTemplateName(elseString));
199199
return buildIfThenElseBlock(etm, tmpl, condition, thenPlaceholder, elsePlaceholder, offset);
200200
}
201201

@@ -206,7 +206,7 @@ function buildStandardIfThenBlock(
206206
.replace(' as ', '; as ')
207207
// replace 'let' with 'as' whatever spaces are between ; and 'let'
208208
.replace(/;\s*let/g, '; as');
209-
const thenPlaceholder = ${etm.getTemplateName(thenString)}δ`;
209+
const thenPlaceholder = getPlaceholder(etm.getTemplateName(thenString));
210210
return buildIfThenBlock(etm, tmpl, condition, thenPlaceholder, offset);
211211
}
212212

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

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,21 @@ function isChildOf(parent: Element, el: Element): boolean {
283283
parent.sourceSpan.end.offset > el.sourceSpan.end.offset;
284284
}
285285

286+
/** Possible placeholders that can be generated by `getPlaceholder`. */
287+
export enum PlaceholderKind {
288+
Default,
289+
Alternate,
290+
}
291+
292+
/**
293+
* Wraps a string in a placeholder that makes it easier to identify during replacement operations.
294+
*/
295+
export function getPlaceholder(
296+
value: string, kind: PlaceholderKind = PlaceholderKind.Default): string {
297+
const name = `<<<ɵɵngControlFlowMigration_${kind}ɵɵ>>>`;
298+
return `___${name}${value}${name}___`;
299+
}
300+
286301
/**
287302
* calculates the level of nesting of the items in the collector
288303
*/
@@ -387,8 +402,8 @@ export function processNgTemplates(template: string): {migrated: string, err: Er
387402

388403
// swap placeholders and remove
389404
for (const [name, t] of templates) {
390-
const replaceRegex = new RegExp(${name.slice(1)}\\δ`, 'g');
391-
const forRegex = new RegExp(${name.slice(1)}\\φ`, 'g');
405+
const replaceRegex = new RegExp(getPlaceholder(name.slice(1)), 'g');
406+
const forRegex = new RegExp(getPlaceholder(name.slice(1), PlaceholderKind.Alternate), 'g');
392407
const forMatches = [...template.matchAll(forRegex)];
393408
const matches = [...forMatches, ...template.matchAll(replaceRegex)];
394409
let safeToRemove = true;
@@ -429,11 +444,15 @@ export function processNgTemplates(template: string): {migrated: string, err: Er
429444
}
430445

431446
function replaceRemainingPlaceholders(template: string): string {
432-
const replaceRegex = new RegExp(`θ.*δ`, 'g');
447+
const pattern = '.*';
448+
const placeholderPattern = getPlaceholder(pattern);
449+
const replaceRegex = new RegExp(placeholderPattern, 'g');
450+
const [placeholderStart, placeholderEnd] = placeholderPattern.split(pattern);
433451
const placeholders = [...template.matchAll(replaceRegex)];
434452
for (let ph of placeholders) {
435453
const placeholder = ph[0];
436-
const name = placeholder.slice(1, placeholder.length - 1);
454+
const name =
455+
placeholder.slice(placeholderStart.length, placeholder.length - placeholderEnd.length);
437456
template =
438457
template.replace(placeholder, `<ng-template [ngTemplateOutlet]="${name}"></ng-template>`);
439458
}

packages/core/schematics/test/control_flow_migration_spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4364,6 +4364,30 @@ describe('control flow migration', () => {
43644364
`}`,
43654365
].join('\n'));
43664366
});
4367+
4368+
it('should migrate a template using the θδ characters', async () => {
4369+
writeFile('/comp.ts', `
4370+
import {Component} from '@angular/core';
4371+
import {NgIf} from '@angular/common';
4372+
4373+
@Component({
4374+
selector: 'declare-comp',
4375+
templateUrl: './comp.html',
4376+
standalone: true,
4377+
imports: [NgIf],
4378+
})
4379+
class DeclareComp {
4380+
show = false;
4381+
}
4382+
`);
4383+
4384+
writeFile('/comp.html', `<div *ngIf="show">Some greek characters: θδ!</div>`);
4385+
4386+
await runMigration();
4387+
4388+
expect(tree.readContent('/comp.html'))
4389+
.toBe('@if (show) {<div>Some greek characters: θδ!</div>}');
4390+
});
43674391
});
43684392

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

0 commit comments

Comments
 (0)