Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(router-store): add migration for getRouterSelectors #3753

Merged
merged 4 commits into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
}
}
}