diff --git a/src/compiler/ts-compiler.spec.ts b/src/compiler/ts-compiler.spec.ts index 4643de8fdb..db1256bb6f 100644 --- a/src/compiler/ts-compiler.spec.ts +++ b/src/compiler/ts-compiler.spec.ts @@ -374,8 +374,8 @@ const t: string = f(5) }) }) - describe('getResolvedModulesMap', () => { - const fileName = 'foo.ts' + describe('getResolvedModules', () => { + const fileName = join(__dirname, '..', '__mocks__', 'thing.spec.ts') const fileContent = 'const foo = 1' test('should return undefined when file name is not known to compiler', () => { @@ -383,7 +383,7 @@ const t: string = f(5) tsJestConfig: baseTsJestConfig, }) - expect(compiler.getResolvedModulesMap(fileContent, fileName)).toBeUndefined() + expect(compiler.getResolvedModules(fileContent, fileName, new Map())).toEqual([]) }) test('should return undefined when it is isolatedModules true', () => { @@ -394,7 +394,7 @@ const t: string = f(5) }, }) - expect(compiler.getResolvedModulesMap(fileContent, fileName)).toBeUndefined() + expect(compiler.getResolvedModules(fileContent, fileName, new Map())).toEqual([]) }) test('should return undefined when file has no resolved modules', () => { @@ -407,12 +407,12 @@ const t: string = f(5) jestCacheFS, ) - expect(compiler.getResolvedModulesMap(fileContent, fileName)).toBeUndefined() + expect(compiler.getResolvedModules(fileContent, fileName, new Map())).toEqual([]) }) test('should return resolved modules when file has resolved modules', () => { const jestCacheFS = new Map() - const fileContentWithModules = readFileSync(join(__dirname, '..', '__mocks__', 'thing.spec.ts'), 'utf-8') + const fileContentWithModules = readFileSync(fileName, 'utf-8') jestCacheFS.set(fileName, fileContentWithModules) const compiler = makeCompiler( { @@ -421,7 +421,7 @@ const t: string = f(5) jestCacheFS, ) - expect(compiler.getResolvedModulesMap(fileContentWithModules, fileName)).toBeDefined() + expect(compiler.getResolvedModules(fileContentWithModules, fileName, new Map())).not.toEqual([]) }) }) @@ -476,6 +476,30 @@ const t: string = f(5) expect(() => compiler.getCompiledOutput(source, fileName, false)).toThrowErrorMatchingSnapshot() }) + + test('should report correct diagnostics when file content has changed', () => { + const compiler = makeCompiler( + { + tsJestConfig: baseTsJestConfig, + }, + jestCacheFS, + ) + const fileName = join(process.cwd(), 'src', '__mocks__', 'thing.spec.ts') + const oldSource = ` + foo.split('-'); + ` + const newSource = ` + const foo = 'bla-bla' + foo.split('-'); + ` + jestCacheFS.set(fileName, oldSource) + + expect(() => compiler.getCompiledOutput(oldSource, fileName, false)).toThrowError() + + jestCacheFS.set(fileName, newSource) + + expect(() => compiler.getCompiledOutput(newSource, fileName, false)).not.toThrowError() + }) }) test('should pass Program instance into custom transformers', () => { diff --git a/src/compiler/ts-compiler.ts b/src/compiler/ts-compiler.ts index e62af47a95..970c9bdd37 100644 --- a/src/compiler/ts-compiler.ts +++ b/src/compiler/ts-compiler.ts @@ -16,11 +16,13 @@ import type { Bundle, CustomTransformerFactory, CustomTransformers, + ModuleResolutionHost, + ModuleResolutionCache, } from 'typescript' import { ConfigSet, TS_JEST_OUT_DIR } from '../config/config-set' import { LINE_FEED } from '../constants' -import type { ResolvedModulesMap, StringMap, TsCompilerInstance, TsJestAstTransformer, TTypeScript } from '../types' +import type { StringMap, TsCompilerInstance, TsJestAstTransformer, TTypeScript } from '../types' import { rootLogger } from '../utils/logger' import { Errors, interpolate } from '../utils/messages' @@ -31,6 +33,14 @@ export class TsCompiler implements TsCompilerInstance { protected readonly _ts: TTypeScript protected readonly _initialCompilerOptions: CompilerOptions protected _compilerOptions: CompilerOptions + /** + * @private + */ + private _runtimeCacheFS: StringMap + /** + * @private + */ + private _fileContentCache: StringMap | undefined /** * @internal */ @@ -38,11 +48,11 @@ export class TsCompiler implements TsCompilerInstance { /** * @internal */ - private readonly _compilerCacheFS: Map = new Map() + private readonly _fileVersionCache: Map | undefined /** * @internal */ - private _cachedReadFile: ((fileName: string) => string | undefined) | undefined + private readonly _cachedReadFile: ((fileName: string) => string | undefined) | undefined /** * @internal */ @@ -51,15 +61,50 @@ export class TsCompiler implements TsCompilerInstance { * @internal */ private _languageService: LanguageService | undefined + /** + * @internal + */ + private readonly _moduleResolutionHost: ModuleResolutionHost | undefined + /** + * @internal + */ + private readonly _moduleResolutionCache: ModuleResolutionCache | undefined + program: Program | undefined - constructor(readonly configSet: ConfigSet, readonly jestCacheFS: StringMap) { + constructor(readonly configSet: ConfigSet, readonly runtimeCacheFS: StringMap) { this._ts = configSet.compilerModule this._logger = rootLogger.child({ namespace: 'ts-compiler' }) this._parsedTsConfig = this.configSet.parsedTsConfig as ParsedCommandLine this._initialCompilerOptions = { ...this._parsedTsConfig.options } this._compilerOptions = { ...this._initialCompilerOptions } + this._runtimeCacheFS = runtimeCacheFS if (!this.configSet.isolatedModules) { + this._fileContentCache = new Map() + this._fileVersionCache = new Map() + this._cachedReadFile = this._logger.wrap( + { + namespace: 'ts:serviceHost', + call: null, + [LogContexts.logLevel]: LogLevels.trace, + }, + 'readFile', + memoize(this._ts.sys.readFile), + ) + /* istanbul ignore next */ + this._moduleResolutionHost = { + fileExists: memoize(this._ts.sys.fileExists), + readFile: this._cachedReadFile, + directoryExists: memoize(this._ts.sys.directoryExists), + getCurrentDirectory: () => this.configSet.cwd, + realpath: this._ts.sys.realpath && memoize(this._ts.sys.realpath), + getDirectories: memoize(this._ts.sys.getDirectories), + } + this._moduleResolutionCache = this._ts.createModuleResolutionCache( + this.configSet.cwd, + (x) => x, + this._compilerOptions, + ) this._createLanguageService() } } @@ -68,11 +113,6 @@ export class TsCompiler implements TsCompilerInstance { * @internal */ private _createLanguageService(): void { - const serviceHostTraceCtx = { - namespace: 'ts:serviceHost', - call: null, - [LogContexts.logLevel]: LogLevels.trace, - } // Initialize memory cache for typescript compiler this._parsedTsConfig.fileNames // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -81,29 +121,17 @@ export class TsCompiler implements TsCompilerInstance { !this.configSet.isTestFile(fileName) && !fileName.includes(this._parsedTsConfig.options.outDir ?? TS_JEST_OUT_DIR), ) - .forEach((fileName) => this._compilerCacheFS.set(fileName, 0)) - this._cachedReadFile = this._logger.wrap(serviceHostTraceCtx, 'readFile', memoize(this._ts.sys.readFile)) - /* istanbul ignore next */ - const moduleResolutionHost = { - fileExists: memoize(this._ts.sys.fileExists), - readFile: this._cachedReadFile, - directoryExists: memoize(this._ts.sys.directoryExists), - getCurrentDirectory: () => this.configSet.cwd, - realpath: this._ts.sys.realpath && memoize(this._ts.sys.realpath), - getDirectories: memoize(this._ts.sys.getDirectories), - } - const moduleResolutionCache = this._ts.createModuleResolutionCache( - this.configSet.cwd, - (x) => x, - this._compilerOptions, - ) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + .forEach((fileName) => this._fileVersionCache!.set(fileName, 0)) /* istanbul ignore next */ const serviceHost: LanguageServiceHost = { getProjectVersion: () => String(this._projectVersion), - getScriptFileNames: () => [...this._compilerCacheFS.keys()], + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + getScriptFileNames: () => [...this._fileVersionCache!.keys()], getScriptVersion: (fileName: string) => { const normalizedFileName = normalize(fileName) - const version = this._compilerCacheFS.get(normalizedFileName) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const version = this._fileVersionCache!.get(normalizedFileName) // We need to return `undefined` and not a string here because TypeScript will use // `getScriptVersion` and compare against their own version - which can be `undefined`. @@ -122,13 +150,20 @@ export class TsCompiler implements TsCompilerInstance { // Read contents from TypeScript memory cache. if (!hit) { const fileContent = - this.jestCacheFS.get(normalizedFileName) ?? this._cachedReadFile?.(normalizedFileName) ?? undefined + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._fileContentCache!.get(normalizedFileName) ?? + this._runtimeCacheFS.get(normalizedFileName) ?? + this._cachedReadFile?.(normalizedFileName) ?? + undefined if (fileContent) { - this.jestCacheFS.set(normalizedFileName, fileContent) - this._compilerCacheFS.set(normalizedFileName, 1) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._fileContentCache!.set(normalizedFileName, fileContent) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._fileVersionCache!.set(normalizedFileName, 1) } } - const contents = this.jestCacheFS.get(normalizedFileName) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const contents = this._fileContentCache!.get(normalizedFileName) if (contents === undefined) return @@ -151,8 +186,10 @@ export class TsCompiler implements TsCompilerInstance { moduleName, containingFile, this._compilerOptions, - moduleResolutionHost, - moduleResolutionCache, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._moduleResolutionHost!, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._moduleResolutionCache!, ) return resolvedModule @@ -165,12 +202,29 @@ export class TsCompiler implements TsCompilerInstance { this.program = this._languageService.getProgram() } - getResolvedModulesMap(fileContent: string, fileName: string): ResolvedModulesMap { - this._updateMemoryCache(fileContent, fileName) + getResolvedModules(fileContent: string, fileName: string, runtimeCacheFS: StringMap): string[] { + // In watch mode, it is possible that the initial cacheFS becomes empty + if (!this.runtimeCacheFS.size) { + this._runtimeCacheFS = runtimeCacheFS + } + + return this._ts + .preProcessFile(fileContent, true, true) + .importedFiles.map((importedFile) => { + const { resolvedModule } = this._ts.resolveModuleName( + importedFile.fileName, + fileName, + this._compilerOptions, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._moduleResolutionHost!, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._moduleResolutionCache!, + ) - // See https://github.com/microsoft/TypeScript/blob/master/src/compiler/utilities.ts#L164 - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return (this._languageService?.getProgram()?.getSourceFile(fileName) as any)?.resolvedModules + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return resolvedModule?.resolvedFileName ?? '' + }) + .filter((resolvedFileName) => !!resolvedFileName) } getCompiledOutput(fileContent: string, fileName: string, supportsStaticESM: boolean): string { @@ -261,7 +315,12 @@ export class TsCompiler implements TsCompilerInstance { */ private _isFileInCache(fileName: string): boolean { return ( - this.jestCacheFS.has(fileName) && this._compilerCacheFS.has(fileName) && this._compilerCacheFS.get(fileName) !== 0 + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._fileContentCache!.has(fileName) && + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._fileVersionCache!.has(fileName) && + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._fileVersionCache!.get(fileName) !== 0 ) } @@ -275,14 +334,20 @@ export class TsCompiler implements TsCompilerInstance { let shouldIncrementProjectVersion = false const hit = this._isFileInCache(fileName) if (!hit) { - this._compilerCacheFS.set(fileName, 1) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._fileVersionCache!.set(fileName, 1) shouldIncrementProjectVersion = true } else { - const prevVersion = this._compilerCacheFS.get(fileName) ?? 0 - const previousContents = this.jestCacheFS.get(fileName) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const prevVersion = this._fileVersionCache!.get(fileName) ?? 0 + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const previousContents = this._fileContentCache!.get(fileName) // Avoid incrementing cache when nothing has changed. if (previousContents !== contents) { - this._compilerCacheFS.set(fileName, prevVersion + 1) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._fileVersionCache!.set(fileName, prevVersion + 1) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this._fileContentCache!.set(fileName, contents) // Only bump project version when file is modified in cache, not when discovered for the first time if (hit) shouldIncrementProjectVersion = true } diff --git a/src/compiler/ts-jest-compiler.ts b/src/compiler/ts-jest-compiler.ts index 73eec3cfb3..9d0c14c75e 100644 --- a/src/compiler/ts-jest-compiler.ts +++ b/src/compiler/ts-jest-compiler.ts @@ -1,5 +1,5 @@ import type { ConfigSet } from '../config/config-set' -import type { CompilerInstance, ResolvedModulesMap, StringMap } from '../types' +import type { CompilerInstance, StringMap } from '../types' import { TsCompiler } from './ts-compiler' @@ -9,13 +9,13 @@ import { TsCompiler } from './ts-compiler' export class TsJestCompiler implements CompilerInstance { private readonly _compilerInstance: CompilerInstance - constructor(readonly configSet: ConfigSet, readonly jestCacheFS: StringMap) { + constructor(configSet: ConfigSet, runtimeCacheFS: StringMap) { // Later we can add swc/esbuild or other typescript compiler instance here - this._compilerInstance = new TsCompiler(configSet, jestCacheFS) + this._compilerInstance = new TsCompiler(configSet, runtimeCacheFS) } - getResolvedModulesMap(fileContent: string, fileName: string): ResolvedModulesMap { - return this._compilerInstance.getResolvedModulesMap(fileContent, fileName) + getResolvedModules(fileContent: string, fileName: string, runtimeCacheFS: StringMap): string[] { + return this._compilerInstance.getResolvedModules(fileContent, fileName, runtimeCacheFS) } getCompiledOutput(fileContent: string, fileName: string, supportsStaticESM: boolean): string { diff --git a/src/ts-jest-transformer.spec.ts b/src/ts-jest-transformer.spec.ts index 43e9ca08af..004fc68f96 100644 --- a/src/ts-jest-transformer.spec.ts +++ b/src/ts-jest-transformer.spec.ts @@ -4,7 +4,6 @@ import { join } from 'path' import { Logger, LogLevels } from 'bs-logger' import { removeSync, writeFileSync } from 'fs-extra' import mkdirp from 'mkdirp' -import { Extension, ResolvedModuleFull } from 'typescript' import { createConfigSet } from './__helpers__/fakers' import { logTargetMock } from './__helpers__/mocks' @@ -13,19 +12,13 @@ import { TsCompiler } from './compiler/ts-compiler' import { TsJestCompiler } from './compiler/ts-jest-compiler' import { ConfigSet } from './config/config-set' import { CACHE_KEY_EL_SEPARATOR, TsJestTransformer } from './ts-jest-transformer' -import type { ProjectConfigTsJest, ResolvedModulesMap, StringMap } from './types' +import type { DepGraphInfo, ProjectConfigTsJest, StringMap } from './types' import { stringify } from './utils/json' import { sha1 } from './utils/sha1' import { VersionCheckers } from './utils/version-checkers' const logTarget = logTargetMock() const cacheDir = join(process.cwd(), 'tmp') -const resolvedModule = { - resolvedFileName: join(__dirname, '__mocks__', 'thing.ts'), - extension: Extension.Ts, - isExternalLibraryImport: false, - packageId: undefined, -} beforeEach(() => { logTarget.clear() @@ -126,8 +119,11 @@ Array [ }) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const tsCacheDir = cs.tsCacheDir! - const depGraphs: ResolvedModulesMap = new Map() - depGraphs.set(fileName, resolvedModule) + const depGraphs: Map = new Map() + depGraphs.set(fileName, { + fileContent: 'const foo = 1', + resolvedModuleNames: [], + }) const resolvedModulesCacheDir = join(tsCacheDir, sha1('ts-jest-resolved-modules', CACHE_KEY_EL_SEPARATOR)) mkdirp.sync(tsCacheDir) writeFileSync(resolvedModulesCacheDir, stringify([...depGraphs])) @@ -165,10 +161,8 @@ Array [ cacheDirectory: cacheDir, }, } - const depGraphs: ResolvedModulesMap = new Map() beforeEach(() => { - depGraphs.clear() // @ts-expect-error testing purpose TsJestTransformer._cachedConfigSets = [] tr = new TsJestTransformer() @@ -202,20 +196,22 @@ Array [ }) test('should be the same with the same file content', () => { - depGraphs.set(input.fileName, resolvedModule) - jest.spyOn(TsJestCompiler.prototype, 'getResolvedModulesMap').mockReturnValueOnce(depGraphs) + jest.spyOn(TsJestCompiler.prototype, 'getResolvedModules').mockReturnValueOnce([]) const cacheKey1 = tr.getCacheKey(input.fileContent, input.fileName, transformOptionsWithCache) const cacheKey2 = tr.getCacheKey(input.fileContent, input.fileName, transformOptionsWithCache) expect(cacheKey1).toEqual(cacheKey2) - expect(TsJestCompiler.prototype.getResolvedModulesMap).toHaveBeenCalledTimes(1) - expect(TsJestCompiler.prototype.getResolvedModulesMap).toHaveBeenCalledWith(input.fileContent, input.fileName) + expect(TsJestCompiler.prototype.getResolvedModules).toHaveBeenCalledTimes(1) + expect(TsJestCompiler.prototype.getResolvedModules).toHaveBeenCalledWith( + input.fileContent, + input.fileName, + new Map(), + ) }) test('should be different between isolatedModules true and isolatedModules false', () => { - depGraphs.set(input.fileName, resolvedModule) - jest.spyOn(TsJestCompiler.prototype, 'getResolvedModulesMap').mockReturnValueOnce(depGraphs) + jest.spyOn(TsJestCompiler.prototype, 'getResolvedModules').mockReturnValueOnce([]) const cacheKey1 = tr.getCacheKey(input.fileContent, input.fileName, { ...input.transformOptions, @@ -225,38 +221,48 @@ Array [ }, }) - jest.spyOn(TsJestCompiler.prototype, 'getResolvedModulesMap').mockReturnValueOnce(depGraphs) + jest.spyOn(TsJestCompiler.prototype, 'getResolvedModules').mockReturnValueOnce([]) const tr1 = new TsJestTransformer() const cacheKey2 = tr1.getCacheKey(input.fileContent, input.fileName, transformOptionsWithCache) - expect(TsJestCompiler.prototype.getResolvedModulesMap).toHaveBeenCalledTimes(1) - expect(TsJestCompiler.prototype.getResolvedModulesMap).toHaveBeenCalledWith(input.fileContent, input.fileName) + expect(TsJestCompiler.prototype.getResolvedModules).toHaveBeenCalledTimes(1) + expect(TsJestCompiler.prototype.getResolvedModules).toHaveBeenCalledWith( + input.fileContent, + input.fileName, + new Map(), + ) expect(cacheKey1).not.toEqual(cacheKey2) }) test('should be different with different file content for the same file', () => { - depGraphs.set(input.fileName, resolvedModule) - jest.spyOn(TsJestCompiler.prototype, 'getResolvedModulesMap').mockReturnValueOnce(depGraphs) + jest.spyOn(TsJestCompiler.prototype, 'getResolvedModules').mockReturnValueOnce([]) const cacheKey1 = tr.getCacheKey(input.fileContent, input.fileName, transformOptionsWithCache) - jest.spyOn(TsJestCompiler.prototype, 'getResolvedModulesMap').mockReturnValueOnce(depGraphs) + jest.spyOn(TsJestCompiler.prototype, 'getResolvedModules').mockReturnValueOnce([]) const newFileContent = 'const foo = 1' const cacheKey2 = tr.getCacheKey(newFileContent, input.fileName, transformOptionsWithCache) expect(cacheKey1).not.toEqual(cacheKey2) - expect(TsJestCompiler.prototype.getResolvedModulesMap).toHaveBeenCalledTimes(2) - expect(TsJestCompiler.prototype.getResolvedModulesMap).toHaveBeenNthCalledWith( + expect(TsJestCompiler.prototype.getResolvedModules).toHaveBeenCalledTimes(2) + expect(TsJestCompiler.prototype.getResolvedModules).toHaveBeenNthCalledWith( 1, input.fileContent, input.fileName, + new Map(), + ) + expect(TsJestCompiler.prototype.getResolvedModules).toHaveBeenNthCalledWith( + 2, + newFileContent, + input.fileName, + new Map(), ) - expect(TsJestCompiler.prototype.getResolvedModulesMap).toHaveBeenNthCalledWith(2, newFileContent, input.fileName) }) test('should be different with non existed imported modules', () => { - depGraphs.set(input.fileName, resolvedModule) - jest.spyOn(TsJestCompiler.prototype, 'getResolvedModulesMap').mockReturnValueOnce(depGraphs) + jest + .spyOn(TsJestCompiler.prototype, 'getResolvedModules') + .mockReturnValueOnce([join(process.cwd(), 'src', '__mocks__', 'thing.ts')]) const cacheKey1 = tr.getCacheKey(input.fileContent, input.fileName, transformOptionsWithCache) @@ -264,8 +270,12 @@ Array [ const cacheKey2 = tr.getCacheKey(input.fileContent, input.fileName, transformOptionsWithCache) expect(cacheKey1).not.toEqual(cacheKey2) - expect(TsJestCompiler.prototype.getResolvedModulesMap).toHaveBeenCalledTimes(1) - expect(TsJestCompiler.prototype.getResolvedModulesMap).toHaveBeenCalledWith(input.fileContent, input.fileName) + expect(TsJestCompiler.prototype.getResolvedModules).toHaveBeenCalledTimes(1) + expect(TsJestCompiler.prototype.getResolvedModules).toHaveBeenCalledWith( + input.fileContent, + input.fileName, + new Map(), + ) }) }) @@ -282,7 +292,7 @@ Array [ beforeEach(() => { tr = new TsJestTransformer() - jest.spyOn(TsJestCompiler.prototype, 'getResolvedModulesMap').mockReturnValueOnce(new Map()) + jest.spyOn(TsJestCompiler.prototype, 'getResolvedModules').mockReturnValueOnce([]) }) test('should process input as stringified content with content matching stringifyContentPathRegex option', () => { @@ -424,8 +434,8 @@ Array [ describe('subclass extends TsJestTransformer', () => { class MyTsCompiler extends TsCompiler { - constructor(readonly configSet: ConfigSet, readonly jestCacheFS: StringMap) { - super(configSet, jestCacheFS) + constructor(readonly configSet: ConfigSet, readonly runtimeCacheFS: StringMap) { + super(configSet, runtimeCacheFS) } } diff --git a/src/ts-jest-transformer.ts b/src/ts-jest-transformer.ts index 223a8355b7..463f05e225 100644 --- a/src/ts-jest-transformer.ts +++ b/src/ts-jest-transformer.ts @@ -9,7 +9,7 @@ import mkdirp from 'mkdirp' import { TsJestCompiler } from './compiler/ts-jest-compiler' import { ConfigSet } from './config/config-set' import { DECLARATION_TYPE_EXT, JS_JSX_REGEX, TS_TSX_REGEX } from './constants' -import type { ProjectConfigTsJest, TransformOptionsTsJest } from './types' +import type { DepGraphInfo, ProjectConfigTsJest, TransformOptionsTsJest } from './types' import { importer } from './utils/importer' import { parse, stringify } from './utils/json' import { JsonableValue } from './utils/jsonable-value' @@ -32,11 +32,6 @@ interface TsJestHooksMap { afterProcess?(args: any[], result: string | TransformedSource): string | TransformedSource | void } -interface DepGraphInfo { - fileContent: string - resolveModuleNames: string[] -} - /** * @internal */ @@ -236,7 +231,7 @@ export class TsJestTransformer implements Transformer { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion resolvedModuleNames = this._depGraphs .get(filePath)! - .resolveModuleNames.filter((moduleName) => existsSync(moduleName)) + .resolvedModuleNames.filter((moduleName) => existsSync(moduleName)) } else { this._logger.debug( { fileName: filePath, transformOptions }, @@ -244,16 +239,10 @@ export class TsJestTransformer implements Transformer { filePath, ) - const resolvedModuleMap = this._compiler.getResolvedModulesMap(fileContent, filePath) - resolvedModuleNames = resolvedModuleMap - ? [...resolvedModuleMap.values()] - .filter((resolvedModule) => resolvedModule !== undefined) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - .map((resolveModule) => resolveModule!.resolvedFileName) - : [] + resolvedModuleNames = this._compiler.getResolvedModules(fileContent, filePath, transformOptions.cacheFS) this._depGraphs.set(filePath, { fileContent, - resolveModuleNames: resolvedModuleNames, + resolvedModuleNames, }) writeFileSync(this._tsResolvedModulesCachePath, stringify([...this._depGraphs])) } diff --git a/src/types.ts b/src/types.ts index c688d20fda..5c6d994672 100644 --- a/src/types.ts +++ b/src/types.ts @@ -204,15 +204,18 @@ export interface InitialOptionsTsJest extends Config.InitialOptions { globals?: GlobalConfigTsJest } -export type ResolvedModulesMap = Map | undefined - /** * @internal */ export type StringMap = Map +export interface DepGraphInfo { + fileContent: string + resolvedModuleNames: string[] +} + export interface CompilerInstance { - getResolvedModulesMap(fileContent: string, fileName: string): ResolvedModulesMap + getResolvedModules(fileContent: string, fileName: string, runtimeCacheFS: StringMap): string[] getCompiledOutput(fileContent: string, fileName: string, supportsStaticESM: boolean): string } export interface TsCompilerInstance extends CompilerInstance {