| 
 | 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.dev/license  | 
 | 7 | + */  | 
 | 8 | + | 
 | 9 | +/**  | 
 | 10 | + * @fileoverview This file contains transformers for miscellaneous Jasmine APIs that don't  | 
 | 11 | + * fit into other categories. This includes timer mocks (`jasmine.clock`), the `fail()`  | 
 | 12 | + * function, and configuration settings like `jasmine.DEFAULT_TIMEOUT_INTERVAL`. It also  | 
 | 13 | + * includes logic to identify and add TODO comments for unsupported Jasmine features.  | 
 | 14 | + */  | 
 | 15 | + | 
 | 16 | +import ts from '../../../third_party/github.com/Microsoft/TypeScript/lib/typescript';  | 
 | 17 | +import { createViCallExpression } from '../utils/ast-helpers';  | 
 | 18 | +import { getJasmineMethodName, isJasmineCallExpression } from '../utils/ast-validation';  | 
 | 19 | +import { addTodoComment } from '../utils/comment-helpers';  | 
 | 20 | +import { RefactorContext } from '../utils/refactor-context';  | 
 | 21 | + | 
 | 22 | +export function transformTimerMocks(  | 
 | 23 | +  node: ts.Node,  | 
 | 24 | +  { sourceFile, reporter }: RefactorContext,  | 
 | 25 | +): ts.Node {  | 
 | 26 | +  if (  | 
 | 27 | +    !ts.isCallExpression(node) ||  | 
 | 28 | +    !ts.isPropertyAccessExpression(node.expression) ||  | 
 | 29 | +    !ts.isIdentifier(node.expression.name)  | 
 | 30 | +  ) {  | 
 | 31 | +    return node;  | 
 | 32 | +  }  | 
 | 33 | + | 
 | 34 | +  const pae = node.expression;  | 
 | 35 | +  const clockCall = pae.expression;  | 
 | 36 | +  if (!isJasmineCallExpression(clockCall, 'clock')) {  | 
 | 37 | +    return node;  | 
 | 38 | +  }  | 
 | 39 | + | 
 | 40 | +  let newMethodName: string | undefined;  | 
 | 41 | +  switch (pae.name.text) {  | 
 | 42 | +    case 'install':  | 
 | 43 | +      newMethodName = 'useFakeTimers';  | 
 | 44 | +      break;  | 
 | 45 | +    case 'tick':  | 
 | 46 | +      newMethodName = 'advanceTimersByTime';  | 
 | 47 | +      break;  | 
 | 48 | +    case 'uninstall':  | 
 | 49 | +      newMethodName = 'useRealTimers';  | 
 | 50 | +      break;  | 
 | 51 | +    case 'mockDate':  | 
 | 52 | +      newMethodName = 'setSystemTime';  | 
 | 53 | +      break;  | 
 | 54 | +  }  | 
 | 55 | + | 
 | 56 | +  if (newMethodName) {  | 
 | 57 | +    reporter.reportTransformation(  | 
 | 58 | +      sourceFile,  | 
 | 59 | +      node,  | 
 | 60 | +      `Transformed \`jasmine.clock().${pae.name.text}\` to \`vi.${newMethodName}\`.`,  | 
 | 61 | +    );  | 
 | 62 | +    const newArgs = newMethodName === 'useFakeTimers' ? [] : node.arguments;  | 
 | 63 | + | 
 | 64 | +    return createViCallExpression(newMethodName, newArgs);  | 
 | 65 | +  }  | 
 | 66 | + | 
 | 67 | +  return node;  | 
 | 68 | +}  | 
 | 69 | + | 
 | 70 | +export function transformFail(node: ts.Node, { sourceFile, reporter }: RefactorContext): ts.Node {  | 
 | 71 | +  if (  | 
 | 72 | +    ts.isExpressionStatement(node) &&  | 
 | 73 | +    ts.isCallExpression(node.expression) &&  | 
 | 74 | +    ts.isIdentifier(node.expression.expression) &&  | 
 | 75 | +    node.expression.expression.text === 'fail'  | 
 | 76 | +  ) {  | 
 | 77 | +    reporter.reportTransformation(sourceFile, node, 'Transformed `fail()` to `throw new Error()`.');  | 
 | 78 | +    const reason = node.expression.arguments[0];  | 
 | 79 | + | 
 | 80 | +    return ts.factory.createThrowStatement(  | 
 | 81 | +      ts.factory.createNewExpression(  | 
 | 82 | +        ts.factory.createIdentifier('Error'),  | 
 | 83 | +        undefined,  | 
 | 84 | +        reason ? [reason] : [],  | 
 | 85 | +      ),  | 
 | 86 | +    );  | 
 | 87 | +  }  | 
 | 88 | + | 
 | 89 | +  return node;  | 
 | 90 | +}  | 
 | 91 | + | 
 | 92 | +export function transformDefaultTimeoutInterval(  | 
 | 93 | +  node: ts.Node,  | 
 | 94 | +  { sourceFile, reporter }: RefactorContext,  | 
 | 95 | +): ts.Node {  | 
 | 96 | +  if (  | 
 | 97 | +    ts.isExpressionStatement(node) &&  | 
 | 98 | +    ts.isBinaryExpression(node.expression) &&  | 
 | 99 | +    node.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken  | 
 | 100 | +  ) {  | 
 | 101 | +    const assignment = node.expression;  | 
 | 102 | +    if (  | 
 | 103 | +      ts.isPropertyAccessExpression(assignment.left) &&  | 
 | 104 | +      ts.isIdentifier(assignment.left.expression) &&  | 
 | 105 | +      assignment.left.expression.text === 'jasmine' &&  | 
 | 106 | +      assignment.left.name.text === 'DEFAULT_TIMEOUT_INTERVAL'  | 
 | 107 | +    ) {  | 
 | 108 | +      reporter.reportTransformation(  | 
 | 109 | +        sourceFile,  | 
 | 110 | +        node,  | 
 | 111 | +        'Transformed `jasmine.DEFAULT_TIMEOUT_INTERVAL` to `vi.setConfig()`.',  | 
 | 112 | +      );  | 
 | 113 | +      const timeoutValue = assignment.right;  | 
 | 114 | +      const setConfigCall = createViCallExpression('setConfig', [  | 
 | 115 | +        ts.factory.createObjectLiteralExpression(  | 
 | 116 | +          [ts.factory.createPropertyAssignment('testTimeout', timeoutValue)],  | 
 | 117 | +          false,  | 
 | 118 | +        ),  | 
 | 119 | +      ]);  | 
 | 120 | + | 
 | 121 | +      return ts.factory.createExpressionStatement(setConfigCall);  | 
 | 122 | +    }  | 
 | 123 | +  }  | 
 | 124 | + | 
 | 125 | +  return node;  | 
 | 126 | +}  | 
 | 127 | + | 
 | 128 | +export function transformGlobalFunctions(  | 
 | 129 | +  node: ts.Node,  | 
 | 130 | +  { sourceFile, reporter }: RefactorContext,  | 
 | 131 | +): ts.Node {  | 
 | 132 | +  if (  | 
 | 133 | +    ts.isCallExpression(node) &&  | 
 | 134 | +    ts.isIdentifier(node.expression) &&  | 
 | 135 | +    (node.expression.text === 'setSpecProperty' || node.expression.text === 'setSuiteProperty')  | 
 | 136 | +  ) {  | 
 | 137 | +    const functionName = node.expression.text;  | 
 | 138 | +    reporter.reportTransformation(  | 
 | 139 | +      sourceFile,  | 
 | 140 | +      node,  | 
 | 141 | +      `Found unsupported global function \`${functionName}\`.`,  | 
 | 142 | +    );  | 
 | 143 | +    reporter.recordTodo(functionName);  | 
 | 144 | +    addTodoComment(  | 
 | 145 | +      node,  | 
 | 146 | +      `Unsupported global function \`${functionName}\` found. This function is used for custom reporters in Jasmine ` +  | 
 | 147 | +        'and has no direct equivalent in Vitest.',  | 
 | 148 | +    );  | 
 | 149 | +  }  | 
 | 150 | + | 
 | 151 | +  return node;  | 
 | 152 | +}  | 
 | 153 | + | 
 | 154 | +const JASMINE_UNSUPPORTED_CALLS = new Map<string, string>([  | 
 | 155 | +  [  | 
 | 156 | +    'addMatchers',  | 
 | 157 | +    'jasmine.addMatchers is not supported. Please manually migrate to expect.extend().',  | 
 | 158 | +  ],  | 
 | 159 | +  [  | 
 | 160 | +    'addCustomEqualityTester',  | 
 | 161 | +    'jasmine.addCustomEqualityTester is not supported. Please manually migrate to expect.addEqualityTesters().',  | 
 | 162 | +  ],  | 
 | 163 | +  [  | 
 | 164 | +    'mapContaining',  | 
 | 165 | +    'jasmine.mapContaining is not supported. Vitest does not have a built-in matcher for Maps.' +  | 
 | 166 | +      ' Please manually assert the contents of the Map.',  | 
 | 167 | +  ],  | 
 | 168 | +  [  | 
 | 169 | +    'setContaining',  | 
 | 170 | +    'jasmine.setContaining is not supported. Vitest does not have a built-in matcher for Sets.' +  | 
 | 171 | +      ' Please manually assert the contents of the Set.',  | 
 | 172 | +  ],  | 
 | 173 | +]);  | 
 | 174 | + | 
 | 175 | +export function transformUnsupportedJasmineCalls(  | 
 | 176 | +  node: ts.Node,  | 
 | 177 | +  { sourceFile, reporter }: RefactorContext,  | 
 | 178 | +): ts.Node {  | 
 | 179 | +  const methodName = getJasmineMethodName(node);  | 
 | 180 | +  if (!methodName) {  | 
 | 181 | +    return node;  | 
 | 182 | +  }  | 
 | 183 | + | 
 | 184 | +  const message = JASMINE_UNSUPPORTED_CALLS.get(methodName);  | 
 | 185 | +  if (message) {  | 
 | 186 | +    reporter.reportTransformation(  | 
 | 187 | +      sourceFile,  | 
 | 188 | +      node,  | 
 | 189 | +      `Found unsupported call \`jasmine.${methodName}\`.`,  | 
 | 190 | +    );  | 
 | 191 | +    reporter.recordTodo(methodName);  | 
 | 192 | +    addTodoComment(node, message);  | 
 | 193 | +  }  | 
 | 194 | + | 
 | 195 | +  return node;  | 
 | 196 | +}  | 
 | 197 | + | 
 | 198 | +// If any additional properties are added to transforms, they should also be added to this list.  | 
 | 199 | +const HANDLED_JASMINE_PROPERTIES = new Set([  | 
 | 200 | +  // Spies  | 
 | 201 | +  'createSpy',  | 
 | 202 | +  'createSpyObj',  | 
 | 203 | +  'spyOnAllFunctions',  | 
 | 204 | +  // Clock  | 
 | 205 | +  'clock',  | 
 | 206 | +  // Matchers  | 
 | 207 | +  'any',  | 
 | 208 | +  'anything',  | 
 | 209 | +  'stringMatching',  | 
 | 210 | +  'objectContaining',  | 
 | 211 | +  'arrayContaining',  | 
 | 212 | +  'arrayWithExactContents',  | 
 | 213 | +  'truthy',  | 
 | 214 | +  'falsy',  | 
 | 215 | +  'empty',  | 
 | 216 | +  'notEmpty',  | 
 | 217 | +  'mapContaining',  | 
 | 218 | +  'setContaining',  | 
 | 219 | +  // Other  | 
 | 220 | +  'DEFAULT_TIMEOUT_INTERVAL',  | 
 | 221 | +  'addMatchers',  | 
 | 222 | +  'addCustomEqualityTester',  | 
 | 223 | +]);  | 
 | 224 | + | 
 | 225 | +export function transformUnknownJasmineProperties(  | 
 | 226 | +  node: ts.Node,  | 
 | 227 | +  { sourceFile, reporter }: RefactorContext,  | 
 | 228 | +): ts.Node {  | 
 | 229 | +  if (  | 
 | 230 | +    ts.isPropertyAccessExpression(node) &&  | 
 | 231 | +    ts.isIdentifier(node.expression) &&  | 
 | 232 | +    node.expression.text === 'jasmine'  | 
 | 233 | +  ) {  | 
 | 234 | +    const propName = node.name.text;  | 
 | 235 | +    if (!HANDLED_JASMINE_PROPERTIES.has(propName)) {  | 
 | 236 | +      reporter.reportTransformation(  | 
 | 237 | +        sourceFile,  | 
 | 238 | +        node,  | 
 | 239 | +        `Found unknown jasmine property \`jasmine.${propName}\`.`,  | 
 | 240 | +      );  | 
 | 241 | +      reporter.recordTodo(`unknown-jasmine-property: ${propName}`);  | 
 | 242 | +      addTodoComment(  | 
 | 243 | +        node,  | 
 | 244 | +        `Unsupported jasmine property "${propName}" found. Please migrate this manually.`,  | 
 | 245 | +      );  | 
 | 246 | +    }  | 
 | 247 | +  }  | 
 | 248 | + | 
 | 249 | +  return node;  | 
 | 250 | +}  | 
0 commit comments