Skip to content

Commit 57bacf5

Browse files
timdeschryverbrandonroberts
authored andcommitted
feat(store): add META_REDUCERS replacement migration (#1640)
1 parent da1ec80 commit 57bacf5

File tree

4 files changed

+239
-0
lines changed

4 files changed

+239
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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 '../../../schematics-core/testing/create-package';
8+
9+
describe('Store Migration 8_0_0', () => {
10+
let appTree: UnitTestTree;
11+
const collectionPath = path.join(__dirname, '../migration.json');
12+
const pkgName = 'store';
13+
beforeEach(() => {
14+
appTree = new UnitTestTree(Tree.empty());
15+
appTree.create(
16+
'/tsconfig.json',
17+
`
18+
{
19+
"include": [**./*.ts"]
20+
}
21+
`
22+
);
23+
createPackageJson('', pkgName, appTree);
24+
});
25+
26+
it(`should replace the meta reducer imports`, () => {
27+
const contents = `
28+
import {
29+
RuntimeChecks,
30+
META_REDUCERS,
31+
Store,
32+
META_REDUCERS,
33+
StoreModule,
34+
META_REDUCERS as foo,
35+
} from '@ngrx/store';`;
36+
const expected = `
37+
import {
38+
RuntimeChecks,
39+
USER_PROVIDED_META_REDUCERS,
40+
Store,
41+
USER_PROVIDED_META_REDUCERS,
42+
StoreModule,
43+
USER_PROVIDED_META_REDUCERS as foo,
44+
} from '@ngrx/store';`;
45+
46+
appTree.create('./app.module.ts', contents);
47+
const runner = new SchematicTestRunner('schematics', collectionPath);
48+
49+
const newTree = runner.runSchematic(
50+
`ngrx-${pkgName}-migration-02`,
51+
{},
52+
appTree
53+
);
54+
const file = newTree.readContent('app.module.ts');
55+
56+
expect(file).toBe(expected);
57+
});
58+
59+
it(`should replace the meta reducer assignments`, () => {
60+
const contents = `
61+
@NgModule({
62+
imports: [
63+
CommonModule,
64+
BrowserModule,
65+
BrowserAnimationsModule,
66+
HttpClientModule,
67+
AuthModule,
68+
AppRoutingModule,
69+
StoreModule.forRoot(reducers),
70+
],
71+
providers: [
72+
{
73+
provide: META_REDUCERS,
74+
useValue: [fooReducer, barReducer]
75+
}
76+
]
77+
bootstrap: [AppComponent],
78+
})
79+
export class AppModule {}`;
80+
const expected = `
81+
@NgModule({
82+
imports: [
83+
CommonModule,
84+
BrowserModule,
85+
BrowserAnimationsModule,
86+
HttpClientModule,
87+
AuthModule,
88+
AppRoutingModule,
89+
StoreModule.forRoot(reducers),
90+
],
91+
providers: [
92+
{
93+
provide: USER_PROVIDED_META_REDUCERS,
94+
useValue: [fooReducer, barReducer]
95+
}
96+
]
97+
bootstrap: [AppComponent],
98+
})
99+
export class AppModule {}`;
100+
101+
appTree.create('./app.module.ts', contents);
102+
const runner = new SchematicTestRunner('schematics', collectionPath);
103+
104+
const newTree = runner.runSchematic(
105+
`ngrx-${pkgName}-migration-02`,
106+
{},
107+
appTree
108+
);
109+
const file = newTree.readContent('app.module.ts');
110+
111+
expect(file).toBe(expected);
112+
});
113+
});
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import * as ts from 'typescript';
2+
import { Rule, chain, Tree } from '@angular-devkit/schematics';
3+
import { Path } from '@angular-devkit/core';
4+
import { ReplaceChange } from '@ngrx/store/schematics-core';
5+
6+
const META_REDUCERS = 'META_REDUCERS';
7+
8+
function updateMetaReducersToken(): Rule {
9+
return (tree: Tree) => {
10+
tree.visit(path => {
11+
if (!path.endsWith('.ts')) {
12+
return;
13+
}
14+
15+
const sourceFile = ts.createSourceFile(
16+
path,
17+
tree.read(path)!.toString(),
18+
ts.ScriptTarget.Latest
19+
);
20+
21+
if (sourceFile.isDeclarationFile) {
22+
return;
23+
}
24+
25+
const createChange = (node: ts.Node) =>
26+
new ReplaceChange(
27+
path,
28+
node.getStart(sourceFile),
29+
META_REDUCERS,
30+
'USER_PROVIDED_META_REDUCERS'
31+
);
32+
33+
const changes: ReplaceChange[] = [];
34+
changes.push(
35+
...findMetaReducersImportStatements(sourceFile, createChange)
36+
);
37+
changes.push(...findMetaReducersAssignment(sourceFile, createChange));
38+
39+
if (changes.length < 1) {
40+
return;
41+
}
42+
43+
const recorder = createChangeRecorder(tree, path, changes);
44+
tree.commitUpdate(recorder);
45+
});
46+
};
47+
}
48+
49+
export default function(): Rule {
50+
return chain([updateMetaReducersToken()]);
51+
}
52+
53+
function findMetaReducersImportStatements(
54+
sourceFile: ts.SourceFile,
55+
createChange: (node: ts.Node) => ReplaceChange
56+
) {
57+
const metaReducerImports = sourceFile.statements
58+
.filter(ts.isImportDeclaration)
59+
.filter(isNgRxStoreImport)
60+
.map(p =>
61+
(p.importClause!.namedBindings! as ts.NamedImports).elements.filter(
62+
isMetaReducersImportSpecifier
63+
)
64+
)
65+
.reduce((imports, curr) => imports.concat(curr), []);
66+
67+
const changes = metaReducerImports.map(createChange);
68+
return changes;
69+
70+
function isNgRxStoreImport(importDeclaration: ts.ImportDeclaration) {
71+
return (
72+
importDeclaration.moduleSpecifier.getText(sourceFile) === "'@ngrx/store'"
73+
);
74+
}
75+
76+
function isMetaReducersImportSpecifier(importSpecifier: ts.ImportSpecifier) {
77+
const isImport = () => importSpecifier.name.text === META_REDUCERS;
78+
const isRenamedImport = () =>
79+
importSpecifier.propertyName &&
80+
importSpecifier.propertyName.text === META_REDUCERS;
81+
82+
return (
83+
ts.isImportSpecifier(importSpecifier) && (isImport() || isRenamedImport())
84+
);
85+
}
86+
}
87+
88+
function findMetaReducersAssignment(
89+
sourceFile: ts.SourceFile,
90+
createChange: (node: ts.Node) => ReplaceChange
91+
) {
92+
let changes: ReplaceChange[] = [];
93+
ts.forEachChild(sourceFile, node => findMetaReducers(node, changes));
94+
return changes;
95+
96+
function findMetaReducers(node: ts.Node, changes: ReplaceChange[]) {
97+
if (
98+
ts.isPropertyAssignment(node) &&
99+
node.initializer.getText(sourceFile) === META_REDUCERS
100+
) {
101+
changes.push(createChange(node.initializer));
102+
}
103+
104+
ts.forEachChild(node, childNode => findMetaReducers(childNode, changes));
105+
}
106+
}
107+
108+
function createChangeRecorder(
109+
tree: Tree,
110+
path: Path,
111+
changes: ReplaceChange[]
112+
) {
113+
const recorder = tree.beginUpdate(path);
114+
for (const change of changes) {
115+
const action = <any>change;
116+
recorder.remove(action.pos, action.oldText.length);
117+
recorder.insertLeft(action.pos, action.newText);
118+
}
119+
return recorder;
120+
}

modules/store/migrations/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ ts_library(
1717
deps = [
1818
"//modules/store/schematics-core",
1919
"@npm//@angular-devkit/schematics",
20+
"@npm//typescript",
2021
],
2122
)
2223

modules/store/migrations/migration.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
"description": "The road to v6",
77
"version": "5.2",
88
"factory": "./6_0_0/index"
9+
},
10+
"ngrx-store-migration-02": {
11+
"description": "The road to v8",
12+
"version": "7.0",
13+
"factory": "./8_0_0/index"
914
}
1015
}
1116
}

0 commit comments

Comments
 (0)