diff --git a/e2e/__tests__/__snapshots__/coverage.test.ts.snap b/e2e/__tests__/__snapshots__/coverage.test.ts.snap index 43252f0ba5..44e8bb80bd 100644 --- a/e2e/__tests__/__snapshots__/coverage.test.ts.snap +++ b/e2e/__tests__/__snapshots__/coverage.test.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`using template "default" should report coverages 1`] = ` +exports[`Code coverage should pass using template "default" 1`] = ` √ jest ↳ exit code: 0 ===[ STDOUT ]=================================================================== @@ -23,7 +23,7 @@ exports[`using template "default" should report coverages 1`] = ` ================================================================================ `; -exports[`using template "with-babel-7" should report coverages 1`] = ` +exports[`Code coverage should pass using template "with-babel-7" 1`] = ` √ jest ↳ exit code: 0 ===[ STDOUT ]=================================================================== @@ -46,7 +46,7 @@ exports[`using template "with-babel-7" should report coverages 1`] = ` ================================================================================ `; -exports[`using template "with-babel-7-string-config" should report coverages 1`] = ` +exports[`Code coverage should pass using template "with-babel-7-string-config" 1`] = ` √ jest ↳ exit code: 0 ===[ STDOUT ]=================================================================== diff --git a/e2e/__tests__/coverage.test.ts b/e2e/__tests__/coverage.test.ts index 97b94bcbd5..e71fafeac0 100644 --- a/e2e/__tests__/coverage.test.ts +++ b/e2e/__tests__/coverage.test.ts @@ -1,15 +1,15 @@ import { allValidPackageSets } from '../__helpers__/templates' import { configureTestCase } from '../__helpers__/test-case' -const testCase = configureTestCase('simple', { - jestConfig: { collectCoverage: true }, -}) +describe('Code coverage', () => { + const testCase = configureTestCase('simple', { + jestConfig: { collectCoverage: true }, + }) -testCase.runWithTemplates(allValidPackageSets, 0, (runTest, { templateName }) => { - describe(`using template "${templateName}"`, () => { - const result = runTest() + testCase.runWithTemplates(allValidPackageSets, 0, (runTest, { testLabel }) => { + it(testLabel, () => { + const result = runTest() - it(`should report coverages`, () => { expect(result.status).toBe(0) expect(result).toMatchSnapshot() }) diff --git a/package.json b/package.json index d1a4007dee..a87e46c9c2 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ }, "homepage": "https://kulshekhar.github.io/ts-jest", "dependencies": { - "@jest/create-cache-key-function": "^26.5.0", "@types/jest": "26.x", "bs-logger": "0.x", "buffer-from": "1.x", diff --git a/src/ts-jest-transformer.spec.ts b/src/ts-jest-transformer.spec.ts index dbff7cd50d..b8ba3bdf48 100644 --- a/src/ts-jest-transformer.spec.ts +++ b/src/ts-jest-transformer.spec.ts @@ -1,4 +1,5 @@ import { LogLevels } from 'bs-logger' +import { sep } from 'path' import { TsJestTransformer } from './ts-jest-transformer' import { ConfigSet } from './config/config-set' @@ -12,39 +13,24 @@ beforeEach(() => { }) describe('TsJestTransformer', () => { - describe('createOrResolveTransformerCfg', () => { - it('should return the same config-set for same values with jest config string is not in cachedConfigSets', () => { - const obj1 = { cwd: '/foo/.' } as any - const tjT = new TsJestTransformer() - - logTarget.clear() - - tjT.getCacheKey('foo', 'foo', JSON.stringify(obj1), { - config: obj1, - } as any) + describe('configFor', () => { + it('should return the same config-set for same values with jest config string is not in configSetsIndex', () => { + const obj1 = { cwd: '/foo/.', rootDir: '/bar//dummy/..', globals: {} } + const cs3 = new TsJestTransformer().configsFor(obj1 as any) - expect(logTarget.lines[0]).toMatchInlineSnapshot(` - "[level:30] no matching config-set found, creating a new one - " - `) + expect(cs3.cwd).toBe(`${sep}foo`) + expect(cs3.rootDir).toBe(`${sep}bar`) }) - it('should return the same config-set for same values with jest config string in cachedConfigSets', () => { + it('should return the same config-set for same values with jest config string in configSetsIndex', () => { const obj1 = { cwd: '/foo/.', rootDir: '/bar//dummy/..', globals: {} } const obj2 = { ...obj1 } - const tjT1 = new TsJestTransformer() - const tjT2 = new TsJestTransformer() - - logTarget.clear() + const cs1 = new TsJestTransformer().configsFor(obj1 as any) + const cs2 = new TsJestTransformer().configsFor(obj2 as any) - tjT1.getCacheKey('foo', 'foo', JSON.stringify(obj1), { - config: obj1, - } as any) - tjT2.getCacheKey('foo', 'foo', JSON.stringify(obj2), { - config: obj2, - } as any) - - expect(logTarget.filteredLines(LogLevels.info)).toHaveLength(1) + expect(cs1.cwd).toBe(`${sep}foo`) + expect(cs1.rootDir).toBe(`${sep}bar`) + expect(cs2).toBe(cs1) }) }) @@ -63,10 +49,13 @@ describe('TsJestTransformer', () => { tr.getCacheKey(input.fileContent, input.fileName, '{}', { ...input.options, instrument: true }), tr.getCacheKey(input.fileContent, input.fileName, '{}', { ...input.options, rootDir: '/bar' }), ] + // each key should have correct length for (const key of keys) { - expect(key).toHaveLength(32) + expect(key).toHaveLength(40) } + // unique array should have same length + expect(keys.filter((k, i, all) => all.indexOf(k) === i)).toHaveLength(keys.length) }) }) diff --git a/src/ts-jest-transformer.ts b/src/ts-jest-transformer.ts index 54c582a60f..14fa3c9f8b 100644 --- a/src/ts-jest-transformer.ts +++ b/src/ts-jest-transformer.ts @@ -1,4 +1,3 @@ -import createCacheKey from '@jest/create-cache-key-function' import type { CacheKeyOptions, TransformedSource, Transformer, TransformOptions } from '@jest/transform' import type { Config } from '@jest/types' import type { Logger } from 'bs-logger' @@ -9,6 +8,7 @@ import { stringify } from './utils/json' import { JsonableValue } from './utils/jsonable-value' import { rootLogger } from './utils/logger' import { Errors, interpolate } from './utils/messages' +import { sha1 } from './utils/sha1' interface CachedConfigSet { configSet: ConfigSet @@ -25,7 +25,6 @@ export class TsJestTransformer implements Transformer { private static readonly _cachedConfigSets: CachedConfigSet[] = [] protected readonly logger: Logger protected _transformCfgStr!: string - protected _configSet!: ConfigSet constructor() { this.logger = rootLogger.child({ namespace: 'ts-jest-transformer' }) @@ -33,6 +32,58 @@ export class TsJestTransformer implements Transformer { this.logger.debug('created new transformer') } + /** + * @public + */ + configsFor(jestConfig: Config.ProjectConfig): ConfigSet { + const ccs: CachedConfigSet | undefined = TsJestTransformer._cachedConfigSets.find( + (cs) => cs.jestConfig.value === jestConfig, + ) + let configSet: ConfigSet + if (ccs) { + this._transformCfgStr = ccs.transformerCfgStr + configSet = ccs.configSet + } else { + // try to look-it up by stringified version + const serializedJestCfg = stringify(jestConfig) + const serializedCcs = TsJestTransformer._cachedConfigSets.find( + (cs) => cs.jestConfig.serialized === serializedJestCfg, + ) + if (serializedCcs) { + // update the object so that we can find it later + // this happens because jest first calls getCacheKey with stringified version of + // the config, and then it calls the transformer with the proper object + serializedCcs.jestConfig.value = jestConfig + this._transformCfgStr = serializedCcs.transformerCfgStr + configSet = serializedCcs.configSet + } else { + // create the new record in the index + this.logger.info('no matching config-set found, creating a new one') + + configSet = new ConfigSet(jestConfig) + this._transformCfgStr = new JsonableValue({ + digest: configSet.tsJestDigest, + babel: configSet.babelConfig, + ...jestConfig, + tsconfig: { + options: configSet.parsedTsConfig.options, + raw: configSet.parsedTsConfig.raw, + }, + }).serialized + TsJestTransformer._cachedConfigSets.push({ + jestConfig: new JsonableValue(jestConfig), + configSet, + transformerCfgStr: this._transformCfgStr, + }) + } + } + + return configSet + } + + /** + * @public + */ process( input: string, filePath: Config.Path, @@ -43,9 +94,10 @@ export class TsJestTransformer implements Transformer { let result: string | TransformedSource const source: string = input - const { hooks } = this._configSet - const shouldStringifyContent = this._configSet.shouldStringifyContent(filePath) - const babelJest = shouldStringifyContent ? undefined : this._configSet.babelJestTransformer + const configs = this.configsFor(jestConfig) + const { hooks } = configs + const shouldStringifyContent = configs.shouldStringifyContent(filePath) + const babelJest = shouldStringifyContent ? undefined : configs.babelJestTransformer const isDefinitionFile = filePath.endsWith(DECLARATION_TYPE_EXT) const isJsFile = JS_JSX_REGEX.test(filePath) const isTsFile = !isDefinitionFile && TS_TSX_REGEX.test(filePath) @@ -55,7 +107,7 @@ export class TsJestTransformer implements Transformer { } else if (isDefinitionFile) { // do not try to compile declaration files result = '' - } else if (!this._configSet.parsedTsConfig.options.allowJs && isJsFile) { + } else if (!configs.parsedTsConfig.options.allowJs && isJsFile) { // we've got a '.js' but the compiler option `allowJs` is not set or set to false this.logger.warn({ fileName: filePath }, interpolate(Errors.GotJsFileButAllowJsFalse, { path: filePath })) @@ -63,7 +115,7 @@ export class TsJestTransformer implements Transformer { } else if (isJsFile || isTsFile) { // transpile TS code (source maps are included) /* istanbul ignore if */ - result = this._configSet.tsCompiler.compile(source, filePath) + result = configs.tsCompiler.compile(source, filePath) } else { // we should not get called for files with other extension than js[x], ts[x] and d.ts, // TypeScript will bail if we try to compile, and if it was to call babel, users can @@ -99,6 +151,8 @@ export class TsJestTransformer implements Transformer { * Jest uses this to cache the compiled version of a file * * @see https://github.com/facebook/jest/blob/v23.5.0/packages/jest-runtime/src/script_transformer.js#L61-L90 + * + * @public */ getCacheKey( fileContent: string, @@ -106,59 +160,23 @@ export class TsJestTransformer implements Transformer { _jestConfigStr: string, transformOptions: CacheKeyOptions, ): string { - this.createOrResolveTransformerCfg(transformOptions.config) + const configs = this.configsFor(transformOptions.config) this.logger.debug({ fileName: filePath, transformOptions }, 'computing cache key for', filePath) - return createCacheKey()(fileContent, filePath, this._transformCfgStr, { - config: transformOptions.config, - instrument: false, - }) - } - - /** - * Users can override this method and provide their own config class - */ - protected createOrResolveTransformerCfg(jestConfig: Config.ProjectConfig): void { - const ccs: CachedConfigSet | undefined = TsJestTransformer._cachedConfigSets.find( - (cs) => cs.jestConfig.value === jestConfig, + // we do not instrument, ensure it is false all the time + const { instrument = false, rootDir = configs.rootDir } = transformOptions + + return sha1( + this._transformCfgStr, + '\x00', + rootDir, + '\x00', + `instrument:${instrument ? 'on' : 'off'}`, + '\x00', + fileContent, + '\x00', + filePath, ) - if (ccs) { - this._transformCfgStr = ccs.transformerCfgStr - this._configSet = ccs.configSet - } else { - // try to look-it up by stringified version - const serializedJestCfg = stringify(jestConfig) - const serializedCcs = TsJestTransformer._cachedConfigSets.find( - (cs) => cs.jestConfig.serialized === serializedJestCfg, - ) - if (serializedCcs) { - // update the object so that we can find it later - // this happens because jest first calls getCacheKey with stringified version of - // the config, and then it calls the transformer with the proper object - serializedCcs.jestConfig.value = jestConfig - this._transformCfgStr = serializedCcs.transformerCfgStr - this._configSet = serializedCcs.configSet - } else { - // create the new record in the index - this.logger.info('no matching config-set found, creating a new one') - - this._configSet = new ConfigSet(jestConfig) - this._transformCfgStr = new JsonableValue({ - digest: this._configSet.tsJestDigest, - babel: this._configSet.babelConfig, - ...jestConfig, - tsconfig: { - options: this._configSet.parsedTsConfig.options, - raw: this._configSet.parsedTsConfig.raw, - }, - }).serialized - TsJestTransformer._cachedConfigSets.push({ - jestConfig: new JsonableValue(jestConfig), - configSet: this._configSet, - transformerCfgStr: this._transformCfgStr, - }) - } - } } }