-
Notifications
You must be signed in to change notification settings - Fork 473
Expand file tree
/
Copy pathutils.ts
More file actions
231 lines (195 loc) · 7.62 KB
/
utils.ts
File metadata and controls
231 lines (195 loc) · 7.62 KB
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
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 { ConfigGlobals, JestConfig, TsJestConfig } from './jest-types';
import { logOnce } from './logger';
import * as _ from 'lodash';
export function getTSJestConfig(globals: ConfigGlobals): TsJestConfig {
return globals && globals['ts-jest'] ? globals['ts-jest'] : {};
}
function formatTscParserErrors(errors: tsc.Diagnostic[]): string {
return errors.map(s => JSON.stringify(s, null, 4)).join('\n');
}
function readCompilerOptions(configPath: string, rootDir: string) {
configPath = path.resolve(rootDir, 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 this 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(`${path.sep}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);
}
function getTSConfigPathFromConfig(globals: ConfigGlobals): string {
const defaultTSConfigFile = getPathToClosestTSConfig();
if (!globals) {
return defaultTSConfigFile;
}
const tsJestConfig = getTSJestConfig(globals);
if (tsJestConfig.tsConfigFile) {
return tsJestConfig.tsConfigFile;
}
return defaultTSConfigFile;
}
export function mockGlobalTSConfigSchema(
globals: ConfigGlobals,
): ConfigGlobals {
const configPath = getTSConfigPathFromConfig(globals);
return { 'ts-jest': { tsConfigFile: configPath } };
}
export const getTSConfig = _.memoize(getTSConfig_local, (globals, rootDir) => {
// 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.
return JSON.stringify(globals, rootDir);
});
// Non-memoized version of TSConfig
function getTSConfig_local(globals, rootDir: string = '') {
const configPath = getTSConfigPathFromConfig(globals);
logOnce(`Reading tsconfig file from path ${configPath}`);
const skipBabel = getTSJestConfig(globals).skipBabel;
const config = readCompilerOptions(configPath, rootDir);
logOnce('Original typescript config before modifications: ', { ...config });
// ts-jest will map lines numbers properly if inlineSourceMap and
// inlineSources are set to true. The sourceMap configuration
// is used to send the sourcemap back to Jest
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;
}
return config;
}
export function cacheFile(
jestConfig: JestConfig,
filePath: string,
src: string,
): void {
// 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(
filePath: string,
typeScriptCode: string,
src: string,
): string {
const start = src.length > 12 ? src.substr(1, 10) : '';
const filePathParam = JSON.stringify(filePath);
const codeParam = JSON.stringify(typeScriptCode);
const sourceMapHook = `require('ts-jest').install(${filePathParam}, ${codeParam})`;
return start === 'use strict'
? `'use strict';${sourceMapHook};${src}`
: `${sourceMapHook};${src}`;
}
export function runTsDiagnostics(
filePath: string,
compilerOptions: tsc.CompilerOptions,
): void {
const program = tsc.createProgram([filePath], compilerOptions);
const allDiagnostics = tsc.getPreEmitDiagnostics(program);
const formattedDiagnostics = allDiagnostics.map(diagnostic => {
if (diagnostic.file) {
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(
diagnostic.start,
);
const message = tsc.flattenDiagnosticMessageText(
diagnostic.messageText,
'\n',
);
return `${path.relative(
process.cwd(),
diagnostic.file.fileName,
)} (${line + 1},${character + 1}): ${message}\n`;
}
return `${tsc.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`;
});
if (formattedDiagnostics.length) {
throw new Error(formattedDiagnostics.join(''));
}
}