Skip to content

Commit 0d07a9d

Browse files
authored
Use Jest sourcemaps and remove languageserver (#554)
This PR uses the jest infrastructure for sourcemaps. It completely removes any dependance on source-map-support from our part. Note that removing the languageServer is a breaking change - however it was never documented. A language-server branch has been created based off master. This PR is based on #552 so merge that in first. It closes #340. Surprisingly enough it also closes #240 - passing the sourcemaps explicitly to babel means that the line# are correct in the other end - you'll see that in one of the updated tests. It fixes part of #529 Note that no line# has changed, but some column names have in the tests. I'm not sure the original column names were ever accurate.
1 parent 08269aa commit 0d07a9d

23 files changed

+176
-224
lines changed

index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,4 @@ function lazyRequire(fnName) {
1212
module.exports = {
1313
process: lazyRequire('process'),
1414
getCacheKey: lazyRequire('getCacheKey'),
15-
install: require('./dist/install').install,
1615
};

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@
7171
"jest-config": "^23.0.0",
7272
"lodash": "^4.17.10",
7373
"pkg-dir": "^2.0.0",
74-
"source-map-support": "^0.5.6",
7574
"yargs": "^11.0.0"
7675
},
7776
"peerDependencies": {

src/install.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

src/jest-types.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { TransformOptions as BabelTransformOpts } from 'babel-core';
1+
import {
2+
BabelFileResult,
3+
TransformOptions as BabelTransformOpts,
4+
} from 'babel-core';
25

36
export interface TransformOptions {
47
instrument: boolean;
@@ -24,11 +27,11 @@ export interface BabelTransformOptions extends BabelTransformOpts {
2427
}
2528

2629
export type PostProcessHook = (
27-
src: string,
30+
codeSourcemapPair: CodeSourceMapPair,
2831
filePath: string,
2932
config: JestConfig,
3033
transformOptions: TransformOptions,
31-
) => string;
34+
) => CodeSourceMapPair;
3235

3336
export type JestConfig = Partial<FullJestConfig>;
3437

@@ -95,3 +98,8 @@ export interface JestConfigNormalize {
9598
hasDeprecationWarnings: boolean;
9699
options: FullJestConfig;
97100
}
101+
102+
export interface CodeSourceMapPair {
103+
code: string;
104+
map: string;
105+
}

src/postprocess.ts

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,47 @@
55
import * as __types__babel from 'babel-core';
66
import __types__istanbulPlugin from 'babel-plugin-istanbul';
77
import * as __types__jestPreset from 'babel-preset-jest';
8+
import * as ts from 'typescript';
89
let babel: typeof __types__babel;
910
let istanbulPlugin: typeof __types__istanbulPlugin;
1011
let jestPreset: typeof __types__jestPreset;
1112
function importBabelDeps() {
12-
if (babel) {
13-
return;
14-
}
15-
babel = require('babel-core');
16-
istanbulPlugin = require('babel-plugin-istanbul').default;
17-
jestPreset = require('babel-preset-jest');
13+
if (babel) {
14+
return;
15+
}
16+
babel = require('babel-core');
17+
istanbulPlugin = require('babel-plugin-istanbul').default;
18+
jestPreset = require('babel-preset-jest');
1819
}
1920
import { CompilerOptions } from 'typescript/lib/typescript';
2021
import {
2122
BabelTransformOptions,
23+
CodeSourceMapPair,
2224
FullJestConfig,
2325
JestConfig,
2426
PostProcessHook,
2527
TransformOptions,
2628
TsJestConfig,
2729
} from './jest-types';
2830
import { logOnce } from './logger';
31+
import { BabelFileResult } from 'babel-core';
2932

3033
// Function that takes the transpiled typescript and runs it through babel/whatever.
3134
export function postProcessCode(
3235
compilerOptions: CompilerOptions,
3336
jestConfig: JestConfig,
3437
tsJestConfig: TsJestConfig,
3538
transformOptions: TransformOptions,
36-
transpiledText: string,
39+
transpileOutput: CodeSourceMapPair,
3740
filePath: string,
38-
): string {
41+
): CodeSourceMapPair {
3942
const postHook = getPostProcessHook(
4043
compilerOptions,
4144
jestConfig,
4245
tsJestConfig,
4346
);
4447

45-
return postHook(transpiledText, filePath, jestConfig, transformOptions);
48+
return postHook(transpileOutput, filePath, jestConfig, transformOptions);
4649
}
4750

4851
function createBabelTransformer(
@@ -53,23 +56,20 @@ function createBabelTransformer(
5356
...options,
5457
plugins: options.plugins || [],
5558
presets: (options.presets || []).concat([jestPreset]),
56-
// If retainLines isn't set to true, the line numbers
57-
// are off by 1
58-
retainLines: true,
59-
// force the sourceMaps property to be 'inline' during testing
60-
// to help generate accurate sourcemaps.
61-
sourceMaps: 'inline',
6259
};
6360
delete options.cacheDirectory;
6461
delete options.filename;
6562

6663
return (
67-
src: string,
64+
codeSourcemapPair: CodeSourceMapPair,
6865
filename: string,
6966
config: JestConfig,
7067
transformOptions: TransformOptions,
71-
): string => {
72-
const theseOptions = Object.assign({ filename }, options);
68+
): CodeSourceMapPair => {
69+
const theseOptions = Object.assign(
70+
{ filename, inputSourceMap: codeSourcemapPair.map },
71+
options,
72+
);
7373
if (transformOptions && transformOptions.instrument) {
7474
theseOptions.auxiliaryCommentBefore = ' istanbul ignore next ';
7575
// Copied from jest-runtime transform.js
@@ -84,8 +84,11 @@ function createBabelTransformer(
8484
],
8585
]);
8686
}
87-
88-
return babel.transform(src, theseOptions).code;
87+
// Babel has incorrect typings, where the map is an object instead of a string. So we have to typecast it here
88+
return (babel.transform(
89+
codeSourcemapPair.code,
90+
theseOptions,
91+
) as any) as CodeSourceMapPair;
8992
};
9093
}
9194

@@ -96,7 +99,8 @@ export const getPostProcessHook = (
9699
): PostProcessHook => {
97100
if (tsJestConfig.skipBabel) {
98101
logOnce('Not using any postprocess hook.');
99-
return src => src; // Identity function
102+
// Identity function
103+
return input => input;
100104
}
101105

102106
const plugins = Array.from(
@@ -107,11 +111,12 @@ export const getPostProcessHook = (
107111
plugins.push('transform-es2015-modules-commonjs');
108112
}
109113

110-
const babelOptions = {
114+
const babelOptions: BabelTransformOptions = {
111115
...tsJestConfig.babelConfig,
112116
babelrc: tsJestConfig.useBabelrc || false,
113117
plugins,
114118
presets: tsJestConfig.babelConfig ? tsJestConfig.babelConfig.presets : [],
119+
sourceMaps: tsJestConfig.disableSourceMapSupport !== true,
115120
};
116121

117122
logOnce('Using babel with options:', babelOptions);

src/preprocessor.ts

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
import * as crypto from 'crypto';
2-
import { JestConfig, Path, TransformOptions } from './jest-types';
2+
import {
3+
BabelTransformOptions,
4+
CodeSourceMapPair,
5+
JestConfig,
6+
Path,
7+
TransformOptions,
8+
} from './jest-types';
39
import { flushLogs, logOnce } from './logger';
410
import { postProcessCode } from './postprocess';
5-
import {
6-
getTSConfig,
7-
getTSJestConfig,
8-
runTsDiagnostics,
9-
injectSourcemapHook,
10-
} from './utils';
11+
import { getTSConfig, getTSJestConfig, runTsDiagnostics } from './utils';
1112
import { transpileTypescript } from './transpiler';
1213

1314
export function process(
1415
src: string,
1516
filePath: Path,
1617
jestConfig: JestConfig,
1718
transformOptions: TransformOptions = { instrument: false },
18-
): string {
19+
): CodeSourceMapPair | string {
1920
// transformOptions.instrument is a proxy for collectCoverage
2021
// https://github.com/kulshekhar/ts-jest/issues/201#issuecomment-300572902
2122
const compilerOptions = getTSConfig(jestConfig.globals, jestConfig.rootDir);
@@ -47,21 +48,16 @@ export function process(
4748
runTsDiagnostics(filePath, compilerOptions);
4849
}
4950

50-
let tsTranspiledText = transpileTypescript(
51-
filePath,
52-
src,
53-
compilerOptions,
54-
tsJestConfig,
55-
);
51+
const transpileOutput = transpileTypescript(filePath, src, compilerOptions);
5652

5753
if (tsJestConfig.ignoreCoverageForAllDecorators === true) {
58-
tsTranspiledText = tsTranspiledText.replace(
54+
transpileOutput.code = transpileOutput.code.replace(
5955
/__decorate/g,
6056
'/* istanbul ignore next */__decorate',
6157
);
6258
}
6359
if (tsJestConfig.ignoreCoverageForDecorators === true) {
64-
tsTranspiledText = tsTranspiledText.replace(
60+
transpileOutput.code = transpileOutput.code.replace(
6561
/(__decorate\(\[\r?\n[^\n\r]*)\/\*\s*istanbul\s*ignore\s*decorator(.*)\*\//g,
6662
'/* istanbul ignore next$2*/$1',
6763
);
@@ -72,18 +68,18 @@ export function process(
7268
jestConfig,
7369
tsJestConfig,
7470
transformOptions,
75-
tsTranspiledText,
71+
transpileOutput,
7672
filePath,
7773
);
7874

79-
const modified =
75+
/*const modified =
8076
tsJestConfig.disableSourceMapSupport === true
8177
? outputText
82-
: injectSourcemapHook(filePath, tsTranspiledText, outputText);
83-
78+
: injectSourcemapHook(filePath, transpileOutput, outputText);
79+
*/
8480
flushLogs();
8581

86-
return modified;
82+
return { code: outputText.code, map: outputText.map };
8783
}
8884

8985
/**

src/transpiler.ts

Lines changed: 12 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -2,96 +2,24 @@ import * as fs from 'fs';
22
import { cwd } from 'process';
33
import * as ts from 'typescript';
44
import { logOnce } from './logger';
5-
import { TsJestConfig } from './jest-types';
5+
import { CodeSourceMapPair, TsJestConfig } from './jest-types';
66

77
// Takes the typescript code and by whatever method configured, makes it into javascript code.
88
export function transpileTypescript(
99
filePath: string,
1010
fileSrc: string,
1111
compilerOptions: ts.CompilerOptions,
12-
tsJestConfig: TsJestConfig,
13-
): string {
14-
if (tsJestConfig.useExperimentalLanguageServer) {
15-
logOnce('Using experimental language server.');
16-
return transpileViaLanguageServer(filePath, fileSrc, compilerOptions);
17-
}
12+
): CodeSourceMapPair {
1813
logOnce('Compiling via normal transpileModule call');
19-
return transpileViaTranspileModile(filePath, fileSrc, compilerOptions);
20-
}
21-
22-
/**
23-
* This is slower, but can properly parse enums and deal with reflect metadata.
24-
* This is an experimental approach from our side. Potentially we should cache
25-
* the languageServer between calls.
26-
*/
27-
function transpileViaLanguageServer(
28-
filePath: string,
29-
fileSrc: string,
30-
compilerOptions: ts.CompilerOptions,
31-
): string {
32-
const serviceHost: ts.LanguageServiceHost = {
33-
// Returns an array of the files we need to consider
34-
getScriptFileNames: () => {
35-
return [filePath];
36-
},
37-
38-
getScriptVersion: fileName => {
39-
// We're not doing any watching or changing files, so versioning is not relevant for us
40-
return undefined;
41-
},
42-
43-
getCurrentDirectory: () => {
44-
return cwd();
45-
},
46-
47-
getScriptSnapshot: fileName => {
48-
if (fileName === filePath) {
49-
// jest has already served this file for us, so no need to hit disk again.
50-
return ts.ScriptSnapshot.fromString(fileSrc);
51-
}
52-
// Read file from disk. I think this could be problematic if the files are not saved as utf8.
53-
const result = fs.readFileSync(fileName, 'utf8');
54-
return ts.ScriptSnapshot.fromString(result);
55-
},
56-
57-
getCompilationSettings: () => {
58-
return compilerOptions;
59-
},
60-
61-
getDefaultLibFileName: () => {
62-
return ts.getDefaultLibFilePath(compilerOptions);
63-
},
64-
fileExists: ts.sys.fileExists,
65-
readFile: ts.sys.readFile,
66-
readDirectory: ts.sys.readDirectory,
67-
realpath: ts.sys.realpath,
68-
getDirectories: ts.sys.getDirectories,
69-
directoryExists: ts.sys.directoryExists,
14+
const transpileOutput = transpileViaTranspileModile(
15+
filePath,
16+
fileSrc,
17+
compilerOptions,
18+
);
19+
return {
20+
code: transpileOutput.outputText,
21+
map: transpileOutput.sourceMapText,
7022
};
71-
const service = ts.createLanguageService(serviceHost);
72-
const serviceOutput = service.getEmitOutput(filePath);
73-
const files = serviceOutput.outputFiles.filter(file => {
74-
// Service outputs both d.ts and .js files - we're not interested in the declarations.
75-
return file.name.endsWith('js');
76-
});
77-
logOnce('JS files parsed', files.map(f => f.name));
78-
79-
// Log some diagnostics here:
80-
const diagnostics = service
81-
.getCompilerOptionsDiagnostics()
82-
.concat(service.getSyntacticDiagnostics(filePath))
83-
.concat(service.getSemanticDiagnostics(filePath));
84-
85-
if (diagnostics.length > 0) {
86-
const errors = `${diagnostics.map(d => d.messageText)}\n`;
87-
logOnce(`Diagnostic errors from TSC: ${errors}`);
88-
// Maybe we should keep compiling even though there are errors. This can possibly be configured.
89-
throw Error(
90-
`TSC language server encountered errors while transpiling. Errors: ${errors}`,
91-
);
92-
}
93-
94-
return files[0].text;
9523
}
9624

9725
/**
@@ -101,9 +29,9 @@ function transpileViaTranspileModile(
10129
filePath: string,
10230
fileSource: string,
10331
compilerOptions: ts.CompilerOptions,
104-
): string {
32+
): ts.TranspileOutput {
10533
return ts.transpileModule(fileSource, {
10634
compilerOptions,
10735
fileName: filePath,
108-
}).outputText;
36+
});
10937
}

0 commit comments

Comments
 (0)