Skip to content

Commit

Permalink
fix(compiler): type check correctly in watch mode when a file content…
Browse files Browse the repository at this point in the history
… itself has changed (#2405)

Closes #2118
ahnpnl authored Feb 25, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 8615306 commit 064bf3a
Showing 6 changed files with 198 additions and 107 deletions.
38 changes: 31 additions & 7 deletions src/compiler/ts-compiler.spec.ts
Original file line number Diff line number Diff line change
@@ -374,16 +374,16 @@ 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', () => {
const compiler = makeCompiler({
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<string, string>()
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', () => {
151 changes: 108 additions & 43 deletions src/compiler/ts-compiler.ts
Original file line number Diff line number Diff line change
@@ -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,18 +33,26 @@ 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
*/
private readonly _parsedTsConfig: ParsedCommandLine
/**
* @internal
*/
private readonly _compilerCacheFS: Map<string, number> = new Map<string, number>()
private readonly _fileVersionCache: Map<string, number> | 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<string, string>()
this._fileVersionCache = new Map<string, number>()
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
}
10 changes: 5 additions & 5 deletions src/compiler/ts-jest-compiler.ts
Original file line number Diff line number Diff line change
@@ -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 {
78 changes: 44 additions & 34 deletions src/ts-jest-transformer.spec.ts
Original file line number Diff line number Diff line change
@@ -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<string, ResolvedModuleFull | undefined>()
depGraphs.set(fileName, resolvedModule)
const depGraphs: Map<string, DepGraphInfo> = new Map<string, DepGraphInfo>()
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<string, ResolvedModuleFull | undefined>()

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,47 +221,61 @@ 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)

jest.spyOn(fs, 'existsSync').mockReturnValueOnce(false)
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)
}
}

19 changes: 4 additions & 15 deletions src/ts-jest-transformer.ts
Original file line number Diff line number Diff line change
@@ -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,24 +231,18 @@ 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 },
'getting resolved modules from TypeScript API for',
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]))
}
9 changes: 6 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -204,15 +204,18 @@ export interface InitialOptionsTsJest extends Config.InitialOptions {
globals?: GlobalConfigTsJest
}

export type ResolvedModulesMap = Map<string, _ts.ResolvedModuleFull | undefined> | undefined

/**
* @internal
*/
export type StringMap = Map<string, string>

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 {

0 comments on commit 064bf3a

Please sign in to comment.