From 11030717e50569046a8457e3a926d720e053feea Mon Sep 17 00:00:00 2001 From: Huafu Gandon Date: Mon, 20 Aug 2018 11:28:16 +0200 Subject: [PATCH] feat: warn about unsupported versions --- jest.config.js | 6 +- package.json | 4 +- preprocessor.js | 9 ++ src/__helpers__/fakers.ts | 1 - src/index.spec.ts | 24 ++++ src/index.ts | 14 ++- src/lib/__snapshots__/backports.spec.ts.snap | 22 ++-- src/lib/__snapshots__/config-set.spec.ts.snap | 1 - src/lib/compiler.spec.ts | 4 +- src/lib/config-set.spec.ts | 1 - src/lib/config-set.ts | 25 +++-- src/lib/debug.spec.ts | 4 +- src/lib/debug.ts | 16 ++- src/lib/get-package-version.spec.ts | 14 +++ src/lib/get-package-version.ts | 6 + src/lib/hacks.ts | 3 +- src/lib/importer.ts | 18 ++- src/lib/messages.ts | 11 +- src/lib/types.ts | 1 - src/lib/version-checkers.spec.ts | 105 ++++++++++++++++++ src/lib/version-checkers.ts | 89 +++++++++++++++ tslint.json | 7 +- 22 files changed, 327 insertions(+), 58 deletions(-) create mode 100644 preprocessor.js create mode 100644 src/lib/get-package-version.spec.ts create mode 100644 src/lib/get-package-version.ts create mode 100644 src/lib/version-checkers.spec.ts create mode 100644 src/lib/version-checkers.ts diff --git a/jest.config.js b/jest.config.js index 5037968b95..1b2ca50bed 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,7 +1,7 @@ module.exports = { rootDir: '.', transform: { - '\\.ts$': '/dist/index.js' + '\\.ts$': '/dist/index.js', }, testMatch: ['/src/**/?(*.)+(spec|test).ts?(x)'], collectCoverageFrom: [ @@ -9,8 +9,8 @@ module.exports = { '!/src/**/*.d.ts', '!/src/**/*.spec.ts', '!/src/**/*.test.ts', - '!/src/**/__*__/*' + '!/src/**/__*__/*', ], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], - testEnvironment: 'node' + testEnvironment: 'node', } diff --git a/package.json b/package.json index 7f7e6139b4..9f2eef9fd2 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,8 @@ "fast-json-stable-stringify": "^2.0.0", "json5": "^1.0.1", "make-error": "^1.3.4", - "mkdirp": "^0.5.1" + "mkdirp": "^0.5.1", + "semver": "^5.5.1" }, "peerDependencies": { "babel-jest": "23.x", @@ -74,7 +75,6 @@ "prettier": "^1.14.2", "prettier-tslint": "^0.4.0", "reflect-metadata": "^0.1.12", - "semver": "^5.5.1", "source-map": "^0.7.3", "tslint": "^5.11.0", "typescript": "^3.0.1" diff --git a/preprocessor.js b/preprocessor.js new file mode 100644 index 0000000000..7668d5a34d --- /dev/null +++ b/preprocessor.js @@ -0,0 +1,9 @@ +console.warn( + 'ts-jest', + '[deprecated]', + `Replace any occurrences of "ts-jest/dist/preprocessor.js" or ` + + ` "/node_modules/ts-jest/preprocessor.js"` + + ` in the 'transform' section of your Jest config with just "ts-jest".`, +) + +module.exports = require('./dist') diff --git a/src/__helpers__/fakers.ts b/src/__helpers__/fakers.ts index 4e8a9c7afc..f5f8549c90 100644 --- a/src/__helpers__/fakers.ts +++ b/src/__helpers__/fakers.ts @@ -54,7 +54,6 @@ describe('hello', () => { export function tsJestConfig(options?: Partial): TsJestConfig { return { - version: '0.0.0-mock0', typeCheck: false, compiler: 'typescript', babelConfig: undefined, diff --git a/src/index.spec.ts b/src/index.spec.ts index ae30edb3c5..6781f7c9f3 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -1,3 +1,5 @@ +// tslint:disable:max-line-length +import { __setup } from './lib/debug' import * as tsJest from '.' import { TsJestTransformer } from './lib/ts-jest-transformer' @@ -31,6 +33,28 @@ describe('ts-jest', () => { }) }) +describe('old entry point', () => { + const MANIFEST = { tsJestIndex: true } + const spy = jest.spyOn(console, 'warn') + spy.mockImplementation(() => undefined) + afterAll(() => { + spy.mockRestore() + }) + + it('should warn when using old path to ts-jest', () => { + jest.mock('../dist/index', () => MANIFEST) + expect(require('../preprocessor.js')).toBe(MANIFEST) + expect(spy).toHaveBeenCalledTimes(1) + expect(spy.mock.calls[0]).toMatchInlineSnapshot(` +Array [ + "ts-jest", + "[deprecated]", + "Replace any occurrences of \\"ts-jest/dist/preprocessor.js\\" or \\"/node_modules/ts-jest/preprocessor.js\\" in the 'transform' section of your Jest config with just \\"ts-jest\\".", +] +`) + }) +}) + describe('createTransformer', () => { it('should create different instances', () => { const tr1 = tsJest.createTransformer() diff --git a/src/index.ts b/src/index.ts index 5387d24c79..d5a42d776f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,18 @@ import { TsJestTransformer } from './lib/ts-jest-transformer' import { createJestPreset } from './lib/create-jest-preset' import { TsJestGlobalOptions } from './lib/types' +import { VersionCheckers } from './lib/version-checkers' + +// tslint:disable-next-line:no-var-requires +const version: string = require('../package.json').version let transformer!: TsJestTransformer function defaultTransformer(): TsJestTransformer { - return transformer || (transformer = new TsJestTransformer()) + return transformer || (transformer = createTransformer()) } function createTransformer(baseConfig?: TsJestGlobalOptions) { + VersionCheckers.jest.warn() return new TsJestTransformer(baseConfig) } function tsProcess(...args: any[]): any { @@ -26,14 +31,15 @@ const __singleton = () => transformer const __resetModule = () => (transformer = undefined as any) export { - // jest API + version, + // jest API =============== createTransformer, tsProcess as process, getCacheKey, - // extra + // extra ================== createJestPreset, jestPreset, - // tests + // tests ================== __singleton, __resetModule, } diff --git a/src/lib/__snapshots__/backports.spec.ts.snap b/src/lib/__snapshots__/backports.spec.ts.snap index b61b47abcb..f9a9d10c82 100644 --- a/src/lib/__snapshots__/backports.spec.ts.snap +++ b/src/lib/__snapshots__/backports.spec.ts.snap @@ -16,7 +16,7 @@ Object { } `; -exports[`backportJestConfig with "globals.__TRANSFORM_HTML__" set to false should wran the user 1`] = `"ts-jest \\"[jest-config].globals.__TRANSFORM_HTML__\\" is deprecated, use \\"[jest-config].globals.ts-jest.stringifyContentPathRegex\\" instead."`; +exports[`backportJestConfig with "globals.__TRANSFORM_HTML__" set to false should wran the user 1`] = `"ts-jest: \\"[jest-config].globals.__TRANSFORM_HTML__\\" is deprecated, use \\"[jest-config].globals.ts-jest.stringifyContentPathRegex\\" instead."`; exports[`backportJestConfig with "globals.__TRANSFORM_HTML__" set to true should have changed the config correctly: before 1`] = ` Object { @@ -36,7 +36,7 @@ Object { } `; -exports[`backportJestConfig with "globals.__TRANSFORM_HTML__" set to true should wran the user 1`] = `"ts-jest \\"[jest-config].globals.__TRANSFORM_HTML__\\" is deprecated, use \\"[jest-config].globals.ts-jest.stringifyContentPathRegex\\" instead."`; +exports[`backportJestConfig with "globals.__TRANSFORM_HTML__" set to true should wran the user 1`] = `"ts-jest: \\"[jest-config].globals.__TRANSFORM_HTML__\\" is deprecated, use \\"[jest-config].globals.ts-jest.stringifyContentPathRegex\\" instead."`; exports[`backportJestConfig with "globals.__TS_CONFIG__" set to { foo: 'bar' } should have changed the config correctly: before 1`] = ` Object { @@ -60,7 +60,7 @@ Object { } `; -exports[`backportJestConfig with "globals.__TS_CONFIG__" set to { foo: 'bar' } should wran the user 1`] = `"ts-jest \\"[jest-config].globals.__TS_CONFIG__\\" is deprecated, use \\"[jest-config].globals.ts-jest.tsConfig\\" instead."`; +exports[`backportJestConfig with "globals.__TS_CONFIG__" set to { foo: 'bar' } should wran the user 1`] = `"ts-jest: \\"[jest-config].globals.__TS_CONFIG__\\" is deprecated, use \\"[jest-config].globals.ts-jest.tsConfig\\" instead."`; exports[`backportJestConfig with "globals.ts-jest.enableTsDiagnostics" set to '\\\\.spec\\\\.ts$' should have changed the config correctly: before 1`] = ` Object { @@ -84,7 +84,7 @@ Object { } `; -exports[`backportJestConfig with "globals.ts-jest.enableTsDiagnostics" set to '\\\\.spec\\\\.ts$' should wran the user 1`] = `"ts-jest \\"[jest-config].globals.ts-jest.enableTsDiagnostics\\" is deprecated, use \\"[jest-config].globals.ts-jest.diagnostics\\" instead."`; +exports[`backportJestConfig with "globals.ts-jest.enableTsDiagnostics" set to '\\\\.spec\\\\.ts$' should wran the user 1`] = `"ts-jest: \\"[jest-config].globals.ts-jest.enableTsDiagnostics\\" is deprecated, use \\"[jest-config].globals.ts-jest.diagnostics\\" instead."`; exports[`backportJestConfig with "globals.ts-jest.enableTsDiagnostics" set to false should have changed the config correctly: before 1`] = ` Object { @@ -106,7 +106,7 @@ Object { } `; -exports[`backportJestConfig with "globals.ts-jest.enableTsDiagnostics" set to false should wran the user 1`] = `"ts-jest \\"[jest-config].globals.ts-jest.enableTsDiagnostics\\" is deprecated, use \\"[jest-config].globals.ts-jest.diagnostics\\" instead."`; +exports[`backportJestConfig with "globals.ts-jest.enableTsDiagnostics" set to false should wran the user 1`] = `"ts-jest: \\"[jest-config].globals.ts-jest.enableTsDiagnostics\\" is deprecated, use \\"[jest-config].globals.ts-jest.diagnostics\\" instead."`; exports[`backportJestConfig with "globals.ts-jest.enableTsDiagnostics" set to true should have changed the config correctly: before 1`] = ` Object { @@ -128,7 +128,7 @@ Object { } `; -exports[`backportJestConfig with "globals.ts-jest.enableTsDiagnostics" set to true should wran the user 1`] = `"ts-jest \\"[jest-config].globals.ts-jest.enableTsDiagnostics\\" is deprecated, use \\"[jest-config].globals.ts-jest.diagnostics\\" instead."`; +exports[`backportJestConfig with "globals.ts-jest.enableTsDiagnostics" set to true should wran the user 1`] = `"ts-jest: \\"[jest-config].globals.ts-jest.enableTsDiagnostics\\" is deprecated, use \\"[jest-config].globals.ts-jest.diagnostics\\" instead."`; exports[`backportJestConfig with "globals.ts-jest.skipBabel" set to false should have changed the config correctly: before 1`] = ` Object { @@ -150,7 +150,7 @@ Object { } `; -exports[`backportJestConfig with "globals.ts-jest.skipBabel" set to false should wran the user 1`] = `"ts-jest \\"[jest-config].globals.ts-jest.skipBabel\\" is deprecated, use \\"[jest-config].globals.ts-jest.babelConfig\\" instead."`; +exports[`backportJestConfig with "globals.ts-jest.skipBabel" set to false should wran the user 1`] = `"ts-jest: \\"[jest-config].globals.ts-jest.skipBabel\\" is deprecated, use \\"[jest-config].globals.ts-jest.babelConfig\\" instead."`; exports[`backportJestConfig with "globals.ts-jest.skipBabel" set to true should have changed the config correctly: before 1`] = ` Object { @@ -170,7 +170,7 @@ Object { } `; -exports[`backportJestConfig with "globals.ts-jest.skipBabel" set to true should wran the user 1`] = `"ts-jest \\"[jest-config].globals.ts-jest.skipBabel\\" is deprecated, use \\"[jest-config].globals.ts-jest.babelConfig\\" instead."`; +exports[`backportJestConfig with "globals.ts-jest.skipBabel" set to true should wran the user 1`] = `"ts-jest: \\"[jest-config].globals.ts-jest.skipBabel\\" is deprecated, use \\"[jest-config].globals.ts-jest.babelConfig\\" instead."`; exports[`backportJestConfig with "globals.ts-jest.tsConfigFile" set to 'tsconfig.build.json' should have changed the config correctly: before 1`] = ` Object { @@ -192,7 +192,7 @@ Object { } `; -exports[`backportJestConfig with "globals.ts-jest.tsConfigFile" set to 'tsconfig.build.json' should wran the user 1`] = `"ts-jest \\"[jest-config].globals.ts-jest.tsConfigFile\\" is deprecated, use \\"[jest-config].globals.ts-jest.tsConfig\\" instead."`; +exports[`backportJestConfig with "globals.ts-jest.tsConfigFile" set to 'tsconfig.build.json' should wran the user 1`] = `"ts-jest: \\"[jest-config].globals.ts-jest.tsConfigFile\\" is deprecated, use \\"[jest-config].globals.ts-jest.tsConfig\\" instead."`; exports[`backportJestConfig with "globals.ts-jest.useBabelrc" set to false should have changed the config correctly: before 1`] = ` Object { @@ -215,7 +215,7 @@ Object { `; exports[`backportJestConfig with "globals.ts-jest.useBabelrc" set to false should wran the user 1`] = ` -"ts-jest \\"[jest-config].globals.ts-jest.useBabelrc\\" is deprecated, use \\"[jest-config].globals.ts-jest.babelConfig\\" instead. +"ts-jest: \\"[jest-config].globals.ts-jest.useBabelrc\\" is deprecated, use \\"[jest-config].globals.ts-jest.babelConfig\\" instead. ↳ See \`babel-jest\` related issue: https://github.com/facebook/jest/issues/3845" `; @@ -240,6 +240,6 @@ Object { `; exports[`backportJestConfig with "globals.ts-jest.useBabelrc" set to true should wran the user 1`] = ` -"ts-jest \\"[jest-config].globals.ts-jest.useBabelrc\\" is deprecated, use \\"[jest-config].globals.ts-jest.babelConfig\\" instead. +"ts-jest: \\"[jest-config].globals.ts-jest.useBabelrc\\" is deprecated, use \\"[jest-config].globals.ts-jest.babelConfig\\" instead. ↳ See \`babel-jest\` related issue: https://github.com/facebook/jest/issues/3845" `; diff --git a/src/lib/__snapshots__/config-set.spec.ts.snap b/src/lib/__snapshots__/config-set.spec.ts.snap index 411268578a..c315592860 100644 --- a/src/lib/__snapshots__/config-set.spec.ts.snap +++ b/src/lib/__snapshots__/config-set.spec.ts.snap @@ -36,6 +36,5 @@ Object { "value": undefined, }, "typeCheck": false, - "version": "X.Y.Z", } `; diff --git a/src/lib/compiler.spec.ts b/src/lib/compiler.spec.ts index 391acf4c18..bcb718141f 100644 --- a/src/lib/compiler.spec.ts +++ b/src/lib/compiler.spec.ts @@ -86,7 +86,7 @@ describe('cache', () => { expect(logger).toHaveBeenCalledTimes(1) expect(logger).toHaveBeenCalledWith( 'log', - 'ts-jest', + 'ts-jest:', 'readThrough:cache-miss', __filename, ) @@ -96,7 +96,7 @@ describe('cache', () => { expect(logger).toHaveBeenCalledTimes(1) expect(logger).toHaveBeenCalledWith( 'log', - 'ts-jest', + 'ts-jest:', 'readThrough:cache-hit', __filename, ) diff --git a/src/lib/config-set.spec.ts b/src/lib/config-set.spec.ts index c5dae7688f..847468e2c2 100644 --- a/src/lib/config-set.spec.ts +++ b/src/lib/config-set.spec.ts @@ -8,7 +8,6 @@ import { resolve } from 'path' import { normalizeSlashes } from './normalize-slashes' jest.mock('./backports') -jest.mock('../../package.json', () => ({ version: 'X.Y.Z' })) const backports = mocked(_backports) diff --git a/src/lib/config-set.ts b/src/lib/config-set.ts index 4bf22f855a..cd499153f9 100644 --- a/src/lib/config-set.ts +++ b/src/lib/config-set.ts @@ -1,11 +1,12 @@ /** - * This is the core of settings and ts-jest. - * Since configuration are used to create a good cache key so that jest can be fast, - * everything depending on it is here. + * This is the core of settings and so ts-jest. + * Since configuration are used to create a good cache key, everything + * depending on it is here. Fast jest relies on correct cache keys + * depending on all settings that could affect the generated output. * - * The big issue with jest is that it calls first `getCacheKey()` with stringified version - * of the `jest.ProjectConfig`, and then later it calls `process()` with the complete, - * object version of it. + * The big issue is that jest calls first `getCacheKey()` with stringified + * version of the `jest.ProjectConfig`, and then later it calls `process()` + * with the complete, object version of it. */ import { JsonableValue } from './jsonable-value' import { @@ -34,6 +35,8 @@ import { sha1 } from './sha1' import { stringify } from './json' import { normalizeSlashes } from './normalize-slashes' import { createCompiler } from './compiler' +import { version as myVersion } from '..' +import semver from 'semver' interface ReadTsConfigResult { // what we get from reading the config file if any, or inline options @@ -193,7 +196,6 @@ export class ConfigSet { // parsed options return { - version: require('../../package.json').version, tsConfig, babelConfig, diagnostics, @@ -249,10 +251,10 @@ export class ConfigSet { // loadOptions is from babel 7+, and OptionManager is backward compatible but deprecated 6 API const { OptionManager, loadOptions, version } = importer.babelCore( - ImportReasons.babelJest, + ImportReasons.BabelJest, ) // cwd is only supported from babel >= 7 - if (parseInt(version.split('.').shift() as string, 10) < 7) { + if (version && semver.satisfies(version, '>=6 <7')) { delete base.cwd } // call babel to load options @@ -268,7 +270,7 @@ export class ConfigSet { @Memoize() get compilerModule(): TTypeScript { - return importer.typescript(ImportReasons.tsJest, this.tsJest.compiler) + return importer.typescript(ImportReasons.TsJest, this.tsJest.compiler) } @Memoize() @@ -276,7 +278,7 @@ export class ConfigSet { const { babel } = this if (!babel) return return importer - .babelJest(ImportReasons.babelJest) + .babelJest(ImportReasons.BabelJest) .createTransformer(babel) as BabelJestTransformer } @@ -478,6 +480,7 @@ export class ConfigSet { @Memoize() get jsonValue() { return new JsonableValue({ + version: myVersion, jest: this.jest, tsJest: this.tsJest, babel: this.babel, diff --git a/src/lib/debug.spec.ts b/src/lib/debug.spec.ts index 231ad9d011..fabc0bf6bc 100644 --- a/src/lib/debug.spec.ts +++ b/src/lib/debug.spec.ts @@ -17,7 +17,7 @@ describe('debug', () => { __setup() debug('foo') expect(consoleSpies.log).toHaveBeenCalledTimes(1) - expect(consoleSpies.log).toHaveBeenCalledWith('ts-jest', 'foo') + expect(consoleSpies.log).toHaveBeenCalledWith('ts-jest:', 'foo') }) it('should NOT log when TS_JEST_DEBUG is falsy', () => { process.env.TS_JEST_DEBUG = '' @@ -42,7 +42,7 @@ describe('wrapWithDebug', () => { __setup() expect(wrapAndCall('bar')).toBe('hello bar') expect(consoleSpies.log).toHaveBeenCalledTimes(1) - expect(consoleSpies.log).toHaveBeenCalledWith('ts-jest', 'foo') + expect(consoleSpies.log).toHaveBeenCalledWith('ts-jest:', 'foo') }) it('should NOT log when TS_JEST_DEBUG is falsy', () => { process.env.TS_JEST_DEBUG = '' diff --git a/src/lib/debug.ts b/src/lib/debug.ts index b25800ae07..b163f85319 100644 --- a/src/lib/debug.ts +++ b/src/lib/debug.ts @@ -1,19 +1,18 @@ export let DEBUG_MODE!: boolean export let debug!: typeof console.log +export let warn!: typeof console.warn export let wrapWithDebug!: any>( msg: string, func: T, ) => T -export const warn = (...msg: any[]) => { - console.warn('ts-jest', ...msg) -} - type LogKind = 'log' | 'warn' | 'debug' | 'info' type Logger = (kind: LogKind, ...args: any[]) => void +export let LOG_PREFIX = 'ts-jest:' + export const defaultLogger: Logger = (kind: LogKind, ...args: any[]) => { console[kind](...args) } @@ -21,21 +20,26 @@ export const defaultLogger: Logger = (kind: LogKind, ...args: any[]) => { interface SetupOptions { enabled?: boolean logger?: Logger + prefix?: string } export function __setup({ logger = defaultLogger, enabled = !!process.env.TS_JEST_DEBUG || logger !== defaultLogger, + prefix = 'ts-jest:', }: SetupOptions = {}) { DEBUG_MODE = enabled + LOG_PREFIX = prefix debug = DEBUG_MODE - ? (...args: any[]) => logger('log', 'ts-jest', ...args) + ? (...args: any[]) => logger('log', LOG_PREFIX, ...args) : () => undefined + warn = (...args: any[]) => logger('warn', LOG_PREFIX, ...args) + wrapWithDebug = DEBUG_MODE ? (msg, func) => function wrapper(this: any) { - debug(msg) + logger('log', LOG_PREFIX, msg) return func.apply(this, arguments) } as any : (_, func) => func diff --git a/src/lib/get-package-version.spec.ts b/src/lib/get-package-version.spec.ts new file mode 100644 index 0000000000..3b643f1888 --- /dev/null +++ b/src/lib/get-package-version.spec.ts @@ -0,0 +1,14 @@ +import { getPackageVersion } from './get-package-version' +import { valid } from 'semver' + +it('should get the version of a package', () => { + const version = require('../../node_modules/jest/package.json').version + // ensure the above call doesn't actually fail + expect(valid(version)).not.toBeNull() + // real test + expect(getPackageVersion('jest')).toBe(version) +}) + +it('should not fail when the package is not installeed', () => { + expect(getPackageVersion('__foo-bar__')).toBeUndefined() +}) diff --git a/src/lib/get-package-version.ts b/src/lib/get-package-version.ts new file mode 100644 index 0000000000..4a0fc7d6b9 --- /dev/null +++ b/src/lib/get-package-version.ts @@ -0,0 +1,6 @@ +export function getPackageVersion(moduleName: string): string | undefined { + try { + return require(`${moduleName}/package.json`).version as string + } catch (err) {} + return +} diff --git a/src/lib/hacks.ts b/src/lib/hacks.ts index f11b44b267..252391c1c9 100644 --- a/src/lib/hacks.ts +++ b/src/lib/hacks.ts @@ -1,4 +1,5 @@ import { TBabelCore, ModulePatcher, BabelConfig } from './types' +import semver from 'semver' // tslint:disable-next-line:variable-name export const patchBabelCore_githubIssue6577: ModulePatcher< @@ -10,7 +11,7 @@ export const patchBabelCore_githubIssue6577: ModulePatcher< // source-maps not being inlined if ( typeof babel.version === 'string' && - parseInt(babel.version.split('.')[0], 10) === 6 + semver.satisfies(babel.version, '>=6 <7') ) { try { const File = require('babel-core/lib/transformation/file').File diff --git a/src/lib/importer.ts b/src/lib/importer.ts index 7b4358ae4c..29503be64d 100644 --- a/src/lib/importer.ts +++ b/src/lib/importer.ts @@ -8,9 +8,7 @@ import { } from './types' import * as hacks from './hacks' import { ImportReasons, Errors, interpolate, Helps } from './messages' - -const importDefault = (mod: any) => - mod && mod.__esModule ? mod : { default: mod } +import { VersionCheckers } from './version-checkers' // When ading an optional dependency which has another reason, add the reason in ImportReasons, and // create a new method in Importer. Thus uses the importer.yourMethod(ImportReasons.TheReason) @@ -22,6 +20,11 @@ interface ImportOptions { installTip?: string | Array<{ module: string; label: string }> } +const passThru = (action: () => void) => (input: any) => { + action() + return input +} + export class Importer implements TsJestImporter { @Memoize() static get instance() { @@ -29,7 +32,14 @@ export class Importer implements TsJestImporter { // it could be fixes that are not deployed, or // abstractions so that multiple versions work the same return new Importer({ - 'babel-core': [hacks.patchBabelCore_githubIssue6577], + 'babel-core': [ + passThru(VersionCheckers.babelCoreLegacy.warn), + hacks.patchBabelCore_githubIssue6577, + ], + '@babel/core': [passThru(VersionCheckers.babelCore.warn)], + 'babel-jest': [passThru(VersionCheckers.babelJest.warn)], + typescript: [passThru(VersionCheckers.typescript.warn)], + jest: [passThru(VersionCheckers.jest.warn)], }) } diff --git a/src/lib/messages.ts b/src/lib/messages.ts index 18b59cdbad..7151de4696 100644 --- a/src/lib/messages.ts +++ b/src/lib/messages.ts @@ -1,14 +1,12 @@ // tslint:disable:max-line-length export enum Errors { - InvalidStringifyContentPathRegex = 'Option "stringifyContentPathRegex" should be a valid regex pattern.', - UnableToFindPackageJson = 'Unable to find package.json from "{{fromPath}}".', - InvalidDiagnosticsOption = 'Invalid value for diagnostics: {{value}}.', UnableToLoadOneModule = 'Unable to load the module {{module}}. {{reason}} To fix it:\n{{fix}}', UnableToLoadAnyModule = 'Unable to load any of these modules: {{module}}. {{reason}}. To fix it:\n{{fix}}', - UnableToFindTsConfig = 'Could not find a TS config file (given: "{{given}}", root: "{{root}}").', TypesUnavailableWithoutTypeCheck = 'Type information is unavailable without "typeCheck"', UnableToRequireDefinitionFile = 'Unable to require `.d.ts` file.\nThis is usually the result of a faulty configuration or import. Make sure there is a `.js`, `.json` or another executable extension available alongside `{{file}}`.', FileNotFound = 'File not found: {{inputPath}} (resolved as: {{resolvedPath}})', + UntestedDependencyVersion = "Version {{actualVersion}} of {{module}} installed has not been tested with ts-jest. If you're experiencing issues, consider using a supported version ({{expectedVersion}}). Please do not report issues in ts-jest if you are using unsupported versions.", + MissingDependency = "Module {{module}} is not installed. If you're experiencing issues, consider installing a supported version ({{expectedVersion}}).", } export enum Helps { @@ -22,9 +20,8 @@ export enum Deprecateds { } export enum ImportReasons { - tsJest = 'Using "ts-jest" requires this package to be installed.', - babelJest = 'Using "babel-jest" requires this package to be installed.', - babelConfigLookup = 'Using "babel-jest" with config lookup relies on this package to be installed.', + TsJest = 'Using "ts-jest" requires this package to be installed.', + BabelJest = 'Using "babel-jest" requires this package to be installed.', } export function interpolate( diff --git a/src/lib/types.ts b/src/lib/types.ts index a115632f9b..72247f3e4f 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -96,7 +96,6 @@ type TsJestConfig$babelConfig = type TsJestConfig$stringifyContentPathRegex = string | undefined export interface TsJestConfig { - version: string tsConfig: TsJestConfig$tsConfig typeCheck: boolean compiler: string diff --git a/src/lib/version-checkers.spec.ts b/src/lib/version-checkers.spec.ts new file mode 100644 index 0000000000..7faf017402 --- /dev/null +++ b/src/lib/version-checkers.spec.ts @@ -0,0 +1,105 @@ +// tslint:disable:max-line-length +import { __setup } from './debug' +import * as _pv from './get-package-version' +import { VersionCheckers, VersionChecker } from './version-checkers' +import { mocked } from '../__helpers__/mocks' + +const logger = jest.fn() +__setup({ logger }) + +jest.mock('./get-package-version') + +const pv = mocked(_pv) + +describeChecker( + VersionCheckers.jest, + 'jest', + ['22.1.3', '23.4.5'], + [undefined, '21.0.0', '24.0.0'], +) +describeChecker( + VersionCheckers.babelJest, + 'babel-jest', + ['22.1.3', '23.4.5'], + [undefined, '21.0.0', '24.0.0'], +) +describeChecker( + VersionCheckers.babelCoreLegacy, + 'babel-core', + ['6.1.3', '7.0.0-bridge.0'], + [undefined, '5.0.0', '7.0.0'], +) +describeChecker( + VersionCheckers.babelCore, + '@babel/core', + ['7.1.3', '7.0.0-beta.56'], + [undefined, '6.0.0', '8.0.0'], +) +describeChecker( + VersionCheckers.typescript, + 'typescript', + ['2.7.0', '3.0.1'], + [undefined, '2.6.99', '4.0.1'], +) + +function describeChecker( + checker: VersionChecker, + moduleName: string, + supportedVersions: string[], + unsupportedVersions: any[], +) { + describe(moduleName, () => { + beforeEach(() => { + logger.mockClear() + checker.forget() + }) + + unsupportedVersions.forEach(testVersion => { + describe(`unsupported version (${testVersion})`, () => { + beforeEach(() => { + pv.getPackageVersion.mockImplementation( + name => (name === moduleName ? testVersion : undefined), + ) + }) + + it(`should log with warn()`, () => { + checker.warn() + expect(logger).toHaveBeenCalledTimes(1) + expect(logger.mock.calls[0][0]).toBe('warn') + expect(logger.mock.calls[0][2]).toMatch( + testVersion + ? 'has not been tested with ts-jest' + : 'is not installed', + ) + }) + it(`should log only once with warn()`, () => { + checker.warn() + checker.warn() + expect(logger).toHaveBeenCalledTimes(1) + }) + it(`should throw with raise()`, () => { + expect(checker.raise).toThrow() + // adds another time as it should throw all the time even if already called + expect(checker.raise).toThrow() + }) + }) // describe unsupported version + }) // unsupported versions loop + + supportedVersions.forEach(testVersion => { + describe(`supported version (${testVersion})`, () => { + beforeEach(() => { + pv.getPackageVersion.mockImplementation( + name => (name === moduleName ? testVersion : undefined), + ) + }) + it(`should not log with warn()`, () => { + checker.warn() + expect(logger).not.toHaveBeenCalled() + }) + it(`should not throw with raise()`, () => { + expect(checker.raise).not.toThrow() + }) + }) // describe supported version + }) // supported versions loop + }) // describe module +} diff --git a/src/lib/version-checkers.ts b/src/lib/version-checkers.ts new file mode 100644 index 0000000000..ab77711f82 --- /dev/null +++ b/src/lib/version-checkers.ts @@ -0,0 +1,89 @@ +import { satisfies, Range } from 'semver' +import { warn } from './debug' +import { interpolate, Errors } from './messages' +import { getPackageVersion } from './get-package-version' + +export enum ExpectedVersions { + Jest = '>=22 <24', + TypeScript = '>=2.7 <4', + BabelJest = '>=22 <24', + BabelCoreLegacy = '>=6 <7 || 7.0.0-bridge.0', + BabelCore = '>=7.0.0-beta.0 <8', +} + +export interface VersionChecker { + raise: () => boolean | never + warn: () => boolean + forget: () => void +} + +// tslint:disable-next-line:variable-name +export const VersionCheckers = { + jest: createVersionChecker('jest', ExpectedVersions.Jest), + typescript: createVersionChecker('typescript', ExpectedVersions.TypeScript), + babelJest: createVersionChecker('babel-jest', ExpectedVersions.BabelJest), + babelCoreLegacy: createVersionChecker( + 'babel-core', + ExpectedVersions.BabelCoreLegacy, + ), + babelCore: createVersionChecker('@babel/core', ExpectedVersions.BabelCore), +} + +type CheckVersionAction = 'warn' | 'throw' + +function checkVersion( + name: string, + expectedRange: string, + action?: Exclude, +): boolean +function checkVersion( + name: string, + expectedRange: string, + action: 'throw', +): true | never +function checkVersion( + name: string, + expectedRange: string, + action: CheckVersionAction | undefined = 'warn', +): boolean | never { + const version = getPackageVersion(name) + const success = !!version && satisfies(version, expectedRange) + if (!action || success) return success + const message = interpolate( + version ? Errors.UntestedDependencyVersion : Errors.MissingDependency, + { + module: name, + actualVersion: version || '??', + expectedVersion: rangeToHumanString(expectedRange), + }, + ) + if (action === 'warn') { + warn(message) + } else if (action === 'throw') { + throw new RangeError(message) + } + return success +} + +function rangeToHumanString(versionRange: string): string { + return new Range(versionRange).toString() +} + +function createVersionChecker( + moduleName: string, + expectedVersion: string, +): VersionChecker { + let memo: boolean | undefined + const warn = () => { + if (memo !== undefined) return memo + return (memo = checkVersion(moduleName, expectedVersion, 'warn')) + } + const raise = () => checkVersion(moduleName, expectedVersion, 'throw') + return { + raise, + warn, + forget() { + memo = undefined + }, + } +} diff --git a/tslint.json b/tslint.json index f47909d690..b0bdc0b34a 100644 --- a/tslint.json +++ b/tslint.json @@ -32,7 +32,8 @@ "quotemark": [ true, "single", - "jsx-double" + "jsx-double", + "avoid-escape" ], "semicolon": [ true, @@ -44,6 +45,10 @@ ], "ordered-imports": false, "align": false, + "object-literal-key-quotes": [ + true, + "as-needed" + ], "object-literal-sort-keys": false, "no-console": [ true,