Skip to content

Commit

Permalink
feat(angular): add ast util to add a provider to a route (#14133)
Browse files Browse the repository at this point in the history
  • Loading branch information
Coly010 committed Jan 4, 2023
1 parent 41898df commit 4de2a9d
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 3 deletions.
60 changes: 57 additions & 3 deletions packages/angular/src/utils/nx-devkit/route-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';
import { addRoute } from './route-utils';
import { addProviderToRoute, addRoute } from './route-utils';

describe.each([
['Route[]', 'Route'],
Expand Down Expand Up @@ -46,14 +46,68 @@ describe.each([
addRoute(
tree,
'routes-file.ts',
"{path: 'test', , loadChildren: () => import('@proj/lib').then(m => m.ROUTES) }"
"{path: 'test', loadChildren: () => import('@proj/lib').then(m => m.ROUTES) }"
);

// ASSERT
expect(tree.read('routes-file.ts', 'utf-8')).toMatchInlineSnapshot(`
"import { ${routeType} } from '@angular/router';
export const ROUTES: ${routes} = [
{path: 'test', , loadChildren: () => import('@proj/lib').then(m => m.ROUTES) },]"
{path: 'test', loadChildren: () => import('@proj/lib').then(m => m.ROUTES) },]"
`);
});

it('should add provider along with providers array to the route when providers do not exist', () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write(
'routes-file.ts',
`import { ${routeType} } from '@angular/router';
export const ROUTES: ${routes} = [{path: 'test', loadChildren: () => import('@proj/lib').then(m => m.ROUTES) }];`
);

// ACT
addProviderToRoute(tree, 'routes-file.ts', 'test', 'provideStore()');

// ASSERT
expect(tree.read('routes-file.ts', 'utf-8'))
.toEqual(`import { ${routeType} } from '@angular/router';
export const ROUTES: ${routes} = [{path: 'test', loadChildren: () => import('@proj/lib').then(m => m.ROUTES) , providers: [provideStore()]}];`);
});

it('should add provider to the providers array to the route', () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write(
'routes-file.ts',
`import { ${routeType} } from '@angular/router';
export const ROUTES: ${routes} = [{path: 'test', providers: [provideState()], loadChildren: () => import('@proj/lib').then(m => m.ROUTES) }];`
);

// ACT
addProviderToRoute(tree, 'routes-file.ts', 'test', 'provideStore()');

// ASSERT
expect(tree.read('routes-file.ts', 'utf-8'))
.toEqual(`import { ${routeType} } from '@angular/router';
export const ROUTES: ${routes} = [{path: 'test', providers: [provideState(), provideStore()], loadChildren: () => import('@proj/lib').then(m => m.ROUTES) }];`);
});

it('should add provider to the providers array of a nested route', () => {
// ARRANGE
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
tree.write(
'routes-file.ts',
`import { ${routeType} } from '@angular/router';
export const ROUTES: ${routes} = [{path: '', providers: [provideState()], children: [{ path: 'test' }]}];`
);

// ACT
addProviderToRoute(tree, 'routes-file.ts', 'test', 'provideStore()');

// ASSERT
expect(tree.read('routes-file.ts', 'utf-8'))
.toEqual(`import { ${routeType} } from '@angular/router';
export const ROUTES: ${routes} = [{path: '', providers: [provideState()], children: [{ path: 'test' , providers: [provideStore()]}]}];`);
});
});
77 changes: 77 additions & 0 deletions packages/angular/src/utils/nx-devkit/route-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,80 @@ export function addRoute(

tree.write(routesFile, newRoutesFileContents);
}

export function addProviderToRoute(
tree: Tree,
routesFile: string,
routeToAddProviderTo: string,
providerToAdd: string
) {
if (!tree.exists(routesFile)) {
throw new Error(
`Path to parent routing declaration (${routesFile}) does not exist. Please ensure path is correct.`
);
}

let routesFileContents = tree.read(routesFile, 'utf-8');

const ast = tsquery.ast(routesFileContents);

const ROUTES_ARRAY_SELECTOR =
'VariableDeclaration:has(ArrayType > TypeReference > Identifier[name=Route], Identifier[name=Routes]) > ArrayLiteralExpression';

const routesArrayNodes = tsquery(ast, ROUTES_ARRAY_SELECTOR, {
visitAllChildren: true,
});
const isRoutesArray = routesArrayNodes.length > 0;

if (!isRoutesArray) {
throw new Error(
`Routing file (${routesFile}) does not a routing configuration. Please ensure the parent contains a routing configuration.`
);
}

const ROUTE_SELECTOR = `ObjectLiteralExpression:has(PropertyAssignment:has(Identifier[name=path]) > StringLiteral[value=${routeToAddProviderTo}]):last-child`;
const ROUTE_PATH_PROVIDERS_SELECTOR =
'ObjectLiteralExpression > PropertyAssignment:has(Identifier[name=providers])';

const selectedRouteNodes = tsquery(routesArrayNodes[0], ROUTE_SELECTOR, {
visitAllChildren: true,
});
if (selectedRouteNodes.length === 0) {
throw new Error(
`Could not find '${routeToAddProviderTo}' in routes definition.`
);
}

for (const selectedRouteNode of selectedRouteNodes) {
const routeProivdersNodes = tsquery(
selectedRouteNode,
ROUTE_PATH_PROVIDERS_SELECTOR,
{
visitAllChildren: true,
}
);

const routeText = selectedRouteNode.getText();
if (routeProivdersNodes.length === 0) {
const newFileContents = `${routesFileContents.slice(
0,
selectedRouteNode.getEnd() - 1
)}, providers: [${providerToAdd}]${routesFileContents.slice(
selectedRouteNode.getEnd() - 1,
routesFileContents.length
)}`;

tree.write(routesFile, newFileContents);
} else {
const newFileContents = `${routesFileContents.slice(
0,
routeProivdersNodes[0].getEnd() - 1
)}, ${providerToAdd}${routesFileContents.slice(
routeProivdersNodes[0].getEnd() - 1,
routesFileContents.length
)}`;

tree.write(routesFile, newFileContents);
}
}
}

0 comments on commit 4de2a9d

Please sign in to comment.