Skip to content

Commit 7dd01ff

Browse files
authored
fix(legacy): throw type check error in ESM mode with reject (#3618)
Fixes #3507
1 parent 719c25e commit 7dd01ff

File tree

7 files changed

+88
-74
lines changed

7 files changed

+88
-74
lines changed

examples/ts-only/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"compilerOptions": {
33
"module": "CommonJS",
4-
"target": "ES2015"
4+
"target": "ES2021"
55
},
66
"files": ["globals.d.ts"]
77
}

src/legacy/compiler/ts-compiler.spec.ts

Lines changed: 15 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {
55
type CompilerOptions,
66
DiagnosticCategory,
77
type EmitOutput,
8-
type TranspileOutput,
98
type transpileModule,
9+
type TranspileOutput,
1010
} from 'typescript'
1111

1212
import { createConfigSet, makeCompiler } from '../../__helpers__/fakers'
@@ -217,7 +217,7 @@ describe('TsCompiler', () => {
217217
emitSkipped: false,
218218
} as EmitOutput)
219219
// @ts-expect-error testing purpose
220-
compiler._doTypeChecking = jest.fn()
220+
compiler.getDiagnostics = jest.fn().mockReturnValue([])
221221

222222
const output = compiler.getCompiledOutput(fileContent, fileName, {
223223
depGraphs: new Map(),
@@ -234,6 +234,7 @@ describe('TsCompiler', () => {
234234
}).toMatchSnapshot()
235235
expect(output).toEqual({
236236
code: updateOutput(jsOutput, fileName, sourceMap),
237+
diagnostics: [],
237238
})
238239

239240
// @ts-expect-error testing purpose
@@ -256,7 +257,7 @@ describe('TsCompiler', () => {
256257
// @ts-expect-error testing purpose
257258
compiler._logger.warn = jest.fn()
258259
// @ts-expect-error testing purpose
259-
compiler._doTypeChecking = jest.fn()
260+
compiler.getDiagnostics = jest.fn().mockReturnValue([])
260261
const fileToCheck = fileName.replace('.ts', '.js')
261262

262263
const output = compiler.getCompiledOutput(fileContent, fileToCheck, {
@@ -286,7 +287,7 @@ describe('TsCompiler', () => {
286287
// @ts-expect-error testing purpose
287288
compiler._logger.warn = jest.fn()
288289
// @ts-expect-error testing purpose
289-
compiler._doTypeChecking = jest.fn()
290+
compiler.getDiagnostics = jest.fn().mockReturnValue([])
290291

291292
// @ts-expect-error testing purpose
292293
expect(compiler._logger.warn).not.toHaveBeenCalled()
@@ -310,7 +311,7 @@ describe('TsCompiler', () => {
310311
emitSkipped: false,
311312
} as EmitOutput)
312313
// @ts-expect-error testing purpose
313-
compiler._doTypeChecking = jest.fn()
314+
compiler.getDiagnostics = jest.fn().mockReturnValue([])
314315

315316
expect(() =>
316317
compiler.getCompiledOutput(fileContent, fileName, {
@@ -527,7 +528,7 @@ describe('TsCompiler', () => {
527528
)
528529
})
529530

530-
describe('_doTypeChecking', () => {
531+
describe('getDiagnostics', () => {
531532
const fileName = join(mockFolder, 'thing.ts')
532533
const fileName1 = join(mockFolder, 'thing1.ts')
533534
const fileContent = 'const bar = 1'
@@ -596,10 +597,10 @@ describe('TsCompiler', () => {
596597
},
597598
)
598599

599-
test.each([true, false])(
600+
test(
600601
'should/should not report diagnostics in watch mode when shouldReportDiagnostics is %p ' +
601602
'and processing file is used by other files',
602-
(shouldReport) => {
603+
() => {
603604
const compiler = makeCompiler({
604605
tsJestConfig: { ...baseTsJestConfig, useESM: false },
605606
})
@@ -627,11 +628,7 @@ describe('TsCompiler', () => {
627628
},
628629
]
629630
compiler.configSet.raiseDiagnostics = jest.fn()
630-
compiler.configSet.shouldReportDiagnostics = jest
631-
.fn<(f: string) => boolean>()
632-
.mockImplementation((fileToCheck) => {
633-
return fileToCheck === fileName1 ? shouldReport : false
634-
})
631+
compiler.configSet.shouldReportDiagnostics = jest.fn<(f: string) => boolean>().mockReturnValue(false)
635632
// @ts-expect-error testing purpose
636633
compiler._languageService.getEmitOutput = jest.fn().mockReturnValueOnce({
637634
outputFiles: [{ text: sourceMap }, { text: jsOutput }],
@@ -654,24 +651,11 @@ describe('TsCompiler', () => {
654651
watchMode: true,
655652
})
656653

657-
if (shouldReport) {
658-
// @ts-expect-error testing purpose
659-
expect(compiler._languageService?.getSemanticDiagnostics).toHaveBeenCalledWith(fileName1)
660-
// @ts-expect-error testing purpose
661-
expect(compiler._languageService?.getSyntacticDiagnostics).toHaveBeenCalledWith(fileName1)
662-
expect(compiler.configSet.raiseDiagnostics).toHaveBeenCalledWith(
663-
diagnostics,
664-
fileName,
665-
// @ts-expect-error testing purpose
666-
compiler._logger,
667-
)
668-
} else {
669-
// @ts-expect-error testing purpose
670-
expect(compiler._languageService?.getSemanticDiagnostics).not.toHaveBeenCalled()
671-
// @ts-expect-error testing purpose
672-
expect(compiler._languageService?.getSyntacticDiagnostics).not.toHaveBeenCalled()
673-
expect(compiler.configSet.raiseDiagnostics).not.toHaveBeenCalled()
674-
}
654+
// @ts-expect-error testing purpose
655+
expect(compiler._languageService?.getSemanticDiagnostics).not.toHaveBeenCalled()
656+
// @ts-expect-error testing purpose
657+
expect(compiler._languageService?.getSyntacticDiagnostics).not.toHaveBeenCalled()
658+
expect(compiler.configSet.raiseDiagnostics).not.toHaveBeenCalled()
675659
},
676660
)
677661

src/legacy/compiler/ts-compiler.ts

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { basename, normalize } from 'path'
22

3-
import type { TransformedSource } from '@jest/transform'
43
import { LogContexts, Logger, LogLevels } from 'bs-logger'
54
import memoize from 'lodash.memoize'
65
import type {
@@ -25,13 +24,13 @@ import type {
2524

2625
import { LINE_FEED, TS_TSX_REGEX } from '../../constants'
2726
import type {
28-
DepGraphInfo,
2927
StringMap,
3028
TsCompilerInstance,
3129
TsJestAstTransformer,
3230
TsJestCompileOptions,
3331
TTypeScript,
3432
} from '../../types'
33+
import { CompiledOutput } from '../../types'
3534
import { rootLogger } from '../../utils'
3635
import { Errors, interpolate } from '../../utils/messages'
3736
import type { ConfigSet } from '../config/config-set'
@@ -146,11 +145,12 @@ export class TsCompiler implements TsCompilerInstance {
146145
return importedModulePaths
147146
}
148147

149-
getCompiledOutput(fileContent: string, fileName: string, options: TsJestCompileOptions): TransformedSource {
148+
getCompiledOutput(fileContent: string, fileName: string, options: TsJestCompileOptions): CompiledOutput {
150149
let moduleKind = this._initialCompilerOptions.module
151150
let esModuleInterop = this._initialCompilerOptions.esModuleInterop
152151
let allowSyntheticDefaultImports = this._initialCompilerOptions.allowSyntheticDefaultImports
153152
const currentModuleKind = this._compilerOptions.module
153+
const isEsmMode = this.configSet.useESM && options.supportsStaticESM
154154
if (
155155
(this.configSet.babelJestTransformer || (!this.configSet.babelJestTransformer && options.supportsStaticESM)) &&
156156
this.configSet.useESM
@@ -179,7 +179,34 @@ export class TsCompiler implements TsCompilerInstance {
179179
// Must set memory cache before attempting to compile
180180
this._updateMemoryCache(fileContent, fileName, currentModuleKind === moduleKind)
181181
const output: EmitOutput = this._languageService.getEmitOutput(fileName)
182-
this._doTypeChecking(fileName, options.depGraphs, options.watchMode)
182+
const diagnostics = this.getDiagnostics(fileName)
183+
if (!isEsmMode && diagnostics.length) {
184+
this.configSet.raiseDiagnostics(diagnostics, fileName, this._logger)
185+
if (options.watchMode) {
186+
this._logger.debug({ fileName }, '_doTypeChecking(): starting watch mode computing diagnostics')
187+
188+
for (const entry of options.depGraphs.entries()) {
189+
const normalizedModuleNames = entry[1].resolvedModuleNames.map((moduleName) => normalize(moduleName))
190+
const fileToReTypeCheck = entry[0]
191+
if (normalizedModuleNames.includes(fileName) && this.configSet.shouldReportDiagnostics(fileToReTypeCheck)) {
192+
this._logger.debug(
193+
{ fileToReTypeCheck },
194+
'_doTypeChecking(): computing diagnostics using language service',
195+
)
196+
197+
this._updateMemoryCache(this._getFileContentFromCache(fileToReTypeCheck), fileToReTypeCheck)
198+
const importedModulesDiagnostics = [
199+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
200+
...this._languageService!.getSemanticDiagnostics(fileToReTypeCheck),
201+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
202+
...this._languageService!.getSyntacticDiagnostics(fileToReTypeCheck),
203+
]
204+
// will raise or just warn diagnostics depending on config
205+
this.configSet.raiseDiagnostics(importedModulesDiagnostics, fileName, this._logger)
206+
}
207+
}
208+
}
209+
}
183210
if (output.emitSkipped) {
184211
if (TS_TSX_REGEX.test(fileName)) {
185212
throw new Error(interpolate(Errors.CannotProcessFile, { file: fileName }))
@@ -204,9 +231,11 @@ export class TsCompiler implements TsCompilerInstance {
204231
return this._compilerOptions.sourceMap
205232
? {
206233
code: updateOutput(outputFiles[1].text, fileName, outputFiles[0].text),
234+
diagnostics,
207235
}
208236
: {
209237
code: updateOutput(outputFiles[0].text, fileName),
238+
diagnostics,
210239
}
211240
} else {
212241
this._logger.debug({ fileName }, 'getCompiledOutput(): compiling as isolated module')
@@ -425,40 +454,20 @@ export class TsCompiler implements TsCompilerInstance {
425454
/**
426455
* @internal
427456
*/
428-
private _doTypeChecking(fileName: string, depGraphs: Map<string, DepGraphInfo>, watchMode: boolean): void {
457+
private getDiagnostics(fileName: string): Diagnostic[] {
458+
const diagnostics: Diagnostic[] = []
429459
if (this.configSet.shouldReportDiagnostics(fileName)) {
430460
this._logger.debug({ fileName }, '_doTypeChecking(): computing diagnostics using language service')
431461

432462
// Get the relevant diagnostics - this is 3x faster than `getPreEmitDiagnostics`.
433-
const diagnostics: Diagnostic[] = [
463+
diagnostics.push(
434464
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
435465
...this._languageService!.getSemanticDiagnostics(fileName),
436466
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
437467
...this._languageService!.getSyntacticDiagnostics(fileName),
438-
]
439-
// will raise or just warn diagnostics depending on config
440-
this.configSet.raiseDiagnostics(diagnostics, fileName, this._logger)
468+
)
441469
}
442-
if (watchMode) {
443-
this._logger.debug({ fileName }, '_doTypeChecking(): starting watch mode computing diagnostics')
444-
445-
for (const entry of depGraphs.entries()) {
446-
const normalizedModuleNames = entry[1].resolvedModuleNames.map((moduleName) => normalize(moduleName))
447-
const fileToReTypeCheck = entry[0]
448-
if (normalizedModuleNames.includes(fileName) && this.configSet.shouldReportDiagnostics(fileToReTypeCheck)) {
449-
this._logger.debug({ fileToReTypeCheck }, '_doTypeChecking(): computing diagnostics using language service')
450470

451-
this._updateMemoryCache(this._getFileContentFromCache(fileToReTypeCheck), fileToReTypeCheck)
452-
const importedModulesDiagnostics = [
453-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
454-
...this._languageService!.getSemanticDiagnostics(fileToReTypeCheck),
455-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
456-
...this._languageService!.getSyntacticDiagnostics(fileToReTypeCheck),
457-
]
458-
// will raise or just warn diagnostics depending on config
459-
this.configSet.raiseDiagnostics(importedModulesDiagnostics, fileName, this._logger)
460-
}
461-
}
462-
}
471+
return diagnostics
463472
}
464473
}

src/legacy/compiler/ts-jest-compiler.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import type { TransformedSource } from '@jest/transform'
2-
3-
import type { CompilerInstance, StringMap, TsJestCompileOptions } from '../../types'
1+
import type { CompilerInstance, CompiledOutput, StringMap, TsJestCompileOptions } from '../../types'
42
import type { ConfigSet } from '../config/config-set'
53

64
import { TsCompiler } from './ts-compiler'
@@ -17,7 +15,7 @@ export class TsJestCompiler implements CompilerInstance {
1715
return this._compilerInstance.getResolvedModules(fileContent, fileName, runtimeCacheFS)
1816
}
1917

20-
getCompiledOutput(fileContent: string, fileName: string, options: TsJestCompileOptions): TransformedSource {
18+
getCompiledOutput(fileContent: string, fileName: string, options: TsJestCompileOptions): CompiledOutput {
2119
return this._compilerInstance.getCompiledOutput(fileContent, fileName, options)
2220
}
2321
}

src/legacy/config/config-set.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,7 @@ export class ConfigSet {
593593
return !ignoreCodes.includes(diagnostic.code)
594594
})
595595
if (!filteredDiagnostics.length) return
596-
const error = this._createTsError(filteredDiagnostics)
596+
const error = this.createTsError(filteredDiagnostics)
597597
// only throw if `warnOnly` and it is a warning or error
598598
const importantCategories = [DiagnosticCategory.Warning, DiagnosticCategory.Error]
599599
if (this._diagnostics.throws && filteredDiagnostics.some((d) => importantCategories.includes(d.category))) {
@@ -614,7 +614,7 @@ export class ConfigSet {
614614
/**
615615
* @internal
616616
*/
617-
private _createTsError(diagnostics: readonly ts.Diagnostic[]): TSError {
617+
createTsError(diagnostics: readonly ts.Diagnostic[]): TSError {
618618
const formatDiagnostics = this._diagnostics.pretty
619619
? this.compilerModule.formatDiagnosticsWithColorAndContext
620620
: this.compilerModule.formatDiagnostics

src/legacy/ts-jest-transformer.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import type { SyncTransformer, TransformedSource } from '@jest/transform'
55
import type { Logger } from 'bs-logger'
66

77
import { DECLARATION_TYPE_EXT, JS_JSX_REGEX, TS_TSX_REGEX } from '../constants'
8-
import type { CompilerInstance, DepGraphInfo, ProjectConfigTsJest, TransformOptionsTsJest } from '../types'
8+
import type {
9+
CompiledOutput,
10+
CompilerInstance,
11+
DepGraphInfo,
12+
ProjectConfigTsJest,
13+
TransformOptionsTsJest,
14+
} from '../types'
915
import { parse, stringify, JsonableValue, rootLogger } from '../utils'
1016
import { importer } from '../utils/importer'
1117
import { Errors, interpolate } from '../utils/messages'
@@ -141,7 +147,9 @@ export class TsJestTransformer implements SyncTransformer {
141147
const configs = this._configsFor(transformOptions)
142148
const shouldStringifyContent = configs.shouldStringifyContent(sourcePath)
143149
const babelJest = shouldStringifyContent ? undefined : configs.babelJestTransformer
144-
let result = this.processWithTs(sourceText, sourcePath, transformOptions)
150+
let result: TransformedSource = {
151+
code: this.processWithTs(sourceText, sourcePath, transformOptions).code,
152+
}
145153
if (babelJest) {
146154
this._logger.debug({ fileName: sourcePath }, 'calling babel-jest processor')
147155

@@ -163,11 +171,18 @@ export class TsJestTransformer implements SyncTransformer {
163171
): Promise<TransformedSource> {
164172
this._logger.debug({ fileName: sourcePath, transformOptions }, 'processing', sourcePath)
165173

166-
return new Promise(async (resolve) => {
174+
return new Promise(async (resolve, reject) => {
167175
const configs = this._configsFor(transformOptions)
168176
const shouldStringifyContent = configs.shouldStringifyContent(sourcePath)
169177
const babelJest = shouldStringifyContent ? undefined : configs.babelJestTransformer
170-
let result = this.processWithTs(sourceText, sourcePath, transformOptions)
178+
let result: TransformedSource
179+
const processWithTsResult = this.processWithTs(sourceText, sourcePath, transformOptions)
180+
result = {
181+
code: processWithTsResult.code,
182+
}
183+
if (processWithTsResult.diagnostics?.length) {
184+
reject(configs.createTsError(processWithTsResult.diagnostics))
185+
}
171186
if (babelJest) {
172187
this._logger.debug({ fileName: sourcePath }, 'calling babel-jest processor')
173188

@@ -183,7 +198,11 @@ export class TsJestTransformer implements SyncTransformer {
183198
})
184199
}
185200

186-
private processWithTs(sourceText: string, sourcePath: string, transformOptions: TransformOptionsTsJest) {
201+
private processWithTs(
202+
sourceText: string,
203+
sourcePath: string,
204+
transformOptions: TransformOptionsTsJest,
205+
): CompiledOutput {
187206
let result: TransformedSource
188207
const configs = this._configsFor(transformOptions)
189208
const shouldStringifyContent = configs.shouldStringifyContent(sourcePath)
@@ -330,7 +349,7 @@ export class TsJestTransformer implements SyncTransformer {
330349
sourcePath: string,
331350
transformOptions: TransformOptionsTsJest,
332351
): Promise<string> {
333-
return new Promise((resolve) => resolve(this.getCacheKey(sourceText, sourcePath, transformOptions)))
352+
return Promise.resolve(this.getCacheKey(sourceText, sourcePath, transformOptions))
334353
}
335354

336355
/**

src/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,13 @@ export interface TsJestCompileOptions {
228228
supportsStaticESM: boolean
229229
}
230230

231+
export interface CompiledOutput extends TransformedSource {
232+
diagnostics?: _ts.Diagnostic[]
233+
}
234+
231235
export interface CompilerInstance {
232236
getResolvedModules(fileContent: string, fileName: string, runtimeCacheFS: StringMap): string[]
233-
getCompiledOutput(fileContent: string, fileName: string, options: TsJestCompileOptions): TransformedSource
237+
getCompiledOutput(fileContent: string, fileName: string, options: TsJestCompileOptions): CompiledOutput
234238
}
235239
export interface TsCompilerInstance extends CompilerInstance {
236240
configSet: ConfigSet

0 commit comments

Comments
 (0)