| 
									
										
										
										
											2019-06-10 19:08:54 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							| 
									
										
										
										
											2020-05-19 12:08:49 -07:00
										 |  |  |  * Copyright Google LLC All Rights Reserved. | 
					
						
							| 
									
										
										
										
											2019-06-10 19:08:54 +02:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Use of this source code is governed by an MIT-style license that can be | 
					
						
							|  |  |  |  * found in the LICENSE file at https://angular.io/license
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics'; | 
					
						
							| 
									
										
										
										
											2020-04-02 11:01:43 +02:00
										 |  |  | import {relative} from 'path'; | 
					
						
							| 
									
										
										
										
											2019-06-10 19:08:54 +02:00
										 |  |  | import * as ts from 'typescript'; | 
					
						
							|  |  |  | import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths'; | 
					
						
							| 
									
										
										
										
											2020-11-20 20:25:30 +01:00
										 |  |  | import {canMigrateFile, createMigrationProgram} from '../../utils/typescript/compiler_host'; | 
					
						
							| 
									
										
										
										
											2019-10-05 23:41:37 +02:00
										 |  |  | import {NgDefinitionCollector} from './definition_collector'; | 
					
						
							| 
									
										
										
										
											2019-06-10 19:08:54 +02:00
										 |  |  | import {MissingInjectableTransform} from './transform'; | 
					
						
							|  |  |  | import {UpdateRecorder} from './update_recorder'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** Entry point for the V9 "missing @Injectable" schematic. */ | 
					
						
							|  |  |  | export default function(): Rule { | 
					
						
							|  |  |  |   return (tree: Tree, ctx: SchematicContext) => { | 
					
						
							|  |  |  |     const {buildPaths, testPaths} = getProjectTsConfigPaths(tree); | 
					
						
							|  |  |  |     const basePath = process.cwd(); | 
					
						
							|  |  |  |     const failures: string[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!buildPaths.length && !testPaths.length) { | 
					
						
							|  |  |  |       throw new SchematicsException( | 
					
						
							|  |  |  |           'Could not find any tsconfig file. Cannot add the "@Injectable" decorator to providers ' + | 
					
						
							|  |  |  |           'which don\'t have that decorator set.'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (const tsconfigPath of [...buildPaths, ...testPaths]) { | 
					
						
							|  |  |  |       failures.push(...runMissingInjectableMigration(tree, tsconfigPath, basePath)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (failures.length) { | 
					
						
							|  |  |  |       ctx.logger.info('Could not migrate all providers automatically. Please'); | 
					
						
							|  |  |  |       ctx.logger.info('manually migrate the following instances:'); | 
					
						
							|  |  |  |       failures.forEach(message => ctx.logger.warn(`⮑   ${message}`)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | function runMissingInjectableMigration( | 
					
						
							|  |  |  |     tree: Tree, tsconfigPath: string, basePath: string): string[] { | 
					
						
							| 
									
										
										
										
											2020-04-02 11:01:43 +02:00
										 |  |  |   const {program} = createMigrationProgram(tree, tsconfigPath, basePath); | 
					
						
							| 
									
										
										
										
											2019-06-10 19:08:54 +02:00
										 |  |  |   const failures: string[] = []; | 
					
						
							|  |  |  |   const typeChecker = program.getTypeChecker(); | 
					
						
							| 
									
										
										
										
											2019-10-05 23:41:37 +02:00
										 |  |  |   const definitionCollector = new NgDefinitionCollector(typeChecker); | 
					
						
							| 
									
										
										
										
											2020-11-20 20:25:30 +01:00
										 |  |  |   const sourceFiles = | 
					
						
							|  |  |  |       program.getSourceFiles().filter(sourceFile => canMigrateFile(basePath, sourceFile, program)); | 
					
						
							| 
									
										
										
										
											2019-06-10 19:08:54 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-05 23:41:37 +02:00
										 |  |  |   // Analyze source files by detecting all modules, directives and components.
 | 
					
						
							|  |  |  |   sourceFiles.forEach(sourceFile => definitionCollector.visitNode(sourceFile)); | 
					
						
							| 
									
										
										
										
											2019-06-10 19:08:54 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-05 23:41:37 +02:00
										 |  |  |   const {resolvedModules, resolvedDirectives} = definitionCollector; | 
					
						
							| 
									
										
										
										
											2019-06-10 19:08:54 +02:00
										 |  |  |   const transformer = new MissingInjectableTransform(typeChecker, getUpdateRecorder); | 
					
						
							|  |  |  |   const updateRecorders = new Map<ts.SourceFile, UpdateRecorder>(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-10-05 23:41:37 +02:00
										 |  |  |   [...transformer.migrateModules(resolvedModules), | 
					
						
							|  |  |  |    ...transformer.migrateDirectives(resolvedDirectives), | 
					
						
							|  |  |  |   ].forEach(({message, node}) => { | 
					
						
							|  |  |  |     const nodeSourceFile = node.getSourceFile(); | 
					
						
							|  |  |  |     const relativeFilePath = relative(basePath, nodeSourceFile.fileName); | 
					
						
							|  |  |  |     const {line, character} = | 
					
						
							|  |  |  |         ts.getLineAndCharacterOfPosition(node.getSourceFile(), node.getStart()); | 
					
						
							|  |  |  |     failures.push(`${relativeFilePath}@${line + 1}:${character + 1}: ${message}`); | 
					
						
							| 
									
										
										
										
											2019-06-10 19:08:54 +02:00
										 |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Record the changes collected in the import manager and transformer.
 | 
					
						
							|  |  |  |   transformer.recordChanges(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Walk through each update recorder and commit the update. We need to commit the
 | 
					
						
							|  |  |  |   // updates in batches per source file as there can be only one recorder per source
 | 
					
						
							|  |  |  |   // file in order to avoid shift character offsets.
 | 
					
						
							|  |  |  |   updateRecorders.forEach(recorder => recorder.commitUpdate()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return failures; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** Gets the update recorder for the specified source file. */ | 
					
						
							|  |  |  |   function getUpdateRecorder(sourceFile: ts.SourceFile): UpdateRecorder { | 
					
						
							|  |  |  |     if (updateRecorders.has(sourceFile)) { | 
					
						
							| 
									
										
										
										
											2020-04-02 11:01:43 +02:00
										 |  |  |       return updateRecorders.get(sourceFile)!; | 
					
						
							| 
									
										
										
										
											2019-06-10 19:08:54 +02:00
										 |  |  |     } | 
					
						
							|  |  |  |     const treeRecorder = tree.beginUpdate(relative(basePath, sourceFile.fileName)); | 
					
						
							|  |  |  |     const recorder: UpdateRecorder = { | 
					
						
							|  |  |  |       addClassDecorator(node: ts.ClassDeclaration, text: string) { | 
					
						
							|  |  |  |         // New imports should be inserted at the left while decorators should be inserted
 | 
					
						
							|  |  |  |         // at the right in order to ensure that imports are inserted before the decorator
 | 
					
						
							|  |  |  |         // if the start position of import and decorator is the source file start.
 | 
					
						
							|  |  |  |         treeRecorder.insertRight(node.getStart(), `${text}\n`); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       replaceDecorator(decorator: ts.Decorator, newText: string) { | 
					
						
							|  |  |  |         treeRecorder.remove(decorator.getStart(), decorator.getWidth()); | 
					
						
							|  |  |  |         treeRecorder.insertRight(decorator.getStart(), newText); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       addNewImport(start: number, importText: string) { | 
					
						
							|  |  |  |         // New imports should be inserted at the left while decorators should be inserted
 | 
					
						
							|  |  |  |         // at the right in order to ensure that imports are inserted before the decorator
 | 
					
						
							|  |  |  |         // if the start position of import and decorator is the source file start.
 | 
					
						
							|  |  |  |         treeRecorder.insertLeft(start, importText); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       updateExistingImport(namedBindings: ts.NamedImports, newNamedBindings: string) { | 
					
						
							|  |  |  |         treeRecorder.remove(namedBindings.getStart(), namedBindings.getWidth()); | 
					
						
							|  |  |  |         treeRecorder.insertRight(namedBindings.getStart(), newNamedBindings); | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2019-11-14 10:16:07 +01:00
										 |  |  |       updateObjectLiteral(node: ts.ObjectLiteralExpression, newText: string) { | 
					
						
							|  |  |  |         treeRecorder.remove(node.getStart(), node.getWidth()); | 
					
						
							|  |  |  |         treeRecorder.insertRight(node.getStart(), newText); | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2020-04-02 11:01:43 +02:00
										 |  |  |       commitUpdate() { | 
					
						
							|  |  |  |         tree.commitUpdate(treeRecorder); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2019-06-10 19:08:54 +02:00
										 |  |  |     }; | 
					
						
							|  |  |  |     updateRecorders.set(sourceFile, recorder); | 
					
						
							|  |  |  |     return recorder; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |