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.
- package/.opencode/archive/sessions/ses_2bfd9d52bffeFFWDJAjOaEp37I/.last.sha256 +1 -0
- package/.opencode/archive/sessions/ses_2bfd9d52bffeFFWDJAjOaEp37I/2026-03-31T07-18-10.759Z.json +14293 -0
- package/.opencode/archive/sessions/ses_2bfd9d52bffeFFWDJAjOaEp37I/2026-03-31T07-18-10.759Z.md +13422 -0
- package/.opencode/plans/pr1.md +337 -0
- package/.opencode/plans/pr2.md +280 -0
- package/.opencode/plans/pr3.md +127 -0
- package/.opencode/plans/pr4.md +229 -0
- package/.opencode/plans/pr5.md +211 -0
- package/.opencode/plans/pr6.md +178 -0
- package/.ts-jest-digest +1 -1
- package/CHANGELOG.md +7 -0
- package/dist/config/define-config.d.ts +17 -0
- package/dist/config/define-config.js +41 -0
- package/dist/config/diagnostics.d.ts +5 -0
- package/dist/config/diagnostics.js +8 -0
- package/dist/experimental/index.d.ts +1 -0
- package/dist/experimental/index.js +17 -0
- package/dist/experimental/option-validator.d.ts +1 -0
- package/dist/experimental/option-validator.js +67 -0
- package/dist/legacy/config/config-set.js +0 -1
- package/package.json +21 -21
|
@@ -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
|