UNPKG

35 kBPlain TextView Raw
1import * as Handlebars from 'handlebars';
2import * as JSON5 from 'json5';
3import * as _ from 'lodash';
4import * as path from 'path';
5import { Project, ts, SourceFile, SyntaxKind, Node } from 'ts-morph';
6
7import FileEngine from '../app/engines/file.engine';
8import { RoutingGraphNode } from '../app/nodes/routing-graph-node';
9
10import ImportsUtil from './imports.util';
11import { logger } from './logger';
12
13const traverse = require('traverse');
14
15const ast = new Project();
16
17export class RouterParserUtil {
18 private routes: any[] = [];
19 private incompleteRoutes = [];
20 private modules = [];
21 private modulesTree;
22 private rootModule: string;
23 private cleanModulesTree;
24 private modulesWithRoutes = [];
25 private transformAngular8ImportSyntax =
26 /(['"]loadChildren['"]:)\(\)(:[^)]+?)?=>"import\((\\'|'|"|`)([^'"]+?)(\\'|'|"|`)\)\.then\(\(?\w+?\)?=>\S+?\.([^)]+?)\)(\\'|'|")/g;
27 private transformAngular8ImportSyntaxAsyncAwait =
28 /(['"]loadChildren['"]:)\(\)(:[^)]+?)?=>\("import\((\\'|'|"|`)([^'"]+?)(\\'|'|"|`)\)"\)\.['"]([^)]+?)['"]/g;
29
30 private static instance: RouterParserUtil;
31 private constructor() {}
32 public static getInstance() {
33 if (!RouterParserUtil.instance) {
34 RouterParserUtil.instance = new RouterParserUtil();
35 }
36 return RouterParserUtil.instance;
37 }
38
39 public addRoute(route): void {
40 this.routes.push(route);
41 this.routes = _.sortBy(_.uniqWith(this.routes, _.isEqual), ['name']);
42 }
43
44 public addIncompleteRoute(route): void {
45 this.incompleteRoutes.push(route);
46 this.incompleteRoutes = _.sortBy(_.uniqWith(this.incompleteRoutes, _.isEqual), ['name']);
47 }
48
49 public addModuleWithRoutes(moduleName, moduleImports, filename): void {
50 this.modulesWithRoutes.push({
51 name: moduleName,
52 importsNode: moduleImports,
53 filename: filename
54 });
55 this.modulesWithRoutes = _.sortBy(_.uniqWith(this.modulesWithRoutes, _.isEqual), ['name']);
56 }
57
58 public addModule(moduleName: string, moduleImports): void {
59 this.modules.push({
60 name: moduleName,
61 importsNode: moduleImports
62 });
63 this.modules = _.sortBy(_.uniqWith(this.modules, _.isEqual), ['name']);
64 }
65
66 public cleanRawRouteParsed(route: string): object {
67 let routesWithoutSpaces = route.replace(/ /gm, '');
68 const testTrailingComma = routesWithoutSpaces.indexOf('},]');
69 if (testTrailingComma !== -1) {
70 routesWithoutSpaces = routesWithoutSpaces.replace('},]', '}]');
71 }
72
73 routesWithoutSpaces = routesWithoutSpaces.replace(
74 this.transformAngular8ImportSyntax,
75 '$1"$4#$6"'
76 );
77
78 routesWithoutSpaces = routesWithoutSpaces.replace(
79 this.transformAngular8ImportSyntaxAsyncAwait,
80 '$1"$4#$6"'
81 );
82
83 return JSON5.parse(routesWithoutSpaces);
84 }
85
86 public cleanRawRoute(route: string): string {
87 let routesWithoutSpaces = route.replace(/ /gm, '');
88 let testTrailingComma = routesWithoutSpaces.indexOf('},]');
89 if (testTrailingComma !== -1) {
90 routesWithoutSpaces = routesWithoutSpaces.replace('},]', '}]');
91 }
92
93 routesWithoutSpaces = routesWithoutSpaces.replace(
94 this.transformAngular8ImportSyntax,
95 '$1"$4#$6"'
96 );
97
98 routesWithoutSpaces = routesWithoutSpaces.replace(
99 this.transformAngular8ImportSyntaxAsyncAwait,
100 '$1"$4#$6"'
101 );
102
103 return routesWithoutSpaces;
104 }
105
106 public setRootModule(module: string): void {
107 this.rootModule = module;
108 }
109
110 public hasRouterModuleInImports(imports: Array<any>): boolean {
111 for (let i = 0; i < imports.length; i++) {
112 if (
113 imports[i].name.indexOf('RouterModule.forChild') !== -1 ||
114 imports[i].name.indexOf('RouterModule.forRoot') !== -1 ||
115 imports[i].name.indexOf('RouterModule') !== -1
116 ) {
117 return true;
118 }
119 }
120
121 return false;
122 }
123
124 public fixIncompleteRoutes(miscellaneousVariables: Array<any>): void {
125 let matchingVariables = [];
126 // For each incompleteRoute, scan if one misc variable is in code
127 // if ok, try recreating complete route
128 for (let i = 0; i < this.incompleteRoutes.length; i++) {
129 for (let j = 0; j < miscellaneousVariables.length; j++) {
130 if (this.incompleteRoutes[i].data.indexOf(miscellaneousVariables[j].name) !== -1) {
131 console.log('found one misc var inside incompleteRoute');
132 console.log(miscellaneousVariables[j].name);
133 matchingVariables.push(miscellaneousVariables[j]);
134 }
135 }
136 // Clean incompleteRoute
137 this.incompleteRoutes[i].data = this.incompleteRoutes[i].data.replace('[', '');
138 this.incompleteRoutes[i].data = this.incompleteRoutes[i].data.replace(']', '');
139 }
140 }
141
142 public linkModulesAndRoutes(): void {
143 let i = 0;
144 let len = this.modulesWithRoutes.length;
145 for (i; i < len; i++) {
146 _.forEach(this.modulesWithRoutes[i].importsNode, (node: ts.PropertyDeclaration) => {
147 let initializer = node.initializer as ts.ArrayLiteralExpression;
148 if (initializer) {
149 if (initializer.elements) {
150 _.forEach(initializer.elements, (element: ts.CallExpression) => {
151 // find element with arguments
152 if (element.arguments) {
153 _.forEach(element.arguments, (argument: ts.Identifier) => {
154 _.forEach(this.routes, route => {
155 if (
156 argument.text &&
157 route.name === argument.text &&
158 route.filename === this.modulesWithRoutes[i].filename
159 ) {
160 route.module = this.modulesWithRoutes[i].name;
161 } else if (
162 argument.text &&
163 route.name === argument.text &&
164 route.filename !== this.modulesWithRoutes[i].filename
165 ) {
166 let argumentImportPath =
167 ImportsUtil.findFilePathOfImportedVariable(
168 argument.text,
169 this.modulesWithRoutes[i].filename
170 );
171
172 argumentImportPath = argumentImportPath
173 .replace(process.cwd() + path.sep, '')
174 .replace(/\\/g, '/');
175
176 if (
177 argument.text &&
178 route.name === argument.text &&
179 route.filename === argumentImportPath
180 ) {
181 route.module = this.modulesWithRoutes[i].name;
182 }
183 }
184 });
185 });
186 }
187 });
188 }
189 }
190 /**
191 * direct support of for example
192 * export const HomeRoutingModule: ModuleWithProviders = RouterModule.forChild(HOME_ROUTES);
193 */
194 if (ts.isCallExpression(node)) {
195 if (node.arguments) {
196 _.forEach(node.arguments, (argument: ts.Identifier) => {
197 _.forEach(this.routes, route => {
198 if (
199 argument.text &&
200 route.name === argument.text &&
201 route.filename === this.modulesWithRoutes[i].filename
202 ) {
203 route.module = this.modulesWithRoutes[i].name;
204 }
205 });
206 });
207 }
208 }
209 });
210 }
211 }
212
213 public foundRouteWithModuleName(moduleName: string): any {
214 return _.find(this.routes, { module: moduleName });
215 }
216
217 public foundLazyModuleWithPath(modulePath: string): string {
218 // path is like app/customers/customers.module#CustomersModule
219 let split = modulePath.split('#');
220 let lazyModulePath = split[0];
221 let lazyModuleName = split[1];
222 return lazyModuleName;
223 }
224
225 public constructRoutesTree() {
226 // routes[] contains routes with module link
227 // modulesTree contains modules tree
228 // make a final routes tree with that
229 traverse(this.modulesTree).forEach(function (node) {
230 if (node) {
231 if (node.parent) {
232 delete node.parent;
233 }
234 if (node.initializer) {
235 delete node.initializer;
236 }
237 if (node.importsNode) {
238 delete node.importsNode;
239 }
240 }
241 });
242
243 this.cleanModulesTree = _.cloneDeep(this.modulesTree);
244
245 let routesTree = {
246 name: '<root>',
247 kind: 'module',
248 className: this.rootModule,
249 children: []
250 };
251
252 let loopModulesParser = node => {
253 if (node.children && node.children.length > 0) {
254 // If module has child modules
255 for (let i in node.children) {
256 let route = this.foundRouteWithModuleName(node.children[i].name);
257 if (route && route.data) {
258 try {
259 route.children = JSON5.parse(route.data);
260 } catch (e) {
261 logger.error(
262 'Error during generation of routes JSON file, maybe a trailing comma or an external variable inside one route.'
263 );
264 }
265 delete route.data;
266 route.kind = 'module';
267 routesTree.children.push(route);
268 }
269 if (node.children[i].children) {
270 loopModulesParser(node.children[i]);
271 }
272 }
273 } else {
274 // else routes are directly inside the module
275 let rawRoutes = this.foundRouteWithModuleName(node.name);
276
277 if (rawRoutes) {
278 let routes = JSON5.parse(rawRoutes.data);
279 if (routes) {
280 let i = 0;
281 let len = routes.length;
282 let routeAddedOnce = false;
283 for (i; i < len; i++) {
284 let route = routes[i];
285 if (routes[i].component) {
286 routeAddedOnce = true;
287 routesTree.children.push({
288 kind: 'component',
289 component: routes[i].component,
290 path: routes[i].path
291 });
292 }
293 }
294 if (!routeAddedOnce) {
295 routesTree.children = [...routesTree.children, ...routes];
296 }
297 }
298 }
299 }
300 };
301
302 let startModule = _.find(this.cleanModulesTree, { name: this.rootModule });
303
304 if (startModule) {
305 loopModulesParser(startModule);
306 // Loop twice for routes with lazy loading
307 // loopModulesParser(routesTree);
308 }
309
310 let cleanedRoutesTree = undefined;
311
312 let cleanRoutesTree = route => {
313 for (let i in route.children) {
314 let routes = route.children[i].routes;
315 }
316 return route;
317 };
318
319 cleanedRoutesTree = cleanRoutesTree(routesTree);
320
321 // Try updating routes with lazy loading
322
323 let loopInsideModule = (mod, _rawModule) => {
324 if (mod.children) {
325 for (let z in mod.children) {
326 let route = this.foundRouteWithModuleName(mod.children[z].name);
327 if (typeof route !== 'undefined') {
328 if (route.data) {
329 route.children = JSON5.parse(route.data);
330 delete route.data;
331 route.kind = 'module';
332 _rawModule.children.push(route);
333 }
334 }
335 }
336 } else {
337 let route = this.foundRouteWithModuleName(mod.name);
338 if (typeof route !== 'undefined') {
339 if (route.data) {
340 route.children = JSON5.parse(route.data);
341 delete route.data;
342 route.kind = 'module';
343 _rawModule.children.push(route);
344 }
345 }
346 }
347 };
348
349 let loopRoutesParser = route => {
350 if (route.children) {
351 for (let i in route.children) {
352 if (route.children[i].loadChildren) {
353 let child = this.foundLazyModuleWithPath(route.children[i].loadChildren);
354 let module: RoutingGraphNode = _.find(this.cleanModulesTree, {
355 name: child
356 });
357 if (module) {
358 let _rawModule: RoutingGraphNode = {};
359 _rawModule.kind = 'module';
360 _rawModule.children = [];
361 _rawModule.module = module.name;
362 loopInsideModule(module, _rawModule);
363
364 route.children[i].children = [];
365 route.children[i].children.push(_rawModule);
366 }
367 }
368 loopRoutesParser(route.children[i]);
369 }
370 }
371 };
372 loopRoutesParser(cleanedRoutesTree);
373
374 return cleanedRoutesTree;
375 }
376
377 public constructModulesTree(): void {
378 let getNestedChildren = (arr, parent?) => {
379 let out = [];
380 for (let i in arr) {
381 if (arr[i].parent === parent) {
382 let children = getNestedChildren(arr, arr[i].name);
383 if (children.length) {
384 arr[i].children = children;
385 }
386 out.push(arr[i]);
387 }
388 }
389 return out;
390 };
391
392 // Scan each module and add parent property
393 _.forEach(this.modules, firstLoopModule => {
394 _.forEach(firstLoopModule.importsNode, importNode => {
395 _.forEach(this.modules, module => {
396 if (module.name === importNode.name) {
397 module.parent = firstLoopModule.name;
398 }
399 });
400 });
401 });
402 this.modulesTree = getNestedChildren(this.modules);
403 }
404
405 public generateRoutesIndex(outputFolder: string, routes: Array<any>): Promise<void> {
406 return FileEngine.get(__dirname + '/../src/templates/partials/routes-index.hbs').then(
407 data => {
408 let template: any = Handlebars.compile(data);
409 let result = template({
410 routes: JSON.stringify(routes)
411 });
412 let testOutputDir = outputFolder.match(process.cwd());
413
414 if (testOutputDir && testOutputDir.length > 0) {
415 outputFolder = outputFolder.replace(process.cwd() + path.sep, '');
416 }
417
418 return FileEngine.write(
419 outputFolder + path.sep + '/js/routes/routes_index.js',
420 result
421 );
422 },
423 err => Promise.reject('Error during routes index generation')
424 );
425 }
426
427 public routesLength(): number {
428 let _n = 0;
429 let routesParser = route => {
430 if (typeof route.path !== 'undefined') {
431 _n += 1;
432 }
433 if (route.children) {
434 for (let j in route.children) {
435 routesParser(route.children[j]);
436 }
437 }
438 };
439
440 for (let i in this.routes) {
441 routesParser(this.routes[i]);
442 }
443
444 return _n;
445 }
446
447 public printRoutes(): void {
448 console.log('');
449 console.log('printRoutes: ');
450 console.log(this.routes);
451 }
452
453 public printModulesRoutes(): void {
454 console.log('');
455 console.log('printModulesRoutes: ');
456 console.log(this.modulesWithRoutes);
457 }
458
459 public isVariableRoutes(node) {
460 let result = false;
461 if (node.declarationList && node.declarationList.declarations) {
462 let i = 0;
463 let len = node.declarationList.declarations.length;
464 for (i; i < len; i++) {
465 if (node.declarationList.declarations[i].type) {
466 if (
467 node.declarationList.declarations[i].type.typeName &&
468 node.declarationList.declarations[i].type.typeName.text === 'Routes'
469 ) {
470 result = true;
471 }
472 }
473 }
474 }
475 return result;
476 }
477
478 public cleanFileIdentifiers(sourceFile: SourceFile): SourceFile {
479 let file = sourceFile;
480 const identifiers = file.getDescendantsOfKind(SyntaxKind.Identifier).filter(p => {
481 return (
482 Node.isArrayLiteralExpression(p.getParentOrThrow()) ||
483 Node.isPropertyAssignment(p.getParentOrThrow())
484 );
485 });
486
487 let identifiersInRoutesVariableStatement = [];
488
489 for (const identifier of identifiers) {
490 // Loop through their parents nodes, and if one is a variableStatement and === 'routes'
491 let foundParentVariableStatement = false;
492 let parent = identifier.getParentWhile(n => {
493 if (n.getKind() === SyntaxKind.VariableStatement) {
494 if (this.isVariableRoutes(n.compilerNode)) {
495 foundParentVariableStatement = true;
496 }
497 }
498 return true;
499 });
500 if (foundParentVariableStatement) {
501 identifiersInRoutesVariableStatement.push(identifier);
502 }
503 }
504
505 // inline the property access expressions
506 for (const identifier of identifiersInRoutesVariableStatement) {
507 const identifierDeclaration = identifier
508 .getSymbolOrThrow()
509 .getValueDeclarationOrThrow();
510 if (
511 !Node.isPropertyAssignment(identifierDeclaration) &&
512 Node.isVariableDeclaration(identifierDeclaration) &&
513 Node.isPropertyAssignment(identifierDeclaration) &&
514 !Node.isVariableDeclaration(identifierDeclaration)
515 ) {
516 throw new Error(
517 `Not implemented referenced declaration kind: ${identifierDeclaration.getKindName()}`
518 );
519 }
520 if (Node.isVariableDeclaration(identifierDeclaration)) {
521 identifier.replaceWithText(identifierDeclaration.getInitializerOrThrow().getText());
522 }
523 }
524
525 return file;
526 }
527
528 public cleanFileSpreads(sourceFile: SourceFile): SourceFile {
529 let file = sourceFile;
530 const spreadElements = file
531 .getDescendantsOfKind(SyntaxKind.SpreadElement)
532 .filter(p => Node.isArrayLiteralExpression(p.getParentOrThrow()));
533
534 let spreadElementsInRoutesVariableStatement = [];
535
536 for (const spreadElement of spreadElements) {
537 // Loop through their parents nodes, and if one is a variableStatement and === 'routes'
538 let foundParentVariableStatement = false;
539 let parent = spreadElement.getParentWhile(n => {
540 if (n.getKind() === SyntaxKind.VariableStatement) {
541 if (this.isVariableRoutes(n.compilerNode)) {
542 foundParentVariableStatement = true;
543 }
544 }
545 return true;
546 });
547 if (foundParentVariableStatement) {
548 spreadElementsInRoutesVariableStatement.push(spreadElement);
549 }
550 }
551
552 // inline the ArrayLiteralExpression SpreadElements
553 for (const spreadElement of spreadElementsInRoutesVariableStatement) {
554 let spreadElementIdentifier = spreadElement.getExpression().getText(),
555 searchedImport,
556 aliasOriginalName = '',
557 foundWithAliasInImports = false,
558 foundWithAlias = false;
559
560 // Try to find it in imports
561 const imports = file.getImportDeclarations();
562
563 imports.forEach(i => {
564 let namedImports = i.getNamedImports(),
565 namedImportsLength = namedImports.length,
566 j = 0;
567
568 if (namedImportsLength > 0) {
569 for (j; j < namedImportsLength; j++) {
570 let importName = namedImports[j].getNameNode().getText() as string,
571 importAlias;
572
573 if (namedImports[j].getAliasNode()) {
574 importAlias = namedImports[j].getAliasNode().getText();
575 }
576
577 if (importName === spreadElementIdentifier) {
578 foundWithAliasInImports = true;
579 searchedImport = i;
580 break;
581 }
582 if (importAlias === spreadElementIdentifier) {
583 foundWithAliasInImports = true;
584 foundWithAlias = true;
585 aliasOriginalName = importName;
586 searchedImport = i;
587 break;
588 }
589 }
590 }
591 });
592
593 let referencedDeclaration;
594
595 if (foundWithAliasInImports) {
596 if (typeof searchedImport !== 'undefined') {
597 const routePathIsBad = path => {
598 return typeof ast.getSourceFile(path) == 'undefined';
599 };
600
601 const getIndicesOf = (searchStr, str, caseSensitive) => {
602 var searchStrLen = searchStr.length;
603 if (searchStrLen == 0) {
604 return [];
605 }
606 var startIndex = 0,
607 index,
608 indices = [];
609 if (!caseSensitive) {
610 str = str.toLowerCase();
611 searchStr = searchStr.toLowerCase();
612 }
613 while ((index = str.indexOf(searchStr, startIndex)) > -1) {
614 indices.push(index);
615 startIndex = index + searchStrLen;
616 }
617 return indices;
618 };
619
620 const dirNamePath = path.dirname(file.getFilePath());
621 const searchedImportPath = searchedImport.getModuleSpecifierValue();
622 const leadingFilePath = searchedImportPath.split('/').shift();
623
624 let importPath = path.resolve(
625 dirNamePath + '/' + searchedImport.getModuleSpecifierValue() + '.ts'
626 );
627
628 if (routePathIsBad(importPath)) {
629 let leadingIndices = getIndicesOf(leadingFilePath, importPath, true);
630 if (leadingIndices.length > 1) {
631 // Nested route fixes
632 let startIndex = leadingIndices[0];
633 let endIndex = leadingIndices[leadingIndices.length - 1];
634 importPath =
635 importPath.slice(0, startIndex) + importPath.slice(endIndex);
636 } else {
637 // Top level route fixes
638 importPath =
639 path.dirname(dirNamePath) + '/' + searchedImportPath + '.ts';
640 }
641 }
642 const sourceFileImport =
643 typeof ast.getSourceFile(importPath) !== 'undefined'
644 ? ast.getSourceFile(importPath)
645 : ast.addSourceFileAtPath(importPath);
646 if (sourceFileImport) {
647 let variableName = foundWithAlias
648 ? aliasOriginalName
649 : spreadElementIdentifier;
650 referencedDeclaration =
651 sourceFileImport.getVariableDeclaration(variableName);
652 }
653 }
654 } else {
655 // if not, try directly in file
656 referencedDeclaration = spreadElement
657 .getExpression()
658 .getSymbolOrThrow()
659 .getValueDeclarationOrThrow();
660 }
661
662 if (!Node.isVariableDeclaration(referencedDeclaration)) {
663 throw new Error(
664 `Not implemented referenced declaration kind: ${referencedDeclaration.getKindName()}`
665 );
666 }
667
668 const referencedArray = referencedDeclaration.getInitializerIfKindOrThrow(
669 SyntaxKind.ArrayLiteralExpression
670 );
671 const spreadElementArray = spreadElement.getParentIfKindOrThrow(
672 SyntaxKind.ArrayLiteralExpression
673 );
674 const insertIndex = spreadElementArray.getElements().indexOf(spreadElement);
675 spreadElementArray.removeElement(spreadElement);
676 spreadElementArray.insertElements(
677 insertIndex,
678 referencedArray.getElements().map(e => e.getText())
679 );
680 }
681
682 return file;
683 }
684
685 public cleanFileDynamics(sourceFile: SourceFile): SourceFile {
686 let file = sourceFile;
687 const propertyAccessExpressions = file
688 .getDescendantsOfKind(SyntaxKind.PropertyAccessExpression)
689 .filter(p => !Node.isPropertyAccessExpression(p.getParentOrThrow()));
690
691 let propertyAccessExpressionsInRoutesVariableStatement = [];
692
693 for (const propertyAccessExpression of propertyAccessExpressions) {
694 // Loop through their parents nodes, and if one is a variableStatement and === 'routes'
695 let foundParentVariableStatement = false;
696 let parent = propertyAccessExpression.getParentWhile(n => {
697 if (n.getKind() === SyntaxKind.VariableStatement) {
698 if (this.isVariableRoutes(n.compilerNode)) {
699 foundParentVariableStatement = true;
700 }
701 }
702 return true;
703 });
704 if (foundParentVariableStatement) {
705 propertyAccessExpressionsInRoutesVariableStatement.push(propertyAccessExpression);
706 }
707 }
708
709 // inline the property access expressions
710 for (const propertyAccessExpression of propertyAccessExpressionsInRoutesVariableStatement) {
711 const propertyAccessExpressionNodeName = propertyAccessExpression.getNameNode();
712 if (propertyAccessExpressionNodeName) {
713 try {
714 const propertyAccessExpressionNodeNameSymbol =
715 propertyAccessExpressionNodeName.getSymbolOrThrow();
716 if (propertyAccessExpressionNodeNameSymbol) {
717 const referencedDeclaration =
718 propertyAccessExpressionNodeNameSymbol.getValueDeclarationOrThrow();
719 if (
720 !Node.isPropertyAssignment(referencedDeclaration) &&
721 Node.isEnumMember(referencedDeclaration) &&
722 Node.isPropertyAssignment(referencedDeclaration) &&
723 !Node.isEnumMember(referencedDeclaration)
724 ) {
725 throw new Error(
726 `Not implemented referenced declaration kind: ${referencedDeclaration.getKindName()}`
727 );
728 }
729 if (typeof referencedDeclaration.getInitializerOrThrow !== 'undefined') {
730 propertyAccessExpression.replaceWithText(
731 referencedDeclaration.getInitializerOrThrow().getText()
732 );
733 }
734 }
735 } catch (e) {}
736 }
737 }
738
739 return file;
740 }
741
742 /**
743 * replace callexpressions with string : utils.doWork() -> 'utils.doWork()' doWork() -> 'doWork()'
744 * @param sourceFile ts.SourceFile
745 */
746 public cleanCallExpressions(sourceFile: SourceFile): SourceFile {
747 let file = sourceFile;
748
749 const variableStatements = sourceFile.getVariableDeclaration(v => {
750 let result = false;
751 const type = v.compilerNode.type;
752 if (typeof type !== 'undefined' && typeof type.typeName !== 'undefined') {
753 result = type.typeName.text === 'Routes';
754 }
755 return result;
756 });
757
758 const initializer = variableStatements.getInitializer();
759
760 for (const callExpr of initializer.getDescendantsOfKind(SyntaxKind.CallExpression)) {
761 if (callExpr.wasForgotten()) {
762 continue;
763 }
764 callExpr.replaceWithText(writer => writer.quote(callExpr.getText()));
765 }
766
767 return file;
768 }
769
770 /**
771 * Clean routes definition with imported data, for example path, children, or dynamic stuff inside data
772 *
773 * const MY_ROUTES: Routes = [
774 * {
775 * path: 'home',
776 * component: HomeComponent
777 * },
778 * {
779 * path: PATHS.home,
780 * component: HomeComponent
781 * }
782 * ];
783 *
784 * The initializer is an array (ArrayLiteralExpression - 177 ), it has elements, objects (ObjectLiteralExpression - 178)
785 * with properties (PropertyAssignment - 261)
786 *
787 * For each know property (https://angular.io/api/router/Routes#description), we try to see if we have what we want
788 *
789 * Ex: path and pathMatch want a string, component a component reference.
790 *
791 * It is an imperative approach, not a generic way, parsing all the tree
792 * and find something like this which willl break JSON.stringify : MYIMPORT.path
793 *
794 * @param {ts.Node} initializer The node of routes definition
795 * @return {ts.Node} The edited node
796 */
797 public cleanRoutesDefinitionWithImport(
798 initializer: ts.ArrayLiteralExpression,
799 node: ts.Node,
800 sourceFile: ts.SourceFile
801 ): ts.Node {
802 initializer.elements.forEach((element: ts.ObjectLiteralExpression) => {
803 element.properties.forEach((property: ts.PropertyAssignment) => {
804 let propertyName = property.name.getText(),
805 propertyInitializer = property.initializer;
806 switch (propertyName) {
807 case 'path':
808 case 'redirectTo':
809 case 'outlet':
810 case 'pathMatch':
811 if (propertyInitializer) {
812 if (propertyInitializer.kind !== SyntaxKind.StringLiteral) {
813 // Identifier(71) won't break parsing, but it will be better to retrive them
814 // PropertyAccessExpression(179) ex: MYIMPORT.path will break it, find it in import
815 if (
816 propertyInitializer.kind === SyntaxKind.PropertyAccessExpression
817 ) {
818 let lastObjectLiteralAttributeName =
819 propertyInitializer.name.getText(),
820 firstObjectLiteralAttributeName;
821 if (propertyInitializer.expression) {
822 firstObjectLiteralAttributeName =
823 propertyInitializer.expression.getText();
824 let result =
825 ImportsUtil.findPropertyValueInImportOrLocalVariables(
826 firstObjectLiteralAttributeName +
827 '.' +
828 lastObjectLiteralAttributeName,
829 sourceFile
830 ); // tslint:disable-line
831 if (result !== '') {
832 propertyInitializer.kind = 9;
833 propertyInitializer.text = result;
834 }
835 }
836 }
837 }
838 }
839 break;
840 }
841 });
842 });
843 return initializer;
844 }
845}
846
847export default RouterParserUtil.getInstance();