ts-jest 29.4.6 → 29.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,337 @@
1
+ # PR 1 — New Interfaces + Config Modules
2
+
3
+ ## Overview
4
+
5
+ Introduces `ResolvedConfigInterface` and `DiagnosticsReporterInterface` as contracts for the new
6
+ config layer, then implements them via focused single-responsibility modules under
7
+ `src/experimental/config/`.
8
+
9
+ **User-visible change**: NO
10
+ **Depends on**: nothing
11
+ **Invariant**: zero changes to any existing file
12
+
13
+ ---
14
+
15
+ ## Files to Create
16
+
17
+ ```
18
+ src/experimental/
19
+ interfaces/
20
+ resolved-config.interface.ts
21
+ diagnostics-reporter.interface.ts
22
+ config/
23
+ options-resolver.ts
24
+ ts-config-resolver.ts
25
+ diagnostics-config.ts
26
+ babel-config-resolver.ts
27
+ ast-transformer-registry.ts
28
+ resolved-config.ts
29
+ cache-suffix.ts
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Requirements
35
+
36
+ ### `src/experimental/interfaces/resolved-config.interface.ts`
37
+
38
+ Read-only config contract consumed by compilers and the transformer. No dependency on `ConfigSet`.
39
+
40
+ ```ts
41
+ interface ResolvedConfigInterface {
42
+ readonly cwd: string
43
+ readonly rootDir: string
44
+ readonly cacheSuffix: string
45
+ readonly tsCacheDir: string | undefined
46
+ readonly useESM: boolean
47
+ readonly isolatedModules: boolean
48
+ readonly diagnosticsMode: DiagnosticsMode // 'disabled' | 'syntactic' | 'full'
49
+ readonly diagnostics: NormalizedDiagnosticsConfig
50
+ readonly parsedTsConfig: ts.ParsedCommandLine
51
+ readonly resolvedTransformers: ResolvedAstTransformers
52
+ readonly babelJestTransformer: BabelTransformer | undefined
53
+ readonly compiler: string // module specifier, default 'typescript'
54
+ shouldReportDiagnostics(filePath: string): boolean
55
+ shouldStringifyContent(filePath: string): boolean
56
+ isTestFile(filePath: string): boolean
57
+ raiseDiagnostics(diagnostics: ts.Diagnostic[], filePath?: string, logger?: Logger): void
58
+ createTsError(diagnostics: readonly ts.Diagnostic[]): TSError
59
+ resolvePath(inputPath: string, opts?: ResolvePathOptions): string
60
+ }
61
+ ```
62
+
63
+ All properties are `readonly`. Methods are behaviourally pure (no mutation of config state).
64
+
65
+ ---
66
+
67
+ ### `src/experimental/interfaces/diagnostics-reporter.interface.ts`
68
+
69
+ ```ts
70
+ interface DiagnosticsReporterInterface {
71
+ raise(diagnostics: ts.Diagnostic[], filePath?: string, logger?: Logger): void
72
+ shouldReport(filePath: string): boolean
73
+ createError(diagnostics: readonly ts.Diagnostic[]): TSError
74
+ }
75
+ ```
76
+
77
+ ---
78
+
79
+ ### `src/experimental/config/options-resolver.ts`
80
+
81
+ Normalises raw `TsJestTransformerOptions` into `NormalizedTsJestOptions`.
82
+
83
+ **Requirements**:
84
+
85
+ - Accept `TsJestTransformerOptions` (imported from `src/types.ts`)
86
+ - Return plain `NormalizedTsJestOptions` with all fields resolved to concrete values
87
+ - Defaults:
88
+ - `compiler`: `'typescript'`
89
+ - `useESM`: `false`
90
+ - `diagnostics`: `{ warnOnly: false, ignoreCodes: [6059, 18002, 18003], exclude: [], pretty: true }`
91
+ - `tsconfig`: `undefined` (resolved later by `ts-config-resolver`)
92
+ - `babelConfig`: `undefined`
93
+ - `stringifyContentPathRegex`: `undefined`
94
+ - NO call to `backportJestConfig` — v30 reads from transform options directly, not from `globals`
95
+
96
+ ---
97
+
98
+ ### `src/experimental/config/ts-config-resolver.ts`
99
+
100
+ Resolves and parses `tsconfig.json`, then applies all hard overrides.
101
+
102
+ **Resolution**:
103
+
104
+ - `string` → resolve path relative to `cwd`; throw if not found
105
+ - `object` → use as inline compiler options (no file on disk)
106
+ - `undefined` → `ts.findConfigFile(rootDir, ts.sys.fileExists, 'tsconfig.json')`; fall back to empty config
107
+
108
+ **Parsing**:
109
+
110
+ - `ts.parseJsonConfigFileContent(rawConfig, ts.sys, basePath, undefined, tsconfigFilePath)`
111
+ - Raise parse errors immediately via `DiagnosticsReporterInterface.raise()`
112
+
113
+ **Hard overrides applied after parsing** (non-negotiable):
114
+
115
+ | Override | Value |
116
+ | ---------------------- | --------------------------------------------- |
117
+ | `target` | `ES2015` if not set |
118
+ | `sourceMap` | `existing ?? true` |
119
+ | `inlineSources` | same as final `sourceMap` value |
120
+ | `module` | `existing ?? CommonJS` |
121
+ | `outDir` | force `'$$ts-jest$$'` if `allowJs && !outDir` |
122
+ | `inlineSourceMap` | **removed** |
123
+ | `declaration` | **removed** |
124
+ | `isolatedDeclarations` | **removed** |
125
+ | `noEmit` | **removed** |
126
+ | `removeComments` | **removed** |
127
+ | `out` | **removed** |
128
+ | `outFile` | **removed** |
129
+ | `composite` | **removed** |
130
+ | `declarationDir` | **removed** |
131
+ | `declarationMap` | **removed** |
132
+ | `emitDeclarationOnly` | **removed** |
133
+ | `sourceRoot` | **removed** |
134
+ | `tsBuildInfoFile` | **removed** |
135
+
136
+ **ESM interop warning** (push `DiagnosticCategory.Message`, do NOT throw):
137
+
138
+ - Condition: no babel config AND module NOT in `[CommonJS, Node16, NodeNext, ESNext, ES2015..ES2022]`
139
+ AND neither `esModuleInterop` nor `allowSyntheticDefaultImports`
140
+ - ESNext/ES2015–ES2022 are excluded (false-positive fix vs legacy behavior in `config-set.ts`)
141
+
142
+ **`isolatedModules` legacy option** (from `TsJestTransformerOptions.isolatedModules`):
143
+
144
+ - If set: force `parsedTsConfig.options.isolatedModules = true` + emit deprecation warning pointing
145
+ to tsconfig `"isolatedModules": true`
146
+
147
+ ---
148
+
149
+ ### `src/experimental/config/diagnostics-config.ts`
150
+
151
+ Normalises the `diagnostics` option and derives `DiagnosticsMode`.
152
+
153
+ ```ts
154
+ type DiagnosticsMode = 'disabled' | 'syntactic' | 'full'
155
+ ```
156
+
157
+ **Normalisation rules**:
158
+
159
+ | Input | `throws` | `pretty` | `ignoreCodes` | `exclude` | DiagnosticsMode |
160
+ | -------------------- | ----------- | -------- | ------------- | ------------ | --------------- |
161
+ | `true` / `undefined` | `true` | `true` | seed codes | `[]` | `'full'` |
162
+ | `false` | `false` | `false` | `[]` | `[]` | see note below |
163
+ | object | `!warnOnly` | `true` | seed + user | user exclude | `'full'` |
164
+
165
+ **`diagnostics: false` in v30**:
166
+
167
+ - Accepted, but emit deprecation warning: _"diagnostics: false is deprecated. Use `noCheck: true` in
168
+ your tsconfig instead."_
169
+ - Treat as `throws: false, ignoreCodes: [], _shouldIgnoreDiagnosticsForFile: () => true`
170
+
171
+ **`DiagnosticsMode` derivation**:
172
+
173
+ - `noCheck: true` in resolved tsconfig compiler options → `'disabled'`
174
+ - `noCheck: false` (default) → `'full'`
175
+ - `'syntactic'` reserved for future use; never set automatically
176
+
177
+ **`noCheck: true` + `warnOnly`**: silent — no deprecation warning emitted for `diagnostics: false`
178
+
179
+ **Seed codes**: `[6059, 18002, 18003]` — always included
180
+
181
+ **Code parsing**: support `number | string | Array<number | string>`.
182
+ Parse string codes: `'TS2322'` → `2322`, `'2322'` → `2322`
183
+
184
+ ---
185
+
186
+ ### `src/experimental/config/babel-config-resolver.ts`
187
+
188
+ Resolves the `babelConfig` option into a `BabelConfig` object or `undefined`.
189
+
190
+ | Input | Behaviour |
191
+ | ------------------- | ---------------------------------------------------------------------------------------------------- |
192
+ | `undefined` / falsy | `undefined` (no babel) |
193
+ | `true` | `{ cwd }` |
194
+ | `string` | resolve path relative to `cwd`, read: `.js`/`.cjs` → `require()`, else `json5.parse(readFileSync())` |
195
+ | `object` | spread as-is |
196
+
197
+ If resolved: create `babelJestTransformer` using `babel-jest` (loaded via `importer.babelJest()`).
198
+
199
+ ---
200
+
201
+ ### `src/experimental/config/ast-transformer-registry.ts`
202
+
203
+ Resolves `TsJestTransformerOptions.transform` into `ResolvedAstTransformers`.
204
+
205
+ **Requirements**:
206
+
207
+ - `before` always starts with `[hoistJestTransformer]` — user `before` transformers **appended** after
208
+ (not prepended before)
209
+ - `.ts` / `.tsx` transformer file paths: compile with esbuild before `require()`
210
+ - Each descriptor: `{ factory: Function, version: number, name: string, options?: unknown }`
211
+ - Preserve order: `before`, `after`, `afterDeclarations`
212
+
213
+ ---
214
+
215
+ ### `src/experimental/config/cache-suffix.ts`
216
+
217
+ Computes a deterministic sha1 cache suffix.
218
+
219
+ **Inputs hashed in this exact order**:
220
+
221
+ 1. TypeScript version string
222
+ 2. `MY_DIGEST` — `readFileSync('.ts-jest-digest')` at module load time (module-level constant)
223
+ 3. Serialised babel config (or empty string)
224
+ 4. Serialised tsconfig compiler options
225
+ 5. Raw tsconfig JSON string
226
+ 6. `isolatedModules` boolean
227
+ 7. Serialised diagnostics config
228
+ 8. Transformer names + versions sorted by name
229
+
230
+ **Output**: `sha1(inputs.join(''))` — 40 hex chars
231
+
232
+ `tsCacheDir` derivation (computed in `resolved-config.ts`):
233
+
234
+ ```ts
235
+ join(jestConfig.cacheDirectory, 'ts-jest', suffix.slice(0, 2), suffix.slice(2))
236
+ ```
237
+
238
+ ---
239
+
240
+ ### `src/experimental/config/resolved-config.ts`
241
+
242
+ Assembles all config modules into `ResolvedConfig implements ResolvedConfigInterface`.
243
+
244
+ **Constructor inputs**: `jestConfig: Config.ProjectConfig`, `transformerOptions: TsJestTransformerOptions`
245
+
246
+ **Construction sequence**:
247
+
248
+ 1. `options-resolver` → `NormalizedTsJestOptions`
249
+ 2. `cwd = normalize(jestConfig.cwd ?? process.cwd())`
250
+ 3. `rootDir = normalize(jestConfig.rootDir ?? cwd)`
251
+ 4. `importer.typescript(reason, options.compiler)` → load TS module
252
+ 5. `babel-config-resolver` → `babelConfig + babelJestTransformer`
253
+ 6. `diagnostics-config` → `NormalizedDiagnosticsConfig + DiagnosticsMode`
254
+ 7. `ts-config-resolver` → `ParsedCommandLine` (passes diagnostics reporter for parse errors)
255
+ 8. `ast-transformer-registry` → `ResolvedAstTransformers`
256
+ 9. `cache-suffix` → `cacheSuffix`
257
+ 10. `tsCacheDir` — computed if `jestConfig.cache && jestConfig.cacheDirectory`
258
+ 11. `isolatedModules = parsedTsConfig.options.isolatedModules ?? false`
259
+
260
+ **Method implementations**:
261
+
262
+ `shouldReportDiagnostics(filePath)`:
263
+
264
+ - JS/JSX → only if `checkJs: true` AND not matched by any exclude glob
265
+ - all others → not matched by any exclude glob
266
+
267
+ `shouldStringifyContent(filePath)`:
268
+
269
+ - test against compiled `stringifyContentPathRegex`; `false` if option not set
270
+
271
+ `isTestFile(filePath)`:
272
+
273
+ - check against `testMatch`/`testRegex` from jest config; fall back to `DEFAULT_JEST_TEST_MATCH`
274
+
275
+ `raiseDiagnostics(diagnostics, filePath?, logger?)`:
276
+
277
+ - filter: `shouldReportDiagnostics(filePath)` AND code not in `ignoreCodes`
278
+ - if any `Warning | Error` in filtered set AND `throws: true` → throw `TSError`
279
+ - else → `logger.warn` each
280
+
281
+ `createTsError(diagnostics)`:
282
+
283
+ - format with `ts.formatDiagnosticsWithColorAndContext` (pretty) or `ts.formatDiagnostics` (plain)
284
+ - return `new TSError(formatted)`
285
+
286
+ `resolvePath(input, opts)`:
287
+
288
+ 1. `<rootDir>` prefix → `join(rootDir, input.slice('<rootDir>'.length))`
289
+ 2. relative without leading `.` AND `nodeResolve: true` → try `require.resolve(input)` first
290
+ 3. else → `resolve(cwd, input)`
291
+ 4. final `require.resolve` attempt if `nodeResolve: true` and not yet resolved
292
+ 5. if `throwIfMissing: true` (default) and file not found → throw
293
+
294
+ ---
295
+
296
+ ## Import Rules
297
+
298
+ - `src/utils/*` — allowed (sha1, logger, importer, memoize, ts-error, messages, json, diagnostics)
299
+ - `src/utils/backports.ts` — NOT imported (no legacy config backport in v30)
300
+ - `src/types.ts` — allowed (TsJestTransformerOptions and related public types)
301
+ - `src/constants.ts` — allowed
302
+ - `src/legacy/**` — FORBIDDEN
303
+ - `src/config/**` — FORBIDDEN
304
+ - `src/transpilers/**` — FORBIDDEN
305
+ - `src/transformers/**` — FORBIDDEN
306
+
307
+ ---
308
+
309
+ ## Test Requirements
310
+
311
+ Each module gets its own unit test under `src/experimental/config/__tests__/`.
312
+
313
+ | Test file | Key scenarios |
314
+ | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
315
+ | `options-resolver.spec.ts` | all defaults applied; each option normalised correctly |
316
+ | `ts-config-resolver.spec.ts` | string/object/undefined tsconfig; parse errors raised; each hard override; ESM interop warning conditions (incl. ESNext excluded) |
317
+ | `diagnostics-config.spec.ts` | all input shapes; DiagnosticsMode derivation; noCheck:true → disabled; noCheck:true + warnOnly → silent; code parsing `'TS2322'` → `2322` |
318
+ | `babel-config-resolver.spec.ts` | each config shape; `.js` vs `.cjs` vs `.json` read paths |
319
+ | `ast-transformer-registry.spec.ts` | hoistJest always first in before; user before appended not prepended; .ts compiled with esbuild |
320
+ | `cache-suffix.spec.ts` | deterministic output; changes when any single input changes |
321
+ | `resolved-config.spec.ts` | full construction; shouldReportDiagnostics; resolvePath all branches; raiseDiagnostics throw vs warn |
322
+
323
+ **Existing tests must remain green** (no changes to legacy):
324
+
325
+ - `src/legacy/config/config-set.spec.ts`
326
+
327
+ ---
328
+
329
+ ## Acceptance Criteria
330
+
331
+ - [ ] All new unit tests pass (`npm test -- --testPathPattern=experimental/config`)
332
+ - [ ] `config-set.spec.ts` unmodified and green
333
+ - [ ] `tsc --noEmit` passes over new files
334
+ - [ ] No import from `src/legacy/`, `src/config/`, `src/transpilers/`, `src/transformers/`
335
+ - [ ] `src/utils/backports.ts` not imported anywhere in `src/experimental/`
336
+ - [ ] Every exported symbol has a TSDoc comment
337
+ - [ ] No `any` types without explicit `// eslint-disable` justification
@@ -0,0 +1,280 @@
1
+ # PR 2 — New Compiler Implementations + CompilerInterface
2
+
3
+ ## Overview
4
+
5
+ Introduces `CompilerInterface` and two clean-room compiler implementations:
6
+
7
+ - `LanguageServiceCompiler` — full `ts.Program` with type checking
8
+ - `FastTranspileCompiler` — `ts.transpileModule`, used only when type checking is explicitly disabled
9
+
10
+ **User-visible change**: NO
11
+ **Depends on**: PR 1 (`ResolvedConfigInterface`, `DiagnosticsMode`)
12
+ **Invariant**: zero changes to any existing file
13
+
14
+ ---
15
+
16
+ ## Files to Create
17
+
18
+ ```
19
+ src/experimental/
20
+ interfaces/
21
+ compiler.interface.ts
22
+ compiler/
23
+ memory-cache.ts
24
+ language-service-compiler.ts
25
+ fast-transpile-compiler.ts
26
+ module-kind-fixup.ts
27
+ transformer-factory.ts
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Requirements
33
+
34
+ ### `src/experimental/interfaces/compiler.interface.ts`
35
+
36
+ ```ts
37
+ interface CompilerInterface {
38
+ getCompiledOutput(fileContent: string, filePath: string, options: TsJestCompileOptions): CompiledOutput
39
+
40
+ getResolvedModules(fileContent: string, filePath: string, runtimeCacheFS: Map<string, string>): string[]
41
+ }
42
+ ```
43
+
44
+ `TsJestCompileOptions` and `CompiledOutput` imported from `src/types.ts`.
45
+
46
+ ---
47
+
48
+ ### `src/experimental/compiler/memory-cache.ts`
49
+
50
+ Extracted from `TsCompiler` lines 59–91, 486–518. Manages per-file content/version tracking used
51
+ by the Language Service host.
52
+
53
+ **Exported types/functions**:
54
+
55
+ ```ts
56
+ interface MemoryCacheEntry {
57
+ version: number
58
+ content: string | undefined
59
+ }
60
+
61
+ class MemoryCache {
62
+ private _files: Map<string, MemoryCacheEntry>
63
+ private _projectVersion: number
64
+
65
+ constructor(initialFiles: string[])
66
+
67
+ // Seed version=0 for all initial files (non-test TS/TSX from fileNames)
68
+ // getScriptVersion returns `undefined` (not "undefined") for unknown files
69
+ getScriptVersion(filePath: string): number | undefined
70
+ getContent(filePath: string): string | undefined
71
+
72
+ update(filePath: string, content: string, moduleKind: ts.ModuleKind): boolean
73
+ // Returns true and increments _projectVersion ONLY when:
74
+ // - file is new
75
+ // - content changed
76
+ // - file was not previously in fileNames
77
+ // - module kind changed
78
+
79
+ getProjectVersion(): string // cast to string for LS host protocol
80
+ list(): string[]
81
+ }
82
+ ```
83
+
84
+ **Critical**: `getScriptVersion` must return `undefined` (the value), NOT `"undefined"` (string),
85
+ for unknown files. Returning the string causes spurious `createProgram` calls in the LS.
86
+
87
+ ---
88
+
89
+ ### `src/experimental/compiler/module-kind-fixup.ts`
90
+
91
+ Pure function — no side effects. Normalises `ts.CompilerOptions` based on CJS vs ESM target.
92
+
93
+ ```ts
94
+ function fixupModuleKind(options: ts.CompilerOptions, useESM: boolean, supportsStaticESM: boolean): ts.CompilerOptions
95
+ ```
96
+
97
+ **CJS path** (`!useESM || !supportsStaticESM`):
98
+
99
+ ```ts
100
+ { ...options, module: ts.ModuleKind.CommonJS, moduleResolution: ts.ModuleResolutionKind.Node10, customConditions: undefined }
101
+ ```
102
+
103
+ **ESM path**:
104
+
105
+ - Keep `module` as ESNext (or existing if already ESNext)
106
+ - If original `module` was Node16 or NodeNext: force `esModuleInterop: true`, coerce module to ESNext
107
+ - Keep `moduleResolution` as-is for ESM
108
+
109
+ ---
110
+
111
+ ### `src/experimental/compiler/transformer-factory.ts`
112
+
113
+ Maps `ResolvedAstTransformers` → `ts.CustomTransformers`.
114
+
115
+ ```ts
116
+ function makeTransformers(
117
+ transformers: ResolvedAstTransformers,
118
+ compilerInstance: CompilerInterface,
119
+ ): ts.CustomTransformers
120
+ ```
121
+
122
+ - Calls each `AstTransformerDesc.factory(compilerInstance, options)` to produce transformer functions
123
+ - Returns `{ before: [...], after: [...], afterDeclarations: [...] }`
124
+
125
+ ---
126
+
127
+ ### `src/experimental/compiler/language-service-compiler.ts`
128
+
129
+ Full TypeScript Language Service path. Used when `diagnosticsMode !== 'disabled'`.
130
+
131
+ **Constructor**:
132
+
133
+ ```ts
134
+ constructor(config: ResolvedConfigInterface, runtimeCacheFS: Map<string, string>)
135
+ ```
136
+
137
+ **Initialisation sequence**:
138
+
139
+ 1. Assign `_config`, `_runtimeCacheFS`
140
+ 2. `_memoryCache = new MemoryCache(config.parsedTsConfig.fileNames)` — seed non-test TS/TSX files
141
+ 3. `_moduleResolutionCache = ts.createModuleResolutionCache(...)`
142
+ 4. `_cachedReadFile = memoize(ts.sys.readFile)` — from `src/utils/memoize.ts`
143
+ 5. `_createLanguageService()` — builds LS host + creates service
144
+
145
+ **Language Service Host** (`ts.LanguageServiceHost`):
146
+
147
+ - `getScriptVersion(file)` → `_memoryCache.getScriptVersion(file)?.toString() ?? undefined`
148
+ - `getScriptSnapshot(file)`:
149
+ 1. Check `_memoryCache.getContent(file)`
150
+ 2. Check `_runtimeCacheFS.get(file)`
151
+ 3. Fall back to `_cachedReadFile(file)`
152
+ - Return `ts.ScriptSnapshot.fromString(content)` or `undefined`
153
+ - `getProjectVersion()` → `_memoryCache.getProjectVersion()`
154
+ - `resolveModuleNames()` → use `_moduleResolutionCache` for perf
155
+
156
+ **`getCompiledOutput(fileContent, filePath, options)`**:
157
+
158
+ 1. `_memoryCache.update(filePath, fileContent, moduleKind)`
159
+ 2. Apply `fixupModuleKind` to compiler options based on `options.supportsStaticESM`
160
+ 3. `customTransformers = makeTransformers(config.resolvedTransformers, this)`
161
+ 4. Emit via `_languageService.getEmitOutput(filePath, false)`
162
+ 5. Collect diagnostics: `getSemanticDiagnostics` + `getSyntacticDiagnostics`
163
+ 6. In watch mode: also re-check all files that import `filePath`
164
+ 7. Handle emit results:
165
+ - `emitSkipped && isTs/Tsx` → throw `Error('emit skipped for TS file')`
166
+ - `emitSkipped && !isTs/Tsx` → warn + return original `fileContent`
167
+ - `outputFiles.length === 0` → throw `TypeError` (catches `.d.ts` require attempts)
168
+ - `sourceMap enabled`: `code = outputFiles[1].text`, map from `outputFiles[0].text`
169
+ - `sourceMap disabled`: `code = outputFiles[0].text`
170
+ 8. Apply `updateOutput(code, filePath, sourceMap)` from `src/legacy/compiler/compiler-utils.ts`
171
+ - **Exception**: `compiler-utils.ts` is in `src/legacy/` — reimplement `updateOutput` logic inline
172
+ or in `src/experimental/compiler/output-utils.ts`
173
+ 9. If `ModernNodeModule` kind: emit info diagnostic
174
+ 10. JS file + `!allowJs`: passthrough with warning
175
+
176
+ **`getResolvedModules(fileContent, filePath, runtimeCacheFS)`**:
177
+
178
+ 1. `ts.preProcessFile(fileContent)` → `importedFiles`
179
+ 2. Resolve each import relative to `filePath` using TS module resolution
180
+ 3. Recurse into resolved paths
181
+ 4. Deduplicate
182
+ 5. Filter out `isExternalLibraryImport: true`
183
+ 6. Return array of absolute file paths
184
+
185
+ ---
186
+
187
+ ### `src/experimental/compiler/fast-transpile-compiler.ts`
188
+
189
+ `ts.transpileModule`-based path. Used only when `diagnosticsMode === 'disabled'` (i.e. `noCheck: true`
190
+ in tsconfig).
191
+
192
+ **Constructor**:
193
+
194
+ ```ts
195
+ constructor(config: ResolvedConfigInterface)
196
+ ```
197
+
198
+ **`getCompiledOutput(fileContent, filePath, options)`**:
199
+
200
+ 1. Apply `fixupModuleKind` to compiler options
201
+ 2. `customTransformers = makeTransformers(config.resolvedTransformers, this)`
202
+ 3. Choose transpile path based on module kind:
203
+ - Non-modern (not Node16/NodeNext/Node18/Node20): native `ts.transpileModule(fileContent, { compilerOptions, transformers, fileName: filePath })`
204
+ - Modern node module kind: `tsTranspileModule(...)` from `src/transpilers/typescript/transpile-module.ts`
205
+ — exposes Program to custom transformers
206
+ 4. Apply `updateOutput` to result
207
+ 5. Return `CompiledOutput`
208
+
209
+ **`getResolvedModules()`**: always returns `[]`
210
+
211
+ **Guard**: if `config.diagnosticsMode !== 'disabled'`:
212
+
213
+ ```
214
+ throw new Error(
215
+ 'FastTranspileCompiler requires diagnosticsMode: disabled (noCheck: true in tsconfig). ' +
216
+ 'Remove noCheck from your tsconfig to use full type checking.'
217
+ )
218
+ ```
219
+
220
+ ---
221
+
222
+ ### `src/experimental/compiler/output-utils.ts`
223
+
224
+ Reimplementation of `updateOutput` logic (avoids importing from `src/legacy/`).
225
+
226
+ ```ts
227
+ function updateOutput(outputText: string, fileName: string, sourceMap?: string): string
228
+ ```
229
+
230
+ If `sourceMap`:
231
+
232
+ - Inline as base64 data URI
233
+ - Replace last `sourceMappingURL=` occurrence in `outputText`
234
+
235
+ Mirrors behavior of `src/legacy/compiler/compiler-utils.ts:updateOutput`.
236
+
237
+ ---
238
+
239
+ ## Import Rules
240
+
241
+ - `src/utils/*` — allowed
242
+ - `src/transpilers/typescript/transpile-module.ts` — allowed for `fast-transpile-compiler.ts` only
243
+ - `src/types.ts` — allowed (`TsJestCompileOptions`, `CompiledOutput`, `AstTransformerDesc`)
244
+ - `src/experimental/interfaces/*` — allowed
245
+ - `src/experimental/config/*` — allowed
246
+ - `src/legacy/**` — FORBIDDEN (including `compiler-utils.ts`)
247
+ - `src/config/**` — FORBIDDEN
248
+ - `src/transformers/**` — FORBIDDEN
249
+
250
+ ---
251
+
252
+ ## Test Requirements
253
+
254
+ Unit tests under `src/experimental/compiler/__tests__/`.
255
+
256
+ | Test file | Key scenarios |
257
+ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
258
+ | `memory-cache.spec.ts` | seed version=0; getScriptVersion returns `undefined` (not `"undefined"`) for unknown file; update increments projectVersion only on change; module kind change triggers increment |
259
+ | `module-kind-fixup.spec.ts` | CJS: forces CommonJS + Node10 + clears customConditions; ESM Node16/NodeNext: coerces to ESNext + forces esModuleInterop; ESM other: keeps as-is |
260
+ | `transformer-factory.spec.ts` | calls factory with compiler instance; returns correct CustomTransformers shape |
261
+ | `language-service-compiler.spec.ts` | emitSkipped + TS → throws; emitSkipped + JS → warns + returns original; no outputFiles → throws TypeError; sourceMap enabled → outputFiles[1]; getResolvedModules deduplicates; JS + !allowJs → passthrough with warning |
262
+ | `fast-transpile-compiler.spec.ts` | non-modern module → native transpileModule; modern module → tsTranspileModule; getResolvedModules → []; diagnosticsMode !== disabled → throws |
263
+ | `output-utils.spec.ts` | no sourceMap → returns as-is; with sourceMap → inlines base64; replaces last sourceMappingURL only |
264
+
265
+ **Existing tests must remain green** (no changes to legacy):
266
+
267
+ - `src/legacy/compiler/ts-compiler.spec.ts`
268
+ - `src/legacy/compiler/ts-jest-compiler.spec.ts`
269
+
270
+ ---
271
+
272
+ ## Acceptance Criteria
273
+
274
+ - [ ] All new unit tests pass
275
+ - [ ] `ts-compiler.spec.ts` and `ts-jest-compiler.spec.ts` unmodified and green
276
+ - [ ] `tsc --noEmit` passes
277
+ - [ ] No import from `src/legacy/`
278
+ - [ ] `FastTranspileCompiler.getResolvedModules()` returns `[]`
279
+ - [ ] `MemoryCache.getScriptVersion()` returns `undefined` (value) not `"undefined"` (string) for unknown files
280
+ - [ ] Every exported symbol has TSDoc