Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(legacy): throw type check error in ESM mode with reject #3618

Merged
merged 1 commit into from Jun 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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