From 01ac417de6e057f0078f8f8c43f958fa517db87e Mon Sep 17 00:00:00 2001 From: Ahn Date: Sat, 20 Jun 2020 15:55:55 +0200 Subject: [PATCH] fix(compiler): generate source map correctly when tsconfig `mapRoot` is set (#1741) Fixes #1718 --- e2e/__helpers__/test-case/types.ts | 3 +- .../__snapshots__/source-map.test.ts.snap | 300 +++++++++++++++++- e2e/__tests__/source-map.test.ts | 22 +- src/compiler/instance.ts | 44 +-- src/config/config-set.ts | 6 +- 5 files changed, 335 insertions(+), 40 deletions(-) diff --git a/e2e/__helpers__/test-case/types.ts b/e2e/__helpers__/test-case/types.ts index 2293690359..ea5e99f494 100644 --- a/e2e/__helpers__/test-case/types.ts +++ b/e2e/__helpers__/test-case/types.ts @@ -10,7 +10,7 @@ export interface RunTestOptions { inject?: (() => any) | string writeIo?: boolean jestConfig?: Config.ProjectConfig | any - tsJestConfig?: TsJestConfig | any + tsJestConfig?: Partial | any noCache?: boolean jestConfigPath?: string } @@ -24,7 +24,6 @@ export interface RunWithTemplateIteratorContext { testLabel: string } - export type TestRunResultsMap = { [key in T]: RunResult } export interface PreparedTest { diff --git a/e2e/__tests__/__snapshots__/source-map.test.ts.snap b/e2e/__tests__/__snapshots__/source-map.test.ts.snap index 3a99ee8626..a1009aa9e3 100644 --- a/e2e/__tests__/__snapshots__/source-map.test.ts.snap +++ b/e2e/__tests__/__snapshots__/source-map.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Source map using template "default" should have the source maps comment 1`] = ` +exports[`Source map with tsconfig mapRoot using template "default" should have the source maps comment 1`] = ` ===[ FILE: main.ts ]============================================================ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); @@ -34,7 +34,7 @@ exports[`Source map using template "default" should have the source maps comment ================================================================================ `; -exports[`Source map using template "default" should report correct line numbers 1`] = ` +exports[`Source map with tsconfig mapRoot using template "default" should report correct line numbers 1`] = ` × jest --no-cache ↳ exit code: 1 ===[ STDOUT ]=================================================================== @@ -92,7 +92,7 @@ exports[`Source map using template "default" should report correct line numbers ================================================================================ `; -exports[`Source map using template "with-babel-7" should have the source maps comment 1`] = ` +exports[`Source map with tsconfig mapRoot using template "with-babel-7" should have the source maps comment 1`] = ` ===[ FILE: main.ts ]============================================================ "use strict"; @@ -132,7 +132,7 @@ exports[`Source map using template "with-babel-7" should have the source maps co ================================================================================ `; -exports[`Source map using template "with-babel-7" should report correct line numbers 1`] = ` +exports[`Source map with tsconfig mapRoot using template "with-babel-7" should report correct line numbers 1`] = ` × jest --no-cache ↳ exit code: 1 ===[ STDOUT ]=================================================================== @@ -190,7 +190,7 @@ exports[`Source map using template "with-babel-7" should report correct line num ================================================================================ `; -exports[`Source map using template "with-babel-7-string-config" should have the source maps comment 1`] = ` +exports[`Source map with tsconfig mapRoot using template "with-babel-7-string-config" should have the source maps comment 1`] = ` ===[ FILE: main.ts ]============================================================ "use strict"; @@ -230,7 +230,295 @@ exports[`Source map using template "with-babel-7-string-config" should have the ================================================================================ `; -exports[`Source map using template "with-babel-7-string-config" should report correct line numbers 1`] = ` +exports[`Source map with tsconfig mapRoot using template "with-babel-7-string-config" should report correct line numbers 1`] = ` + × jest --no-cache + ↳ exit code: 1 + ===[ STDOUT ]=================================================================== + console.log + WITHIN SOURCE + + at Object.consoleLog (main.ts:2:11) + + console.log + WITHIN TEST + + at Object. (main.spec.ts:9:13) + ===[ STDERR ]=================================================================== + FAIL ./main.spec.ts + console.log() + √ from sources + √ from tests + throw new Error() + × throws from sources + × throws from tests + + ● throw new Error() › throws from sources + + WITHIN SOURCE + + 4 | + 5 | export function throwError() { + > 6 | throw new Error('WITHIN SOURCE') + | ^ + 7 | } + 8 | + + at Object.throwError (main.ts:6:9) + at Object. (main.spec.ts:16:5) + + ● throw new Error() › throws from tests + + WITHIN TEST + + 18 | }); + 19 | test('throws from tests', () => { + > 20 | throw new Error('WITHIN TEST'); + | ^ + 21 | expect(true).toBe(true); + 22 | }); + 23 | }); + + at Object. (main.spec.ts:20:11) + + Test Suites: 1 failed, 1 total + Tests: 2 failed, 2 passed, 4 total + Snapshots: 0 total + Time: XXs + Ran all test suites. + ================================================================================ +`; + +exports[`Source map without tsconfig mapRoot using template "default" should have the source maps comment 1`] = ` + ===[ FILE: main.ts ]============================================================ + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.throwError = exports.consoleLog = void 0; + function consoleLog() { + console.log('WITHIN SOURCE'); + } + exports.consoleLog = consoleLog; + function throwError() { + throw new Error('WITHIN SOURCE'); + } + exports.throwError = throwError; + //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoiPGN3ZD4vbWFpbi50cyIsIm1hcHBpbmdzIjoiOzs7QUFBQSxTQUFnQixVQUFVO0lBQ3hCLE9BQU8sQ0FBQyxHQUFHLENBQUMsZUFBZSxDQUFDLENBQUE7QUFDOUIsQ0FBQztBQUZELGdDQUVDO0FBRUQsU0FBZ0IsVUFBVTtJQUN4QixNQUFNLElBQUksS0FBSyxDQUFDLGVBQWUsQ0FBQyxDQUFBO0FBQ2xDLENBQUM7QUFGRCxnQ0FFQyIsIm5hbWVzIjpbXSwic291cmNlcyI6WyI8Y3dkPi9tYWluLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBmdW5jdGlvbiBjb25zb2xlTG9nKCkge1xuICBjb25zb2xlLmxvZygnV0lUSElOIFNPVVJDRScpXG59XG5cbmV4cG9ydCBmdW5jdGlvbiB0aHJvd0Vycm9yKCkge1xuICB0aHJvdyBuZXcgRXJyb3IoJ1dJVEhJTiBTT1VSQ0UnKVxufVxuIl0sInZlcnNpb24iOjN9 + ===[ INLINE SOURCE MAPS ]======================================================= + file: /main.ts + mappings: >- + ;;;AAAA,SAAgB,UAAU;IACxB,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;AAC9B,CAAC;AAFD,gCAEC;AAED,SAAgB,UAAU;IACxB,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAA;AAClC,CAAC;AAFD,gCAEC + names: [] + sources: + - /main.ts + sourcesContent: + - | + export function consoleLog() { + console.log('WITHIN SOURCE') + } + + export function throwError() { + throw new Error('WITHIN SOURCE') + } + version: 3 + ================================================================================ +`; + +exports[`Source map without tsconfig mapRoot using template "default" should report correct line numbers 1`] = ` + × jest --no-cache + ↳ exit code: 1 + ===[ STDOUT ]=================================================================== + console.log + WITHIN SOURCE + + at Object.consoleLog (main.ts:2:11) + + console.log + WITHIN TEST + + at Object. (main.spec.ts:9:13) + ===[ STDERR ]=================================================================== + FAIL ./main.spec.ts + console.log() + √ from sources + √ from tests + throw new Error() + × throws from sources + × throws from tests + + ● throw new Error() › throws from sources + + WITHIN SOURCE + + 4 | + 5 | export function throwError() { + > 6 | throw new Error('WITHIN SOURCE') + | ^ + 7 | } + 8 | + + at Object.throwError (main.ts:6:9) + at Object. (main.spec.ts:16:5) + + ● throw new Error() › throws from tests + + WITHIN TEST + + 18 | }); + 19 | test('throws from tests', () => { + > 20 | throw new Error('WITHIN TEST'); + | ^ + 21 | expect(true).toBe(true); + 22 | }); + 23 | }); + + at Object. (main.spec.ts:20:11) + + Test Suites: 1 failed, 1 total + Tests: 2 failed, 2 passed, 4 total + Snapshots: 0 total + Time: XXs + Ran all test suites. + ================================================================================ +`; + +exports[`Source map without tsconfig mapRoot using template "with-babel-7" should have the source maps comment 1`] = ` + ===[ FILE: main.ts ]============================================================ + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.throwError = exports.consoleLog = void 0; + + function consoleLog() { + console.log('WITHIN SOURCE'); + } + + exports.consoleLog = consoleLog; + + function throwError() { + throw new Error('WITHIN SOURCE'); + } + + exports.throwError = throwError; + //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJtYXBwaW5ncyI6Ijs7Ozs7OztBQUFBLFNBQWdCLFVBQWhCLEdBQTBCO0FBQ3hCLEVBQUEsT0FBTyxDQUFDLEdBQVIsQ0FBWSxlQUFaO0FBQ0Q7O0FBRkQsT0FBQSxDQUFBLFVBQUEsR0FBQSxVQUFBOztBQUlBLFNBQWdCLFVBQWhCLEdBQTBCO0FBQ3hCLFFBQU0sSUFBSSxLQUFKLENBQVUsZUFBVixDQUFOO0FBQ0Q7O0FBRkQsT0FBQSxDQUFBLFVBQUEsR0FBQSxVQUFBIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbIjxjd2Q+L21haW4udHMiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGZ1bmN0aW9uIGNvbnNvbGVMb2coKSB7XG4gIGNvbnNvbGUubG9nKCdXSVRISU4gU09VUkNFJylcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRocm93RXJyb3IoKSB7XG4gIHRocm93IG5ldyBFcnJvcignV0lUSElOIFNPVVJDRScpXG59XG4iXSwidmVyc2lvbiI6M30= + ===[ INLINE SOURCE MAPS ]======================================================= + mappings: >- + ;;;;;;;AAAA,SAAgB,UAAhB,GAA0B;AACxB,EAAA,OAAO,CAAC,GAAR,CAAY,eAAZ;AACD;;AAFD,OAAA,CAAA,UAAA,GAAA,UAAA;;AAIA,SAAgB,UAAhB,GAA0B;AACxB,QAAM,IAAI,KAAJ,CAAU,eAAV,CAAN;AACD;;AAFD,OAAA,CAAA,UAAA,GAAA,UAAA + names: [] + sources: + - /main.ts + sourcesContent: + - | + export function consoleLog() { + console.log('WITHIN SOURCE') + } + + export function throwError() { + throw new Error('WITHIN SOURCE') + } + version: 3 + ================================================================================ +`; + +exports[`Source map without tsconfig mapRoot using template "with-babel-7" should report correct line numbers 1`] = ` + × jest --no-cache + ↳ exit code: 1 + ===[ STDOUT ]=================================================================== + console.log + WITHIN SOURCE + + at Object.consoleLog (main.ts:2:11) + + console.log + WITHIN TEST + + at Object. (main.spec.ts:9:13) + ===[ STDERR ]=================================================================== + FAIL ./main.spec.ts + console.log() + √ from sources + √ from tests + throw new Error() + × throws from sources + × throws from tests + + ● throw new Error() › throws from sources + + WITHIN SOURCE + + 4 | + 5 | export function throwError() { + > 6 | throw new Error('WITHIN SOURCE') + | ^ + 7 | } + 8 | + + at Object.throwError (main.ts:6:9) + at Object. (main.spec.ts:16:5) + + ● throw new Error() › throws from tests + + WITHIN TEST + + 18 | }); + 19 | test('throws from tests', () => { + > 20 | throw new Error('WITHIN TEST'); + | ^ + 21 | expect(true).toBe(true); + 22 | }); + 23 | }); + + at Object. (main.spec.ts:20:11) + + Test Suites: 1 failed, 1 total + Tests: 2 failed, 2 passed, 4 total + Snapshots: 0 total + Time: XXs + Ran all test suites. + ================================================================================ +`; + +exports[`Source map without tsconfig mapRoot using template "with-babel-7-string-config" should have the source maps comment 1`] = ` + ===[ FILE: main.ts ]============================================================ + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.throwError = exports.consoleLog = void 0; + + function consoleLog() { + console.log('WITHIN SOURCE'); + } + + exports.consoleLog = consoleLog; + + function throwError() { + throw new Error('WITHIN SOURCE'); + } + + exports.throwError = throwError; + //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJtYXBwaW5ncyI6Ijs7Ozs7OztBQUFBLFNBQWdCLFVBQWhCLEdBQTBCO0FBQ3hCLEVBQUEsT0FBTyxDQUFDLEdBQVIsQ0FBWSxlQUFaO0FBQ0Q7O0FBRkQsT0FBQSxDQUFBLFVBQUEsR0FBQSxVQUFBOztBQUlBLFNBQWdCLFVBQWhCLEdBQTBCO0FBQ3hCLFFBQU0sSUFBSSxLQUFKLENBQVUsZUFBVixDQUFOO0FBQ0Q7O0FBRkQsT0FBQSxDQUFBLFVBQUEsR0FBQSxVQUFBIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbIjxjd2Q+L21haW4udHMiXSwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGZ1bmN0aW9uIGNvbnNvbGVMb2coKSB7XG4gIGNvbnNvbGUubG9nKCdXSVRISU4gU09VUkNFJylcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHRocm93RXJyb3IoKSB7XG4gIHRocm93IG5ldyBFcnJvcignV0lUSElOIFNPVVJDRScpXG59XG4iXSwidmVyc2lvbiI6M30= + ===[ INLINE SOURCE MAPS ]======================================================= + mappings: >- + ;;;;;;;AAAA,SAAgB,UAAhB,GAA0B;AACxB,EAAA,OAAO,CAAC,GAAR,CAAY,eAAZ;AACD;;AAFD,OAAA,CAAA,UAAA,GAAA,UAAA;;AAIA,SAAgB,UAAhB,GAA0B;AACxB,QAAM,IAAI,KAAJ,CAAU,eAAV,CAAN;AACD;;AAFD,OAAA,CAAA,UAAA,GAAA,UAAA + names: [] + sources: + - /main.ts + sourcesContent: + - | + export function consoleLog() { + console.log('WITHIN SOURCE') + } + + export function throwError() { + throw new Error('WITHIN SOURCE') + } + version: 3 + ================================================================================ +`; + +exports[`Source map without tsconfig mapRoot using template "with-babel-7-string-config" should report correct line numbers 1`] = ` × jest --no-cache ↳ exit code: 1 ===[ STDOUT ]=================================================================== diff --git a/e2e/__tests__/source-map.test.ts b/e2e/__tests__/source-map.test.ts index 2a9db091db..2c72c0da36 100644 --- a/e2e/__tests__/source-map.test.ts +++ b/e2e/__tests__/source-map.test.ts @@ -2,13 +2,14 @@ import { join } from 'path' import { allValidPackageSets } from '../__helpers__/templates' import { configureTestCase } from '../__helpers__/test-case' +import { TsJestConfig } from '../../src/types' -describe('Source map', () => { +function runSourceMapTests(tsJestConfig: Partial | any) { const testCase = configureTestCase('source-maps', { writeIo: true, // TS5023 - unrecognized compiler option // TS7027 - unreachable code - tsJestConfig: { diagnostics: { ignoreCodes: [5023, 7027] } }, + tsJestConfig, }) testCase.runWithTemplates(allValidPackageSets, 1, (runTest, { templateName }) => { @@ -35,4 +36,21 @@ describe('Source map', () => { }) }) }) +} + +const BASE_CONFIG = { diagnostics: { ignoreCodes: [5023, 7027], pretty: true, throws: false } } + +describe('Source map', () => { + describe('without tsconfig mapRoot', () => { + runSourceMapTests(BASE_CONFIG) + }) + + describe('with tsconfig mapRoot', () => { + runSourceMapTests({ + ...BASE_CONFIG, + tsConfig: { + mapRoot: './' + } + }) + }) }) diff --git a/src/compiler/instance.ts b/src/compiler/instance.ts index 2e5e75494e..8f52ef44d7 100644 --- a/src/compiler/instance.ts +++ b/src/compiler/instance.ts @@ -1,7 +1,6 @@ import { Logger } from 'bs-logger' import { readFileSync } from 'fs' import mkdirp = require('mkdirp') -import { basename, extname } from 'path' import { ConfigSet } from '../config/config-set' import { CompileFn, CompilerInstance, MemoryCache, TSFile, TsCompiler } from '../types' @@ -10,22 +9,22 @@ import { getResolvedModulesCache } from './compiler-utils' import { initializeLanguageServiceInstance } from './language-service' import { initializeTranspilerInstance } from './transpiler' +/** + * Rely on TypeScript compiled output generation which contains this prefix to point to sourcemap location. + */ +const SOURCE_MAPPING_PREFIX = 'sourceMappingURL=' + /** * Update the output remapping the source map. */ -function updateOutput( - outputText: string, - normalizedFileName: string, - sourceMap: string, - getExtension: (fileName: string) => string, -) { - const base = basename(normalizedFileName) +function updateOutput(outputText: string, normalizedFileName: string, sourceMap: string) { const base64Map = Buffer.from(updateSourceMap(sourceMap, normalizedFileName), 'utf8').toString('base64') const sourceMapContent = `data:application/json;charset=utf-8;base64,${base64Map}` - const sourceMapLength = - `${base}.map`.length + (getExtension(normalizedFileName).length - extname(normalizedFileName).length) - return outputText.slice(0, -sourceMapLength) + sourceMapContent + // sourceMappingURL= prefix is always at the end of compiledOutput, using lastIndexOf should be the safest way to substring + return ( + outputText.slice(0, outputText.lastIndexOf(SOURCE_MAPPING_PREFIX) + SOURCE_MAPPING_PREFIX.length) + sourceMapContent + ) } /** @@ -44,16 +43,15 @@ const updateSourceMap = (sourceMapText: string, normalizedFileName: string): str * Compile files which are provided by jest via transform config and cache the result in file system if users run with * cache mode */ -const compileAndCacheResult = ( - memoryCache: MemoryCache, - compileFn: CompileFn, - getExtension: (fileName: string) => string, - logger: Logger, -) => (code: string, fileName: string, lineOffset?: number) => { +const compileAndCacheResult = (memoryCache: MemoryCache, compileFn: CompileFn, logger: Logger) => ( + code: string, + fileName: string, + lineOffset?: number, +) => { logger.debug({ fileName }, 'compileAndCacheResult(): get compile output') const [value, sourceMap] = compileFn(code, fileName, lineOffset) - const output = updateOutput(value, fileName, sourceMap, getExtension) + const output = updateOutput(value, fileName, sourceMap) memoryCache.files.set(fileName, { ...memoryCache.files.get(fileName)!, // eslint-disable-line @typescript-eslint/no-non-null-assertion output, @@ -74,7 +72,6 @@ export const createCompilerInstance = (configs: ConfigSet): TsCompiler => { tsJest, } = configs const cacheDir = configs.tsCacheDir - const ts = configs.compilerModule // Require the TypeScript compiler and configuration. const extensions = ['.ts', '.tsx'] const memoryCache: MemoryCache = { files: new Map(), @@ -100,13 +97,6 @@ export const createCompilerInstance = (configs: ConfigSet): TsCompiler => { version: 0, }) }) - /** - * Get the extension for a transpiled file. - */ - const getExtension = - compilerOptions.jsx === ts.JsxEmit.Preserve - ? (path: string) => (/\.[tj]sx$/.test(path) ? '.jsx' : '.js') - : (_: string) => '.js' let compilerInstance: CompilerInstance if (!tsJest.isolatedModules) { // Use language services by default @@ -114,7 +104,7 @@ export const createCompilerInstance = (configs: ConfigSet): TsCompiler => { } else { compilerInstance = initializeTranspilerInstance(configs, memoryCache, logger) } - const compile = compileAndCacheResult(memoryCache, compilerInstance.compileFn, getExtension, logger) + const compile = compileAndCacheResult(memoryCache, compilerInstance.compileFn, logger) return { cwd: configs.cwd, compile, program: compilerInstance.program } } diff --git a/src/config/config-set.ts b/src/config/config-set.ts index 3559d66130..51694947d6 100644 --- a/src/config/config-set.ts +++ b/src/config/config-set.ts @@ -353,9 +353,9 @@ export class ConfigSet { const { tsJest: { tsConfig }, } = this - const configFilePath = tsConfig && tsConfig.kind === 'file' ? tsConfig.value : undefined + const configFilePath = tsConfig?.kind === 'file' ? tsConfig.value : undefined const result = this.readTsConfig( - tsConfig && tsConfig.kind === 'inline' ? tsConfig.value : undefined, + tsConfig?.kind === 'inline' ? tsConfig.value : undefined, configFilePath, tsConfig == null, ) @@ -728,7 +728,7 @@ export class ConfigSet { * @internal */ readTsConfig( - compilerOptions?: Record, + compilerOptions?: CompilerOptions, resolvedConfigFile?: string | null, noProject?: boolean | null, ): ParsedCommandLine {