Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix(legacy): throw type check error in ESM mode with reject (#3618)
Fixes #3507
  • Loading branch information
ahnpnl committed Jun 7, 2022
1 parent 719c25e commit 7dd01ff
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 74 deletions.
2 changes: 1 addition & 1 deletion examples/ts-only/tsconfig.json
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"module": "CommonJS",
"target": "ES2015"
"target": "ES2021"
},
"files": ["globals.d.ts"]
}
46 changes: 15 additions & 31 deletions src/legacy/compiler/ts-compiler.spec.ts
Expand Up @@ -5,8 +5,8 @@ import {
type CompilerOptions,
DiagnosticCategory,
type EmitOutput,
type TranspileOutput,
type transpileModule,
type TranspileOutput,
} from 'typescript'

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

const output = compiler.getCompiledOutput(fileContent, fileName, {
depGraphs: new Map(),
Expand All @@ -234,6 +234,7 @@ describe('TsCompiler', () => {
}).toMatchSnapshot()
expect(output).toEqual({
code: updateOutput(jsOutput, fileName, sourceMap),
diagnostics: [],
})

// @ts-expect-error testing purpose
Expand All @@ -256,7 +257,7 @@ describe('TsCompiler', () => {
// @ts-expect-error testing purpose
compiler._logger.warn = jest.fn()
// @ts-expect-error testing purpose
compiler._doTypeChecking = jest.fn()
compiler.getDiagnostics = jest.fn().mockReturnValue([])
const fileToCheck = fileName.replace('.ts', '.js')

const output = compiler.getCompiledOutput(fileContent, fileToCheck, {
Expand Down Expand Up @@ -286,7 +287,7 @@ describe('TsCompiler', () => {
// @ts-expect-error testing purpose
compiler._logger.warn = jest.fn()
// @ts-expect-error testing purpose
compiler._doTypeChecking = jest.fn()
compiler.getDiagnostics = jest.fn().mockReturnValue([])

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

expect(() =>
compiler.getCompiledOutput(fileContent, fileName, {
Expand Down Expand Up @@ -527,7 +528,7 @@ describe('TsCompiler', () => {
)
})

describe('_doTypeChecking', () => {
describe('getDiagnostics', () => {
const fileName = join(mockFolder, 'thing.ts')
const fileName1 = join(mockFolder, 'thing1.ts')
const fileContent = 'const bar = 1'
Expand Down Expand Up @@ -596,10 +597,10 @@ describe('TsCompiler', () => {
},
)

test.each([true, false])(
test(
'should/should not report diagnostics in watch mode when shouldReportDiagnostics is %p ' +
'and processing file is used by other files',
(shouldReport) => {
() => {
const compiler = makeCompiler({
tsJestConfig: { ...baseTsJestConfig, useESM: false },
})
Expand Down Expand Up @@ -627,11 +628,7 @@ describe('TsCompiler', () => {
},
]
compiler.configSet.raiseDiagnostics = jest.fn()
compiler.configSet.shouldReportDiagnostics = jest
.fn<(f: string) => boolean>()
.mockImplementation((fileToCheck) => {
return fileToCheck === fileName1 ? shouldReport : false
})
compiler.configSet.shouldReportDiagnostics = jest.fn<(f: string) => boolean>().mockReturnValue(false)
// @ts-expect-error testing purpose
compiler._languageService.getEmitOutput = jest.fn().mockReturnValueOnce({
outputFiles: [{ text: sourceMap }, { text: jsOutput }],
Expand All @@ -654,24 +651,11 @@ describe('TsCompiler', () => {
watchMode: true,
})

if (shouldReport) {
// @ts-expect-error testing purpose
expect(compiler._languageService?.getSemanticDiagnostics).toHaveBeenCalledWith(fileName1)
// @ts-expect-error testing purpose
expect(compiler._languageService?.getSyntacticDiagnostics).toHaveBeenCalledWith(fileName1)
expect(compiler.configSet.raiseDiagnostics).toHaveBeenCalledWith(
diagnostics,
fileName,
// @ts-expect-error testing purpose
compiler._logger,
)
} else {
// @ts-expect-error testing purpose
expect(compiler._languageService?.getSemanticDiagnostics).not.toHaveBeenCalled()
// @ts-expect-error testing purpose
expect(compiler._languageService?.getSyntacticDiagnostics).not.toHaveBeenCalled()
expect(compiler.configSet.raiseDiagnostics).not.toHaveBeenCalled()
}
// @ts-expect-error testing purpose
expect(compiler._languageService?.getSemanticDiagnostics).not.toHaveBeenCalled()
// @ts-expect-error testing purpose
expect(compiler._languageService?.getSyntacticDiagnostics).not.toHaveBeenCalled()
expect(compiler.configSet.raiseDiagnostics).not.toHaveBeenCalled()
},
)

Expand Down
67 changes: 38 additions & 29 deletions src/legacy/compiler/ts-compiler.ts
@@ -1,6 +1,5 @@
import { basename, normalize } from 'path'

import type { TransformedSource } from '@jest/transform'
import { LogContexts, Logger, LogLevels } from 'bs-logger'
import memoize from 'lodash.memoize'
import type {
Expand All @@ -25,13 +24,13 @@ import type {

import { LINE_FEED, TS_TSX_REGEX } from '../../constants'
import type {
DepGraphInfo,
StringMap,
TsCompilerInstance,
TsJestAstTransformer,
TsJestCompileOptions,
TTypeScript,
} from '../../types'
import { CompiledOutput } from '../../types'
import { rootLogger } from '../../utils'
import { Errors, interpolate } from '../../utils/messages'
import type { ConfigSet } from '../config/config-set'
Expand Down Expand Up @@ -146,11 +145,12 @@ export class TsCompiler implements TsCompilerInstance {
return importedModulePaths
}

getCompiledOutput(fileContent: string, fileName: string, options: TsJestCompileOptions): TransformedSource {
getCompiledOutput(fileContent: string, fileName: string, options: TsJestCompileOptions): CompiledOutput {
let moduleKind = this._initialCompilerOptions.module
let esModuleInterop = this._initialCompilerOptions.esModuleInterop
let allowSyntheticDefaultImports = this._initialCompilerOptions.allowSyntheticDefaultImports
const currentModuleKind = this._compilerOptions.module
const isEsmMode = this.configSet.useESM && options.supportsStaticESM
if (
(this.configSet.babelJestTransformer || (!this.configSet.babelJestTransformer && options.supportsStaticESM)) &&
this.configSet.useESM
Expand Down Expand Up @@ -179,7 +179,34 @@ export class TsCompiler implements TsCompilerInstance {
// Must set memory cache before attempting to compile
this._updateMemoryCache(fileContent, fileName, currentModuleKind === moduleKind)
const output: EmitOutput = this._languageService.getEmitOutput(fileName)
this._doTypeChecking(fileName, options.depGraphs, options.watchMode)
const diagnostics = this.getDiagnostics(fileName)
if (!isEsmMode && diagnostics.length) {
this.configSet.raiseDiagnostics(diagnostics, fileName, this._logger)
if (options.watchMode) {
this._logger.debug({ fileName }, '_doTypeChecking(): starting watch mode computing diagnostics')

for (const entry of options.depGraphs.entries()) {
const normalizedModuleNames = entry[1].resolvedModuleNames.map((moduleName) => normalize(moduleName))
const fileToReTypeCheck = entry[0]
if (normalizedModuleNames.includes(fileName) && this.configSet.shouldReportDiagnostics(fileToReTypeCheck)) {
this._logger.debug(
{ fileToReTypeCheck },
'_doTypeChecking(): computing diagnostics using language service',
)

this._updateMemoryCache(this._getFileContentFromCache(fileToReTypeCheck), fileToReTypeCheck)
const importedModulesDiagnostics = [
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
...this._languageService!.getSemanticDiagnostics(fileToReTypeCheck),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
...this._languageService!.getSyntacticDiagnostics(fileToReTypeCheck),
]
// will raise or just warn diagnostics depending on config
this.configSet.raiseDiagnostics(importedModulesDiagnostics, fileName, this._logger)
}
}
}
}
if (output.emitSkipped) {
if (TS_TSX_REGEX.test(fileName)) {
throw new Error(interpolate(Errors.CannotProcessFile, { file: fileName }))
Expand All @@ -204,9 +231,11 @@ export class TsCompiler implements TsCompilerInstance {
return this._compilerOptions.sourceMap
? {
code: updateOutput(outputFiles[1].text, fileName, outputFiles[0].text),
diagnostics,
}
: {
code: updateOutput(outputFiles[0].text, fileName),
diagnostics,
}
} else {
this._logger.debug({ fileName }, 'getCompiledOutput(): compiling as isolated module')
Expand Down Expand Up @@ -425,40 +454,20 @@ export class TsCompiler implements TsCompilerInstance {
/**
* @internal
*/
private _doTypeChecking(fileName: string, depGraphs: Map<string, DepGraphInfo>, watchMode: boolean): void {
private getDiagnostics(fileName: string): Diagnostic[] {
const diagnostics: Diagnostic[] = []
if (this.configSet.shouldReportDiagnostics(fileName)) {
this._logger.debug({ fileName }, '_doTypeChecking(): computing diagnostics using language service')

// Get the relevant diagnostics - this is 3x faster than `getPreEmitDiagnostics`.
const diagnostics: Diagnostic[] = [
diagnostics.push(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
...this._languageService!.getSemanticDiagnostics(fileName),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
...this._languageService!.getSyntacticDiagnostics(fileName),
]
// will raise or just warn diagnostics depending on config
this.configSet.raiseDiagnostics(diagnostics, fileName, this._logger)
)
}
if (watchMode) {
this._logger.debug({ fileName }, '_doTypeChecking(): starting watch mode computing diagnostics')

for (const entry of depGraphs.entries()) {
const normalizedModuleNames = entry[1].resolvedModuleNames.map((moduleName) => normalize(moduleName))
const fileToReTypeCheck = entry[0]
if (normalizedModuleNames.includes(fileName) && this.configSet.shouldReportDiagnostics(fileToReTypeCheck)) {
this._logger.debug({ fileToReTypeCheck }, '_doTypeChecking(): computing diagnostics using language service')

this._updateMemoryCache(this._getFileContentFromCache(fileToReTypeCheck), fileToReTypeCheck)
const importedModulesDiagnostics = [
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
...this._languageService!.getSemanticDiagnostics(fileToReTypeCheck),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
...this._languageService!.getSyntacticDiagnostics(fileToReTypeCheck),
]
// will raise or just warn diagnostics depending on config
this.configSet.raiseDiagnostics(importedModulesDiagnostics, fileName, this._logger)
}
}
}
return diagnostics
}
}
6 changes: 2 additions & 4 deletions src/legacy/compiler/ts-jest-compiler.ts
@@ -1,6 +1,4 @@
import type { TransformedSource } from '@jest/transform'

import type { CompilerInstance, StringMap, TsJestCompileOptions } from '../../types'
import type { CompilerInstance, CompiledOutput, StringMap, TsJestCompileOptions } from '../../types'
import type { ConfigSet } from '../config/config-set'

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

getCompiledOutput(fileContent: string, fileName: string, options: TsJestCompileOptions): TransformedSource {
getCompiledOutput(fileContent: string, fileName: string, options: TsJestCompileOptions): CompiledOutput {
return this._compilerInstance.getCompiledOutput(fileContent, fileName, options)
}
}
4 changes: 2 additions & 2 deletions src/legacy/config/config-set.ts
Expand Up @@ -593,7 +593,7 @@ export class ConfigSet {
return !ignoreCodes.includes(diagnostic.code)
})
if (!filteredDiagnostics.length) return
const error = this._createTsError(filteredDiagnostics)
const error = this.createTsError(filteredDiagnostics)
// only throw if `warnOnly` and it is a warning or error
const importantCategories = [DiagnosticCategory.Warning, DiagnosticCategory.Error]
if (this._diagnostics.throws && filteredDiagnostics.some((d) => importantCategories.includes(d.category))) {
Expand All @@ -614,7 +614,7 @@ export class ConfigSet {
/**
* @internal
*/
private _createTsError(diagnostics: readonly ts.Diagnostic[]): TSError {
createTsError(diagnostics: readonly ts.Diagnostic[]): TSError {
const formatDiagnostics = this._diagnostics.pretty
? this.compilerModule.formatDiagnosticsWithColorAndContext
: this.compilerModule.formatDiagnostics
Expand Down
31 changes: 25 additions & 6 deletions src/legacy/ts-jest-transformer.ts
Expand Up @@ -5,7 +5,13 @@ import type { SyncTransformer, TransformedSource } from '@jest/transform'
import type { Logger } from 'bs-logger'

import { DECLARATION_TYPE_EXT, JS_JSX_REGEX, TS_TSX_REGEX } from '../constants'
import type { CompilerInstance, DepGraphInfo, ProjectConfigTsJest, TransformOptionsTsJest } from '../types'
import type {
CompiledOutput,
CompilerInstance,
DepGraphInfo,
ProjectConfigTsJest,
TransformOptionsTsJest,
} from '../types'
import { parse, stringify, JsonableValue, rootLogger } from '../utils'
import { importer } from '../utils/importer'
import { Errors, interpolate } from '../utils/messages'
Expand Down Expand Up @@ -141,7 +147,9 @@ export class TsJestTransformer implements SyncTransformer {
const configs = this._configsFor(transformOptions)
const shouldStringifyContent = configs.shouldStringifyContent(sourcePath)
const babelJest = shouldStringifyContent ? undefined : configs.babelJestTransformer
let result = this.processWithTs(sourceText, sourcePath, transformOptions)
let result: TransformedSource = {
code: this.processWithTs(sourceText, sourcePath, transformOptions).code,
}
if (babelJest) {
this._logger.debug({ fileName: sourcePath }, 'calling babel-jest processor')

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

return new Promise(async (resolve) => {
return new Promise(async (resolve, reject) => {
const configs = this._configsFor(transformOptions)
const shouldStringifyContent = configs.shouldStringifyContent(sourcePath)
const babelJest = shouldStringifyContent ? undefined : configs.babelJestTransformer
let result = this.processWithTs(sourceText, sourcePath, transformOptions)
let result: TransformedSource
const processWithTsResult = this.processWithTs(sourceText, sourcePath, transformOptions)
result = {
code: processWithTsResult.code,
}
if (processWithTsResult.diagnostics?.length) {
reject(configs.createTsError(processWithTsResult.diagnostics))
}
if (babelJest) {
this._logger.debug({ fileName: sourcePath }, 'calling babel-jest processor')

Expand All @@ -183,7 +198,11 @@ export class TsJestTransformer implements SyncTransformer {
})
}

private processWithTs(sourceText: string, sourcePath: string, transformOptions: TransformOptionsTsJest) {
private processWithTs(
sourceText: string,
sourcePath: string,
transformOptions: TransformOptionsTsJest,
): CompiledOutput {
let result: TransformedSource
const configs = this._configsFor(transformOptions)
const shouldStringifyContent = configs.shouldStringifyContent(sourcePath)
Expand Down Expand Up @@ -330,7 +349,7 @@ export class TsJestTransformer implements SyncTransformer {
sourcePath: string,
transformOptions: TransformOptionsTsJest,
): Promise<string> {
return new Promise((resolve) => resolve(this.getCacheKey(sourceText, sourcePath, transformOptions)))
return Promise.resolve(this.getCacheKey(sourceText, sourcePath, transformOptions))
}

/**
Expand Down
6 changes: 5 additions & 1 deletion src/types.ts
Expand Up @@ -228,9 +228,13 @@ export interface TsJestCompileOptions {
supportsStaticESM: boolean
}

export interface CompiledOutput extends TransformedSource {
diagnostics?: _ts.Diagnostic[]
}

export interface CompilerInstance {
getResolvedModules(fileContent: string, fileName: string, runtimeCacheFS: StringMap): string[]
getCompiledOutput(fileContent: string, fileName: string, options: TsJestCompileOptions): TransformedSource
getCompiledOutput(fileContent: string, fileName: string, options: TsJestCompileOptions): CompiledOutput
}
export interface TsCompilerInstance extends CompilerInstance {
configSet: ConfigSet
Expand Down

0 comments on commit 7dd01ff

Please sign in to comment.