| 
									
										
										
										
											2019-06-09 15:38:18 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							| 
									
										
										
										
											2020-05-19 12:08:49 -07:00
										 |  |  |  * Copyright Google LLC All Rights Reserved. | 
					
						
							| 
									
										
										
										
											2019-06-09 15:38:18 +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 {Replacement, RuleFailure, Rules} from 'tslint'; | 
					
						
							|  |  |  | import * as ts from 'typescript'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-12 18:07:34 +02:00
										 |  |  | import {getImportSpecifier, replaceImport} from '../../utils/typescript/imports'; | 
					
						
							|  |  |  | import {closestNode} from '../../utils/typescript/nodes'; | 
					
						
							| 
									
										
										
										
											2020-04-13 16:40:21 -07:00
										 |  |  | import {getHelper, HelperFunction} from '../renderer-to-renderer2/helpers'; | 
					
						
							| 
									
										
										
										
											2020-10-12 18:07:34 +02:00
										 |  |  | import {migrateExpression} from '../renderer-to-renderer2/migration'; | 
					
						
							|  |  |  | import {findRendererReferences} from '../renderer-to-renderer2/util'; | 
					
						
							| 
									
										
										
										
											2019-06-09 15:38:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * TSLint rule that migrates from `Renderer` to `Renderer2`. More information on how it works: | 
					
						
							|  |  |  |  * https://hackmd.angular.io/UTzUZTnPRA-cSa_4mHyfYw
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export class Rule extends Rules.TypedRule { | 
					
						
							|  |  |  |   applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] { | 
					
						
							|  |  |  |     const typeChecker = program.getTypeChecker(); | 
					
						
							|  |  |  |     const printer = ts.createPrinter(); | 
					
						
							|  |  |  |     const failures: RuleFailure[] = []; | 
					
						
							| 
									
										
										
										
											2020-09-12 14:33:06 +02:00
										 |  |  |     const rendererImportSpecifier = getImportSpecifier(sourceFile, '@angular/core', 'Renderer'); | 
					
						
							| 
									
										
										
										
											2020-10-12 18:07:34 +02:00
										 |  |  |     const rendererImport = rendererImportSpecifier ? | 
					
						
							|  |  |  |         closestNode<ts.NamedImports>(rendererImportSpecifier, ts.SyntaxKind.NamedImports) : | 
					
						
							|  |  |  |         null; | 
					
						
							| 
									
										
										
										
											2019-06-09 15:38:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // If there are no imports for the `Renderer`, we can exit early.
 | 
					
						
							| 
									
										
										
										
											2020-09-12 14:33:06 +02:00
										 |  |  |     if (!rendererImportSpecifier || !rendererImport) { | 
					
						
							| 
									
										
										
										
											2019-06-09 15:38:18 +02:00
										 |  |  |       return failures; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const {typedNodes, methodCalls, forwardRefs} = | 
					
						
							| 
									
										
										
										
											2020-09-12 14:33:06 +02:00
										 |  |  |         findRendererReferences(sourceFile, typeChecker, rendererImportSpecifier); | 
					
						
							| 
									
										
										
										
											2019-06-09 15:38:18 +02:00
										 |  |  |     const helpersToAdd = new Set<HelperFunction>(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-12 18:07:34 +02:00
										 |  |  |     failures.push(this._getNamedImportsFailure(rendererImport, sourceFile, printer)); | 
					
						
							| 
									
										
										
										
											2019-06-09 15:38:18 +02:00
										 |  |  |     typedNodes.forEach(node => failures.push(this._getTypedNodeFailure(node, sourceFile))); | 
					
						
							|  |  |  |     forwardRefs.forEach(node => failures.push(this._getIdentifierNodeFailure(node, sourceFile))); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     methodCalls.forEach(call => { | 
					
						
							|  |  |  |       const {failure, requiredHelpers} = | 
					
						
							|  |  |  |           this._getMethodCallFailure(call, sourceFile, typeChecker, printer); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       failures.push(failure); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (requiredHelpers) { | 
					
						
							|  |  |  |         requiredHelpers.forEach(helperName => helpersToAdd.add(helperName)); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Some of the methods can't be mapped directly to `Renderer2` and need extra logic around them.
 | 
					
						
							|  |  |  |     // The safest way to do so is to declare helper functions similar to the ones emitted by TS
 | 
					
						
							|  |  |  |     // which encapsulate the extra "glue" logic. We should only emit these functions once per
 | 
					
						
							|  |  |  |     // file and only if they're needed.
 | 
					
						
							|  |  |  |     if (helpersToAdd.size) { | 
					
						
							|  |  |  |       failures.push(this._getHelpersFailure(helpersToAdd, sourceFile, printer)); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return failures; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** Gets a failure for an import of the Renderer. */ | 
					
						
							|  |  |  |   private _getNamedImportsFailure( | 
					
						
							| 
									
										
										
										
											2020-10-12 18:07:34 +02:00
										 |  |  |       node: ts.NamedImports, sourceFile: ts.SourceFile, printer: ts.Printer): RuleFailure { | 
					
						
							| 
									
										
										
										
											2019-06-09 15:38:18 +02:00
										 |  |  |     const replacementText = printer.printNode( | 
					
						
							| 
									
										
										
										
											2020-10-12 18:07:34 +02:00
										 |  |  |         ts.EmitHint.Unspecified, replaceImport(node, 'Renderer', 'Renderer2'), sourceFile); | 
					
						
							| 
									
										
										
										
											2019-06-09 15:38:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return new RuleFailure( | 
					
						
							|  |  |  |         sourceFile, node.getStart(), node.getEnd(), | 
					
						
							|  |  |  |         'Imports of deprecated Renderer are not allowed. Please use Renderer2 instead.', | 
					
						
							|  |  |  |         this.ruleName, new Replacement(node.getStart(), node.getWidth(), replacementText)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** Gets a failure for a typed node (e.g. function parameter or property). */ | 
					
						
							|  |  |  |   private _getTypedNodeFailure( | 
					
						
							|  |  |  |       node: ts.ParameterDeclaration|ts.PropertyDeclaration|ts.AsExpression, | 
					
						
							|  |  |  |       sourceFile: ts.SourceFile): RuleFailure { | 
					
						
							| 
									
										
										
										
											2020-04-13 16:40:21 -07:00
										 |  |  |     const type = node.type!; | 
					
						
							| 
									
										
										
										
											2019-06-09 15:38:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return new RuleFailure( | 
					
						
							|  |  |  |         sourceFile, type.getStart(), type.getEnd(), | 
					
						
							|  |  |  |         'References to deprecated Renderer are not allowed. Please use Renderer2 instead.', | 
					
						
							|  |  |  |         this.ruleName, new Replacement(type.getStart(), type.getWidth(), 'Renderer2')); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** Gets a failure for an identifier node. */ | 
					
						
							|  |  |  |   private _getIdentifierNodeFailure(node: ts.Identifier, sourceFile: ts.SourceFile): RuleFailure { | 
					
						
							|  |  |  |     return new RuleFailure( | 
					
						
							|  |  |  |         sourceFile, node.getStart(), node.getEnd(), | 
					
						
							|  |  |  |         'References to deprecated Renderer are not allowed. Please use Renderer2 instead.', | 
					
						
							|  |  |  |         this.ruleName, new Replacement(node.getStart(), node.getWidth(), 'Renderer2')); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** Gets a failure for a Renderer method call. */ | 
					
						
							|  |  |  |   private _getMethodCallFailure( | 
					
						
							|  |  |  |       call: ts.CallExpression, sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker, | 
					
						
							|  |  |  |       printer: ts.Printer): {failure: RuleFailure, requiredHelpers?: HelperFunction[]} { | 
					
						
							|  |  |  |     const {node, requiredHelpers} = migrateExpression(call, typeChecker); | 
					
						
							|  |  |  |     let fix: Replacement|undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (node) { | 
					
						
							|  |  |  |       // If we migrated the node to a new expression, replace only the call expression.
 | 
					
						
							|  |  |  |       fix = new Replacement( | 
					
						
							|  |  |  |           call.getStart(), call.getWidth(), | 
					
						
							|  |  |  |           printer.printNode(ts.EmitHint.Unspecified, node, sourceFile)); | 
					
						
							|  |  |  |     } else if (call.parent && ts.isExpressionStatement(call.parent)) { | 
					
						
							|  |  |  |       // Otherwise if the call is inside an expression statement, drop the entire statement.
 | 
					
						
							|  |  |  |       // This takes care of any trailing semicolons. We only need to drop nodes for cases like
 | 
					
						
							|  |  |  |       // `setBindingDebugInfo` which have been noop for a while so they can be removed safely.
 | 
					
						
							|  |  |  |       fix = new Replacement(call.parent.getStart(), call.parent.getWidth(), ''); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       failure: new RuleFailure( | 
					
						
							|  |  |  |           sourceFile, call.getStart(), call.getEnd(), 'Calls to Renderer methods are not allowed', | 
					
						
							|  |  |  |           this.ruleName, fix), | 
					
						
							|  |  |  |       requiredHelpers | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   /** Gets a failure that inserts the required helper functions at the bottom of the file. */ | 
					
						
							|  |  |  |   private _getHelpersFailure( | 
					
						
							|  |  |  |       helpersToAdd: Set<HelperFunction>, sourceFile: ts.SourceFile, | 
					
						
							|  |  |  |       printer: ts.Printer): RuleFailure { | 
					
						
							|  |  |  |     const helpers: Replacement[] = []; | 
					
						
							|  |  |  |     const endOfFile = sourceFile.endOfFileToken; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     helpersToAdd.forEach(helperName => { | 
					
						
							|  |  |  |       helpers.push(new Replacement( | 
					
						
							|  |  |  |           endOfFile.getStart(), endOfFile.getWidth(), getHelper(helperName, sourceFile, printer))); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Add a failure at the end of the file which we can use as an anchor to insert the helpers.
 | 
					
						
							|  |  |  |     return new RuleFailure( | 
					
						
							|  |  |  |         sourceFile, endOfFile.getStart(), endOfFile.getStart() + 1, | 
					
						
							|  |  |  |         'File should contain Renderer helper functions. Run tslint with --fix to generate them.', | 
					
						
							|  |  |  |         this.ruleName, helpers); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |