/
utils.ts
200 lines (167 loc) · 6.61 KB
/
utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import * as crypto from 'crypto';
import * as fs from 'fs';
import * as fsExtra from 'fs-extra';
import * as path from 'path';
import * as tsc from 'typescript';
import { JestConfig, TsJestConfig } from './jest-types';
export function getTSJestConfig(globals: any): TsJestConfig {
return globals && globals['ts-jest'] ? globals['ts-jest'] : {};
}
function formatTscParserErrors(errors: tsc.Diagnostic[]) {
return errors.map(s => JSON.stringify(s, null, 4)).join('\n');
}
function readCompilerOptions(configPath: string) {
configPath = path.resolve(configPath);
// First step: Let tsc pick up the config.
const loaded = tsc.readConfigFile(configPath, file => {
const read = tsc.sys.readFile(file);
// See
// https://github.com/Microsoft/TypeScript/blob/a757e8428410c2196886776785c16f8f0c2a62d9/src/compiler/sys.ts#L203 :
// `readFile` returns `undefined` in case the file does not exist!
if (!read) {
throw new Error(
`ENOENT: no such file or directory, open '${configPath}'`,
);
}
return read;
});
// In case of an error, we cannot go further - the config is malformed.
if (loaded.error) {
throw new Error(JSON.stringify(loaded.error, null, 4));
}
// Second step: Parse the config, resolving all potential references.
const basePath = path.dirname(configPath); // equal to "getDirectoryPath" from ts, at least in our case.
const parsedConfig = tsc.parseJsonConfigFileContent(
loaded.config,
tsc.sys,
basePath,
);
// In case the config is present, it already contains possibly merged entries from following the
// 'extends' entry, thus it is not required to follow it manually.
// This procedure does NOT throw, but generates a list of errors that can/should be evaluated.
if (parsedConfig.errors.length > 0) {
const formattedErrors = formatTscParserErrors(parsedConfig.errors);
throw new Error(
`Some errors occurred while attempting to read from ${configPath}: ${formattedErrors}`,
);
}
return parsedConfig.options;
}
function getStartDir(): string {
// This is needed because of the way our tests are structured.
// If this is being executed as a library (under node_modules)
// we want to start with the project directory that's three
// levels above.
// If t his is being executed from the test suite, we want to start
// in the directory of the test
const grandparent = path.resolve(__dirname, '..', '..');
if (grandparent.endsWith('/node_modules')) {
return process.cwd();
}
return '.';
}
function getPathToClosestTSConfig(
startDir?: string,
previousDir?: string,
): string {
// Starting with the startDir directory and moving on to the
// parent directory recursively (going no further than the root directory)
// find and return the path to the first encountered tsconfig.json file
if (!startDir) {
return getPathToClosestTSConfig(getStartDir());
}
const tsConfigPath = path.join(startDir, 'tsconfig.json');
const startDirPath = path.resolve(startDir);
const previousDirPath = path.resolve(previousDir || '/');
if (startDirPath === previousDirPath || fs.existsSync(tsConfigPath)) {
return tsConfigPath;
}
return getPathToClosestTSConfig(path.join(startDir, '..'), startDir);
}
// TODO: This can take something more specific than globals
function getTSConfigPathFromConfig(globals: any): string {
const defaultTSConfigFile = getPathToClosestTSConfig();
if (!globals) {
return defaultTSConfigFile;
}
const tsJestConfig = getTSJestConfig(globals);
if (tsJestConfig.tsConfigFile) {
return tsJestConfig.tsConfigFile;
}
return defaultTSConfigFile;
}
export function mockGlobalTSConfigSchema(globals: any) {
const configPath = getTSConfigPathFromConfig(globals);
return { 'ts-jest': { tsConfigFile: configPath } };
}
const tsConfigCache: { [key: string]: any } = {};
// TODO: Perhaps rename collectCoverage to here, as it seems to be the official jest name now:
// https://github.com/facebook/jest/issues/3524
export function getTSConfig(globals, collectCoverage: boolean = false) {
let configPath = getTSConfigPathFromConfig(globals);
const skipBabel = getTSJestConfig(globals).skipBabel;
// check cache before resolving configuration
// NB: We use JSON.stringify() to create a consistent, unique signature. Although it lacks a uniform
// shape, this is simpler and faster than using the crypto package to generate a hash signature.
const tsConfigCacheKey = JSON.stringify([
skipBabel,
collectCoverage,
configPath,
]);
if (tsConfigCacheKey in tsConfigCache) {
return tsConfigCache[tsConfigCacheKey];
}
const config = readCompilerOptions(configPath);
// ts-jest will map lines numbers properly if inlineSourceMap and
// inlineSources are set to true. For testing, we don't need the
// sourceMap configuration
delete config.sourceMap;
config.inlineSourceMap = true;
config.inlineSources = true;
// the coverage report is broken if `.outDir` is set
// see https://github.com/kulshekhar/ts-jest/issues/201
// `.outDir` is removed even for test files as it affects with breakpoints
// see https://github.com/kulshekhar/ts-jest/issues/309
delete config.outDir;
if (configPath === path.join(getStartDir(), 'tsconfig.json')) {
// hardcode module to 'commonjs' in case the config is being loaded
// from the default tsconfig file. This is to ensure that coverage
// works well. If there's a need to override, it can be done using
// a custom tsconfig for testing
config.module = tsc.ModuleKind.CommonJS;
}
config.module = config.module || tsc.ModuleKind.CommonJS;
config.jsx = config.jsx || tsc.JsxEmit.React;
if (config.allowSyntheticDefaultImports && !skipBabel) {
// compile ts to es2015 and transform with babel afterwards
config.module = tsc.ModuleKind.ES2015;
}
// cache result for future requests
tsConfigCache[tsConfigCacheKey] = config;
return config;
}
export function cacheFile(
jestConfig: JestConfig,
filePath: string,
src: string,
) {
// store transpiled code contains source map into cache, except test cases
if (!jestConfig.testRegex || !filePath.match(jestConfig.testRegex)) {
const outputFilePath = path.join(
jestConfig.cacheDirectory,
'/ts-jest/',
crypto
.createHash('md5')
.update(filePath)
.digest('hex'),
);
fsExtra.outputFileSync(outputFilePath, src);
}
}
export function injectSourcemapHook(src: string): string {
const start = src.length > 12 ? src.substr(1, 10) : '';
const sourceMapHook = `require('ts-jest').install()`;
return start === 'use strict'
? `'use strict';${sourceMapHook};${src}`
: `${sourceMapHook};${src}`;
}