From b30f4db14dcfb98ef03e21cd1f1a691384ae5565 Mon Sep 17 00:00:00 2001 From: David Zilburg Date: Mon, 10 May 2021 13:05:48 -0400 Subject: [PATCH 1/9] Custom haste map module config --- e2e/__tests__/customHaste.test.ts | 30 ++++ .../__tests__/hasteExample.test.js | 18 +++ .../__tests__/hasteExampleHelper.js | 5 + e2e/custom-haste-map/hasteMap.js | 139 ++++++++++++++++++ e2e/custom-haste-map/package.json | 7 + packages/jest-config/src/ValidConfig.ts | 1 + packages/jest-haste-map/src/ModuleMap.ts | 23 ++- packages/jest-haste-map/src/index.ts | 16 +- packages/jest-haste-map/src/types.ts | 9 ++ packages/jest-resolve/src/index.ts | 6 +- packages/jest-runner/src/testWorker.ts | 7 +- packages/jest-runtime/src/index.ts | 11 +- packages/jest-test-sequencer/src/index.ts | 7 +- .../jest-transform/src/ScriptTransformer.ts | 7 +- packages/jest-types/src/Config.ts | 2 + 15 files changed, 274 insertions(+), 14 deletions(-) create mode 100644 e2e/__tests__/customHaste.test.ts create mode 100644 e2e/custom-haste-map/__tests__/hasteExample.test.js create mode 100644 e2e/custom-haste-map/__tests__/hasteExampleHelper.js create mode 100644 e2e/custom-haste-map/hasteMap.js create mode 100644 e2e/custom-haste-map/package.json diff --git a/e2e/__tests__/customHaste.test.ts b/e2e/__tests__/customHaste.test.ts new file mode 100644 index 000000000000..4085381b79c4 --- /dev/null +++ b/e2e/__tests__/customHaste.test.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as path from 'path'; +import runJest from '../runJest'; + +describe('Custom Haste Integration', () => { + test('valid test with fake module resolutions', () => { + const config = { + haste: { + hasteMapModulePath: path.resolve( + __dirname, + '..', + 'custom-haste-map/hasteMap.js', + ), + }, + }; + + const {exitCode} = runJest('custom-haste-map', [ + '--config', + JSON.stringify(config), + 'hasteExample.test.js', + ]); + expect(exitCode).toBe(0); + }); +}); diff --git a/e2e/custom-haste-map/__tests__/hasteExample.test.js b/e2e/custom-haste-map/__tests__/hasteExample.test.js new file mode 100644 index 000000000000..03942bad0799 --- /dev/null +++ b/e2e/custom-haste-map/__tests__/hasteExample.test.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +'use strict'; + +const add = require('fakeModuleName'); + +describe('Custom Haste', () => { + test('adds ok', () => { + expect(true).toBe(true); + expect(add(1, 2)).toBe(3); + }); +}); diff --git a/e2e/custom-haste-map/__tests__/hasteExampleHelper.js b/e2e/custom-haste-map/__tests__/hasteExampleHelper.js new file mode 100644 index 000000000000..aa666c97a732 --- /dev/null +++ b/e2e/custom-haste-map/__tests__/hasteExampleHelper.js @@ -0,0 +1,5 @@ +function add(a, b) { + return a + b; +} + +module.exports = add; diff --git a/e2e/custom-haste-map/hasteMap.js b/e2e/custom-haste-map/hasteMap.js new file mode 100644 index 000000000000..a7a5bcfb99e4 --- /dev/null +++ b/e2e/custom-haste-map/hasteMap.js @@ -0,0 +1,139 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const path = require('path'); +const fakeFile = { + file: path.resolve(__dirname, '__tests__/hasteExampleHelper.js'), + moduleName: 'fakeModuleName', + sha1: 'fakeSha1', +}; + +const fakeJSON = 'fakeJSON'; + +const testPath = path.resolve(__dirname, '__tests__/hasteExample.test.js'); + +const allFiles = [fakeFile.file, testPath]; + +class HasteFS { + getModuleName(file) { + if (file === fakeFile.file) { + return fakeFile.moduleName; + } + return null; + } + + getSize(file) { + return null; + } + + getDependencies(file) { + if (file === testPath) { + return fakeFile.file; + } + return []; + } + + getSha1(file) { + if (file === fakeFile.file) { + return fakeFile.sha1; + } + return null; + } + + exists(file) { + return allFiles.includes(file); + } + + getAllFiles() { + return allFiles; + } + + getFileIterator() { + return allFiles; + } + + getAbsoluteFileIterator() { + return allFiles; + } + + matchFiles(pattern) { + if (!(pattern instanceof RegExp)) { + pattern = new RegExp(pattern); + } + const files = []; + for (const file of this.getAbsoluteFileIterator()) { + if (pattern.test(file)) { + files.push(file); + } + } + return files; + } + + matchFilesWithGlob(globs, root) { + return []; + } +} + +class ModuleMap { + getModule(name, platform, supportsNativePlatform, type) { + if (name === fakeFile.moduleName) { + return fakeFile.file; + } + return null; + } + + getPackage() { + return null; + } + + getMockModule() { + return undefined; + } + + getRawModuleMap() { + return {}; + } + + toJSON() { + return fakeJSON; + } +} + +class HasteMap { + constructor(options) { + this._cachePath = HasteMap.getCacheFilePath( + options.cacheDirectory, + options.name, + ); + } + + async build() { + return { + hasteFS: new HasteFS(), + moduleMap: new ModuleMap(), + }; + } + + static getCacheFilePath(tmpdir, name) { + return path.join(tmpdir, name); + } + + getCacheFilePath() { + return this._cachePath; + } + + static getModuleMapFromJSON(json) { + if (json === fakeJSON) { + return new ModuleMap(); + } + throw new Error('Failed to parse serialized module map'); + } +} + +module.exports = HasteMap; diff --git a/e2e/custom-haste-map/package.json b/e2e/custom-haste-map/package.json new file mode 100644 index 000000000000..9adbf08c90b5 --- /dev/null +++ b/e2e/custom-haste-map/package.json @@ -0,0 +1,7 @@ +{ + "jest": { + "haste": { + "hasteMapModulePath": "/hasteMap.js" + } + } +} diff --git a/packages/jest-config/src/ValidConfig.ts b/packages/jest-config/src/ValidConfig.ts index d655914a100e..a0789cb3aeaa 100644 --- a/packages/jest-config/src/ValidConfig.ts +++ b/packages/jest-config/src/ValidConfig.ts @@ -58,6 +58,7 @@ const initialOptions: Config.InitialOptions = { enableSymlinks: false, forceNodeFilesystemAPI: false, hasteImplModulePath: '/haste_impl.js', + hasteMapModulePath: '', platforms: ['ios', 'android'], throwOnModuleCollision: false, }, diff --git a/packages/jest-haste-map/src/ModuleMap.ts b/packages/jest-haste-map/src/ModuleMap.ts index 1501e4df5df4..7a53cf8e8f73 100644 --- a/packages/jest-haste-map/src/ModuleMap.ts +++ b/packages/jest-haste-map/src/ModuleMap.ts @@ -29,7 +29,28 @@ export type SerializableModuleMap = { rootDir: Config.Path; }; -export default class ModuleMap { +export interface IModuleMap { + getModule( + name: string, + platform?: string | null, + supportsNativePlatform?: boolean | null, + type?: HTypeValue | null, + ): Config.Path | null; + + getPackage( + name: string, + platform: string | null | undefined, + _supportsNativePlatform: boolean | null, + ): Config.Path | null; + + getMockModule(name: string): Config.Path | undefined; + + getRawModuleMap(): RawModuleMap; + + toJSON(): S; +} + +export default class ModuleMap implements IModuleMap { static DuplicateHasteCandidatesError: typeof DuplicateHasteCandidatesError; private readonly _raw: RawModuleMap; private json: SerializableModuleMap | undefined; diff --git a/packages/jest-haste-map/src/index.ts b/packages/jest-haste-map/src/index.ts index 5969c12df484..0968773dfbf9 100644 --- a/packages/jest-haste-map/src/index.ts +++ b/packages/jest-haste-map/src/index.ts @@ -18,7 +18,7 @@ import {escapePathForRegex} from 'jest-regex-util'; import serializer from 'jest-serializer'; import {Worker} from 'jest-worker'; import HasteFS from './HasteFS'; -import HasteModuleMap from './ModuleMap'; +import HasteModuleMap, {IModuleMap, SerializableModuleMap} from './ModuleMap'; import H from './constants'; import nodeCrawl = require('./crawlers/node'); import watchmanCrawl = require('./crawlers/watchman'); @@ -132,6 +132,16 @@ function invariant(condition: unknown, message?: string): asserts condition { } } +export type HasteMapStatic = { + new (options: Options): HasteMap; + getCacheFilePath( + tmpdir: Config.Path, + name: string, + ...extra: Array + ): string; + getModuleMapFromJSON(json: S): IModuleMap; +}; + /** * HasteMap is a JavaScript implementation of Facebook's haste module system. * @@ -324,6 +334,10 @@ export default class HasteMap extends EventEmitter { ); } + static getModuleMapFromJSON(json: SerializableModuleMap): HasteModuleMap { + return HasteModuleMap.fromJSON(json); + } + getCacheFilePath(): string { return this._cachePath; } diff --git a/packages/jest-haste-map/src/types.ts b/packages/jest-haste-map/src/types.ts index 106f209d9c68..d239d6f3293e 100644 --- a/packages/jest-haste-map/src/types.ts +++ b/packages/jest-haste-map/src/types.ts @@ -8,7 +8,10 @@ import type {Stats} from 'graceful-fs'; import type {Config} from '@jest/types'; import type HasteFS from './HasteFS'; +// eslint-disable-next-line import/no-duplicates import type ModuleMap from './ModuleMap'; +// eslint-disable-next-line import/no-duplicates +import type {IModuleMap} from './ModuleMap'; export type IgnoreMatcher = (item: string) => boolean; @@ -71,6 +74,12 @@ export type InternalHasteMap = { mocks: MockData; }; +export type IHasteMap = { + hasteFS: HasteFS; + moduleMap: IModuleMap; + __hasteMapForTest?: InternalHasteMap | null; +}; + export type HasteMap = { hasteFS: HasteFS; moduleMap: ModuleMap; diff --git a/packages/jest-resolve/src/index.ts b/packages/jest-resolve/src/index.ts index 2a76b92238b7..5f8ec04803f7 100644 --- a/packages/jest-resolve/src/index.ts +++ b/packages/jest-resolve/src/index.ts @@ -9,9 +9,9 @@ import * as path from 'path'; import chalk = require('chalk'); +import type {IModuleMap} from 'jest-haste-map/src/ModuleMap'; import slash = require('slash'); import type {Config} from '@jest/types'; -import type {ModuleMap} from 'jest-haste-map'; import {tryRealpath} from 'jest-util'; import ModuleNotFoundError from './ModuleNotFoundError'; import defaultResolver, {clearDefaultResolverCache} from './defaultResolver'; @@ -50,13 +50,13 @@ const nodePaths = NODE_PATH export default class Resolver { private readonly _options: ResolverConfig; - private readonly _moduleMap: ModuleMap; + private readonly _moduleMap: IModuleMap; private readonly _moduleIDCache: Map; private readonly _moduleNameCache: Map; private readonly _modulePathCache: Map>; private readonly _supportsNativePlatform: boolean; - constructor(moduleMap: ModuleMap, options: ResolverConfig) { + constructor(moduleMap: IModuleMap, options: ResolverConfig) { this._options = { defaultPlatform: options.defaultPlatform, extensions: options.extensions, diff --git a/packages/jest-runner/src/testWorker.ts b/packages/jest-runner/src/testWorker.ts index 919eeccd9c8f..6c4d9c60f190 100644 --- a/packages/jest-runner/src/testWorker.ts +++ b/packages/jest-runner/src/testWorker.ts @@ -9,7 +9,7 @@ import exit = require('exit'); import type {SerializableError, TestResult} from '@jest/test-result'; import type {Config} from '@jest/types'; -import {ModuleMap, SerializableModuleMap} from 'jest-haste-map'; +import HasteMap, {HasteMapStatic, SerializableModuleMap} from 'jest-haste-map'; import {separateMessageFromStack} from 'jest-message-util'; import type Resolver from 'jest-resolve'; import Runtime from 'jest-runtime'; @@ -74,7 +74,10 @@ export function setup(setupData: { config, serializableModuleMap, } of setupData.serializableResolvers) { - const moduleMap = ModuleMap.fromJSON(serializableModuleMap); + const HasteMapClass = config.haste.hasteMapModulePath + ? (require(config.haste.hasteMapModulePath) as HasteMapStatic) + : HasteMap; + const moduleMap = HasteMapClass.getModuleMapFromJSON(serializableModuleMap); resolvers.set(config.name, Runtime.createResolver(config, moduleMap)); } } diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 0838e25d5b5c..be3228dc3f03 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -21,6 +21,7 @@ import { import {parse as parseCjs} from 'cjs-module-lexer'; import {CoverageInstrumenter, V8Coverage} from 'collect-v8-coverage'; import * as fs from 'graceful-fs'; +import type {IModuleMap} from 'jest-haste-map/src/ModuleMap'; import stripBOM = require('strip-bom'); import type { Jest, @@ -42,7 +43,7 @@ import { shouldInstrument, } from '@jest/transform'; import type {Config, Global} from '@jest/types'; -import HasteMap, {ModuleMap} from 'jest-haste-map'; +import HasteMap, {HasteMapStatic} from 'jest-haste-map'; import {formatStackTrace, separateMessageFromStack} from 'jest-message-util'; import type {MockFunctionMetadata, ModuleMocker} from 'jest-mock'; import {escapePathForRegex} from 'jest-regex-util'; @@ -314,7 +315,11 @@ export default class Runtime { ? new RegExp(ignorePatternParts.join('|')) : undefined; - return new HasteMap({ + const HasteMapClass = config.haste.hasteMapModulePath + ? (require(config.haste.hasteMapModulePath) as HasteMapStatic) + : HasteMap; + + return new HasteMapClass({ cacheDirectory: config.cacheDirectory, computeSha1: config.haste.computeSha1, console: options?.console, @@ -340,7 +345,7 @@ export default class Runtime { static createResolver( config: Config.ProjectConfig, - moduleMap: ModuleMap, + moduleMap: IModuleMap, ): Resolver { return new Resolver(moduleMap, { defaultPlatform: config.haste.defaultPlatform, diff --git a/packages/jest-test-sequencer/src/index.ts b/packages/jest-test-sequencer/src/index.ts index 8904c8428399..3e32e88f70e6 100644 --- a/packages/jest-test-sequencer/src/index.ts +++ b/packages/jest-test-sequencer/src/index.ts @@ -7,7 +7,7 @@ import * as fs from 'graceful-fs'; import type {AggregatedResult} from '@jest/test-result'; -import HasteMap from 'jest-haste-map'; +import HasteMap, {HasteMapStatic} from 'jest-haste-map'; import type {Test} from 'jest-runner'; import type {Context} from 'jest-runtime'; @@ -36,7 +36,10 @@ export default class TestSequencer { _getCachePath(context: Context): string { const {config} = context; - return HasteMap.getCacheFilePath( + const HasteMapClass = config.haste.hasteMapModulePath + ? (require(config.haste.hasteMapModulePath) as HasteMapStatic) + : HasteMap; + return HasteMapClass.getCacheFilePath( config.cacheDirectory, 'perf-cache-' + config.name, ); diff --git a/packages/jest-transform/src/ScriptTransformer.ts b/packages/jest-transform/src/ScriptTransformer.ts index 0cf2de288a88..868a9999162a 100644 --- a/packages/jest-transform/src/ScriptTransformer.ts +++ b/packages/jest-transform/src/ScriptTransformer.ts @@ -17,7 +17,7 @@ import {addHook} from 'pirates'; import slash = require('slash'); import {sync as writeFileAtomic} from 'write-file-atomic'; import type {Config} from '@jest/types'; -import HasteMap from 'jest-haste-map'; +import HasteMap, {HasteMapStatic} from 'jest-haste-map'; import { createDirectory, isPromise, @@ -194,7 +194,10 @@ class ScriptTransformer { filename: Config.Path, cacheKey: string, ): Config.Path { - const baseCacheDir = HasteMap.getCacheFilePath( + const HasteMapClass = this._config.haste.hasteMapModulePath + ? (require(this._config.haste.hasteMapModulePath) as HasteMapStatic) + : HasteMap; + const baseCacheDir = HasteMapClass.getCacheFilePath( this._config.cacheDirectory, 'jest-transform-cache-' + this._config.name, VERSION, diff --git a/packages/jest-types/src/Config.ts b/packages/jest-types/src/Config.ts index 49113fa9ce34..766b65ae4349 100644 --- a/packages/jest-types/src/Config.ts +++ b/packages/jest-types/src/Config.ts @@ -36,6 +36,8 @@ export type HasteConfig = { platforms?: Array; /** Whether to throw on error on module collision. */ throwOnModuleCollision?: boolean; + /** Custom HasteMap */ + hasteMapModulePath?: string; }; export type CoverageReporterName = keyof ReportOptions; From ca656d22fd96f987572c4106511da4e9dd4c53e8 Mon Sep 17 00:00:00 2001 From: David Zilburg Date: Mon, 10 May 2021 14:03:54 -0400 Subject: [PATCH 2/9] Fix tests --- .../jest-test-sequencer/src/__tests__/test_sequencer.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/jest-test-sequencer/src/__tests__/test_sequencer.test.js b/packages/jest-test-sequencer/src/__tests__/test_sequencer.test.js index 65653fd6ee54..7950bc02a324 100644 --- a/packages/jest-test-sequencer/src/__tests__/test_sequencer.test.js +++ b/packages/jest-test-sequencer/src/__tests__/test_sequencer.test.js @@ -23,6 +23,7 @@ const context = { config: { cache: true, cacheDirectory: '/cache', + haste: {}, name: 'test', }, hasteFS: { @@ -34,6 +35,7 @@ const secondContext = { config: { cache: true, cacheDirectory: '/cache2', + haste: {}, name: 'test2', }, hasteFS: { From c0ce5fd8d531ad6be16d5fc03ba77f17d228240e Mon Sep 17 00:00:00 2001 From: David Zilburg Date: Mon, 10 May 2021 15:26:17 -0400 Subject: [PATCH 3/9] Address comments --- docs/Configuration.md | 2 ++ packages/jest-haste-map/src/index.ts | 18 ++++++++++++++++-- packages/jest-runner/src/testWorker.ts | 6 ++---- .../src/__tests__/Runtime-statics.test.js | 4 ++-- packages/jest-runtime/src/index.ts | 9 +++------ packages/jest-test-sequencer/src/index.ts | 6 ++---- .../jest-transform/src/ScriptTransformer.ts | 6 ++---- .../src/__tests__/ScriptTransformer.test.ts | 7 ++++++- packages/jest-types/src/Config.ts | 2 +- 9 files changed, 36 insertions(+), 24 deletions(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index b2ba31e0d322..5d4c0eee8ca2 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -514,6 +514,8 @@ type HasteConfig = { platforms?: Array; /** Whether to throw on error on module collision. */ throwOnModuleCollision?: boolean; + /** Custom HasteMap module */ + hasteMapModulePath?: string; }; ``` diff --git a/packages/jest-haste-map/src/index.ts b/packages/jest-haste-map/src/index.ts index 0968773dfbf9..5f2b733dc778 100644 --- a/packages/jest-haste-map/src/index.ts +++ b/packages/jest-haste-map/src/index.ts @@ -60,6 +60,7 @@ type Options = { extensions: Array; forceNodeFilesystemAPI?: boolean; hasteImplModulePath?: string; + hasteMapModulePath?: string; ignorePattern?: HasteRegExp; maxWorkers: number; mocksPattern?: string; @@ -133,7 +134,6 @@ function invariant(condition: unknown, message?: string): asserts condition { } export type HasteMapStatic = { - new (options: Options): HasteMap; getCacheFilePath( tmpdir: Config.Path, name: string, @@ -229,7 +229,21 @@ export default class HasteMap extends EventEmitter { private _watchers: Array; private _worker: WorkerInterface | null; - constructor(options: Options) { + static getStatic(config: Config.ProjectConfig): HasteMapStatic { + if (config.haste.hasteMapModulePath) { + return require(config.haste.hasteMapModulePath); + } + return this; + } + + static create(options: Options): HasteMap { + if (options.hasteMapModulePath) { + return require(options.hasteMapModulePath); + } + return new HasteMap(options); + } + + private constructor(options: Options) { super(); this._options = { cacheDirectory: options.cacheDirectory || tmpdir(), diff --git a/packages/jest-runner/src/testWorker.ts b/packages/jest-runner/src/testWorker.ts index 6c4d9c60f190..9d594458ae39 100644 --- a/packages/jest-runner/src/testWorker.ts +++ b/packages/jest-runner/src/testWorker.ts @@ -9,7 +9,7 @@ import exit = require('exit'); import type {SerializableError, TestResult} from '@jest/test-result'; import type {Config} from '@jest/types'; -import HasteMap, {HasteMapStatic, SerializableModuleMap} from 'jest-haste-map'; +import {SerializableModuleMap, getHasteMapStatic} from 'jest-haste-map'; import {separateMessageFromStack} from 'jest-message-util'; import type Resolver from 'jest-resolve'; import Runtime from 'jest-runtime'; @@ -74,9 +74,7 @@ export function setup(setupData: { config, serializableModuleMap, } of setupData.serializableResolvers) { - const HasteMapClass = config.haste.hasteMapModulePath - ? (require(config.haste.hasteMapModulePath) as HasteMapStatic) - : HasteMap; + const HasteMapClass = getHasteMapStatic(config); const moduleMap = HasteMapClass.getModuleMapFromJSON(serializableModuleMap); resolvers.set(config.name, Runtime.createResolver(config, moduleMap)); } diff --git a/packages/jest-runtime/src/__tests__/Runtime-statics.test.js b/packages/jest-runtime/src/__tests__/Runtime-statics.test.js index a198c99051e9..d30e6eb335f2 100644 --- a/packages/jest-runtime/src/__tests__/Runtime-statics.test.js +++ b/packages/jest-runtime/src/__tests__/Runtime-statics.test.js @@ -26,7 +26,7 @@ describe('Runtime statics', () => { test('Runtime.createHasteMap passes correct ignore files to HasteMap', () => { Runtime.createHasteMap(projectConfig, options); - expect(HasteMap).toBeCalledWith( + expect(HasteMap.create).toBeCalledWith( expect.objectContaining({ ignorePattern: /\/root\/ignore-1|\/root\/ignore-2/, }), @@ -35,7 +35,7 @@ describe('Runtime statics', () => { test('Runtime.createHasteMap passes correct ignore files to HasteMap in watch mode', () => { Runtime.createHasteMap(projectConfig, {...options, watch: true}); - expect(HasteMap).toBeCalledWith( + expect(HasteMap.create).toBeCalledWith( expect.objectContaining({ ignorePattern: /\/root\/ignore-1|\/root\/ignore-2|\/watch-root\/ignore-1/, watch: true, diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index be3228dc3f03..ba8f3887e333 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -43,7 +43,7 @@ import { shouldInstrument, } from '@jest/transform'; import type {Config, Global} from '@jest/types'; -import HasteMap, {HasteMapStatic} from 'jest-haste-map'; +import HasteMap from 'jest-haste-map'; import {formatStackTrace, separateMessageFromStack} from 'jest-message-util'; import type {MockFunctionMetadata, ModuleMocker} from 'jest-mock'; import {escapePathForRegex} from 'jest-regex-util'; @@ -315,11 +315,7 @@ export default class Runtime { ? new RegExp(ignorePatternParts.join('|')) : undefined; - const HasteMapClass = config.haste.hasteMapModulePath - ? (require(config.haste.hasteMapModulePath) as HasteMapStatic) - : HasteMap; - - return new HasteMapClass({ + return HasteMap.create({ cacheDirectory: config.cacheDirectory, computeSha1: config.haste.computeSha1, console: options?.console, @@ -328,6 +324,7 @@ export default class Runtime { extensions: [Snapshot.EXTENSION].concat(config.moduleFileExtensions), forceNodeFilesystemAPI: config.haste.forceNodeFilesystemAPI, hasteImplModulePath: config.haste.hasteImplModulePath, + hasteMapModulePath: config.haste.hasteMapModulePath, ignorePattern, maxWorkers: options?.maxWorkers || 1, mocksPattern: escapePathForRegex(path.sep + '__mocks__' + path.sep), diff --git a/packages/jest-test-sequencer/src/index.ts b/packages/jest-test-sequencer/src/index.ts index 3e32e88f70e6..8038771a3047 100644 --- a/packages/jest-test-sequencer/src/index.ts +++ b/packages/jest-test-sequencer/src/index.ts @@ -7,7 +7,7 @@ import * as fs from 'graceful-fs'; import type {AggregatedResult} from '@jest/test-result'; -import HasteMap, {HasteMapStatic} from 'jest-haste-map'; +import HasteMap from 'jest-haste-map'; import type {Test} from 'jest-runner'; import type {Context} from 'jest-runtime'; @@ -36,9 +36,7 @@ export default class TestSequencer { _getCachePath(context: Context): string { const {config} = context; - const HasteMapClass = config.haste.hasteMapModulePath - ? (require(config.haste.hasteMapModulePath) as HasteMapStatic) - : HasteMap; + const HasteMapClass = HasteMap.getStatic(config); return HasteMapClass.getCacheFilePath( config.cacheDirectory, 'perf-cache-' + config.name, diff --git a/packages/jest-transform/src/ScriptTransformer.ts b/packages/jest-transform/src/ScriptTransformer.ts index 868a9999162a..4a0e632a79b5 100644 --- a/packages/jest-transform/src/ScriptTransformer.ts +++ b/packages/jest-transform/src/ScriptTransformer.ts @@ -17,7 +17,7 @@ import {addHook} from 'pirates'; import slash = require('slash'); import {sync as writeFileAtomic} from 'write-file-atomic'; import type {Config} from '@jest/types'; -import HasteMap, {HasteMapStatic} from 'jest-haste-map'; +import HasteMap from 'jest-haste-map'; import { createDirectory, isPromise, @@ -194,9 +194,7 @@ class ScriptTransformer { filename: Config.Path, cacheKey: string, ): Config.Path { - const HasteMapClass = this._config.haste.hasteMapModulePath - ? (require(this._config.haste.hasteMapModulePath) as HasteMapStatic) - : HasteMap; + const HasteMapClass = HasteMap.getStatic(this._config); const baseCacheDir = HasteMapClass.getCacheFilePath( this._config.cacheDirectory, 'jest-transform-cache-' + this._config.name, diff --git a/packages/jest-transform/src/__tests__/ScriptTransformer.test.ts b/packages/jest-transform/src/__tests__/ScriptTransformer.test.ts index 0b3dee805b39..35f962296509 100644 --- a/packages/jest-transform/src/__tests__/ScriptTransformer.test.ts +++ b/packages/jest-transform/src/__tests__/ScriptTransformer.test.ts @@ -38,7 +38,12 @@ jest }, })) .mock('jest-haste-map', () => ({ - getCacheFilePath: (cacheDir: string, baseDir: string) => cacheDir + baseDir, + getStatic() { + return { + getCacheFilePath: (cacheDir: string, baseDir: string) => + cacheDir + baseDir, + }; + }, })) .mock('jest-util', () => ({ ...jest.requireActual('jest-util'), diff --git a/packages/jest-types/src/Config.ts b/packages/jest-types/src/Config.ts index 766b65ae4349..7815929e5bd5 100644 --- a/packages/jest-types/src/Config.ts +++ b/packages/jest-types/src/Config.ts @@ -36,7 +36,7 @@ export type HasteConfig = { platforms?: Array; /** Whether to throw on error on module collision. */ throwOnModuleCollision?: boolean; - /** Custom HasteMap */ + /** Custom HasteMap module */ hasteMapModulePath?: string; }; From d169986af5dee5981431d897805eca9ddf04ff8d Mon Sep 17 00:00:00 2001 From: David Zilburg Date: Mon, 10 May 2021 15:37:59 -0400 Subject: [PATCH 4/9] Fix bug in refactor --- packages/jest-haste-map/src/index.ts | 3 ++- packages/jest-runner/src/testWorker.ts | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/jest-haste-map/src/index.ts b/packages/jest-haste-map/src/index.ts index 5f2b733dc778..3b3fb61af9c3 100644 --- a/packages/jest-haste-map/src/index.ts +++ b/packages/jest-haste-map/src/index.ts @@ -238,7 +238,8 @@ export default class HasteMap extends EventEmitter { static create(options: Options): HasteMap { if (options.hasteMapModulePath) { - return require(options.hasteMapModulePath); + const CustomHasteMap = require(options.hasteMapModulePath); + return new CustomHasteMap(options); } return new HasteMap(options); } diff --git a/packages/jest-runner/src/testWorker.ts b/packages/jest-runner/src/testWorker.ts index 9d594458ae39..37a0a6881644 100644 --- a/packages/jest-runner/src/testWorker.ts +++ b/packages/jest-runner/src/testWorker.ts @@ -9,7 +9,7 @@ import exit = require('exit'); import type {SerializableError, TestResult} from '@jest/test-result'; import type {Config} from '@jest/types'; -import {SerializableModuleMap, getHasteMapStatic} from 'jest-haste-map'; +import HasteMap, {SerializableModuleMap} from 'jest-haste-map'; import {separateMessageFromStack} from 'jest-message-util'; import type Resolver from 'jest-resolve'; import Runtime from 'jest-runtime'; @@ -74,8 +74,9 @@ export function setup(setupData: { config, serializableModuleMap, } of setupData.serializableResolvers) { - const HasteMapClass = getHasteMapStatic(config); - const moduleMap = HasteMapClass.getModuleMapFromJSON(serializableModuleMap); + const moduleMap = HasteMap.getStatic(config).getModuleMapFromJSON( + serializableModuleMap, + ); resolvers.set(config.name, Runtime.createResolver(config, moduleMap)); } } From 67555534318c7971b6ed6605a0c45b0ebb1893d7 Mon Sep 17 00:00:00 2001 From: David Zilburg Date: Mon, 10 May 2021 16:38:59 -0400 Subject: [PATCH 5/9] Lints --- packages/jest-haste-map/src/ModuleMap.ts | 22 +-------------------- packages/jest-haste-map/src/index.ts | 25 +++++++++++++++++++++++- packages/jest-haste-map/src/types.ts | 4 +--- packages/jest-resolve/src/index.ts | 2 +- 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/packages/jest-haste-map/src/ModuleMap.ts b/packages/jest-haste-map/src/ModuleMap.ts index 7a53cf8e8f73..069d53395b25 100644 --- a/packages/jest-haste-map/src/ModuleMap.ts +++ b/packages/jest-haste-map/src/ModuleMap.ts @@ -16,6 +16,7 @@ import type { ModuleMetaData, RawModuleMap, } from './types'; +import type {IModuleMap} from '.'; const EMPTY_OBJ: Record = {}; const EMPTY_MAP = new Map(); @@ -29,27 +30,6 @@ export type SerializableModuleMap = { rootDir: Config.Path; }; -export interface IModuleMap { - getModule( - name: string, - platform?: string | null, - supportsNativePlatform?: boolean | null, - type?: HTypeValue | null, - ): Config.Path | null; - - getPackage( - name: string, - platform: string | null | undefined, - _supportsNativePlatform: boolean | null, - ): Config.Path | null; - - getMockModule(name: string): Config.Path | undefined; - - getRawModuleMap(): RawModuleMap; - - toJSON(): S; -} - export default class ModuleMap implements IModuleMap { static DuplicateHasteCandidatesError: typeof DuplicateHasteCandidatesError; private readonly _raw: RawModuleMap; diff --git a/packages/jest-haste-map/src/index.ts b/packages/jest-haste-map/src/index.ts index 3b3fb61af9c3..041b5b0428e7 100644 --- a/packages/jest-haste-map/src/index.ts +++ b/packages/jest-haste-map/src/index.ts @@ -18,7 +18,7 @@ import {escapePathForRegex} from 'jest-regex-util'; import serializer from 'jest-serializer'; import {Worker} from 'jest-worker'; import HasteFS from './HasteFS'; -import HasteModuleMap, {IModuleMap, SerializableModuleMap} from './ModuleMap'; +import HasteModuleMap, {SerializableModuleMap} from './ModuleMap'; import H from './constants'; import nodeCrawl = require('./crawlers/node'); import watchmanCrawl = require('./crawlers/watchman'); @@ -32,12 +32,14 @@ import type { EventsQueue, FileData, FileMetaData, + HTypeValue, HasteRegExp, InternalHasteMap, HasteMap as InternalHasteMapObject, MockData, ModuleMapData, ModuleMetaData, + RawModuleMap, WorkerMetadata, } from './types'; import FSEventsWatcher = require('./watchers/FSEventsWatcher'); @@ -133,6 +135,27 @@ function invariant(condition: unknown, message?: string): asserts condition { } } +export interface IModuleMap { + getModule( + name: string, + platform?: string | null, + supportsNativePlatform?: boolean | null, + type?: HTypeValue | null, + ): Config.Path | null; + + getPackage( + name: string, + platform: string | null | undefined, + _supportsNativePlatform: boolean | null, + ): Config.Path | null; + + getMockModule(name: string): Config.Path | undefined; + + getRawModuleMap(): RawModuleMap; + + toJSON(): S; +} + export type HasteMapStatic = { getCacheFilePath( tmpdir: Config.Path, diff --git a/packages/jest-haste-map/src/types.ts b/packages/jest-haste-map/src/types.ts index d239d6f3293e..8e1ce10f78aa 100644 --- a/packages/jest-haste-map/src/types.ts +++ b/packages/jest-haste-map/src/types.ts @@ -8,10 +8,8 @@ import type {Stats} from 'graceful-fs'; import type {Config} from '@jest/types'; import type HasteFS from './HasteFS'; -// eslint-disable-next-line import/no-duplicates import type ModuleMap from './ModuleMap'; -// eslint-disable-next-line import/no-duplicates -import type {IModuleMap} from './ModuleMap'; +import type {IModuleMap} from '.'; export type IgnoreMatcher = (item: string) => boolean; diff --git a/packages/jest-resolve/src/index.ts b/packages/jest-resolve/src/index.ts index 5f8ec04803f7..a492cf198978 100644 --- a/packages/jest-resolve/src/index.ts +++ b/packages/jest-resolve/src/index.ts @@ -9,9 +9,9 @@ import * as path from 'path'; import chalk = require('chalk'); -import type {IModuleMap} from 'jest-haste-map/src/ModuleMap'; import slash = require('slash'); import type {Config} from '@jest/types'; +import type {IModuleMap} from 'jest-haste-map'; import {tryRealpath} from 'jest-util'; import ModuleNotFoundError from './ModuleNotFoundError'; import defaultResolver, {clearDefaultResolverCache} from './defaultResolver'; From 4555c9f1c99a4ca8786e94fa512dd53c91a340a1 Mon Sep 17 00:00:00 2001 From: David Zilburg Date: Tue, 11 May 2021 11:41:24 -0400 Subject: [PATCH 6/9] Fix import --- packages/jest-runtime/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index ba8f3887e333..6f46746d5c02 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -21,7 +21,6 @@ import { import {parse as parseCjs} from 'cjs-module-lexer'; import {CoverageInstrumenter, V8Coverage} from 'collect-v8-coverage'; import * as fs from 'graceful-fs'; -import type {IModuleMap} from 'jest-haste-map/src/ModuleMap'; import stripBOM = require('strip-bom'); import type { Jest, @@ -43,6 +42,7 @@ import { shouldInstrument, } from '@jest/transform'; import type {Config, Global} from '@jest/types'; +import type {IModuleMap} from 'jest-haste-map'; import HasteMap from 'jest-haste-map'; import {formatStackTrace, separateMessageFromStack} from 'jest-message-util'; import type {MockFunctionMetadata, ModuleMocker} from 'jest-mock'; From c7a7fd8e5d180cfb25a0b2eba2f34210e52a597a Mon Sep 17 00:00:00 2001 From: David Zilburg Date: Tue, 11 May 2021 12:59:41 -0400 Subject: [PATCH 7/9] Fix copyright header --- e2e/custom-haste-map/__tests__/hasteExampleHelper.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/e2e/custom-haste-map/__tests__/hasteExampleHelper.js b/e2e/custom-haste-map/__tests__/hasteExampleHelper.js index aa666c97a732..6a583e26591b 100644 --- a/e2e/custom-haste-map/__tests__/hasteExampleHelper.js +++ b/e2e/custom-haste-map/__tests__/hasteExampleHelper.js @@ -1,3 +1,13 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +'use strict'; + function add(a, b) { return a + b; } From 41287a38a70883c4b48d07efa79e9a3ec8b8a188 Mon Sep 17 00:00:00 2001 From: David Zilburg Date: Tue, 18 May 2021 17:02:59 -0400 Subject: [PATCH 8/9] Address comments --- packages/jest-haste-map/src/ModuleMap.ts | 14 ++------ packages/jest-haste-map/src/index.ts | 41 ++++-------------------- packages/jest-haste-map/src/types.ts | 40 ++++++++++++++++++++++- 3 files changed, 47 insertions(+), 48 deletions(-) diff --git a/packages/jest-haste-map/src/ModuleMap.ts b/packages/jest-haste-map/src/ModuleMap.ts index 069d53395b25..81ca36bc9709 100644 --- a/packages/jest-haste-map/src/ModuleMap.ts +++ b/packages/jest-haste-map/src/ModuleMap.ts @@ -11,25 +11,15 @@ import * as fastPath from './lib/fast_path'; import type { DuplicatesSet, HTypeValue, - MockData, - ModuleMapData, + IModuleMap, ModuleMetaData, RawModuleMap, + SerializableModuleMap, } from './types'; -import type {IModuleMap} from '.'; const EMPTY_OBJ: Record = {}; const EMPTY_MAP = new Map(); -type ValueType = T extends Map ? V : never; - -export type SerializableModuleMap = { - duplicates: ReadonlyArray<[string, [string, [string, [string, number]]]]>; - map: ReadonlyArray<[string, ValueType]>; - mocks: ReadonlyArray<[string, ValueType]>; - rootDir: Config.Path; -}; - export default class ModuleMap implements IModuleMap { static DuplicateHasteCandidatesError: typeof DuplicateHasteCandidatesError; private readonly _raw: RawModuleMap; diff --git a/packages/jest-haste-map/src/index.ts b/packages/jest-haste-map/src/index.ts index 041b5b0428e7..213e623a4cfe 100644 --- a/packages/jest-haste-map/src/index.ts +++ b/packages/jest-haste-map/src/index.ts @@ -18,7 +18,7 @@ import {escapePathForRegex} from 'jest-regex-util'; import serializer from 'jest-serializer'; import {Worker} from 'jest-worker'; import HasteFS from './HasteFS'; -import HasteModuleMap, {SerializableModuleMap} from './ModuleMap'; +import HasteModuleMap from './ModuleMap'; import H from './constants'; import nodeCrawl = require('./crawlers/node'); import watchmanCrawl = require('./crawlers/watchman'); @@ -32,14 +32,14 @@ import type { EventsQueue, FileData, FileMetaData, - HTypeValue, + HasteMapStatic, HasteRegExp, InternalHasteMap, HasteMap as InternalHasteMapObject, MockData, ModuleMapData, ModuleMetaData, - RawModuleMap, + SerializableModuleMap, WorkerMetadata, } from './types'; import FSEventsWatcher = require('./watchers/FSEventsWatcher'); @@ -109,7 +109,8 @@ type Watcher = { type WorkerInterface = {worker: typeof worker; getSha1: typeof getSha1}; export {default as ModuleMap} from './ModuleMap'; -export type {SerializableModuleMap} from './ModuleMap'; +export type {SerializableModuleMap} from './types'; +export type {IModuleMap} from './types'; export type {default as FS} from './HasteFS'; export type {ChangeEvent, HasteMap as HasteMapObject} from './types'; @@ -135,36 +136,6 @@ function invariant(condition: unknown, message?: string): asserts condition { } } -export interface IModuleMap { - getModule( - name: string, - platform?: string | null, - supportsNativePlatform?: boolean | null, - type?: HTypeValue | null, - ): Config.Path | null; - - getPackage( - name: string, - platform: string | null | undefined, - _supportsNativePlatform: boolean | null, - ): Config.Path | null; - - getMockModule(name: string): Config.Path | undefined; - - getRawModuleMap(): RawModuleMap; - - toJSON(): S; -} - -export type HasteMapStatic = { - getCacheFilePath( - tmpdir: Config.Path, - name: string, - ...extra: Array - ): string; - getModuleMapFromJSON(json: S): IModuleMap; -}; - /** * HasteMap is a JavaScript implementation of Facebook's haste module system. * @@ -256,7 +227,7 @@ export default class HasteMap extends EventEmitter { if (config.haste.hasteMapModulePath) { return require(config.haste.hasteMapModulePath); } - return this; + return HasteMap; } static create(options: Options): HasteMap { diff --git a/packages/jest-haste-map/src/types.ts b/packages/jest-haste-map/src/types.ts index 8e1ce10f78aa..27eec1928d4b 100644 --- a/packages/jest-haste-map/src/types.ts +++ b/packages/jest-haste-map/src/types.ts @@ -9,7 +9,45 @@ import type {Stats} from 'graceful-fs'; import type {Config} from '@jest/types'; import type HasteFS from './HasteFS'; import type ModuleMap from './ModuleMap'; -import type {IModuleMap} from '.'; + +type ValueType = T extends Map ? V : never; + +export type SerializableModuleMap = { + duplicates: ReadonlyArray<[string, [string, [string, [string, number]]]]>; + map: ReadonlyArray<[string, ValueType]>; + mocks: ReadonlyArray<[string, ValueType]>; + rootDir: Config.Path; +}; + +export interface IModuleMap { + getModule( + name: string, + platform?: string | null, + supportsNativePlatform?: boolean | null, + type?: HTypeValue | null, + ): Config.Path | null; + + getPackage( + name: string, + platform: string | null | undefined, + _supportsNativePlatform: boolean | null, + ): Config.Path | null; + + getMockModule(name: string): Config.Path | undefined; + + getRawModuleMap(): RawModuleMap; + + toJSON(): S; +} + +export type HasteMapStatic = { + getCacheFilePath( + tmpdir: Config.Path, + name: string, + ...extra: Array + ): string; + getModuleMapFromJSON(json: S): IModuleMap; +}; export type IgnoreMatcher = (item: string) => boolean; From e62ffbddbe5cd30023568b0e19d97e386fe75ffd Mon Sep 17 00:00:00 2001 From: David Zilburg Date: Wed, 19 May 2021 12:08:40 -0400 Subject: [PATCH 9/9] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e38d4a8e26a..b9f939a8b95e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features +- `[jest-config, jest-haste-map, jest-resolve, jest-runner, jest-runtime, jest-test-sequencer, jest-transform, jest-types]` [**BREAKING**] Add custom HasteMap class implementation config option ([#11107](https://github.com/facebook/jest/pull/11107)) - `[babel-jest]` Add async transformation ([#11192](https://github.com/facebook/jest/pull/11192)) - `[jest-changed-files]` Use '--' to separate paths from revisions ([#11160](https://github.com/facebook/jest/pull/11160)) - `[jest-circus]` [**BREAKING**] Fail tests when multiple `done()` calls are made ([#10624](https://github.com/facebook/jest/pull/10624))