@schematics/angular
Version:
Schematics specific to Angular
207 lines • 9.9 kB
JavaScript
"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.transformJasmineToVitest = transformJasmineToVitest;
/**
* @fileoverview This is the main entry point for the Jasmine to Vitest transformation.
* It orchestrates the application of various AST transformers to convert Jasmine test
* syntax and APIs to their Vitest equivalents. It also handles import management,
* blank line preservation, and reporting of transformation details.
*/
const typescript_1 = __importDefault(require("../../third_party/github.com/Microsoft/TypeScript/lib/typescript"));
const jasmine_lifecycle_1 = require("./transformers/jasmine-lifecycle");
const jasmine_matcher_1 = require("./transformers/jasmine-matcher");
const jasmine_misc_1 = require("./transformers/jasmine-misc");
const jasmine_spy_1 = require("./transformers/jasmine-spy");
const jasmine_type_1 = require("./transformers/jasmine-type");
const ast_helpers_1 = require("./utils/ast-helpers");
/**
* A placeholder used to temporarily replace blank lines in the source code.
* This is necessary because TypeScript's printer removes blank lines by default.
*/
const BLANK_LINE_PLACEHOLDER = '// __PRESERVE_BLANK_LINE__';
/**
* Vitest function names that should be imported when using the --add-imports option.
*/
const VITEST_FUNCTION_NAMES = new Set([
'describe',
'it',
'expect',
'beforeEach',
'afterEach',
'beforeAll',
'afterAll',
]);
/**
* Replaces blank lines in the content with a placeholder to prevent TypeScript's printer
* from removing them. This ensures that the original formatting of blank lines is preserved.
* @param content The source code content.
* @returns The content with blank lines replaced by placeholders.
*/
function preserveBlankLines(content) {
return content
.split('\n')
.map((line) => (line.trim() === '' ? BLANK_LINE_PLACEHOLDER : line))
.join('\n');
}
/**
* Restores blank lines in the content by replacing the placeholder with actual blank lines.
* This is called after TypeScript's printer has processed the file.
* @param content The content with blank line placeholders.
* @returns The content with blank lines restored.
*/
function restoreBlankLines(content) {
const regex = /^\s*\/\/ __PRESERVE_BLANK_LINE__\s*$/gm;
return content.replace(regex, '');
}
/**
* A collection of transformers that operate on `ts.CallExpression` nodes.
* These are applied in stages to ensure correct order of operations:
* 1. High-Level & Context-Sensitive: Transformations that fundamentally change the call.
* 2. Core Matcher & Spy: Bulk conversions for `expect(...)` and `spyOn(...)`.
* 3. Global Functions & Cleanup: Handles global Jasmine functions and unsupported APIs.
*/
const callExpressionTransformers = [
// **Stage 1: High-Level & Context-Sensitive Transformations**
// These transformers often wrap or fundamentally change the nature of the call,
// so they need to run before more specific matchers.
jasmine_matcher_1.transformWithContext,
jasmine_matcher_1.transformExpectAsync,
jasmine_lifecycle_1.transformFocusedAndSkippedTests,
jasmine_lifecycle_1.transformPending,
jasmine_lifecycle_1.transformDoneCallback,
// **Stage 2: Core Matcher & Spy Transformations**
// This is the bulk of the `expect(...)` and `spyOn(...)` conversions.
jasmine_matcher_1.transformSyntacticSugarMatchers,
jasmine_matcher_1.transformComplexMatchers,
jasmine_spy_1.transformSpies,
jasmine_spy_1.transformCreateSpyObj,
jasmine_spy_1.transformSpyReset,
jasmine_spy_1.transformSpyCallInspection,
jasmine_matcher_1.transformtoHaveBeenCalledBefore,
jasmine_matcher_1.transformToHaveClass,
// **Stage 3: Global Functions & Cleanup**
// These handle global Jasmine functions and catch-alls for unsupported APIs.
jasmine_misc_1.transformTimerMocks,
jasmine_misc_1.transformGlobalFunctions,
jasmine_misc_1.transformUnsupportedJasmineCalls,
];
/**
* A collection of transformers that operate on `ts.PropertyAccessExpression` nodes.
* These primarily handle `jasmine.any()` and other `jasmine.*` properties.
*/
const propertyAccessExpressionTransformers = [
// These transformers handle `jasmine.any()` and other `jasmine.*` properties.
jasmine_matcher_1.transformAsymmetricMatchers,
jasmine_spy_1.transformSpyCallInspection,
jasmine_misc_1.transformUnknownJasmineProperties,
];
/**
* A collection of transformers that operate on `ts.ExpressionStatement` nodes.
* These are mutually exclusive; the first one that matches will be applied.
*/
const expressionStatementTransformers = [
jasmine_matcher_1.transformCalledOnceWith,
jasmine_matcher_1.transformArrayWithExactContents,
jasmine_matcher_1.transformExpectNothing,
jasmine_misc_1.transformFail,
jasmine_misc_1.transformDefaultTimeoutInterval,
];
/**
* Transforms a string of Jasmine test code to Vitest test code.
* This is the main entry point for the transformation.
* @param filePath The path to the file being transformed.
* @param content The source code to transform.
* @param reporter The reporter to track TODOs.
* @param options Transformation options, including whether to add Vitest API imports.
* @returns The transformed code.
*/
function transformJasmineToVitest(filePath, content, reporter, options) {
const contentWithPlaceholders = preserveBlankLines(content);
const sourceFile = typescript_1.default.createSourceFile(filePath, contentWithPlaceholders, typescript_1.default.ScriptTarget.Latest, true, typescript_1.default.ScriptKind.TS);
const pendingVitestValueImports = new Set();
const pendingVitestTypeImports = new Set();
const transformer = (context) => {
const refactorCtx = {
sourceFile,
reporter,
tsContext: context,
pendingVitestValueImports,
pendingVitestTypeImports,
};
const visitor = (node) => {
let transformedNode = node;
// Transform the node itself based on its type
if (typescript_1.default.isCallExpression(transformedNode)) {
if (options.addImports && typescript_1.default.isIdentifier(transformedNode.expression)) {
const name = transformedNode.expression.text;
if (VITEST_FUNCTION_NAMES.has(name)) {
(0, ast_helpers_1.addVitestValueImport)(pendingVitestValueImports, name);
}
}
for (const transformer of callExpressionTransformers) {
transformedNode = transformer(transformedNode, refactorCtx);
}
}
else if (typescript_1.default.isPropertyAccessExpression(transformedNode)) {
for (const transformer of propertyAccessExpressionTransformers) {
transformedNode = transformer(transformedNode, refactorCtx);
}
}
else if (typescript_1.default.isExpressionStatement(transformedNode)) {
// Statement-level transformers are mutually exclusive. The first one that
// matches will be applied, and then the visitor will stop for this node.
for (const transformer of expressionStatementTransformers) {
const result = transformer(transformedNode, refactorCtx);
if (result !== transformedNode) {
transformedNode = result;
break;
}
}
}
else if (typescript_1.default.isQualifiedName(transformedNode) || typescript_1.default.isTypeReferenceNode(transformedNode)) {
transformedNode = (0, jasmine_type_1.transformJasmineTypes)(transformedNode, refactorCtx);
}
// Visit the children of the node to ensure they are transformed
if (Array.isArray(transformedNode)) {
return transformedNode.map((node) => typescript_1.default.visitEachChild(node, visitor, context));
}
else {
return typescript_1.default.visitEachChild(transformedNode, visitor, context);
}
};
return (node) => typescript_1.default.visitEachChild(node, visitor, context);
};
const result = typescript_1.default.transform(sourceFile, [transformer]);
let transformedSourceFile = result.transformed[0];
const hasPendingValueImports = pendingVitestValueImports.size > 0;
const hasPendingTypeImports = pendingVitestTypeImports.size > 0;
if (transformedSourceFile === sourceFile &&
!reporter.hasTodos &&
!hasPendingValueImports &&
!hasPendingTypeImports) {
return content;
}
if (hasPendingTypeImports || (options.addImports && hasPendingValueImports)) {
const vitestImport = (0, ast_helpers_1.getVitestAutoImports)(options.addImports ? pendingVitestValueImports : new Set(), pendingVitestTypeImports);
if (vitestImport) {
transformedSourceFile = typescript_1.default.factory.updateSourceFile(transformedSourceFile, [
vitestImport,
...transformedSourceFile.statements,
]);
}
}
const printer = typescript_1.default.createPrinter();
const transformedContentWithPlaceholders = printer.printFile(transformedSourceFile);
return restoreBlankLines(transformedContentWithPlaceholders);
}
//# sourceMappingURL=test-file-transformer.js.map