Skip to content

Commit f4895d6

Browse files
committed
refactor(@schematics/angular): improve code quality in jasmine-vitest transformer
This commit introduces several code quality improvements to the `test-file-transformer.ts` file within the Jasmine to Vitest schematic. These changes enhance readability, maintainability, and performance without altering the existing behavior. Key improvements include: - **Module-level transformer arrays:** The `callExpressionTransformers`, `propertyAccessExpressionTransformers`, and `expressionStatementTransformers` arrays have been refactored to be module-level constants. This ensures they are initialized only once when the module is loaded, improving performance, and clearly separates static configuration from dynamic execution logic. - **Comprehensive JSDoc comments:** Extensive JSDoc comments have been added or updated throughout the file, including a file-level overview, detailed explanations for module-level constants, and updated documentation for key functions. This significantly improves code clarity and maintainability for future development.
1 parent cc132e9 commit f4895d6

File tree

2 files changed

+116
-47
lines changed

2 files changed

+116
-47
lines changed

packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer.ts

Lines changed: 87 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9+
/**
10+
* @fileoverview This is the main entry point for the Jasmine to Vitest transformation.
11+
* It orchestrates the application of various AST transformers to convert Jasmine test
12+
* syntax and APIs to their Vitest equivalents. It also handles import management,
13+
* blank line preservation, and reporting of transformation details.
14+
*/
15+
916
import ts from '../../third_party/github.com/Microsoft/TypeScript/lib/typescript';
1017
import {
1118
transformDoneCallback,
@@ -43,28 +50,102 @@ import { addVitestValueImport, getVitestAutoImports } from './utils/ast-helpers'
4350
import { RefactorContext } from './utils/refactor-context';
4451
import { RefactorReporter } from './utils/refactor-reporter';
4552

53+
/**
54+
* A placeholder used to temporarily replace blank lines in the source code.
55+
* This is necessary because TypeScript's printer removes blank lines by default.
56+
*/
4657
const BLANK_LINE_PLACEHOLDER = '// __PRESERVE_BLANK_LINE__';
4758

59+
/**
60+
* Replaces blank lines in the content with a placeholder to prevent TypeScript's printer
61+
* from removing them. This ensures that the original formatting of blank lines is preserved.
62+
* @param content The source code content.
63+
* @returns The content with blank lines replaced by placeholders.
64+
*/
4865
function preserveBlankLines(content: string): string {
4966
return content
5067
.split('\n')
5168
.map((line) => (line.trim() === '' ? BLANK_LINE_PLACEHOLDER : line))
5269
.join('\n');
5370
}
5471

72+
/**
73+
* Restores blank lines in the content by replacing the placeholder with actual blank lines.
74+
* This is called after TypeScript's printer has processed the file.
75+
* @param content The content with blank line placeholders.
76+
* @returns The content with blank lines restored.
77+
*/
5578
function restoreBlankLines(content: string): string {
56-
const regex = new RegExp(`^\\s*${BLANK_LINE_PLACEHOLDER.replace(/\//g, '\\/')}\\s*$`, 'gm');
79+
const regex = /^\s*\/\/ __PRESERVE_BLANK_LINE__\s*$/gm;
5780

5881
return content.replace(regex, '');
5982
}
6083

84+
/**
85+
* A collection of transformers that operate on `ts.CallExpression` nodes.
86+
* These are applied in stages to ensure correct order of operations:
87+
* 1. High-Level & Context-Sensitive: Transformations that fundamentally change the call.
88+
* 2. Core Matcher & Spy: Bulk conversions for `expect(...)` and `spyOn(...)`.
89+
* 3. Global Functions & Cleanup: Handles global Jasmine functions and unsupported APIs.
90+
*/
91+
const callExpressionTransformers = [
92+
// **Stage 1: High-Level & Context-Sensitive Transformations**
93+
// These transformers often wrap or fundamentally change the nature of the call,
94+
// so they need to run before more specific matchers.
95+
transformWithContext,
96+
transformExpectAsync,
97+
transformFocusedAndSkippedTests,
98+
transformPending,
99+
transformDoneCallback,
100+
101+
// **Stage 2: Core Matcher & Spy Transformations**
102+
// This is the bulk of the `expect(...)` and `spyOn(...)` conversions.
103+
transformSyntacticSugarMatchers,
104+
transformComplexMatchers,
105+
transformSpies,
106+
transformCreateSpyObj,
107+
transformSpyReset,
108+
transformSpyCallInspection,
109+
transformtoHaveBeenCalledBefore,
110+
transformToHaveClass,
111+
112+
// **Stage 3: Global Functions & Cleanup**
113+
// These handle global Jasmine functions and catch-alls for unsupported APIs.
114+
transformTimerMocks,
115+
transformGlobalFunctions,
116+
transformUnsupportedJasmineCalls,
117+
];
118+
119+
/**
120+
* A collection of transformers that operate on `ts.PropertyAccessExpression` nodes.
121+
* These primarily handle `jasmine.any()` and other `jasmine.*` properties.
122+
*/
123+
const propertyAccessExpressionTransformers = [
124+
// These transformers handle `jasmine.any()` and other `jasmine.*` properties.
125+
transformAsymmetricMatchers,
126+
transformSpyCallInspection,
127+
transformUnknownJasmineProperties,
128+
];
129+
130+
/**
131+
* A collection of transformers that operate on `ts.ExpressionStatement` nodes.
132+
* These are mutually exclusive; the first one that matches will be applied.
133+
*/
134+
const expressionStatementTransformers = [
135+
transformCalledOnceWith,
136+
transformArrayWithExactContents,
137+
transformExpectNothing,
138+
transformFail,
139+
transformDefaultTimeoutInterval,
140+
];
141+
61142
/**
62143
* Transforms a string of Jasmine test code to Vitest test code.
63144
* This is the main entry point for the transformation.
64145
* @param filePath The path to the file being transformed.
65146
* @param content The source code to transform.
66147
* @param reporter The reporter to track TODOs.
67-
* @param options Transformation options.
148+
* @param options Transformation options, including whether to add Vitest API imports.
68149
* @returns The transformed code.
69150
*/
70151
export function transformJasmineToVitest(
@@ -85,6 +166,7 @@ export function transformJasmineToVitest(
85166

86167
const pendingVitestValueImports = new Set<string>();
87168
const pendingVitestTypeImports = new Set<string>();
169+
88170
const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
89171
const refactorCtx: RefactorContext = {
90172
sourceFile,
@@ -106,59 +188,17 @@ export function transformJasmineToVitest(
106188
}
107189
}
108190

109-
const transformations = [
110-
// **Stage 1: High-Level & Context-Sensitive Transformations**
111-
// These transformers often wrap or fundamentally change the nature of the call,
112-
// so they need to run before more specific matchers.
113-
transformWithContext,
114-
transformExpectAsync,
115-
transformFocusedAndSkippedTests,
116-
transformPending,
117-
transformDoneCallback,
118-
119-
// **Stage 2: Core Matcher & Spy Transformations**
120-
// This is the bulk of the `expect(...)` and `spyOn(...)` conversions.
121-
transformSyntacticSugarMatchers,
122-
transformComplexMatchers,
123-
transformSpies,
124-
transformCreateSpyObj,
125-
transformSpyReset,
126-
transformSpyCallInspection,
127-
transformtoHaveBeenCalledBefore,
128-
transformToHaveClass,
129-
130-
// **Stage 3: Global Functions & Cleanup**
131-
// These handle global Jasmine functions and catch-alls for unsupported APIs.
132-
transformTimerMocks,
133-
transformGlobalFunctions,
134-
transformUnsupportedJasmineCalls,
135-
];
136-
137-
for (const transformer of transformations) {
191+
for (const transformer of callExpressionTransformers) {
138192
transformedNode = transformer(transformedNode, refactorCtx);
139193
}
140194
} else if (ts.isPropertyAccessExpression(transformedNode)) {
141-
const transformations = [
142-
// These transformers handle `jasmine.any()` and other `jasmine.*` properties.
143-
transformAsymmetricMatchers,
144-
transformSpyCallInspection,
145-
transformUnknownJasmineProperties,
146-
];
147-
for (const transformer of transformations) {
195+
for (const transformer of propertyAccessExpressionTransformers) {
148196
transformedNode = transformer(transformedNode, refactorCtx);
149197
}
150198
} else if (ts.isExpressionStatement(transformedNode)) {
151199
// Statement-level transformers are mutually exclusive. The first one that
152200
// matches will be applied, and then the visitor will stop for this node.
153-
const statementTransformers = [
154-
transformCalledOnceWith,
155-
transformArrayWithExactContents,
156-
transformExpectNothing,
157-
transformFail,
158-
transformDefaultTimeoutInterval,
159-
];
160-
161-
for (const transformer of statementTransformers) {
201+
for (const transformer of expressionStatementTransformers) {
162202
const result = transformer(transformedNode, refactorCtx);
163203
if (result !== transformedNode) {
164204
transformedNode = result;

packages/schematics/angular/refactor/jasmine-vitest/test-file-transformer_add-imports_spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,34 @@ describe('Jasmine to Vitest Transformer', () => {
5252
const expected = `const a = 1;`;
5353
await expectTransformation(input, expected, true);
5454
});
55+
56+
it('should add imports for top-level describe and it when addImports is true', async () => {
57+
const input = `
58+
describe('My Suite', () => {
59+
it('should do something', () => {
60+
// test content
61+
});
62+
});
63+
`;
64+
const expected = `
65+
import { describe, it } from 'vitest';
66+
67+
describe('My Suite', () => {
68+
it('should do something', () => {
69+
// test content
70+
});
71+
});
72+
`;
73+
await expectTransformation(input, expected, true);
74+
});
75+
76+
it('should add imports for top-level expect when addImports is true', async () => {
77+
const input = `expect(true).toBe(true);`;
78+
const expected = `
79+
import { expect } from 'vitest';
80+
expect(true).toBe(true);
81+
`;
82+
await expectTransformation(input, expected, true);
83+
});
5584
});
5685
});

0 commit comments

Comments
 (0)