Skip to content

Commit

Permalink
feat(router-store): add migration for getRouterSelectors (#3753)
Browse files Browse the repository at this point in the history
  • Loading branch information
timdeschryver committed Jan 24, 2023
1 parent ccb3b93 commit a785331
Show file tree
Hide file tree
Showing 3 changed files with 293 additions and 0 deletions.
161 changes: 161 additions & 0 deletions modules/router-store/migrations/15_2_0/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { Tree } from '@angular-devkit/schematics';
import {
SchematicTestRunner,
UnitTestTree,
} from '@angular-devkit/schematics/testing';
import * as path from 'path';
import { createPackageJson } from '@ngrx/schematics-core/testing/create-package';
import { waitForAsync } from '@angular/core/testing';

describe('Router Store Migration 15_2_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);
});

describe('Rename selector', () => {
it(`renames getSelectors to getRouterSelectors as named imports`, waitForAsync(async () => {
const input = `
import { getSelectors } from '@ngrx/router-store';
export const {
selectCurrentRoute,
selectQueryParams,
selectQueryParam,
selectRouteParams,
selectRouteParam,
selectRouteData,
selectUrl,
selectTitle,
} = getSelectors(selectRouter);
`;
const expected = `
import { getRouterSelectors } from '@ngrx/router-store';
export const {
selectCurrentRoute,
selectQueryParams,
selectQueryParam,
selectRouteParams,
selectRouteParam,
selectRouteData,
selectUrl,
selectTitle,
} = getSelectors(selectRouter);
`;

appTree.create('./selector.ts', input);
const runner = new SchematicTestRunner('schematics', collectionPath);

const newTree = await runner
.runSchematicAsync(`ngrx-${pkgName}-migration-05`, {}, appTree)
.toPromise();
const file = newTree.readContent('selector.ts');

expect(file).toBe(expected);
}));

it(`renames getSelectors to getRouterSelectors as namespace import`, waitForAsync(async () => {
const input = `
import * as routerStore from '@ngrx/router-store';
export const selectors = routerStore.getSelectors(selectRouter);
`;
const expected = `
import * as routerStore from '@ngrx/router-store';
export const selectors = routerStore.getRouterSelectors(selectRouter);
`;

appTree.create('./selector.ts', input);
const runner = new SchematicTestRunner('schematics', collectionPath);

const newTree = await runner
.runSchematicAsync(`ngrx-${pkgName}-migration-05`, {}, appTree)
.toPromise();
const file = newTree.readContent('selector.ts');

expect(file).toBe(expected);
}));

it(`renames getSelectors to getRouterSelectors as namespace import with deconstruct`, waitForAsync(async () => {
const input = `
import * as routerStore from '@ngrx/router-store';
export const {
selectCurrentRoute,
selectQueryParams,
selectQueryParam,
selectRouteParams,
selectRouteParam,
selectRouteData,
selectUrl,
selectTitle,
} = routerStore.getSelectors(selectRouter);
`;
const expected = `
import * as routerStore from '@ngrx/router-store';
export const {
selectCurrentRoute,
selectQueryParams,
selectQueryParam,
selectRouteParams,
selectRouteParam,
selectRouteData,
selectUrl,
selectTitle,
} = routerStore.getRouterSelectors(selectRouter);
`;

appTree.create('./selector.ts', input);
const runner = new SchematicTestRunner('schematics', collectionPath);

const newTree = await runner
.runSchematicAsync(`ngrx-${pkgName}-migration-05`, {}, appTree)
.toPromise();
const file = newTree.readContent('selector.ts');

expect(file).toBe(expected);
}));

it(`does not rename getSelectors if not imported from router-store`, waitForAsync(async () => {
const input = `
import { getSelectors } from '@ngrx/something';
export const { selectCurrentRoute } = getSelectors(selectRouter);
`;

appTree.create('./selector.ts', input);
const runner = new SchematicTestRunner('schematics', collectionPath);

const newTree = await runner
.runSchematicAsync(`ngrx-${pkgName}-migration-05`, {}, appTree)
.toPromise();
const file = newTree.readContent('selector.ts');

expect(file).toBe(input);
}));
it(`does not rename other methods on namespace import`, waitForAsync(async () => {
const input = `
import * as routerStore from '@ngrx/router-store';
const root = routerStore.forRoot();
`;

appTree.create('./selector.ts', input);
const runner = new SchematicTestRunner('schematics', collectionPath);

const newTree = await runner
.runSchematicAsync(`ngrx-${pkgName}-migration-05`, {}, appTree)
.toPromise();
const file = newTree.readContent('selector.ts');

expect(file).toBe(input);
}));
});
});
127 changes: 127 additions & 0 deletions modules/router-store/migrations/15_2_0/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import * as ts from 'typescript';
import { Rule, chain, Tree } from '@angular-devkit/schematics';
import {
visitTSSourceFiles,
commitChanges,
createReplaceChange,
ReplaceChange,
} from '../../schematics-core';

const renames: { [key: string]: string } = {
getSelectors: 'getRouterSelectors',
};

function renameSelector() {
return (tree: Tree) => {
visitTSSourceFiles(tree, (sourceFile) => {
const routerStoreImports = sourceFile.statements
.filter((p): p is ts.ImportDeclaration => ts.isImportDeclaration(p))
.filter(({ moduleSpecifier }) =>
moduleSpecifier.getText(sourceFile).includes('@ngrx/router-store')
);
const changes: ReplaceChange[] = [
...replaceNamedImports(routerStoreImports, sourceFile),
...replaceNamespaceImports(routerStoreImports, sourceFile),
];

if (changes.length) {
commitChanges(tree, sourceFile.fileName, changes);
}
});
};
}

function replaceNamedImports(
routerStoreImports: ts.ImportDeclaration[],
sourceFile: ts.SourceFile
): ReplaceChange[] {
const changes: ReplaceChange[] = [];

const namedImports = routerStoreImports
.flatMap((p) =>
!!p.importClause && ts.isImportClause(p.importClause)
? p.importClause.namedBindings
: []
)
.flatMap((p) => (!!p && ts.isNamedImports(p) ? p.elements : []));

for (const namedImport of namedImports) {
tryToAddReplacement(namedImport.name, sourceFile, changes);
}
return changes;
}

function replaceNamespaceImports(
routerStoreImports: ts.ImportDeclaration[],
sourceFile: ts.SourceFile
): ReplaceChange[] {
const changes: ReplaceChange[] = [];

const namespaceImports = routerStoreImports
.map((p) =>
!!p.importClause &&
ts.isImportClause(p.importClause) &&
!!p.importClause.namedBindings &&
ts.isNamespaceImport(p.importClause.namedBindings)
? p.importClause.namedBindings.name.getText(sourceFile)
: null
)
.filter((p): p is string => !!p);

if (namespaceImports.length === 0) {
return changes;
}

for (const statement of sourceFile.statements) {
statement.forEachChild((child) => {
if (ts.isVariableDeclarationList(child)) {
const [declaration] = child.declarations;
if (
ts.isVariableDeclaration(declaration) &&
declaration.initializer &&
ts.isCallExpression(declaration.initializer) &&
declaration.initializer.expression &&
ts.isPropertyAccessExpression(declaration.initializer.expression) &&
ts.isIdentifier(declaration.initializer.expression.expression) &&
ts.isIdentifier(declaration.initializer.expression.name)
) {
if (
namespaceImports.includes(
declaration.initializer.expression.expression.getText(sourceFile)
)
) {
tryToAddReplacement(
declaration.initializer.expression.name,
sourceFile,
changes
);
}
}
}
});
}

return changes;
}

function tryToAddReplacement(
oldName: ts.Identifier,
sourceFile: ts.SourceFile,
changes: ReplaceChange[]
) {
const oldNameText = oldName.getText(sourceFile);
const newName = renames[oldNameText];
if (newName) {
const change = createReplaceChange(
sourceFile,
oldName,
oldNameText,
newName
);
changes.push(change);
}
}

export default function (): Rule {
return chain([renameSelector()]);
}
5 changes: 5 additions & 0 deletions modules/router-store/migrations/migration.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
"description": "The road to v14",
"version": "14-beta",
"factory": "./14_0_0/index"
},
"ngrx-router-store-migration-05": {
"description": "The road to v15.2.0",
"version": "15.2.0",
"factory": "./15_2_0/index"
}
}
}

0 comments on commit a785331

Please sign in to comment.