From 0b794cef209f01de512b4c85b376e077ce2af408 Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Tue, 9 Apr 2019 20:11:53 +0200 Subject: [PATCH] feat(router-store): add v8 migration schematic (#1699) --- modules/effects/schematics-core/index.ts | 2 + .../effects/schematics-core/utility/change.ts | 28 ++++ modules/entity/schematics-core/index.ts | 2 + .../entity/schematics-core/utility/change.ts | 28 ++++ .../migrations/8_0_0/index.spec.ts | 120 ++++++++++++++++++ .../router-store/migrations/8_0_0/index.ts | 71 +++++++++++ modules/router-store/migrations/BUILD | 1 + .../router-store/migrations/migration.json | 5 + modules/router-store/schematics-core/index.ts | 2 + .../schematics-core/utility/change.ts | 28 ++++ modules/schematics-core/index.ts | 2 + modules/schematics-core/utility/change.ts | 28 ++++ modules/schematics/schematics-core/index.ts | 2 + .../schematics-core/utility/change.ts | 28 ++++ .../store-devtools/schematics-core/index.ts | 2 + .../schematics-core/utility/change.ts | 28 ++++ modules/store/migrations/8_0_0/index.ts | 26 ++-- modules/store/schematics-core/index.ts | 2 + .../store/schematics-core/utility/change.ts | 28 ++++ 19 files changed, 415 insertions(+), 18 deletions(-) create mode 100644 modules/router-store/migrations/8_0_0/index.spec.ts create mode 100644 modules/router-store/migrations/8_0_0/index.ts diff --git a/modules/effects/schematics-core/index.ts b/modules/effects/schematics-core/index.ts index 9608baec44..7da4fa8c2d 100644 --- a/modules/effects/schematics-core/index.ts +++ b/modules/effects/schematics-core/index.ts @@ -30,6 +30,8 @@ export { InsertChange, RemoveChange, ReplaceChange, + createReplaceChange, + createChangeRecorder, } from './utility/change'; export { AppConfig, getWorkspace, getWorkspacePath } from './utility/config'; diff --git a/modules/effects/schematics-core/utility/change.ts b/modules/effects/schematics-core/utility/change.ts index 22997483b6..5dff73e3b6 100644 --- a/modules/effects/schematics-core/utility/change.ts +++ b/modules/effects/schematics-core/utility/change.ts @@ -1,3 +1,7 @@ +import * as ts from 'typescript'; +import { Tree, UpdateRecorder } from '@angular-devkit/schematics'; +import { Path } from '@angular-devkit/core'; + /* istanbul ignore file */ /** * @license @@ -133,3 +137,27 @@ export class ReplaceChange implements Change { }); } } + +export function createReplaceChange( + sourceFile: ts.SourceFile, + path: Path, + node: ts.Node, + oldText: string, + newText: string +): ReplaceChange { + return new ReplaceChange(path, node.getStart(sourceFile), oldText, newText); +} + +export function createChangeRecorder( + tree: Tree, + path: Path, + changes: ReplaceChange[] +): UpdateRecorder { + const recorder = tree.beginUpdate(path); + for (const change of changes) { + const action = change; + recorder.remove(action.pos, action.oldText.length); + recorder.insertLeft(action.pos, action.newText); + } + return recorder; +} diff --git a/modules/entity/schematics-core/index.ts b/modules/entity/schematics-core/index.ts index 9608baec44..7da4fa8c2d 100644 --- a/modules/entity/schematics-core/index.ts +++ b/modules/entity/schematics-core/index.ts @@ -30,6 +30,8 @@ export { InsertChange, RemoveChange, ReplaceChange, + createReplaceChange, + createChangeRecorder, } from './utility/change'; export { AppConfig, getWorkspace, getWorkspacePath } from './utility/config'; diff --git a/modules/entity/schematics-core/utility/change.ts b/modules/entity/schematics-core/utility/change.ts index 22997483b6..5dff73e3b6 100644 --- a/modules/entity/schematics-core/utility/change.ts +++ b/modules/entity/schematics-core/utility/change.ts @@ -1,3 +1,7 @@ +import * as ts from 'typescript'; +import { Tree, UpdateRecorder } from '@angular-devkit/schematics'; +import { Path } from '@angular-devkit/core'; + /* istanbul ignore file */ /** * @license @@ -133,3 +137,27 @@ export class ReplaceChange implements Change { }); } } + +export function createReplaceChange( + sourceFile: ts.SourceFile, + path: Path, + node: ts.Node, + oldText: string, + newText: string +): ReplaceChange { + return new ReplaceChange(path, node.getStart(sourceFile), oldText, newText); +} + +export function createChangeRecorder( + tree: Tree, + path: Path, + changes: ReplaceChange[] +): UpdateRecorder { + const recorder = tree.beginUpdate(path); + for (const change of changes) { + const action = change; + recorder.remove(action.pos, action.oldText.length); + recorder.insertLeft(action.pos, action.newText); + } + return recorder; +} diff --git a/modules/router-store/migrations/8_0_0/index.spec.ts b/modules/router-store/migrations/8_0_0/index.spec.ts new file mode 100644 index 0000000000..9fd7571015 --- /dev/null +++ b/modules/router-store/migrations/8_0_0/index.spec.ts @@ -0,0 +1,120 @@ +import { Tree } from '@angular-devkit/schematics'; +import { + SchematicTestRunner, + UnitTestTree, +} from '@angular-devkit/schematics/testing'; +import * as path from 'path'; +import { createPackageJson } from '../../../schematics-core/testing/create-package'; + +describe('Router Store Migration 8_0_0', () => { + let appTree: UnitTestTree; + const collectionPath = path.join(__dirname, '../migration.json'); + const pkgName = 'router-store'; + + beforeEach(() => { + appTree = new UnitTestTree(Tree.empty()); + appTree.create( + '/tsconfig.json', + ` + { + "include": [**./*.ts"] + } + ` + ); + createPackageJson('', pkgName, appTree); + }); + + it(`should import StoreRouterConnectingModule as StoreRouterConnectingModule.forRoot()`, () => { + const contents = ` + import { StoreRouterConnectingModule } from '@ngrx/router-store'; + @NgModule({ + imports: [ + AuthModule, + AppRoutingModule, + /** + * @ngrx/router-store keeps router state up-to-date in the store. + */ + StoreRouterConnectingModule, + CoreModule, + ], + bootstrap: [AppComponent], + }) + export class AppModule {} + `; + const expected = ` + import { StoreRouterConnectingModule } from '@ngrx/router-store'; + @NgModule({ + imports: [ + AuthModule, + AppRoutingModule, + /** + * @ngrx/router-store keeps router state up-to-date in the store. + */ + StoreRouterConnectingModule.forRoot(), + CoreModule, + ], + bootstrap: [AppComponent], + }) + export class AppModule {} + `; + + appTree.create('./app.module.ts', contents); + const runner = new SchematicTestRunner('schematics', collectionPath); + + const newTree = runner.runSchematic( + `ngrx-${pkgName}-migration-02`, + {}, + appTree + ); + const file = newTree.readContent('app.module.ts'); + + expect(file).toBe(expected); + }); + + it(`should not replace StoreRouterConnectingModule.forRoot()`, () => { + const contents = ` + import { StoreRouterConnectingModule } from '@ngrx/router-store'; + @NgModule({ + imports: [ + AuthModule, + AppRoutingModule, + /** + * @ngrx/router-store keeps router state up-to-date in the store. + */ + StoreRouterConnectingModule.forRoot(), + CoreModule, + ], + bootstrap: [AppComponent], + }) + export class AppModule {} + `; + const expected = ` + import { StoreRouterConnectingModule } from '@ngrx/router-store'; + @NgModule({ + imports: [ + AuthModule, + AppRoutingModule, + /** + * @ngrx/router-store keeps router state up-to-date in the store. + */ + StoreRouterConnectingModule.forRoot(), + CoreModule, + ], + bootstrap: [AppComponent], + }) + export class AppModule {} + `; + + appTree.create('./app.module.ts', contents); + const runner = new SchematicTestRunner('schematics', collectionPath); + + const newTree = runner.runSchematic( + `ngrx-${pkgName}-migration-02`, + {}, + appTree + ); + const file = newTree.readContent('app.module.ts'); + + expect(file).toBe(expected); + }); +}); diff --git a/modules/router-store/migrations/8_0_0/index.ts b/modules/router-store/migrations/8_0_0/index.ts new file mode 100644 index 0000000000..4d1b13f294 --- /dev/null +++ b/modules/router-store/migrations/8_0_0/index.ts @@ -0,0 +1,71 @@ +import * as ts from 'typescript'; +import { Rule, chain, Tree } from '@angular-devkit/schematics'; +import { + ReplaceChange, + createChangeRecorder, + createReplaceChange, +} from '@ngrx/router-store/schematics-core'; + +function updateRouterStoreImport(): Rule { + return (tree: Tree) => { + tree.visit(path => { + if (!path.endsWith('.ts')) { + return; + } + + const sourceFile = ts.createSourceFile( + path, + tree.read(path)!.toString(), + ts.ScriptTarget.Latest + ); + + if (sourceFile.isDeclarationFile) { + return; + } + let changes: ReplaceChange[] = []; + ts.forEachChild(sourceFile, function findDecorator(node) { + if (!ts.isDecorator(node)) { + ts.forEachChild(node, findDecorator); + return; + } + + ts.forEachChild(node, function findImports(node) { + if ( + ts.isPropertyAssignment(node) && + ts.isArrayLiteralExpression(node.initializer) && + ts.isIdentifier(node.name) && + node.name.text === 'imports' + ) { + node.initializer.elements + .filter(ts.isIdentifier) + .filter(element => element.text === 'StoreRouterConnectingModule') + .forEach(element => { + changes.push( + createReplaceChange( + sourceFile, + path, + element, + 'StoreRouterConnectingModule', + 'StoreRouterConnectingModule.forRoot()' + ) + ); + }); + } + + ts.forEachChild(node, findImports); + }); + }); + + if (changes.length < 1) { + return; + } + + const recorder = createChangeRecorder(tree, path, changes); + tree.commitUpdate(recorder); + }); + }; +} + +export default function(): Rule { + return chain([updateRouterStoreImport()]); +} diff --git a/modules/router-store/migrations/BUILD b/modules/router-store/migrations/BUILD index 1f0453bc62..5f93e1df05 100644 --- a/modules/router-store/migrations/BUILD +++ b/modules/router-store/migrations/BUILD @@ -17,6 +17,7 @@ ts_library( deps = [ "//modules/router-store/schematics-core", "@npm//@angular-devkit/schematics", + "@npm//typescript", ], ) diff --git a/modules/router-store/migrations/migration.json b/modules/router-store/migrations/migration.json index c77cd6c1ec..e6ae63738a 100644 --- a/modules/router-store/migrations/migration.json +++ b/modules/router-store/migrations/migration.json @@ -6,6 +6,11 @@ "description": "The road to v6", "version": "5.2", "factory": "./6_0_0/index" + }, + "ngrx-router-store-migration-02": { + "description": "The road to v8", + "version": "8-beta", + "factory": "./8_0_0/index" } } } diff --git a/modules/router-store/schematics-core/index.ts b/modules/router-store/schematics-core/index.ts index 9608baec44..7da4fa8c2d 100644 --- a/modules/router-store/schematics-core/index.ts +++ b/modules/router-store/schematics-core/index.ts @@ -30,6 +30,8 @@ export { InsertChange, RemoveChange, ReplaceChange, + createReplaceChange, + createChangeRecorder, } from './utility/change'; export { AppConfig, getWorkspace, getWorkspacePath } from './utility/config'; diff --git a/modules/router-store/schematics-core/utility/change.ts b/modules/router-store/schematics-core/utility/change.ts index 22997483b6..5dff73e3b6 100644 --- a/modules/router-store/schematics-core/utility/change.ts +++ b/modules/router-store/schematics-core/utility/change.ts @@ -1,3 +1,7 @@ +import * as ts from 'typescript'; +import { Tree, UpdateRecorder } from '@angular-devkit/schematics'; +import { Path } from '@angular-devkit/core'; + /* istanbul ignore file */ /** * @license @@ -133,3 +137,27 @@ export class ReplaceChange implements Change { }); } } + +export function createReplaceChange( + sourceFile: ts.SourceFile, + path: Path, + node: ts.Node, + oldText: string, + newText: string +): ReplaceChange { + return new ReplaceChange(path, node.getStart(sourceFile), oldText, newText); +} + +export function createChangeRecorder( + tree: Tree, + path: Path, + changes: ReplaceChange[] +): UpdateRecorder { + const recorder = tree.beginUpdate(path); + for (const change of changes) { + const action = change; + recorder.remove(action.pos, action.oldText.length); + recorder.insertLeft(action.pos, action.newText); + } + return recorder; +} diff --git a/modules/schematics-core/index.ts b/modules/schematics-core/index.ts index 9608baec44..7da4fa8c2d 100644 --- a/modules/schematics-core/index.ts +++ b/modules/schematics-core/index.ts @@ -30,6 +30,8 @@ export { InsertChange, RemoveChange, ReplaceChange, + createReplaceChange, + createChangeRecorder, } from './utility/change'; export { AppConfig, getWorkspace, getWorkspacePath } from './utility/config'; diff --git a/modules/schematics-core/utility/change.ts b/modules/schematics-core/utility/change.ts index 22997483b6..5dff73e3b6 100644 --- a/modules/schematics-core/utility/change.ts +++ b/modules/schematics-core/utility/change.ts @@ -1,3 +1,7 @@ +import * as ts from 'typescript'; +import { Tree, UpdateRecorder } from '@angular-devkit/schematics'; +import { Path } from '@angular-devkit/core'; + /* istanbul ignore file */ /** * @license @@ -133,3 +137,27 @@ export class ReplaceChange implements Change { }); } } + +export function createReplaceChange( + sourceFile: ts.SourceFile, + path: Path, + node: ts.Node, + oldText: string, + newText: string +): ReplaceChange { + return new ReplaceChange(path, node.getStart(sourceFile), oldText, newText); +} + +export function createChangeRecorder( + tree: Tree, + path: Path, + changes: ReplaceChange[] +): UpdateRecorder { + const recorder = tree.beginUpdate(path); + for (const change of changes) { + const action = change; + recorder.remove(action.pos, action.oldText.length); + recorder.insertLeft(action.pos, action.newText); + } + return recorder; +} diff --git a/modules/schematics/schematics-core/index.ts b/modules/schematics/schematics-core/index.ts index 9608baec44..7da4fa8c2d 100644 --- a/modules/schematics/schematics-core/index.ts +++ b/modules/schematics/schematics-core/index.ts @@ -30,6 +30,8 @@ export { InsertChange, RemoveChange, ReplaceChange, + createReplaceChange, + createChangeRecorder, } from './utility/change'; export { AppConfig, getWorkspace, getWorkspacePath } from './utility/config'; diff --git a/modules/schematics/schematics-core/utility/change.ts b/modules/schematics/schematics-core/utility/change.ts index 22997483b6..5dff73e3b6 100644 --- a/modules/schematics/schematics-core/utility/change.ts +++ b/modules/schematics/schematics-core/utility/change.ts @@ -1,3 +1,7 @@ +import * as ts from 'typescript'; +import { Tree, UpdateRecorder } from '@angular-devkit/schematics'; +import { Path } from '@angular-devkit/core'; + /* istanbul ignore file */ /** * @license @@ -133,3 +137,27 @@ export class ReplaceChange implements Change { }); } } + +export function createReplaceChange( + sourceFile: ts.SourceFile, + path: Path, + node: ts.Node, + oldText: string, + newText: string +): ReplaceChange { + return new ReplaceChange(path, node.getStart(sourceFile), oldText, newText); +} + +export function createChangeRecorder( + tree: Tree, + path: Path, + changes: ReplaceChange[] +): UpdateRecorder { + const recorder = tree.beginUpdate(path); + for (const change of changes) { + const action = change; + recorder.remove(action.pos, action.oldText.length); + recorder.insertLeft(action.pos, action.newText); + } + return recorder; +} diff --git a/modules/store-devtools/schematics-core/index.ts b/modules/store-devtools/schematics-core/index.ts index 9608baec44..7da4fa8c2d 100644 --- a/modules/store-devtools/schematics-core/index.ts +++ b/modules/store-devtools/schematics-core/index.ts @@ -30,6 +30,8 @@ export { InsertChange, RemoveChange, ReplaceChange, + createReplaceChange, + createChangeRecorder, } from './utility/change'; export { AppConfig, getWorkspace, getWorkspacePath } from './utility/config'; diff --git a/modules/store-devtools/schematics-core/utility/change.ts b/modules/store-devtools/schematics-core/utility/change.ts index 22997483b6..5dff73e3b6 100644 --- a/modules/store-devtools/schematics-core/utility/change.ts +++ b/modules/store-devtools/schematics-core/utility/change.ts @@ -1,3 +1,7 @@ +import * as ts from 'typescript'; +import { Tree, UpdateRecorder } from '@angular-devkit/schematics'; +import { Path } from '@angular-devkit/core'; + /* istanbul ignore file */ /** * @license @@ -133,3 +137,27 @@ export class ReplaceChange implements Change { }); } } + +export function createReplaceChange( + sourceFile: ts.SourceFile, + path: Path, + node: ts.Node, + oldText: string, + newText: string +): ReplaceChange { + return new ReplaceChange(path, node.getStart(sourceFile), oldText, newText); +} + +export function createChangeRecorder( + tree: Tree, + path: Path, + changes: ReplaceChange[] +): UpdateRecorder { + const recorder = tree.beginUpdate(path); + for (const change of changes) { + const action = change; + recorder.remove(action.pos, action.oldText.length); + recorder.insertLeft(action.pos, action.newText); + } + return recorder; +} diff --git a/modules/store/migrations/8_0_0/index.ts b/modules/store/migrations/8_0_0/index.ts index b23913c749..a7c0e65794 100644 --- a/modules/store/migrations/8_0_0/index.ts +++ b/modules/store/migrations/8_0_0/index.ts @@ -1,7 +1,10 @@ import * as ts from 'typescript'; import { Rule, chain, Tree } from '@angular-devkit/schematics'; -import { Path } from '@angular-devkit/core'; -import { ReplaceChange } from '@ngrx/store/schematics-core'; +import { + ReplaceChange, + createChangeRecorder, + createReplaceChange, +} from '@ngrx/store/schematics-core'; const META_REDUCERS = 'META_REDUCERS'; @@ -23,9 +26,10 @@ function updateMetaReducersToken(): Rule { } const createChange = (node: ts.Node) => - new ReplaceChange( + createReplaceChange( + sourceFile, path, - node.getStart(sourceFile), + node, META_REDUCERS, 'USER_PROVIDED_META_REDUCERS' ); @@ -104,17 +108,3 @@ function findMetaReducersAssignment( ts.forEachChild(node, childNode => findMetaReducers(childNode, changes)); } } - -function createChangeRecorder( - tree: Tree, - path: Path, - changes: ReplaceChange[] -) { - const recorder = tree.beginUpdate(path); - for (const change of changes) { - const action = change; - recorder.remove(action.pos, action.oldText.length); - recorder.insertLeft(action.pos, action.newText); - } - return recorder; -} diff --git a/modules/store/schematics-core/index.ts b/modules/store/schematics-core/index.ts index 9608baec44..7da4fa8c2d 100644 --- a/modules/store/schematics-core/index.ts +++ b/modules/store/schematics-core/index.ts @@ -30,6 +30,8 @@ export { InsertChange, RemoveChange, ReplaceChange, + createReplaceChange, + createChangeRecorder, } from './utility/change'; export { AppConfig, getWorkspace, getWorkspacePath } from './utility/config'; diff --git a/modules/store/schematics-core/utility/change.ts b/modules/store/schematics-core/utility/change.ts index 22997483b6..5dff73e3b6 100644 --- a/modules/store/schematics-core/utility/change.ts +++ b/modules/store/schematics-core/utility/change.ts @@ -1,3 +1,7 @@ +import * as ts from 'typescript'; +import { Tree, UpdateRecorder } from '@angular-devkit/schematics'; +import { Path } from '@angular-devkit/core'; + /* istanbul ignore file */ /** * @license @@ -133,3 +137,27 @@ export class ReplaceChange implements Change { }); } } + +export function createReplaceChange( + sourceFile: ts.SourceFile, + path: Path, + node: ts.Node, + oldText: string, + newText: string +): ReplaceChange { + return new ReplaceChange(path, node.getStart(sourceFile), oldText, newText); +} + +export function createChangeRecorder( + tree: Tree, + path: Path, + changes: ReplaceChange[] +): UpdateRecorder { + const recorder = tree.beginUpdate(path); + for (const change of changes) { + const action = change; + recorder.remove(action.pos, action.oldText.length); + recorder.insertLeft(action.pos, action.newText); + } + return recorder; +}