Skip to content

Commit 7153246

Browse files
authoredMar 19, 2020
feat(compiler): expose internal ts Program via ConfigSet TsCompiler (#1433)
1 parent 7c97267 commit 7153246

17 files changed

+380
-163
lines changed
 

‎e2e/__monorepos__/simple/with-dependency/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@
3232
"ts-jest": {
3333
"diagnostics": true,
3434
"tsConfig": "<rootDir>/tsconfig.json",
35-
"compilerHost": true,
36-
"incremental": true
35+
"compilerHost": true
3736
}
3837
}
3938
},

‎package-lock.json

+22-18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
"@types/lodash.set": "4.x",
100100
"@types/mkdirp": "latest",
101101
"@types/node": "10.x",
102+
"@types/react": "^16.x",
102103
"@types/resolve": "latest",
103104
"@types/semver": "latest",
104105
"@types/yargs": "latest",

‎src/__mocks__/tsconfig.json

-6
This file was deleted.

‎src/compiler/__snapshots__/language-service.spec.ts.snap

+27-2
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ exports[`language service should compile js file for allowJs true 1`] = `
2020

2121
exports[`language service should compile tsx file for jsx preserve 1`] = `
2222
===[ FILE: test-jsx-preserve.tsx ]==============================================
23+
"use strict";
2324
var App = function () {
2425
return <>Test</>;
2526
};
26-
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtcHJlc2VydmUudHN4IiwibWFwcGluZ3MiOiJBQUNRLElBQU0sR0FBRyxHQUFHO0lBQ1YsT0FBTyxFQUFFLElBQUksR0FBRyxDQUFBO0FBQ2xCLENBQUMsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJ0ZXN0LWpzeC1wcmVzZXJ2ZS50c3giXSwic291cmNlc0NvbnRlbnQiOlsiXG4gICAgICAgIGNvbnN0IEFwcCA9ICgpID0+IHtcbiAgICAgICAgICByZXR1cm4gPD5UZXN0PC8+XG4gICAgICAgIH1cbiAgICAgICJdLCJ2ZXJzaW9uIjozfQ==
27+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtcHJlc2VydmUudHN4IiwibWFwcGluZ3MiOiI7QUFDUSxJQUFNLEdBQUcsR0FBRztJQUNWLE9BQU8sRUFBRSxJQUFJLEdBQUcsQ0FBQTtBQUNsQixDQUFDLENBQUEiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsidGVzdC1qc3gtcHJlc2VydmUudHN4Il0sInNvdXJjZXNDb250ZW50IjpbIlxuICAgICAgICBjb25zdCBBcHAgPSAoKSA9PiB7XG4gICAgICAgICAgcmV0dXJuIDw+VGVzdDwvPlxuICAgICAgICB9XG4gICAgICAiXSwidmVyc2lvbiI6M30=
2728
===[ INLINE SOURCE MAPS ]=======================================================
2829
file: test-jsx-preserve.tsx
29-
mappings: 'AACQ,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA'
30+
mappings: ';AACQ,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA'
3031
names: []
3132
sources:
3233
- test-jsx-preserve.tsx
@@ -41,6 +42,30 @@ exports[`language service should compile tsx file for jsx preserve 1`] = `
4142
================================================================================
4243
`;
4344

45+
exports[`language service should compile tsx file for other jsx options 1`] = `
46+
===[ FILE: test-jsx-options.tsx ]===============================================
47+
"use strict";
48+
var App = function () {
49+
return React.createElement(React.Fragment, null, "Test");
50+
};
51+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtb3B0aW9ucy50c3giLCJtYXBwaW5ncyI6IjtBQUNRLElBQU0sR0FBRyxHQUFHO0lBQ1YsT0FBTyxpREFBUyxDQUFBO0FBQ2xCLENBQUMsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJ0ZXN0LWpzeC1vcHRpb25zLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJcbiAgICAgICAgY29uc3QgQXBwID0gKCkgPT4ge1xuICAgICAgICAgIHJldHVybiA8PlRlc3Q8Lz5cbiAgICAgICAgfVxuICAgICAgIl0sInZlcnNpb24iOjN9
52+
===[ INLINE SOURCE MAPS ]=======================================================
53+
file: test-jsx-options.tsx
54+
mappings: ';AACQ,IAAM,GAAG,GAAG;IACV,OAAO,iDAAS,CAAA;AAClB,CAAC,CAAA'
55+
names: []
56+
sources:
57+
- test-jsx-options.tsx
58+
sourcesContent:
59+
- |2-
60+
61+
const App = () => {
62+
return <>Test</>
63+
}
64+
65+
version: 3
66+
================================================================================
67+
`;
68+
4469
exports[`language service should report diagnostics related to typings with pathRegex config matches file name 1`] = `
4570
"TypeScript diagnostics (customize using \`[jest-config].globals.ts-jest.diagnostics\` option):
4671
test-match-regex-diagnostics.ts(3,7): error TS2322: Type 'number' is not assignable to type 'string'."

‎src/compiler/__snapshots__/program.spec.ts.snap

+60-6
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,25 @@ exports[`cannot compile should throw error with incremental program 1`] = `
4141
This is usually the result of a faulty configuration or import. Make sure there is a \`.js\`, \`.json\` or another executable extension available alongside \`test-cannot-compile.d.ts\`."
4242
`;
4343

44+
exports[`cannot compile should throw error with incremental program 2`] = `"Unable to read file: test-cannot-compile.jsx"`;
45+
4446
exports[`cannot compile should throw error with normal program 1`] = `
4547
"Unable to require \`.d.ts\` file for file: test-cannot-compile.d.ts.
4648
This is usually the result of a faulty configuration or import. Make sure there is a \`.js\`, \`.json\` or another executable extension available alongside \`test-cannot-compile.d.ts\`."
4749
`;
4850

49-
exports[`jsx preserve should compile tsx file for jsx preserve with incremental program 1`] = `
51+
exports[`cannot compile should throw error with normal program 2`] = `"Unable to read file: test-cannot-compile.jsx"`;
52+
53+
exports[`jsx preserve should compile tsx file for with incremental program 1`] = `
5054
===[ FILE: test-jsx-preserve.tsx ]==============================================
55+
"use strict";
5156
var App = function () {
5257
return <>Test</>;
5358
};
54-
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtcHJlc2VydmUudHN4IiwibWFwcGluZ3MiOiJBQUNNLElBQU0sR0FBRyxHQUFHO0lBQ1YsT0FBTyxFQUFFLElBQUksR0FBRyxDQUFBO0FBQ2xCLENBQUMsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJ0ZXN0LWpzeC1wcmVzZXJ2ZS50c3giXSwic291cmNlc0NvbnRlbnQiOlsiXG4gICAgICBjb25zdCBBcHAgPSAoKSA9PiB7XG4gICAgICAgIHJldHVybiA8PlRlc3Q8Lz5cbiAgICAgIH1cbiAgICAiXSwidmVyc2lvbiI6M30=
59+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtcHJlc2VydmUudHN4IiwibWFwcGluZ3MiOiI7QUFDTSxJQUFNLEdBQUcsR0FBRztJQUNWLE9BQU8sRUFBRSxJQUFJLEdBQUcsQ0FBQTtBQUNsQixDQUFDLENBQUEiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsidGVzdC1qc3gtcHJlc2VydmUudHN4Il0sInNvdXJjZXNDb250ZW50IjpbIlxuICAgICAgY29uc3QgQXBwID0gKCkgPT4ge1xuICAgICAgICByZXR1cm4gPD5UZXN0PC8+XG4gICAgICB9XG4gICAgIl0sInZlcnNpb24iOjN9
5560
===[ INLINE SOURCE MAPS ]=======================================================
5661
file: test-jsx-preserve.tsx
57-
mappings: 'AACM,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA'
62+
mappings: ';AACM,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA'
5863
names: []
5964
sources:
6065
- test-jsx-preserve.tsx
@@ -69,15 +74,16 @@ exports[`jsx preserve should compile tsx file for jsx preserve with incremental
6974
================================================================================
7075
`;
7176

72-
exports[`jsx preserve should compile tsx file for jsx preserve with program 1`] = `
77+
exports[`jsx preserve should compile tsx file with program 1`] = `
7378
===[ FILE: test-jsx-preserve.tsx ]==============================================
79+
"use strict";
7480
var App = function () {
7581
return <>Test</>;
7682
};
77-
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtcHJlc2VydmUudHN4IiwibWFwcGluZ3MiOiJBQUNNLElBQU0sR0FBRyxHQUFHO0lBQ1YsT0FBTyxFQUFFLElBQUksR0FBRyxDQUFBO0FBQ2xCLENBQUMsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJ0ZXN0LWpzeC1wcmVzZXJ2ZS50c3giXSwic291cmNlc0NvbnRlbnQiOlsiXG4gICAgICBjb25zdCBBcHAgPSAoKSA9PiB7XG4gICAgICAgIHJldHVybiA8PlRlc3Q8Lz5cbiAgICAgIH1cbiAgICAiXSwidmVyc2lvbiI6M30=
83+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtcHJlc2VydmUudHN4IiwibWFwcGluZ3MiOiI7QUFDTSxJQUFNLEdBQUcsR0FBRztJQUNWLE9BQU8sRUFBRSxJQUFJLEdBQUcsQ0FBQTtBQUNsQixDQUFDLENBQUEiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsidGVzdC1qc3gtcHJlc2VydmUudHN4Il0sInNvdXJjZXNDb250ZW50IjpbIlxuICAgICAgY29uc3QgQXBwID0gKCkgPT4ge1xuICAgICAgICByZXR1cm4gPD5UZXN0PC8+XG4gICAgICB9XG4gICAgIl0sInZlcnNpb24iOjN9
7884
===[ INLINE SOURCE MAPS ]=======================================================
7985
file: test-jsx-preserve.tsx
80-
mappings: 'AACM,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA'
86+
mappings: ';AACM,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA'
8187
names: []
8288
sources:
8389
- test-jsx-preserve.tsx
@@ -92,6 +98,54 @@ exports[`jsx preserve should compile tsx file for jsx preserve with program 1`]
9298
================================================================================
9399
`;
94100

101+
exports[`other jsx options should compile tsx file for with incremental program 1`] = `
102+
===[ FILE: test-jsx-options.tsx ]===============================================
103+
"use strict";
104+
var App = function () {
105+
return React.createElement(React.Fragment, null, "Test");
106+
};
107+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtb3B0aW9ucy50c3giLCJtYXBwaW5ncyI6IjtBQUNNLElBQU0sR0FBRyxHQUFHO0lBQ1YsT0FBTyxpREFBUyxDQUFBO0FBQ2xCLENBQUMsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJ0ZXN0LWpzeC1vcHRpb25zLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJcbiAgICAgIGNvbnN0IEFwcCA9ICgpID0+IHtcbiAgICAgICAgcmV0dXJuIDw+VGVzdDwvPlxuICAgICAgfVxuICAgICJdLCJ2ZXJzaW9uIjozfQ==
108+
===[ INLINE SOURCE MAPS ]=======================================================
109+
file: test-jsx-options.tsx
110+
mappings: ';AACM,IAAM,GAAG,GAAG;IACV,OAAO,iDAAS,CAAA;AAClB,CAAC,CAAA'
111+
names: []
112+
sources:
113+
- test-jsx-options.tsx
114+
sourcesContent:
115+
- |2-
116+
117+
const App = () => {
118+
return <>Test</>
119+
}
120+
121+
version: 3
122+
================================================================================
123+
`;
124+
125+
exports[`other jsx options should compile tsx file for with program 1`] = `
126+
===[ FILE: test-jsx-options.tsx ]===============================================
127+
"use strict";
128+
var App = function () {
129+
return React.createElement(React.Fragment, null, "Test");
130+
};
131+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtb3B0aW9ucy50c3giLCJtYXBwaW5ncyI6IjtBQUNNLElBQU0sR0FBRyxHQUFHO0lBQ1YsT0FBTyxpREFBUyxDQUFBO0FBQ2xCLENBQUMsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJ0ZXN0LWpzeC1vcHRpb25zLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJcbiAgICAgIGNvbnN0IEFwcCA9ICgpID0+IHtcbiAgICAgICAgcmV0dXJuIDw+VGVzdDwvPlxuICAgICAgfVxuICAgICJdLCJ2ZXJzaW9uIjozfQ==
132+
===[ INLINE SOURCE MAPS ]=======================================================
133+
file: test-jsx-options.tsx
134+
mappings: ';AACM,IAAM,GAAG,GAAG;IACV,OAAO,iDAAS,CAAA;AAClB,CAAC,CAAA'
135+
names: []
136+
sources:
137+
- test-jsx-options.tsx
138+
sourcesContent:
139+
- |2-
140+
141+
const App = () => {
142+
return <>Test</>
143+
}
144+
145+
version: 3
146+
================================================================================
147+
`;
148+
95149
exports[`typings incremental program should report diagnostics with pathRegex config matches file name 1`] = `"test-typings.ts: Emit skipped"`;
96150

97151
exports[`typings normal program should report diagnostics with pathRegex config matches file name 1`] = `

‎src/compiler/__snapshots__/transpile-module.spec.ts.snap

+27-2
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,38 @@ exports[`transpile module with isolatedModule: true should compile js file for a
2020
2121
exports[`transpile module with isolatedModule: true should compile tsx file for jsx preserve 1`] = `
2222
===[ FILE: foo.tsx ]============================================================
23+
"use strict";
2324
var App = function () {
2425
return <>Test</>;
2526
};
26-
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoiZm9vLnRzeCIsIm1hcHBpbmdzIjoiQUFDUSxJQUFNLEdBQUcsR0FBRztJQUNWLE9BQU8sRUFBRSxJQUFJLEdBQUcsQ0FBQTtBQUNsQixDQUFDLENBQUEiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsiZm9vLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJcbiAgICAgICAgY29uc3QgQXBwID0gKCkgPT4ge1xuICAgICAgICAgIHJldHVybiA8PlRlc3Q8Lz5cbiAgICAgICAgfVxuICAgICAgIl0sInZlcnNpb24iOjN9
27+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoiZm9vLnRzeCIsIm1hcHBpbmdzIjoiO0FBQ1EsSUFBTSxHQUFHLEdBQUc7SUFDVixPQUFPLEVBQUUsSUFBSSxHQUFHLENBQUE7QUFDbEIsQ0FBQyxDQUFBIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbImZvby50c3giXSwic291cmNlc0NvbnRlbnQiOlsiXG4gICAgICAgIGNvbnN0IEFwcCA9ICgpID0+IHtcbiAgICAgICAgICByZXR1cm4gPD5UZXN0PC8+XG4gICAgICAgIH1cbiAgICAgICJdLCJ2ZXJzaW9uIjozfQ==
28+
===[ INLINE SOURCE MAPS ]=======================================================
29+
file: foo.tsx
30+
mappings: ';AACQ,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA'
31+
names: []
32+
sources:
33+
- foo.tsx
34+
sourcesContent:
35+
- |2-
36+
37+
const App = () => {
38+
return <>Test</>
39+
}
40+
41+
version: 3
42+
================================================================================
43+
`;
44+
45+
exports[`transpile module with isolatedModule: true should compile tsx file for other jsx options 1`] = `
46+
===[ FILE: foo.tsx ]============================================================
47+
"use strict";
48+
var App = function () {
49+
return React.createElement(React.Fragment, null, "Test");
50+
};
51+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoiZm9vLnRzeCIsIm1hcHBpbmdzIjoiO0FBQ1EsSUFBTSxHQUFHLEdBQUc7SUFDVixPQUFPLGlEQUFTLENBQUE7QUFDbEIsQ0FBQyxDQUFBIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbImZvby50c3giXSwic291cmNlc0NvbnRlbnQiOlsiXG4gICAgICAgIGNvbnN0IEFwcCA9ICgpID0+IHtcbiAgICAgICAgICByZXR1cm4gPD5UZXN0PC8+XG4gICAgICAgIH1cbiAgICAgICJdLCJ2ZXJzaW9uIjozfQ==
2752
===[ INLINE SOURCE MAPS ]=======================================================
2853
file: foo.tsx
29-
mappings: 'AACQ,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA'
54+
mappings: ';AACQ,IAAM,GAAG,GAAG;IACV,OAAO,iDAAS,CAAA;AAClB,CAAC,CAAA'
3055
names: []
3156
sources:
3257
- foo.tsx

‎src/compiler/instance.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import mkdirp = require('mkdirp')
3535
import { basename, extname, join, normalize } from 'path'
3636

3737
import { ConfigSet } from '../config/config-set'
38-
import { CompileResult, MemoryCache, TsCompiler } from '../types'
38+
import { CompileFn, CompileResult, MemoryCache, TsCompiler } from '../types'
3939
import { sha1 } from '../util/sha1'
4040

4141
import { compileUsingLanguageService } from './language-service'
@@ -93,15 +93,15 @@ const isValidCacheContent = (contents: string): boolean => {
9393
const readThrough = (
9494
cachedir: string | undefined,
9595
memoryCache: MemoryCache,
96-
compile: CompileResult,
96+
compileFn: CompileFn,
9797
getExtension: (fileName: string) => string,
9898
logger: Logger,
9999
) => {
100100
if (!cachedir) {
101101
return (code: string, fileName: string, lineOffset?: number) => {
102102
const normalizedFileName = normalize(fileName)
103103
logger.debug({ normalizedFileName }, 'readThrough(): no cache')
104-
const [value, sourceMap] = compile(code, normalizedFileName, lineOffset)
104+
const [value, sourceMap] = compileFn(code, normalizedFileName, lineOffset)
105105
const output = updateOutput(value, fileName, sourceMap, getExtension)
106106
memoryCache.outputs.set(normalizedFileName, output)
107107

@@ -129,7 +129,7 @@ const readThrough = (
129129
} catch (err) {}
130130

131131
logger.debug({ fileName }, 'readThrough(): cache miss')
132-
const [value, sourceMap] = compile(code, normalizedFileName, lineOffset)
132+
const [value, sourceMap] = compileFn(code, normalizedFileName, lineOffset)
133133
const output = updateOutput(value, normalizedFileName, sourceMap, getExtension)
134134

135135
logger.debug({ normalizedFileName, outputPath }, 'readThrough(): writing caches')
@@ -181,7 +181,7 @@ export const createCompiler = (configs: ConfigSet): TsCompiler => {
181181
} else {
182182
compileResult = compileUsingTranspileModule(configs, logger)
183183
}
184-
const compile = readThrough(cachedir, memoryCache, compileResult, getExtension, logger)
184+
const compile = readThrough(cachedir, memoryCache, compileResult.compileFn, getExtension, logger)
185185

186-
return { cwd: configs.cwd, compile, extensions, cachedir, ts }
186+
return { cwd: configs.cwd, compile, extensions, cachedir, ts, program: compileResult.program }
187187
}

‎src/compiler/language-service.spec.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,33 @@ describe('language service', () => {
8080
it('should compile tsx file for jsx preserve', () => {
8181
const fileName = 'test-jsx-preserve.tsx',
8282
compiler = makeCompiler({
83-
tsJestConfig: { tsConfig: 'src/__mocks__/tsconfig.json' },
83+
tsJestConfig: {
84+
tsConfig: {
85+
jsx: 'preserve' as any,
86+
},
87+
},
88+
}),
89+
source = `
90+
const App = () => {
91+
return <>Test</>
92+
}
93+
`
94+
writeFileSync(fileName, source, 'utf8')
95+
const compiled = compiler.compile(source, fileName)
96+
97+
expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot()
98+
99+
removeSync(fileName)
100+
})
101+
102+
it('should compile tsx file for other jsx options', () => {
103+
const fileName = 'test-jsx-options.tsx',
104+
compiler = makeCompiler({
105+
tsJestConfig: {
106+
tsConfig: {
107+
jsx: 'react' as any,
108+
},
109+
},
84110
}),
85111
source = `
86112
const App = () => {

‎src/compiler/language-service.ts

+41-38
Original file line numberDiff line numberDiff line change
@@ -88,50 +88,53 @@ export const compileUsingLanguageService = (
8888
}
8989
let previousProgram: _ts.Program | undefined
9090

91-
return (code: string, fileName: string): SourceOutput => {
92-
const normalizedFileName = normalize(fileName)
93-
// Must set memory cache before attempting to read file.
94-
updateMemoryCache(code, normalizedFileName)
95-
const programBefore = service.getProgram()
96-
97-
if (programBefore !== previousProgram) {
98-
logger.debug({ normalizedFileName }, `compiler rebuilt Program instance when getting output`)
99-
}
91+
return {
92+
compileFn: (code: string, fileName: string): SourceOutput => {
93+
const normalizedFileName = normalize(fileName)
94+
// Must set memory cache before attempting to read file.
95+
updateMemoryCache(code, normalizedFileName)
96+
const programBefore = service.getProgram()
10097

101-
const output: _ts.EmitOutput = service.getEmitOutput(normalizedFileName)
102-
if (configs.shouldReportDiagnostic(normalizedFileName)) {
103-
logger.debug({ normalizedFileName }, 'getOutput(): computing diagnostics for language service')
104-
// Get the relevant diagnostics - this is 3x faster than `getPreEmitDiagnostics`.
105-
const diagnostics = service
106-
.getCompilerOptionsDiagnostics()
107-
.concat(service.getSyntacticDiagnostics(normalizedFileName))
108-
.concat(service.getSemanticDiagnostics(normalizedFileName))
109-
// will raise or just warn diagnostics depending on config
110-
configs.raiseDiagnostics(diagnostics, normalizedFileName, logger)
111-
}
98+
if (programBefore !== previousProgram) {
99+
logger.debug({ normalizedFileName }, `compiler rebuilt Program instance when getting output`)
100+
}
112101

113-
/* istanbul ignore next (this should never happen but is kept for security) */
114-
if (output.emitSkipped) {
115-
throw new TypeError(`${relative(cwd, normalizedFileName)}: Emit skipped for language service`)
116-
}
102+
const output: _ts.EmitOutput = service.getEmitOutput(normalizedFileName)
103+
if (configs.shouldReportDiagnostic(normalizedFileName)) {
104+
logger.debug({ normalizedFileName }, 'getOutput(): computing diagnostics for language service')
105+
// Get the relevant diagnostics - this is 3x faster than `getPreEmitDiagnostics`.
106+
const diagnostics = service
107+
.getCompilerOptionsDiagnostics()
108+
.concat(service.getSyntacticDiagnostics(normalizedFileName))
109+
.concat(service.getSemanticDiagnostics(normalizedFileName))
110+
// will raise or just warn diagnostics depending on config
111+
configs.raiseDiagnostics(diagnostics, normalizedFileName, logger)
112+
}
117113

118-
const programAfter = service.getProgram()
114+
/* istanbul ignore next (this should never happen but is kept for security) */
115+
if (output.emitSkipped) {
116+
throw new TypeError(`${relative(cwd, normalizedFileName)}: Emit skipped for language service`)
117+
}
119118

120-
logger.debug(
121-
'invariant: Is service.getProject() identical before and after getting emit output and diagnostics? (should always be true) ',
122-
programBefore === programAfter,
123-
)
119+
const programAfter = service.getProgram()
124120

125-
previousProgram = programAfter
126-
// Throw an error when requiring `.d.ts` files.
127-
if (!output.outputFiles.length) {
128-
throw new TypeError(
129-
interpolate(Errors.UnableToRequireDefinitionFile, {
130-
file: basename(normalizedFileName),
131-
}),
121+
logger.debug(
122+
'invariant: Is service.getProject() identical before and after getting emit output and diagnostics? (should always be true) ',
123+
programBefore === programAfter,
132124
)
133-
}
134125

135-
return [output.outputFiles[1].text, output.outputFiles[0].text]
126+
previousProgram = programAfter
127+
// Throw an error when requiring `.d.ts` files.
128+
if (!output.outputFiles.length) {
129+
throw new TypeError(
130+
interpolate(Errors.UnableToRequireDefinitionFile, {
131+
file: basename(normalizedFileName),
132+
}),
133+
)
134+
}
135+
136+
return [output.outputFiles[1].text, output.outputFiles[0].text]
137+
},
138+
program: service.getProgram(),
136139
}
137140
}

‎src/compiler/program.spec.ts

+55-8
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,9 @@ describe('jsx preserve', () => {
268268
return <>Test</>
269269
}
270270
`,
271-
tsConfig = 'src/__mocks__/tsconfig.json'
271+
tsConfig = {
272+
jsx: 'preserve' as any,
273+
}
272274

273275
beforeAll(() => {
274276
writeFileSync(fileName, source, 'utf8')
@@ -278,7 +280,7 @@ describe('jsx preserve', () => {
278280
removeSync(fileName)
279281
})
280282

281-
it('should compile tsx file for jsx preserve with program', () => {
283+
it('should compile tsx file with program', () => {
282284
const compiler = makeCompiler({
283285
tsJestConfig: { ...baseTsJestConfig, incremental: false, tsConfig },
284286
})
@@ -288,7 +290,47 @@ describe('jsx preserve', () => {
288290
expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot()
289291
})
290292

291-
it('should compile tsx file for jsx preserve with incremental program', () => {
293+
it('should compile tsx file for with incremental program', () => {
294+
const compiler = makeCompiler({
295+
tsJestConfig: { ...baseTsJestConfig, incremental: true, tsConfig },
296+
})
297+
298+
const compiled = compiler.compile(source, fileName)
299+
300+
expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot()
301+
})
302+
})
303+
304+
describe('other jsx options', () => {
305+
const fileName = 'test-jsx-options.tsx',
306+
source = `
307+
const App = () => {
308+
return <>Test</>
309+
}
310+
`,
311+
tsConfig = {
312+
jsx: 'react' as any,
313+
}
314+
315+
beforeAll(() => {
316+
writeFileSync(fileName, source, 'utf8')
317+
})
318+
319+
afterAll(() => {
320+
removeSync(fileName)
321+
})
322+
323+
it('should compile tsx file for with program', () => {
324+
const compiler = makeCompiler({
325+
tsJestConfig: { ...baseTsJestConfig, incremental: false, tsConfig },
326+
})
327+
328+
const compiled = compiler.compile(source, fileName)
329+
330+
expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot()
331+
})
332+
333+
it('should compile tsx file for with incremental program', () => {
292334
const compiler = makeCompiler({
293335
tsJestConfig: { ...baseTsJestConfig, incremental: true, tsConfig },
294336
})
@@ -300,34 +342,39 @@ describe('jsx preserve', () => {
300342
})
301343

302344
describe('cannot compile', () => {
303-
const fileName = 'test-cannot-compile.d.ts',
345+
const fileName1 = 'test-cannot-compile.d.ts',
346+
fileName2 = 'test-cannot-compile.jsx',
304347
source = `
305348
interface Foo {
306349
a: string
307350
}
308351
`
309352

310353
beforeAll(() => {
311-
writeFileSync(fileName, source, 'utf8')
354+
writeFileSync(fileName1, source, 'utf8')
355+
writeFileSync(fileName2, source, 'utf8')
312356
})
313357

314358
afterAll(() => {
315-
removeSync(fileName)
359+
removeSync(fileName1)
360+
removeSync(fileName2)
316361
})
317362

318363
it('should throw error with normal program', () => {
319364
const compiler = makeCompiler({
320365
tsJestConfig: { ...baseTsJestConfig, incremental: false, tsConfig: false },
321366
})
322367

323-
expect(() => compiler.compile(source, fileName)).toThrowErrorMatchingSnapshot()
368+
expect(() => compiler.compile(source, fileName1)).toThrowErrorMatchingSnapshot()
369+
expect(() => compiler.compile(source, fileName2)).toThrowErrorMatchingSnapshot()
324370
})
325371

326372
it('should throw error with incremental program', () => {
327373
const compiler = makeCompiler({
328374
tsJestConfig: { ...baseTsJestConfig, incremental: true, tsConfig: false },
329375
})
330376

331-
expect(() => compiler.compile(source, fileName)).toThrowErrorMatchingSnapshot()
377+
expect(() => compiler.compile(source, fileName1)).toThrowErrorMatchingSnapshot()
378+
expect(() => compiler.compile(source, fileName2)).toThrowErrorMatchingSnapshot()
332379
})
333380
})

‎src/compiler/program.ts

+58-58
Original file line numberDiff line numberDiff line change
@@ -112,70 +112,70 @@ export const compileUsingProgram = (configs: ConfigSet, logger: Logger, memoryCa
112112
}
113113
}
114114

115-
return (code: string, fileName: string): SourceOutput => {
116-
const normalizedFileName = normalize(fileName),
117-
output: [string, string] = ['', '']
118-
// Must set memory cache before attempting to read file.
119-
updateMemoryCache(code, normalizedFileName)
120-
const sourceFile = incremental
121-
? builderProgram.getSourceFile(normalizedFileName)
122-
: program.getSourceFile(normalizedFileName)
115+
return {
116+
compileFn: (code: string, fileName: string): SourceOutput => {
117+
const normalizedFileName = normalize(fileName),
118+
output: [string, string] = ['', '']
119+
// Must set memory cache before attempting to read file.
120+
updateMemoryCache(code, normalizedFileName)
121+
const sourceFile = incremental
122+
? builderProgram.getSourceFile(normalizedFileName)
123+
: program.getSourceFile(normalizedFileName)
123124

124-
if (!sourceFile) throw new TypeError(`Unable to read file: ${fileName}`)
125+
if (!sourceFile) throw new TypeError(`Unable to read file: ${fileName}`)
125126

126-
const result: _ts.EmitResult = incremental
127-
? builderProgram.emit(
128-
sourceFile,
129-
(path, file, _writeByteOrderMark) => {
130-
output[path.endsWith('.map') ? 1 : 0] = file
131-
},
132-
undefined,
133-
undefined,
134-
customTransformers,
135-
)
136-
: program.emit(
137-
sourceFile,
138-
(path, file, _writeByteOrderMark) => {
139-
output[path.endsWith('.map') ? 1 : 0] = file
140-
},
141-
undefined,
142-
undefined,
143-
customTransformers,
127+
const result: _ts.EmitResult = incremental
128+
? builderProgram.emit(
129+
sourceFile,
130+
(path, file, _writeByteOrderMark) => {
131+
output[path.endsWith('.map') ? 1 : 0] = file
132+
},
133+
undefined,
134+
undefined,
135+
customTransformers,
136+
)
137+
: program.emit(
138+
sourceFile,
139+
(path, file, _writeByteOrderMark) => {
140+
output[path.endsWith('.map') ? 1 : 0] = file
141+
},
142+
undefined,
143+
undefined,
144+
customTransformers,
145+
)
146+
if (configs.shouldReportDiagnostic(normalizedFileName)) {
147+
logger.debug(
148+
{ normalizedFileName },
149+
`getOutput(): computing diagnostics for ${incremental ? 'incremental program' : 'program'}`,
144150
)
145-
if (configs.shouldReportDiagnostic(normalizedFileName)) {
146-
logger.debug(
147-
{ normalizedFileName },
148-
`getOutput(): computing diagnostics for ${incremental ? 'incremental program' : 'program'}`,
149-
)
150-
const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile).slice()
151-
// will raise or just warn diagnostics depending on config
152-
configs.raiseDiagnostics(diagnostics, normalizedFileName, logger)
153-
}
154-
155-
if (result.emitSkipped) {
156-
throw new TypeError(`${relative(cwd, fileName)}: Emit skipped`)
157-
}
151+
const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile).slice()
152+
// will raise or just warn diagnostics depending on config
153+
configs.raiseDiagnostics(diagnostics, normalizedFileName, logger)
154+
}
158155

159-
// Throw an error when requiring files that cannot be compiled.
160-
if (output[0] === '') {
161-
if (program.isSourceFileFromExternalLibrary(sourceFile)) {
162-
throw new TypeError(`Unable to compile file from external library: ${relative(cwd, fileName)}`)
156+
if (result.emitSkipped) {
157+
throw new TypeError(`${relative(cwd, fileName)}: Emit skipped`)
163158
}
164159

165-
throw new TypeError(
166-
interpolate(Errors.UnableToRequireDefinitionFile, {
167-
file: basename(normalizedFileName),
168-
}),
169-
)
170-
}
171-
if (configs.tsJest.emit && incremental) {
172-
process.on('exit', () => {
173-
// Emits `.tsbuildinfo` to filesystem.
174-
// @ts-ignore
175-
program.emitBuildInfo()
176-
})
177-
}
160+
// Throw an error when requiring files that cannot be compiled.
161+
if (output[0] === '') {
162+
throw new TypeError(
163+
interpolate(Errors.UnableToRequireDefinitionFile, {
164+
file: basename(normalizedFileName),
165+
}),
166+
)
167+
}
168+
/* istanbul ignore next */
169+
if (configs.tsJest.emit && incremental) {
170+
process.on('exit', () => {
171+
// Emits `.tsbuildinfo` to filesystem.
172+
// @ts-ignore
173+
program.emitBuildInfo()
174+
})
175+
}
178176

179-
return output
177+
return output
178+
},
179+
program,
180180
}
181181
}

‎src/compiler/transpile-module.spec.ts

+30-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,36 @@ describe('transpile module with isolatedModule: true', () => {
5757
it('should compile tsx file for jsx preserve', () => {
5858
const fileName = `foo.tsx`,
5959
compiler = makeCompiler({
60-
tsJestConfig: { ...baseTsJestConfig, tsConfig: 'src/__mocks__/tsconfig.json' },
60+
tsJestConfig: {
61+
...baseTsJestConfig,
62+
tsConfig: {
63+
jsx: 'preserve' as any,
64+
},
65+
},
66+
}),
67+
source = `
68+
const App = () => {
69+
return <>Test</>
70+
}
71+
`
72+
73+
writeFileSync(fileName, source, 'utf8')
74+
const compiled = compiler.compile(source, fileName)
75+
76+
expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot()
77+
78+
removeSync(fileName)
79+
})
80+
81+
it('should compile tsx file for other jsx options', () => {
82+
const fileName = `foo.tsx`,
83+
compiler = makeCompiler({
84+
tsJestConfig: {
85+
...baseTsJestConfig,
86+
tsConfig: {
87+
jsx: 'react' as any,
88+
},
89+
},
6190
}),
6291
source = `
6392
const App = () => {

‎src/compiler/transpile-module.ts

+15-13
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,23 @@ import { CompileResult, SourceOutput } from '../types'
1010
export const compileUsingTranspileModule = (configs: ConfigSet, logger: Logger): CompileResult => {
1111
logger.debug('compileUsingTranspileModule(): create typescript compiler')
1212

13-
return (code: string, fileName: string): SourceOutput => {
14-
logger.debug({ fileName }, 'getOutput(): compiling as isolated module')
13+
return {
14+
compileFn: (code: string, fileName: string): SourceOutput => {
15+
logger.debug({ fileName }, 'getOutput(): compiling as isolated module')
1516

16-
const normalizedFileName = normalize(fileName)
17-
const result = configs.compilerModule.transpileModule(code, {
18-
fileName: normalizedFileName,
19-
transformers: configs.tsCustomTransformers,
20-
compilerOptions: configs.typescript.options,
21-
reportDiagnostics: configs.shouldReportDiagnostic(normalizedFileName),
22-
})
17+
const normalizedFileName = normalize(fileName)
18+
const result = configs.compilerModule.transpileModule(code, {
19+
fileName: normalizedFileName,
20+
transformers: configs.tsCustomTransformers,
21+
compilerOptions: configs.typescript.options,
22+
reportDiagnostics: configs.shouldReportDiagnostic(normalizedFileName),
23+
})
2324

24-
if (result.diagnostics && configs.shouldReportDiagnostic(normalizedFileName)) {
25-
configs.raiseDiagnostics(result.diagnostics, normalizedFileName, logger)
26-
}
25+
if (result.diagnostics && configs.shouldReportDiagnostic(normalizedFileName)) {
26+
configs.raiseDiagnostics(result.diagnostics, normalizedFileName, logger)
27+
}
2728

28-
return [result.outputText, result.sourceMapText!]
29+
return [result.outputText, result.sourceMapText!]
30+
},
2931
}
3032
}

‎src/transformers/hoist-jest.ts

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export function factory(cs: ConfigSet) {
3838
const logger = cs.logger.child({ namespace: 'ts-hoisting' })
3939
/**
4040
* Our compiler (typescript, or a module with typescript-like interface)
41+
* To access Program or TypeChecker, do: cs.tsCompiler.program or cs.tsCompiler.program.getTypeChecker()
4142
*/
4243
const ts = cs.compilerModule
4344

‎src/types.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ export interface TsCompiler {
196196
cachedir: string | undefined
197197
ts: TSCommon
198198
compile(code: string, fileName: string, lineOffset?: number): string
199+
program: _ts.Program | undefined
199200
}
200201

201202
/**
@@ -213,7 +214,12 @@ export interface MemoryCache {
213214
outputs: Map<string, string>
214215
}
215216

216-
export type CompileResult = (code: string, fileName: string, lineOffset?: number) => SourceOutput
217+
export type CompileFn = (code: string, fileName: string, lineOffset?: number) => SourceOutput
218+
219+
export interface CompileResult {
220+
compileFn: CompileFn
221+
program?: _ts.Program
222+
}
217223

218224
export interface AstTransformerDesc {
219225
name: string

‎tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"target": "es5",
2828
"types": [
2929
"jest",
30-
"node"
30+
"node",
31+
"react"
3132
]
3233
},
3334
"include": [

0 commit comments

Comments
 (0)
Please sign in to comment.