|
| 1 | +/** |
| 2 | + * @license |
| 3 | + * Copyright Google LLC All Rights Reserved. |
| 4 | + * |
| 5 | + * Use of this source code is governed by an MIT-style license that can be |
| 6 | + * found in the LICENSE file at https://angular.io/license |
| 7 | + */ |
| 8 | + |
| 9 | +import {bold, red} from 'chalk'; |
| 10 | +import {ProgramAwareRuleWalker, RuleFailure, Rules} from 'tslint'; |
| 11 | +import * as ts from 'typescript'; |
| 12 | +import { |
| 13 | + convertSpeedFactorToDuration, |
| 14 | + createSpeedFactorConvertExpression, |
| 15 | +} from './ripple-speed-factor'; |
| 16 | + |
| 17 | +/** |
| 18 | + * Note that will be added whenever a speed factor expression has been converted to calculate |
| 19 | + * the according duration. This note should encourage people to clean up their code by switching |
| 20 | + * away from the speed factors to explicit durations. |
| 21 | + */ |
| 22 | +const removeNote = `TODO: Cleanup duration calculation.`; |
| 23 | + |
| 24 | +/** |
| 25 | + * Rule that walks through every property assignment and switches the global `baseSpeedFactor` |
| 26 | + * ripple option to the new global animation config. Also updates every class member assignment |
| 27 | + * that refers to MatRipple#speedFactor. |
| 28 | + */ |
| 29 | +export class Rule extends Rules.TypedRule { |
| 30 | + applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] { |
| 31 | + return this.applyWithWalker(new Walker(sourceFile, this.getOptions(), program)); |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +export class Walker extends ProgramAwareRuleWalker { |
| 36 | + |
| 37 | + /** Switches binary expressions (e.g. myRipple.speedFactor = 0.5) to the new animation config. */ |
| 38 | + visitBinaryExpression(expression: ts.BinaryExpression) { |
| 39 | + if (!ts.isPropertyAccessExpression(expression.left)) { |
| 40 | + return; |
| 41 | + } |
| 42 | + |
| 43 | + // Left side expression consists of target object and property name (e.g. myInstance.val) |
| 44 | + const leftExpression = expression.left as ts.PropertyAccessExpression; |
| 45 | + const targetTypeNode = this.getTypeChecker().getTypeAtLocation(leftExpression.expression); |
| 46 | + |
| 47 | + if (!targetTypeNode.symbol) { |
| 48 | + return; |
| 49 | + } |
| 50 | + |
| 51 | + const targetTypeName = targetTypeNode.symbol.getName(); |
| 52 | + const propertyName = leftExpression.name.getText(); |
| 53 | + |
| 54 | + if (targetTypeName === 'MatRipple' && propertyName === 'speedFactor') { |
| 55 | + if (ts.isNumericLiteral(expression.right)) { |
| 56 | + const numericValue = parseFloat(expression.right.text); |
| 57 | + const newEnterDurationValue = convertSpeedFactorToDuration(numericValue); |
| 58 | + |
| 59 | + // Replace the `speedFactor` property name with `animation`. |
| 60 | + const propertyNameReplacement = this.createReplacement(leftExpression.name.getStart(), |
| 61 | + leftExpression.name.getWidth(), 'animation'); |
| 62 | + |
| 63 | + // Replace the value assignment with the new animation config. |
| 64 | + const rightExpressionReplacement = this.createReplacement(expression.right.getStart(), |
| 65 | + expression.right.getWidth(), `{enterDuration: ${newEnterDurationValue}}`); |
| 66 | + |
| 67 | + this.addFailureAtNode(expression, |
| 68 | + `Found deprecated variable assignment for "${bold('MatRipple')}#${red('speedFactor')}"`, |
| 69 | + [propertyNameReplacement, rightExpressionReplacement]); |
| 70 | + } else { |
| 71 | + // Handle the right expression differently if the previous speed factor value can't |
| 72 | + // be resolved statically. In that case, we just create a TypeScript expression that |
| 73 | + // calculates the explicit duration based on the non-static speed factor expression. |
| 74 | + const newExpression = createSpeedFactorConvertExpression(expression.right.getText()); |
| 75 | + |
| 76 | + // Replace the `speedFactor` property name with `animation`. |
| 77 | + const propertyNameReplacement = this.createReplacement(leftExpression.name.getStart(), |
| 78 | + leftExpression.name.getWidth(), 'animation'); |
| 79 | + |
| 80 | + // Replace the value assignment with the new animation config and remove TODO. |
| 81 | + const rightExpressionReplacement = this.createReplacement(expression.right.getStart(), |
| 82 | + expression.right.getWidth(), `/** ${removeNote} */ {enterDuration: ${newExpression}}`); |
| 83 | + |
| 84 | + this.addFailureAtNode(expression, |
| 85 | + `Found deprecated variable assignment for "${bold('MatRipple')}#${red('speedFactor')}"`, |
| 86 | + [propertyNameReplacement, rightExpressionReplacement]); |
| 87 | + } |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + /** |
| 92 | + * Switches a potential global option `baseSpeedFactor` to the new animation config. For this |
| 93 | + * we assume that the `baseSpeedFactor` is not used in combination with individual speed factors. |
| 94 | + */ |
| 95 | + visitPropertyAssignment(assignment: ts.PropertyAssignment) { |
| 96 | + // For switching the `baseSpeedFactor` global option we expect the property assignment |
| 97 | + // to be inside of a normal object literal. Custom ripple global options cannot be switched |
| 98 | + // automatically. |
| 99 | + if (!ts.isObjectLiteralExpression(assignment.parent)) { |
| 100 | + return; |
| 101 | + } |
| 102 | + |
| 103 | + // The assignment consists of a name (key) and initializer (value). |
| 104 | + if (assignment.name.getText() !== 'baseSpeedFactor') { |
| 105 | + return; |
| 106 | + } |
| 107 | + |
| 108 | + // We could technically lazily check for the MAT_RIPPLE_GLOBAL_OPTIONS injection token to |
| 109 | + // be present, but it's not right to assume that everyone sets the ripple global options |
| 110 | + // immediately in the provider object (e.g. it can happen that someone just imports the |
| 111 | + // config from a separate file). |
| 112 | + |
| 113 | + const {initializer, name} = assignment; |
| 114 | + |
| 115 | + if (ts.isNumericLiteral(initializer)) { |
| 116 | + const numericValue = parseFloat(initializer.text); |
| 117 | + const newEnterDurationValue = convertSpeedFactorToDuration(numericValue); |
| 118 | + |
| 119 | + const keyNameReplacement = this.createReplacement(name.getStart(), |
| 120 | + assignment.name.getWidth(), `animation`); |
| 121 | + |
| 122 | + const initializerReplacement = this.createReplacement(initializer.getStart(), |
| 123 | + initializer.getWidth(), `{enterDuration: ${newEnterDurationValue}}`); |
| 124 | + |
| 125 | + this.addFailureAtNode(assignment, |
| 126 | + `Found deprecated property assignment for "${bold('MAT_RIPPLE_GLOBAL_OPTIONS')}:` + |
| 127 | + `${red('baseSpeedFactor')}"`, |
| 128 | + [keyNameReplacement, initializerReplacement]); |
| 129 | + } else { |
| 130 | + // Handle the right expression differently if the previous speed factor value can't |
| 131 | + // be resolved statically. In that case, we just create a TypeScript expression that |
| 132 | + // calculates the explicit duration based on the non-static speed factor expression. |
| 133 | + const newExpression = createSpeedFactorConvertExpression(initializer.getText()); |
| 134 | + |
| 135 | + // Replace the `baseSpeedFactor` property name with `animation`. |
| 136 | + const propertyNameReplacement = this.createReplacement(name.getStart(), |
| 137 | + name.getWidth(), 'animation'); |
| 138 | + |
| 139 | + // Replace the value assignment with the new animation config and remove TODO. |
| 140 | + const rightExpressionReplacement = this.createReplacement(initializer.getStart(), |
| 141 | + initializer.getWidth(), `/** ${removeNote} */ {enterDuration: ${newExpression}}`); |
| 142 | + |
| 143 | + this.addFailureAtNode(assignment, |
| 144 | + `Found a deprecated property assignment for "${bold('MAT_RIPPLE_GLOBAL_OPTIONS')}:` + |
| 145 | + `${red('baseSpeedFactor')}.`, |
| 146 | + [propertyNameReplacement, rightExpressionReplacement]); |
| 147 | + } |
| 148 | + } |
| 149 | +} |
0 commit comments