Skip to content

Commit d122fc4

Browse files
thePunderWomanalxhub
authored andcommitted
refactor(migrations): Add optional path param for control flow migration (angular#52403)
This adds the option to pass in a path to the control flow migration in order to run the migration against one single file. PR Close angular#52403
1 parent fa03f0a commit d122fc4

File tree

4 files changed

+122
-12
lines changed

4 files changed

+122
-12
lines changed

packages/core/schematics/ng-generate/control-flow-migration/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
Angular v17 introduces a new control flow syntax. This migration replaces the
44
existing usages of `*ngIf`, `*ngFor`, and `*ngSwitch` to their equivalent block
55
syntax. Existing ng-templates are preserved in case they are used elsewhere in
6-
the template.
6+
the template. It has the following option:
7+
8+
* `path` - Relative path within the project that the migration should apply to. Can be used to
9+
migrate specific sub-directories individually. Defaults to the project root.
710

811
NOTE: This is a developer preview migration
912

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

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,25 @@
77
*/
88

99
import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics';
10-
import {relative} from 'path';
10+
import {join, relative} from 'path';
1111

12+
import {normalizePath} from '../../utils/change_tracker';
1213
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
1314
import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host';
1415

1516
import {AnalyzedFile, MigrateError} from './types';
1617
import {analyze, migrateTemplate} from './util';
1718

18-
export default function(): Rule {
19+
interface Options {
20+
path: string;
21+
}
22+
23+
export default function(options: Options): Rule {
1924
return async (tree: Tree, context: SchematicContext) => {
2025
const {buildPaths, testPaths} = await getProjectTsConfigPaths(tree);
2126
const basePath = process.cwd();
22-
const allPaths = [...buildPaths, ...testPaths];
27+
const pathToMigrate = normalizePath(join(basePath, options.path));
28+
const allPaths = options.path !== './' ? [...buildPaths, ...testPaths] : [pathToMigrate];
2329

2430
if (!allPaths.length) {
2531
throw new SchematicsException(
@@ -30,7 +36,8 @@ export default function(): Rule {
3036
let errors: string[] = [];
3137

3238
for (const tsconfigPath of allPaths) {
33-
const migrateErrors = runControlFlowMigration(tree, tsconfigPath, basePath);
39+
const migrateErrors =
40+
runControlFlowMigration(tree, tsconfigPath, basePath, pathToMigrate, options);
3441
errors = [...errors, ...migrateErrors];
3542
}
3643

@@ -43,10 +50,24 @@ export default function(): Rule {
4350
};
4451
}
4552

46-
function runControlFlowMigration(tree: Tree, tsconfigPath: string, basePath: string): string[] {
53+
function runControlFlowMigration(
54+
tree: Tree, tsconfigPath: string, basePath: string, pathToMigrate: string,
55+
schematicOptions: Options): string[] {
56+
if (schematicOptions.path.startsWith('..')) {
57+
throw new SchematicsException(
58+
'Cannot run control flow migration outside of the current project.');
59+
}
60+
4761
const program = createMigrationProgram(tree, tsconfigPath, basePath);
48-
const sourceFiles =
49-
program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program));
62+
const sourceFiles = program.getSourceFiles().filter(
63+
sourceFile => sourceFile.fileName.startsWith(pathToMigrate) &&
64+
canMigrateFile(basePath, sourceFile, program));
65+
66+
if (sourceFiles.length === 0) {
67+
throw new SchematicsException(`Could not find any files to migrate under the path ${
68+
pathToMigrate}. Cannot run the control flow migration.`);
69+
}
70+
5071
const analysis = new Map<string, AnalyzedFile>();
5172
const migrateErrors = new Map<string, MigrateError[]>();
5273

packages/core/schematics/ng-generate/control-flow-migration/schema.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,12 @@
33
"$id": "AngularControlFlowMigration",
44
"title": "Angular Control Flow Migration Schema",
55
"type": "object",
6-
"properties": {}
7-
}
6+
"properties": {
7+
"path": {
8+
"type": "string",
9+
"description": "Path relative to the project root which should be migrated",
10+
"x-prompt": "Which path in your project should be migrated?",
11+
"default": "./"
12+
}
13+
}
14+
}

packages/core/schematics/test/control_flow_migration_spec.ts

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ describe('control flow migration', () => {
2626
host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents));
2727
}
2828

29-
function runMigration() {
30-
return runner.runSchematic('control-flow-migration', {}, tree);
29+
function runMigration(path: string|undefined = undefined) {
30+
return runner.runSchematic('control-flow-migration', {path}, tree);
3131
}
3232

3333
beforeEach(() => {
@@ -64,6 +64,85 @@ describe('control flow migration', () => {
6464
shx.rm('-r', tmpDirPath);
6565
});
6666

67+
describe('path', () => {
68+
it('should throw an error if no files match the passed-in path', async () => {
69+
let error: string|null = null;
70+
71+
writeFile('dir.ts', `
72+
import {Directive} from '@angular/core';
73+
74+
@Directive({selector: '[dir]'})
75+
export class MyDir {}
76+
`);
77+
78+
try {
79+
await runMigration('./foo');
80+
} catch (e: any) {
81+
error = e.message;
82+
}
83+
84+
expect(error).toMatch(
85+
/Could not find any files to migrate under the path .*\/foo\. Cannot run the control flow migration/);
86+
});
87+
88+
it('should throw an error if a path outside of the project is passed in', async () => {
89+
let error: string|null = null;
90+
91+
writeFile('dir.ts', `
92+
import {Directive} from '@angular/core';
93+
94+
@Directive({selector: '[dir]'})
95+
export class MyDir {}
96+
`);
97+
98+
try {
99+
await runMigration('../foo');
100+
} catch (e: any) {
101+
error = e.message;
102+
}
103+
104+
expect(error).toBe('Cannot run control flow migration outside of the current project.');
105+
});
106+
107+
it('should only migrate the paths that were passed in', async () => {
108+
let error: string|null = null;
109+
110+
writeFile('comp.ts', `
111+
import {Component} from '@angular/core';
112+
import {NgIf} from '@angular/common';
113+
114+
@Component({
115+
imports: [NgIf],
116+
template: \`<div><span *ngIf="toggle">This should be hidden</span></div>\`
117+
})
118+
class Comp {
119+
toggle = false;
120+
}
121+
`);
122+
123+
writeFile('skip.ts', `
124+
import {Component} from '@angular/core';
125+
import {NgIf} from '@angular/common';
126+
127+
@Component({
128+
imports: [NgIf],
129+
template: \`<div *ngIf="show">Show me</div>\`
130+
})
131+
class Comp {
132+
show = false;
133+
}
134+
`);
135+
136+
await runMigration('./comp.ts');
137+
const migratedContent = tree.readContent('/comp.ts');
138+
const skippedContent = tree.readContent('/skip.ts');
139+
140+
expect(migratedContent)
141+
.toContain('template: `<div>@if (toggle) {<span>This should be hidden</span>}</div>`');
142+
expect(skippedContent).toContain('template: `<div *ngIf="show">Show me</div>`');
143+
});
144+
});
145+
67146
describe('ngIf', () => {
68147
it('should migrate an inline template', async () => {
69148
writeFile('/comp.ts', `

0 commit comments

Comments
 (0)