Skip to content

Commit 387964f

Browse files
authored
feat(compiler): allow custom transformers to access internal Program (#2299)
BREAKING CHANGE `ts-jest` custom AST transformer function signature has changed to ``` import type { TsCompilerInstance } from 'ts-jest/dist/types' export function factory(compilerInstance: TsCompilerInstance) { //... } ```
1 parent 59e59ff commit 387964f

16 files changed

+231
-127
lines changed

e2e/__cases__/ast-transformers/with-extra-options/foo.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
const { LogContexts, LogLevels } = require('bs-logger')
22

3-
function factory(cs, extraOpts = Object.create(null)) {
4-
const logger = cs.logger.child({ namespace: 'dummy-transformer' })
5-
const ts = cs.compilerModule
3+
function factory({ configSet }, extraOpts = Object.create(null)) {
4+
const logger = configSet.logger.child({ namespace: 'dummy-transformer' })
5+
const ts = configSet.compilerModule
66
logger.debug('Dummy transformer with extra options', JSON.stringify(extraOpts))
77

88
function createVisitor(_ctx, _sf) {

src/__mocks__/dummy-transformer.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
const { LogContexts, LogLevels } = require('bs-logger')
22

3-
function factory(cs) {
4-
const logger = cs.logger.child({ namespace: 'dummy-transformer' })
5-
const ts = cs.compilerModule
3+
function factory(tsCompiler) {
4+
const logger = tsCompiler.configSet.logger.child({ namespace: 'dummy-transformer' })
5+
const ts = tsCompiler.configSet.compilerModule
6+
// eslint-disable-next-line no-console
7+
console.log(tsCompiler.program)
68

79
function createVisitor(_ctx, _) {
810
return (node) => node

src/compiler/ts-compiler.spec.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ describe('TsCompiler', () => {
3030
expect(new ProcessedSource(compiledOutput, fileName).outputCodeWithoutMaps).toMatchSnapshot()
3131
})
3232

33-
it('should compile js file for allowJs true', () => {
33+
test('should compile js file for allowJs true', () => {
3434
const fileName = 'foo.js'
3535
const compiler = makeCompiler({
3636
tsJestConfig: { ...baseTsJestConfig, tsconfig: { allowJs: true, outDir: TS_JEST_OUT_DIR } },
@@ -179,6 +179,32 @@ const t: string = f(5)
179179
).not.toThrowError()
180180
})
181181
})
182+
183+
test('should use correct custom AST transformers', () => {
184+
// eslint-disable-next-line no-console
185+
console.log = jest.fn()
186+
const fileName = 'foo.js'
187+
const compiler = makeCompiler({
188+
tsJestConfig: {
189+
...baseTsJestConfig,
190+
tsconfig: {
191+
allowJs: true,
192+
outDir: TS_JEST_OUT_DIR,
193+
},
194+
astTransformers: {
195+
before: ['dummy-transformer'],
196+
after: ['dummy-transformer'],
197+
afterDeclarations: ['dummy-transformer'],
198+
},
199+
},
200+
})
201+
const source = 'export default 42'
202+
203+
compiler.getCompiledOutput(source, fileName, false)
204+
205+
// eslint-disable-next-line no-console
206+
expect(console.log).toHaveBeenCalledTimes(3)
207+
})
182208
})
183209

184210
describe('isolatedModule false', () => {
@@ -452,5 +478,31 @@ const t: string = f(5)
452478
expect(() => compiler.getCompiledOutput(source, fileName, false)).toThrowErrorMatchingSnapshot()
453479
})
454480
})
481+
482+
test('should pass Program instance into custom transformers', () => {
483+
// eslint-disable-next-line no-console
484+
console.log = jest.fn()
485+
const fileName = join(mockFolder, 'thing.spec.ts')
486+
const compiler = makeCompiler(
487+
{
488+
tsJestConfig: {
489+
...baseTsJestConfig,
490+
astTransformers: {
491+
before: ['dummy-transformer'],
492+
after: ['dummy-transformer'],
493+
afterDeclarations: ['dummy-transformer'],
494+
},
495+
},
496+
},
497+
jestCacheFS,
498+
)
499+
500+
compiler.getCompiledOutput(readFileSync(fileName, 'utf-8'), fileName, false)
501+
502+
// eslint-disable-next-line no-console
503+
expect(console.log).toHaveBeenCalled()
504+
// eslint-disable-next-line no-console
505+
expect(((console.log as any) as jest.MockInstance<any, any>).mock.calls[0][0].emit).toBeDefined()
506+
})
455507
})
456508
})

src/compiler/ts-compiler.ts

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@ import type {
1010
ResolvedModuleFull,
1111
TranspileOutput,
1212
CompilerOptions,
13+
SourceFile,
14+
Program,
15+
TransformerFactory,
16+
Bundle,
17+
CustomTransformerFactory,
1318
} from 'typescript'
1419

1520
import type { ConfigSet } from '../config/config-set'
1621
import { LINE_FEED } from '../constants'
17-
import type { CompilerInstance, ResolvedModulesMap, StringMap, TTypeScript } from '../types'
22+
import type { ResolvedModulesMap, StringMap, TsCompilerInstance, TTypeScript } from '../types'
1823
import { rootLogger } from '../utils/logger'
1924
import { Errors, interpolate } from '../utils/messages'
2025

@@ -23,23 +28,22 @@ import { updateOutput } from './compiler-utils'
2328
/**
2429
* @internal
2530
*/
26-
export class TsCompiler implements CompilerInstance {
31+
export class TsCompiler implements TsCompilerInstance {
2732
private readonly _logger: Logger
2833
private readonly _ts: TTypeScript
2934
private readonly _parsedTsConfig: ParsedCommandLine
3035
private readonly _compilerCacheFS: Map<string, number> = new Map<string, number>()
31-
private readonly _jestCacheFS: StringMap
3236
private readonly _initialCompilerOptions: CompilerOptions
3337
private _compilerOptions: CompilerOptions
3438
private _cachedReadFile: ((fileName: string) => string | undefined) | undefined
3539
private _projectVersion = 1
3640
private _languageService: LanguageService | undefined
41+
program: Program | undefined
3742

3843
constructor(readonly configSet: ConfigSet, readonly jestCacheFS: StringMap) {
3944
this._ts = configSet.compilerModule
4045
this._logger = rootLogger.child({ namespace: 'ts-compiler' })
4146
this._parsedTsConfig = this.configSet.parsedTsConfig as ParsedCommandLine
42-
this._jestCacheFS = jestCacheFS
4347
this._initialCompilerOptions = { ...this._parsedTsConfig.options }
4448
this._compilerOptions = { ...this._initialCompilerOptions }
4549
if (!this.configSet.isolatedModules) {
@@ -96,13 +100,13 @@ export class TsCompiler implements CompilerInstance {
96100
// Read contents from TypeScript memory cache.
97101
if (!hit) {
98102
const fileContent =
99-
this._jestCacheFS.get(normalizedFileName) ?? this._cachedReadFile?.(normalizedFileName) ?? undefined
103+
this.jestCacheFS.get(normalizedFileName) ?? this._cachedReadFile?.(normalizedFileName) ?? undefined
100104
if (fileContent) {
101-
this._jestCacheFS.set(normalizedFileName, fileContent)
105+
this.jestCacheFS.set(normalizedFileName, fileContent)
102106
this._compilerCacheFS.set(normalizedFileName, 1)
103107
}
104108
}
105-
const contents = this._jestCacheFS.get(normalizedFileName)
109+
const contents = this.jestCacheFS.get(normalizedFileName)
106110

107111
if (contents === undefined) return
108112

@@ -118,7 +122,17 @@ export class TsCompiler implements CompilerInstance {
118122
getCurrentDirectory: () => this.configSet.cwd,
119123
getCompilationSettings: () => this._compilerOptions,
120124
getDefaultLibFileName: () => this._ts.getDefaultLibFilePath(this._compilerOptions),
121-
getCustomTransformers: () => this.configSet.customTransformers,
125+
getCustomTransformers: () => ({
126+
before: this.configSet.resolvedTransformers.before.map((beforeTransformer) =>
127+
beforeTransformer.factory(this, beforeTransformer.options),
128+
) as (TransformerFactory<SourceFile> | CustomTransformerFactory)[],
129+
after: this.configSet.resolvedTransformers.after.map((afterTransformer) =>
130+
afterTransformer.factory(this, afterTransformer.options),
131+
) as (TransformerFactory<SourceFile> | CustomTransformerFactory)[],
132+
afterDeclarations: this.configSet.resolvedTransformers.afterDeclarations.map((afterDeclarations) =>
133+
afterDeclarations.factory(this, afterDeclarations.options),
134+
) as TransformerFactory<SourceFile | Bundle>[],
135+
}),
122136
resolveModuleNames: (moduleNames: string[], containingFile: string): (ResolvedModuleFull | undefined)[] =>
123137
moduleNames.map((moduleName) => {
124138
const { resolvedModule } = this._ts.resolveModuleName(
@@ -136,6 +150,7 @@ export class TsCompiler implements CompilerInstance {
136150
this._logger.debug('created language service')
137151

138152
this._languageService = this._ts.createLanguageService(serviceHost, this._ts.createDocumentRegistry())
153+
this.program = this._languageService.getProgram()
139154
}
140155

141156
getResolvedModulesMap(fileContent: string, fileName: string): ResolvedModulesMap {
@@ -197,7 +212,17 @@ export class TsCompiler implements CompilerInstance {
197212

198213
const result: TranspileOutput = this._ts.transpileModule(fileContent, {
199214
fileName,
200-
transformers: this.configSet.customTransformers,
215+
transformers: {
216+
before: this.configSet.resolvedTransformers.before.map((beforeTransformer) =>
217+
beforeTransformer.factory(this, beforeTransformer.options),
218+
) as (TransformerFactory<SourceFile> | CustomTransformerFactory)[],
219+
after: this.configSet.resolvedTransformers.after.map((afterTransformer) =>
220+
afterTransformer.factory(this, afterTransformer.options),
221+
) as (TransformerFactory<SourceFile> | CustomTransformerFactory)[],
222+
afterDeclarations: this.configSet.resolvedTransformers.afterDeclarations.map((afterDeclarations) =>
223+
afterDeclarations.factory(this, afterDeclarations.options),
224+
) as TransformerFactory<SourceFile | Bundle>[],
225+
},
201226
compilerOptions: this._compilerOptions,
202227
reportDiagnostics: this.configSet.shouldReportDiagnostics(fileName),
203228
})
@@ -212,9 +237,7 @@ export class TsCompiler implements CompilerInstance {
212237

213238
private _isFileInCache(fileName: string): boolean {
214239
return (
215-
this._jestCacheFS.has(fileName) &&
216-
this._compilerCacheFS.has(fileName) &&
217-
this._compilerCacheFS.get(fileName) !== 0
240+
this.jestCacheFS.has(fileName) && this._compilerCacheFS.has(fileName) && this._compilerCacheFS.get(fileName) !== 0
218241
)
219242
}
220243

@@ -229,7 +252,7 @@ export class TsCompiler implements CompilerInstance {
229252
shouldIncrementProjectVersion = true
230253
} else {
231254
const prevVersion = this._compilerCacheFS.get(fileName) ?? 0
232-
const previousContents = this._jestCacheFS.get(fileName)
255+
const previousContents = this.jestCacheFS.get(fileName)
233256
// Avoid incrementing cache when nothing has changed.
234257
if (previousContents !== contents) {
235258
this._compilerCacheFS.set(fileName, prevVersion + 1)

src/config/__snapshots__/config-set.spec.ts.snap

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,48 +54,85 @@ Array [
5454

5555
exports[`customTransformers should return an object containing all resolved transformers 1`] = `
5656
Object {
57+
"after": Array [],
58+
"afterDeclarations": Array [],
5759
"before": Array [
58-
[Function],
60+
Object {
61+
"factory": [Function],
62+
"name": "hoisting-jest-mock",
63+
"version": 4,
64+
},
5965
],
6066
}
6167
`;
6268

6369
exports[`customTransformers should return an object containing all resolved transformers 2`] = `
6470
Object {
71+
"after": Array [],
72+
"afterDeclarations": Array [],
6573
"before": Array [
66-
[Function],
67-
[Function],
74+
Object {
75+
"factory": [Function],
76+
"name": "hoisting-jest-mock",
77+
"version": 4,
78+
},
79+
Object {
80+
"factory": [Function],
81+
},
6882
],
6983
}
7084
`;
7185

7286
exports[`customTransformers should return an object containing all resolved transformers 3`] = `
7387
Object {
7488
"after": Array [
75-
[Function],
89+
Object {
90+
"factory": [Function],
91+
},
7692
],
93+
"afterDeclarations": Array [],
7794
"before": Array [
78-
[Function],
95+
Object {
96+
"factory": [Function],
97+
"name": "hoisting-jest-mock",
98+
"version": 4,
99+
},
79100
],
80101
}
81102
`;
82103

83104
exports[`customTransformers should return an object containing all resolved transformers 4`] = `
84105
Object {
106+
"after": Array [],
85107
"afterDeclarations": Array [
86-
[Function],
108+
Object {
109+
"factory": [Function],
110+
},
87111
],
88112
"before": Array [
89-
[Function],
113+
Object {
114+
"factory": [Function],
115+
"name": "hoisting-jest-mock",
116+
"version": 4,
117+
},
90118
],
91119
}
92120
`;
93121

94122
exports[`customTransformers should return an object containing all resolved transformers 5`] = `
95123
Object {
124+
"after": Array [],
125+
"afterDeclarations": Array [],
96126
"before": Array [
97-
[Function],
98-
[Function],
127+
Object {
128+
"factory": [Function],
129+
"name": "hoisting-jest-mock",
130+
"version": 4,
131+
},
132+
Object {
133+
"factory": [Function],
134+
"options": Object {},
135+
},
99136
],
100137
}
101138
`;

src/config/config-set.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ describe('customTransformers', () => {
169169
resolve: null,
170170
})
171171

172-
expect(cs.customTransformers).toMatchSnapshot()
172+
expect(cs.resolvedTransformers).toMatchSnapshot()
173173
})
174174
})
175175

0 commit comments

Comments
 (0)