Skip to content

Commit a785331

Browse files
feat(router-store): add migration for getRouterSelectors (#3753)
1 parent ccb3b93 commit a785331

File tree

3 files changed

+293
-0
lines changed

3 files changed

+293
-0
lines changed
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { Tree } from '@angular-devkit/schematics';
2+
import {
3+
SchematicTestRunner,
4+
UnitTestTree,
5+
} from '@angular-devkit/schematics/testing';
6+
import * as path from 'path';
7+
import { createPackageJson } from '@ngrx/schematics-core/testing/create-package';
8+
import { waitForAsync } from '@angular/core/testing';
9+
10+
describe('Router Store Migration 15_2_0', () => {
11+
let appTree: UnitTestTree;
12+
const collectionPath = path.join(__dirname, '../migration.json');
13+
const pkgName = 'router-store';
14+
15+
beforeEach(() => {
16+
appTree = new UnitTestTree(Tree.empty());
17+
appTree.create(
18+
'/tsconfig.json',
19+
`
20+
{
21+
"include": [**./*.ts"]
22+
}
23+
`
24+
);
25+
createPackageJson('', pkgName, appTree);
26+
});
27+
28+
describe('Rename selector', () => {
29+
it(`renames getSelectors to getRouterSelectors as named imports`, waitForAsync(async () => {
30+
const input = `
31+
import { getSelectors } from '@ngrx/router-store';
32+
export const {
33+
selectCurrentRoute,
34+
selectQueryParams,
35+
selectQueryParam,
36+
selectRouteParams,
37+
selectRouteParam,
38+
selectRouteData,
39+
selectUrl,
40+
selectTitle,
41+
} = getSelectors(selectRouter);
42+
`;
43+
const expected = `
44+
import { getRouterSelectors } from '@ngrx/router-store';
45+
export const {
46+
selectCurrentRoute,
47+
selectQueryParams,
48+
selectQueryParam,
49+
selectRouteParams,
50+
selectRouteParam,
51+
selectRouteData,
52+
selectUrl,
53+
selectTitle,
54+
} = getSelectors(selectRouter);
55+
`;
56+
57+
appTree.create('./selector.ts', input);
58+
const runner = new SchematicTestRunner('schematics', collectionPath);
59+
60+
const newTree = await runner
61+
.runSchematicAsync(`ngrx-${pkgName}-migration-05`, {}, appTree)
62+
.toPromise();
63+
const file = newTree.readContent('selector.ts');
64+
65+
expect(file).toBe(expected);
66+
}));
67+
68+
it(`renames getSelectors to getRouterSelectors as namespace import`, waitForAsync(async () => {
69+
const input = `
70+
import * as routerStore from '@ngrx/router-store';
71+
export const selectors = routerStore.getSelectors(selectRouter);
72+
`;
73+
const expected = `
74+
import * as routerStore from '@ngrx/router-store';
75+
export const selectors = routerStore.getRouterSelectors(selectRouter);
76+
`;
77+
78+
appTree.create('./selector.ts', input);
79+
const runner = new SchematicTestRunner('schematics', collectionPath);
80+
81+
const newTree = await runner
82+
.runSchematicAsync(`ngrx-${pkgName}-migration-05`, {}, appTree)
83+
.toPromise();
84+
const file = newTree.readContent('selector.ts');
85+
86+
expect(file).toBe(expected);
87+
}));
88+
89+
it(`renames getSelectors to getRouterSelectors as namespace import with deconstruct`, waitForAsync(async () => {
90+
const input = `
91+
import * as routerStore from '@ngrx/router-store';
92+
export const {
93+
selectCurrentRoute,
94+
selectQueryParams,
95+
selectQueryParam,
96+
selectRouteParams,
97+
selectRouteParam,
98+
selectRouteData,
99+
selectUrl,
100+
selectTitle,
101+
} = routerStore.getSelectors(selectRouter);
102+
`;
103+
const expected = `
104+
import * as routerStore from '@ngrx/router-store';
105+
export const {
106+
selectCurrentRoute,
107+
selectQueryParams,
108+
selectQueryParam,
109+
selectRouteParams,
110+
selectRouteParam,
111+
selectRouteData,
112+
selectUrl,
113+
selectTitle,
114+
} = routerStore.getRouterSelectors(selectRouter);
115+
`;
116+
117+
appTree.create('./selector.ts', input);
118+
const runner = new SchematicTestRunner('schematics', collectionPath);
119+
120+
const newTree = await runner
121+
.runSchematicAsync(`ngrx-${pkgName}-migration-05`, {}, appTree)
122+
.toPromise();
123+
const file = newTree.readContent('selector.ts');
124+
125+
expect(file).toBe(expected);
126+
}));
127+
128+
it(`does not rename getSelectors if not imported from router-store`, waitForAsync(async () => {
129+
const input = `
130+
import { getSelectors } from '@ngrx/something';
131+
export const { selectCurrentRoute } = getSelectors(selectRouter);
132+
`;
133+
134+
appTree.create('./selector.ts', input);
135+
const runner = new SchematicTestRunner('schematics', collectionPath);
136+
137+
const newTree = await runner
138+
.runSchematicAsync(`ngrx-${pkgName}-migration-05`, {}, appTree)
139+
.toPromise();
140+
const file = newTree.readContent('selector.ts');
141+
142+
expect(file).toBe(input);
143+
}));
144+
it(`does not rename other methods on namespace import`, waitForAsync(async () => {
145+
const input = `
146+
import * as routerStore from '@ngrx/router-store';
147+
const root = routerStore.forRoot();
148+
`;
149+
150+
appTree.create('./selector.ts', input);
151+
const runner = new SchematicTestRunner('schematics', collectionPath);
152+
153+
const newTree = await runner
154+
.runSchematicAsync(`ngrx-${pkgName}-migration-05`, {}, appTree)
155+
.toPromise();
156+
const file = newTree.readContent('selector.ts');
157+
158+
expect(file).toBe(input);
159+
}));
160+
});
161+
});
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import * as ts from 'typescript';
2+
import { Rule, chain, Tree } from '@angular-devkit/schematics';
3+
import {
4+
visitTSSourceFiles,
5+
commitChanges,
6+
createReplaceChange,
7+
ReplaceChange,
8+
} from '../../schematics-core';
9+
10+
const renames: { [key: string]: string } = {
11+
getSelectors: 'getRouterSelectors',
12+
};
13+
14+
function renameSelector() {
15+
return (tree: Tree) => {
16+
visitTSSourceFiles(tree, (sourceFile) => {
17+
const routerStoreImports = sourceFile.statements
18+
.filter((p): p is ts.ImportDeclaration => ts.isImportDeclaration(p))
19+
.filter(({ moduleSpecifier }) =>
20+
moduleSpecifier.getText(sourceFile).includes('@ngrx/router-store')
21+
);
22+
const changes: ReplaceChange[] = [
23+
...replaceNamedImports(routerStoreImports, sourceFile),
24+
...replaceNamespaceImports(routerStoreImports, sourceFile),
25+
];
26+
27+
if (changes.length) {
28+
commitChanges(tree, sourceFile.fileName, changes);
29+
}
30+
});
31+
};
32+
}
33+
34+
function replaceNamedImports(
35+
routerStoreImports: ts.ImportDeclaration[],
36+
sourceFile: ts.SourceFile
37+
): ReplaceChange[] {
38+
const changes: ReplaceChange[] = [];
39+
40+
const namedImports = routerStoreImports
41+
.flatMap((p) =>
42+
!!p.importClause && ts.isImportClause(p.importClause)
43+
? p.importClause.namedBindings
44+
: []
45+
)
46+
.flatMap((p) => (!!p && ts.isNamedImports(p) ? p.elements : []));
47+
48+
for (const namedImport of namedImports) {
49+
tryToAddReplacement(namedImport.name, sourceFile, changes);
50+
}
51+
return changes;
52+
}
53+
54+
function replaceNamespaceImports(
55+
routerStoreImports: ts.ImportDeclaration[],
56+
sourceFile: ts.SourceFile
57+
): ReplaceChange[] {
58+
const changes: ReplaceChange[] = [];
59+
60+
const namespaceImports = routerStoreImports
61+
.map((p) =>
62+
!!p.importClause &&
63+
ts.isImportClause(p.importClause) &&
64+
!!p.importClause.namedBindings &&
65+
ts.isNamespaceImport(p.importClause.namedBindings)
66+
? p.importClause.namedBindings.name.getText(sourceFile)
67+
: null
68+
)
69+
.filter((p): p is string => !!p);
70+
71+
if (namespaceImports.length === 0) {
72+
return changes;
73+
}
74+
75+
for (const statement of sourceFile.statements) {
76+
statement.forEachChild((child) => {
77+
if (ts.isVariableDeclarationList(child)) {
78+
const [declaration] = child.declarations;
79+
if (
80+
ts.isVariableDeclaration(declaration) &&
81+
declaration.initializer &&
82+
ts.isCallExpression(declaration.initializer) &&
83+
declaration.initializer.expression &&
84+
ts.isPropertyAccessExpression(declaration.initializer.expression) &&
85+
ts.isIdentifier(declaration.initializer.expression.expression) &&
86+
ts.isIdentifier(declaration.initializer.expression.name)
87+
) {
88+
if (
89+
namespaceImports.includes(
90+
declaration.initializer.expression.expression.getText(sourceFile)
91+
)
92+
) {
93+
tryToAddReplacement(
94+
declaration.initializer.expression.name,
95+
sourceFile,
96+
changes
97+
);
98+
}
99+
}
100+
}
101+
});
102+
}
103+
104+
return changes;
105+
}
106+
107+
function tryToAddReplacement(
108+
oldName: ts.Identifier,
109+
sourceFile: ts.SourceFile,
110+
changes: ReplaceChange[]
111+
) {
112+
const oldNameText = oldName.getText(sourceFile);
113+
const newName = renames[oldNameText];
114+
if (newName) {
115+
const change = createReplaceChange(
116+
sourceFile,
117+
oldName,
118+
oldNameText,
119+
newName
120+
);
121+
changes.push(change);
122+
}
123+
}
124+
125+
export default function (): Rule {
126+
return chain([renameSelector()]);
127+
}

modules/router-store/migrations/migration.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
"description": "The road to v14",
2121
"version": "14-beta",
2222
"factory": "./14_0_0/index"
23+
},
24+
"ngrx-router-store-migration-05": {
25+
"description": "The road to v15.2.0",
26+
"version": "15.2.0",
27+
"factory": "./15_2_0/index"
2328
}
2429
}
2530
}

0 commit comments

Comments
 (0)