From 310fb9a1d7b40a8274d6fb93745e66a6da891a75 Mon Sep 17 00:00:00 2001 From: Ahn Date: Mon, 19 Jul 2021 23:46:46 +0200 Subject: [PATCH] fix(config): include AST transformer's name and version into cache key (#2755) Closes #2753 --- src/__mocks__/hummy-transformer.js | 19 ++++++ .../__snapshots__/config-set.spec.ts.snap | 40 ++++++++++++ src/config/config-set.spec.ts | 65 ++++++++++++++----- src/config/config-set.ts | 20 ++++-- src/transformers/README.md | 8 +++ src/transformers/hoist-jest.ts | 8 +++ src/transformers/path-mapping.ts | 8 +++ src/utils/messages.ts | 2 + 8 files changed, 151 insertions(+), 19 deletions(-) create mode 100644 src/__mocks__/hummy-transformer.js diff --git a/src/__mocks__/hummy-transformer.js b/src/__mocks__/hummy-transformer.js new file mode 100644 index 0000000000..0b143889cd --- /dev/null +++ b/src/__mocks__/hummy-transformer.js @@ -0,0 +1,19 @@ +const { LogContexts, LogLevels } = require('bs-logger') + +function factory(tsCompiler) { + const logger = tsCompiler.configSet.logger.child({ namespace: 'hummy-transformer' }) + const ts = tsCompiler.configSet.compilerModule + function createVisitor(_ctx, _) { + return (node) => node + } + + return (ctx) => + logger.wrap({ [LogContexts.logLevel]: LogLevels.debug, call: null }, 'visitSourceFileNode(): dummy', (sf) => + ts.visitNode(sf, createVisitor(ctx, sf)) + ) +} + +module.exports = { + factory, + version: 1, +} diff --git a/src/config/__snapshots__/config-set.spec.ts.snap b/src/config/__snapshots__/config-set.spec.ts.snap index cdf4b615f8..89b24268ad 100644 --- a/src/config/__snapshots__/config-set.spec.ts.snap +++ b/src/config/__snapshots__/config-set.spec.ts.snap @@ -59,6 +59,8 @@ Object { "before": Array [ Object { "factory": [Function], + "name": "hoist-jest", + "version": 1, }, ], } @@ -71,6 +73,8 @@ Object { "before": Array [ Object { "factory": [Function], + "name": "hoist-jest", + "version": 1, }, Object { "factory": [Function], @@ -90,6 +94,8 @@ Object { "before": Array [ Object { "factory": [Function], + "name": "hoist-jest", + "version": 1, }, ], } @@ -106,6 +112,8 @@ Object { "before": Array [ Object { "factory": [Function], + "name": "hoist-jest", + "version": 1, }, ], } @@ -118,6 +126,8 @@ Object { "before": Array [ Object { "factory": [Function], + "name": "hoist-jest", + "version": 1, }, Object { "factory": [Function], @@ -127,6 +137,36 @@ Object { } `; +exports[`customTransformers should return an object containing all resolved transformers: warning-log 1`] = `Array []`; + +exports[`customTransformers should return an object containing all resolved transformers: warning-log 2`] = ` +Array [ + "[level:40] The AST transformer {{file}} must have an \`export const version = ", + "[level:40] The AST transformer {{file}} must have an \`export const name = ", +] +`; + +exports[`customTransformers should return an object containing all resolved transformers: warning-log 3`] = ` +Array [ + "[level:40] The AST transformer {{file}} must have an \`export const version = ", + "[level:40] The AST transformer {{file}} must have an \`export const name = ", +] +`; + +exports[`customTransformers should return an object containing all resolved transformers: warning-log 4`] = ` +Array [ + "[level:40] The AST transformer {{file}} must have an \`export const version = ", + "[level:40] The AST transformer {{file}} must have an \`export const name = ", +] +`; + +exports[`customTransformers should return an object containing all resolved transformers: warning-log 5`] = ` +Array [ + "[level:40] The AST transformer {{file}} must have an \`export const version = ", + "[level:40] The AST transformer {{file}} must have an \`export const name = ", +] +`; + exports[`isTestFile should return a boolean value whether the file matches test pattern 1`] = `true`; exports[`isTestFile should return a boolean value whether the file matches test pattern 2`] = `true`; diff --git a/src/config/config-set.spec.ts b/src/config/config-set.spec.ts index 25cff81ccd..821ee61c96 100644 --- a/src/config/config-set.spec.ts +++ b/src/config/config-set.spec.ts @@ -2,15 +2,17 @@ import { join, resolve } from 'path' import type { Transformer } from '@jest/transform' -import { testing } from 'bs-logger' +import { LogLevels, testing } from 'bs-logger' import ts from 'typescript' import { createConfigSet } from '../__helpers__/fakers' import { logTargetMock } from '../__helpers__/mocks' -import type { TsJestGlobalOptions } from '../types' +import type { AstTransformerDesc, TsJestGlobalOptions } from '../types' import * as _backports from '../utils/backports' import { getPackageVersion } from '../utils/get-package-version' +import { stringify } from '../utils/json' import { normalizeSlashes } from '../utils/normalize-slashes' +import { sha1 } from '../utils/sha1' import { mocked } from '../utils/testing' import { ConfigSet, MY_DIGEST } from './config-set' @@ -158,7 +160,9 @@ describe('customTransformers', () => { ], }, ])('should return an object containing all resolved transformers', (data) => { + const logger = testing.createLoggerMock() const cs = createConfigSet({ + logger, jestConfig: { rootDir: 'src', cwd: 'src', @@ -169,6 +173,9 @@ describe('customTransformers', () => { resolve: null, }) + expect( + logger.target.filteredLines(LogLevels.warn).map((logLine) => logLine.substring(0, logLine.indexOf('>') + 1)), + ).toMatchSnapshot('warning-log') expect(cs.resolvedTransformers).toMatchSnapshot() }) }) @@ -335,23 +342,51 @@ describe('babelJestTransformer', () => { describe('tsCacheDir', () => { const cacheName = 'configSetTmp' - const cacheDir = join(process.cwd(), cacheName) + const cacheDir = join(cacheName) const partialTsJestCacheDir = join(cacheDir, 'ts-jest') - it.each([undefined, Object.create(null)])( + it.each([ + undefined, + { + 'ts-jest': { + astTransformers: { + before: ['hummy-transformer'], + }, + }, + }, + ])( 'should return value from which is the combination of ts jest config and jest config when running test with cache', (data) => { - expect( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - createConfigSet({ - jestConfig: { - cache: true, - cacheDirectory: cacheDir, - globals: data, - }, - resolve: null, - }).tsCacheDir!.indexOf(partialTsJestCacheDir), - ).toEqual(0) + const configSet = createConfigSet({ + jestConfig: { + cache: true, + cacheDirectory: cacheDir, + globals: data, + }, + resolve: null, + }) + + expect(configSet.cacheSuffix).toEqual( + sha1( + stringify({ + version: configSet.compilerModule.version, + digest: configSet.tsJestDigest, + babelConfig: configSet.babelConfig, + tsconfig: { + options: configSet.parsedTsConfig.options, + raw: configSet.parsedTsConfig.raw, + }, + isolatedModules: configSet.isolatedModules, + // @ts-expect-error testing purpose + diagnostics: configSet._diagnostics, + transformers: Object.values(configSet.resolvedTransformers) + .reduce((prevVal, currentVal) => [...prevVal, currentVal]) + .map((transformer: AstTransformerDesc) => `${transformer.name}-${transformer.version}`), + }), + ), + ) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + expect(configSet.tsCacheDir!.indexOf(partialTsJestCacheDir)).toEqual(0) }, ) diff --git a/src/config/config-set.ts b/src/config/config-set.ts index b4ebd6d642..fe8a0fb6a8 100644 --- a/src/config/config-set.ts +++ b/src/config/config-set.ts @@ -299,13 +299,24 @@ export class ConfigSet { this.resolvedTransformers.before = [require('../transformers/hoist-jest')] const { astTransformers } = options if (astTransformers) { + const resolveTransformerFunc = (transformerPath: string) => { + const transformerFunc = require(transformerPath) + if (!transformerFunc.version) { + this.logger.warn(Errors.MissingTransformerVersion, { file: transformerPath }) + } + if (!transformerFunc.name) { + this.logger.warn(Errors.MissingTransformerName, { file: transformerPath }) + } + + return transformerFunc + } const resolveTransformers = (transformers: Array): AstTransformerDesc[] => transformers.map((transformer) => { if (typeof transformer === 'string') { - return require(this.resolvePath(transformer, { nodeResolve: true })) + return resolveTransformerFunc(this.resolvePath(transformer, { nodeResolve: true })) } else { return { - ...require(this.resolvePath(transformer.path, { nodeResolve: true })), + ...resolveTransformerFunc(this.resolvePath(transformer.path, { nodeResolve: true })), options: transformer.options, } } @@ -356,14 +367,15 @@ export class ConfigSet { version: this.compilerModule.version, digest: this.tsJestDigest, babelConfig: this.babelConfig, - compilerModule: this.compilerModule, tsconfig: { options: this.parsedTsConfig.options, raw: this.parsedTsConfig.raw, }, isolatedModules: this.isolatedModules, diagnostics: this._diagnostics, - transformers: this.resolvedTransformers, + transformers: Object.values(this.resolvedTransformers) + .reduce((prevVal, currentVal) => [...prevVal, currentVal]) + .map((transformer: AstTransformerDesc) => `${transformer.name}-${transformer.version}`), }), ) if (!this._jestCfg.cache) { diff --git a/src/transformers/README.md b/src/transformers/README.md index f4b9b0e55e..9b4fd9aac7 100644 --- a/src/transformers/README.md +++ b/src/transformers/README.md @@ -9,6 +9,14 @@ import { SourceFile, TransformationContext, Transformer, Visitor } from 'typescr import type { TsCompilerInstance } from 'ts-jest/dist/types' +/** + * Remember to increase the version whenever transformer's content is changed. This is to inform Jest to not reuse + * the previous cache which contains old transformer's content + */ +export const version = 1 +// Used for constructing cache key +export const name = 'hoist-jest' + export function factory(compilerInstance: TsCompilerInstance) { const ts = compilerInstance.configSet.compilerModule function createVisitor(ctx: TransformationContext, sf: SourceFile) { diff --git a/src/transformers/hoist-jest.ts b/src/transformers/hoist-jest.ts index b0511a41a3..af48dcbedb 100644 --- a/src/transformers/hoist-jest.ts +++ b/src/transformers/hoist-jest.ts @@ -13,6 +13,14 @@ import type { import type { TsCompilerInstance } from '../types' +/** + * Remember to increase the version whenever transformer's content is changed. This is to inform Jest to not reuse + * the previous cache which contains old transformer's content + */ +export const version = 1 +// Used for constructing cache key +export const name = 'hoist-jest' + /** * What methods of `jest` we should hoist */ diff --git a/src/transformers/path-mapping.ts b/src/transformers/path-mapping.ts index dfdcb6aa85..6c3a229815 100644 --- a/src/transformers/path-mapping.ts +++ b/src/transformers/path-mapping.ts @@ -11,6 +11,14 @@ import type * as _ts from 'typescript' import type { TsCompilerInstance } from '../types' +/** + * Remember to increase the version whenever transformer's content is changed. This is to inform Jest to not reuse + * the previous cache which contains old transformer's content + */ +export const version = 1 +// Used for constructing cache key +export const name = 'hoist-jest' + const isBaseDir = (base: string, dir: string) => !relative(base, dir)?.startsWith('.') /** diff --git a/src/utils/messages.ts b/src/utils/messages.ts index 35e6abdc4a..835e2187ad 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -19,6 +19,8 @@ export const enum Errors { MismatchNodeTargetMapping = 'There is a mismatch between your NodeJs version {{nodeJsVer}} and your TypeScript target {{compilationTarget}}. This might lead to some unexpected errors when running tests with `ts-jest`. To fix this, you can check https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping', CannotProcessFileReturnOriginal = "Unable to process '{{file}}', falling back to original file content. You can also configure Jest config option `transformIgnorePatterns` to ignore {{file}} from transformation or make sure that `outDir` in your tsconfig is neither `''` or `'.'`", CannotProcessFile = "Unable to process '{{file}}', please make sure that `outDir` in your tsconfig is neither `''` or `'.'`. You can also configure Jest config option `transformIgnorePatterns` to inform `ts-jest` to transform {{file}}", + MissingTransformerName = 'The AST transformer {{file}} must have an `export const name = `', + MissingTransformerVersion = 'The AST transformer {{file}} must have an `export const version = `', } /**