UNPKG

@schematics/angular

Version:
276 lines • 15.7 kB
"use strict"; /** * @license * Copyright Google LLC 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.dev/license */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.transformFocusedAndSkippedTests = transformFocusedAndSkippedTests; exports.transformPending = transformPending; exports.transformDoneCallback = transformDoneCallback; /** * @fileoverview This file contains transformers that convert Jasmine lifecycle functions * and test setup/teardown patterns to their Vitest equivalents. This includes handling * focused/skipped tests (fdescribe, fit, xdescribe, xit), pending tests, and asynchronous * operations that use the `done` callback. */ const typescript_1 = __importDefault(require("../../../third_party/github.com/Microsoft/TypeScript/lib/typescript")); const ast_helpers_1 = require("../utils/ast-helpers"); const comment_helpers_1 = require("../utils/comment-helpers"); const FOCUSED_SKIPPED_RENAMES = new Map([ ['fdescribe', { newBase: 'describe', newName: 'only' }], ['fit', { newBase: 'it', newName: 'only' }], ['xdescribe', { newBase: 'describe', newName: 'skip' }], ['xit', { newBase: 'it', newName: 'skip' }], ]); function transformFocusedAndSkippedTests(node, { sourceFile, reporter }) { if (!typescript_1.default.isCallExpression(node) || !typescript_1.default.isIdentifier(node.expression)) { return node; } const oldName = node.expression.text; const rename = FOCUSED_SKIPPED_RENAMES.get(oldName); if (rename) { reporter.reportTransformation(sourceFile, node, `Transformed \`${oldName}\` to \`${rename.newBase}.${rename.newName}\`.`); const newPropAccess = (0, ast_helpers_1.createPropertyAccess)(rename.newBase, rename.newName); return typescript_1.default.factory.updateCallExpression(node, newPropAccess, node.typeArguments, node.arguments); } return node; } function transformPending(node, { sourceFile, reporter, tsContext }) { if (!typescript_1.default.isCallExpression(node) || !typescript_1.default.isIdentifier(node.expression) || node.expression.text !== 'it') { return node; } const testFn = node.arguments[1]; if (!testFn || (!typescript_1.default.isArrowFunction(testFn) && !typescript_1.default.isFunctionExpression(testFn))) { return node; } let hasPending = false; const bodyTransformVisitor = (bodyNode) => { if (typescript_1.default.isExpressionStatement(bodyNode) && typescript_1.default.isCallExpression(bodyNode.expression) && typescript_1.default.isIdentifier(bodyNode.expression.expression) && bodyNode.expression.expression.text === 'pending') { hasPending = true; const replacement = typescript_1.default.factory.createEmptyStatement(); const originalText = bodyNode.getFullText().trim(); reporter.reportTransformation(sourceFile, bodyNode, 'Converted `pending()` to a skipped test (`it.skip`).'); const category = 'pending'; reporter.recordTodo(category); (0, comment_helpers_1.addTodoComment)(replacement, category); typescript_1.default.addSyntheticLeadingComment(replacement, typescript_1.default.SyntaxKind.SingleLineCommentTrivia, ` ${originalText}`, true); return replacement; } return typescript_1.default.visitEachChild(bodyNode, bodyTransformVisitor, tsContext); }; const newBody = typescript_1.default.visitNode(testFn.body, bodyTransformVisitor); if (!hasPending) { return node; } const newExpression = (0, ast_helpers_1.createPropertyAccess)(node.expression, 'skip'); const newTestFn = typescript_1.default.isArrowFunction(testFn) ? typescript_1.default.factory.updateArrowFunction(testFn, testFn.modifiers, testFn.typeParameters, testFn.parameters, testFn.type, testFn.equalsGreaterThanToken, newBody ?? typescript_1.default.factory.createBlock([])) : typescript_1.default.factory.updateFunctionExpression(testFn, testFn.modifiers, testFn.asteriskToken, testFn.name, testFn.typeParameters, testFn.parameters, testFn.type, newBody ?? typescript_1.default.factory.createBlock([])); const newArgs = [node.arguments[0], newTestFn, ...node.arguments.slice(2)]; return typescript_1.default.factory.updateCallExpression(node, newExpression, node.typeArguments, newArgs); } function transformComplexDoneCallback(node, doneIdentifier, refactorCtx) { const { sourceFile, reporter } = refactorCtx; if (!typescript_1.default.isExpressionStatement(node) || !typescript_1.default.isCallExpression(node.expression) || !typescript_1.default.isPropertyAccessExpression(node.expression.expression)) { return node; } const call = node.expression; const pae = call.expression; if (!typescript_1.default.isPropertyAccessExpression(pae)) { return node; } if (pae.name.text !== 'then' || call.arguments.length !== 1) { return node; } const thenCallback = call.arguments[0]; if (!typescript_1.default.isArrowFunction(thenCallback) && !typescript_1.default.isFunctionExpression(thenCallback)) { return node; } // Re-create the .then() call but with a modified callback that has `done()` removed. const thenCallbackBody = typescript_1.default.isBlock(thenCallback.body) ? thenCallback.body : typescript_1.default.factory.createBlock([typescript_1.default.factory.createExpressionStatement(thenCallback.body)]); const newStatements = thenCallbackBody.statements.filter((stmt) => { return (!typescript_1.default.isExpressionStatement(stmt) || !typescript_1.default.isCallExpression(stmt.expression) || !typescript_1.default.isIdentifier(stmt.expression.expression) || stmt.expression.expression.text !== doneIdentifier.text); }); if (newStatements.length === thenCallbackBody.statements.length) { // No "done()" call was removed, so don't transform. return node; } reporter.reportTransformation(sourceFile, node, 'Transformed promise `.then()` with `done()` to `await`.'); const newThenCallback = typescript_1.default.isArrowFunction(thenCallback) ? typescript_1.default.factory.updateArrowFunction(thenCallback, thenCallback.modifiers, thenCallback.typeParameters, thenCallback.parameters, thenCallback.type, thenCallback.equalsGreaterThanToken, typescript_1.default.factory.updateBlock(thenCallbackBody, newStatements)) : typescript_1.default.factory.updateFunctionExpression(thenCallback, thenCallback.modifiers, thenCallback.asteriskToken, thenCallback.name, thenCallback.typeParameters, thenCallback.parameters, thenCallback.type, typescript_1.default.factory.updateBlock(thenCallbackBody, newStatements)); const newCall = typescript_1.default.factory.updateCallExpression(call, call.expression, call.typeArguments, [ newThenCallback, ]); return typescript_1.default.factory.createExpressionStatement(typescript_1.default.factory.createAwaitExpression(newCall)); } function transformPromiseBasedDone(callExpr, doneIdentifier, refactorCtx) { const { sourceFile, reporter } = refactorCtx; if (typescript_1.default.isPropertyAccessExpression(callExpr.expression) && (callExpr.expression.name.text === 'then' || callExpr.expression.name.text === 'catch')) { const promiseHandler = callExpr.arguments[0]; if (promiseHandler) { let isDoneHandler = false; // promise.then(done) if (typescript_1.default.isIdentifier(promiseHandler) && promiseHandler.text === doneIdentifier.text) { isDoneHandler = true; } // promise.catch(done.fail) if (typescript_1.default.isPropertyAccessExpression(promiseHandler) && typescript_1.default.isIdentifier(promiseHandler.expression) && promiseHandler.expression.text === doneIdentifier.text && promiseHandler.name.text === 'fail') { isDoneHandler = true; } // promise.then(() => done()) if (typescript_1.default.isArrowFunction(promiseHandler) && !promiseHandler.parameters.length) { const body = promiseHandler.body; if (typescript_1.default.isCallExpression(body) && typescript_1.default.isIdentifier(body.expression) && body.expression.text === doneIdentifier.text) { isDoneHandler = true; } if (typescript_1.default.isBlock(body) && body.statements.length === 1) { const stmt = body.statements[0]; if (typescript_1.default.isExpressionStatement(stmt) && typescript_1.default.isCallExpression(stmt.expression) && typescript_1.default.isIdentifier(stmt.expression.expression) && stmt.expression.expression.text === doneIdentifier.text) { isDoneHandler = true; } } } if (isDoneHandler) { reporter.reportTransformation(sourceFile, callExpr, 'Transformed promise `.then(done)` to `await`.'); return typescript_1.default.factory.createExpressionStatement(typescript_1.default.factory.createAwaitExpression(callExpr.expression.expression)); } } } return undefined; } function countDoneUsages(node, doneIdentifier) { let count = 0; const visitor = (n) => { if (typescript_1.default.isIdentifier(n) && n.text === doneIdentifier.text) { count++; } typescript_1.default.forEachChild(n, visitor); }; typescript_1.default.forEachChild(node, visitor); return count; } function transformDoneCallback(node, refactorCtx) { const { sourceFile, reporter, tsContext } = refactorCtx; if (!typescript_1.default.isCallExpression(node) || !typescript_1.default.isIdentifier(node.expression) || !['it', 'beforeEach', 'afterEach', 'beforeAll', 'afterAll'].includes(node.expression.text)) { return node; } const functionArg = node.arguments.find((arg) => typescript_1.default.isArrowFunction(arg) || typescript_1.default.isFunctionExpression(arg)); if (!functionArg || (!typescript_1.default.isArrowFunction(functionArg) && !typescript_1.default.isFunctionExpression(functionArg))) { return node; } if (functionArg.parameters.length !== 1) { return node; } const doneParam = functionArg.parameters[0]; if (!typescript_1.default.isIdentifier(doneParam.name)) { return node; } const doneIdentifier = doneParam.name; // Count total usages of 'done' in the body const totalUsages = countDoneUsages(functionArg.body, doneIdentifier); let handledUsages = 0; let doneWasUsed = false; const bodyVisitor = (bodyNode) => { const complexTransformed = transformComplexDoneCallback(bodyNode, doneIdentifier, refactorCtx); if (complexTransformed !== bodyNode) { doneWasUsed = true; handledUsages++; // complex transform handles one usage return complexTransformed; } if (typescript_1.default.isExpressionStatement(bodyNode) && typescript_1.default.isCallExpression(bodyNode.expression)) { const callExpr = bodyNode.expression; // Transform `done.fail('message')` to `throw new Error('message')` if (typescript_1.default.isPropertyAccessExpression(callExpr.expression) && typescript_1.default.isIdentifier(callExpr.expression.expression) && callExpr.expression.expression.text === doneIdentifier.text && callExpr.expression.name.text === 'fail') { doneWasUsed = true; handledUsages++; reporter.reportTransformation(sourceFile, bodyNode, 'Transformed `done.fail()` to `throw new Error()`.'); const errorArgs = callExpr.arguments.length > 0 ? [callExpr.arguments[0]] : []; return typescript_1.default.factory.createThrowStatement(typescript_1.default.factory.createNewExpression(typescript_1.default.factory.createIdentifier('Error'), undefined, errorArgs)); } // Transform `promise.then(done)` or `promise.catch(done.fail)` to `await promise` const promiseTransformed = transformPromiseBasedDone(callExpr, doneIdentifier, refactorCtx); if (promiseTransformed) { doneWasUsed = true; handledUsages++; return promiseTransformed; } // Remove `done()` if (typescript_1.default.isIdentifier(callExpr.expression) && callExpr.expression.text === doneIdentifier.text) { doneWasUsed = true; handledUsages++; return typescript_1.default.setTextRange(typescript_1.default.factory.createEmptyStatement(), callExpr.expression); } } return typescript_1.default.visitEachChild(bodyNode, bodyVisitor, tsContext); }; const newBody = typescript_1.default.visitNode(functionArg.body, (node) => { if (typescript_1.default.isBlock(node)) { const newStatements = node.statements.flatMap((stmt) => bodyVisitor(stmt)); return typescript_1.default.factory.updateBlock(node, newStatements.filter((s) => !!s)); } return bodyVisitor(node); }); // Safety check: if we found usages but didn't handle all of them, abort. if (handledUsages < totalUsages) { reporter.reportTransformation(sourceFile, node, `Found unhandled usage of \`${doneIdentifier.text}\` callback. Skipping transformation.`); const category = 'unhandled-done-usage'; reporter.recordTodo(category); (0, comment_helpers_1.addTodoComment)(node, category); return node; } if (!doneWasUsed) { return node; } reporter.reportTransformation(sourceFile, node, `Converted test with \`done\` callback to an \`async\` test.`); const newModifiers = [ typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.AsyncKeyword), ...(typescript_1.default.getModifiers(functionArg) ?? []).filter((mod) => mod.kind !== typescript_1.default.SyntaxKind.AsyncKeyword), ]; let newFunction; if (typescript_1.default.isArrowFunction(functionArg)) { newFunction = typescript_1.default.factory.updateArrowFunction(functionArg, newModifiers, functionArg.typeParameters, [], // remove parameters functionArg.type, functionArg.equalsGreaterThanToken, newBody ?? typescript_1.default.factory.createBlock([])); } else { // isFunctionExpression newFunction = typescript_1.default.factory.updateFunctionExpression(functionArg, newModifiers, functionArg.asteriskToken, functionArg.name, functionArg.typeParameters, [], // remove parameters functionArg.type, newBody ?? typescript_1.default.factory.createBlock([])); } const newArgs = node.arguments.map((arg) => (arg === functionArg ? newFunction : arg)); return typescript_1.default.factory.updateCallExpression(node, node.expression, node.typeArguments, newArgs); } //# sourceMappingURL=jasmine-lifecycle.js.map