Moves the `renderer_to_renderer2` migration google3 tslint rule into the new `google3` directory. This is done for consistency as we recently moved all google3 migration rules into a new `google3` folder (see: f69e4e6f770907f6811de88e52692df1a3d2f43e). PR Close #31817
		
			
				
	
	
		
			140 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			140 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright Google Inc. All Rights Reserved.
 | |
|  *
 | |
|  * 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';
 | |
| 
 | |
| import {HelperFunction, getHelper} from '../renderer-to-renderer2/helpers';
 | |
| import {migrateExpression, replaceImport} from '../renderer-to-renderer2/migration';
 | |
| import {findCoreImport, findRendererReferences} from '../renderer-to-renderer2/util';
 | |
| 
 | |
| /**
 | |
|  * 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[] = [];
 | |
|     const rendererImport = findCoreImport(sourceFile, 'Renderer');
 | |
| 
 | |
|     // If there are no imports for the `Renderer`, we can exit early.
 | |
|     if (!rendererImport) {
 | |
|       return failures;
 | |
|     }
 | |
| 
 | |
|     const {typedNodes, methodCalls, forwardRefs} =
 | |
|         findRendererReferences(sourceFile, typeChecker, rendererImport);
 | |
|     const helpersToAdd = new Set<HelperFunction>();
 | |
| 
 | |
|     failures.push(this._getNamedImportsFailure(rendererImport, sourceFile, printer));
 | |
|     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(
 | |
|       node: ts.NamedImports, sourceFile: ts.SourceFile, printer: ts.Printer): RuleFailure {
 | |
|     const replacementText = printer.printNode(
 | |
|         ts.EmitHint.Unspecified, replaceImport(node, 'Renderer', 'Renderer2'), sourceFile);
 | |
| 
 | |
|     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 {
 | |
|     const type = node.type !;
 | |
| 
 | |
|     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);
 | |
|   }
 | |
| }
 |