diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e7cc0c566a5..d531766adb1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Chore & Maintenance - `[*]` Fix inconsistent workspace prefixes ([#13217](https://github.com/facebook/jest/pull/13217)) +- `[jest-haste-map]` Expose a minimal public API to TypeScript ([#13023](https://github.com/facebook/jest/pull/13023)) ### Performance diff --git a/packages/jest-core/src/cli/index.ts b/packages/jest-core/src/cli/index.ts index 6556a919ebb9..95598c8b4e30 100644 --- a/packages/jest-core/src/cli/index.ts +++ b/packages/jest-core/src/cli/index.ts @@ -13,7 +13,7 @@ import type {AggregatedResult, TestContext} from '@jest/test-result'; import type {Config} from '@jest/types'; import type {ChangedFilesPromise} from 'jest-changed-files'; import {readConfigs} from 'jest-config'; -import type HasteMap from 'jest-haste-map'; +import type {IHasteMap} from 'jest-haste-map'; import Runtime from 'jest-runtime'; import {createDirectory, preRunMessage} from 'jest-util'; import {TestWatcher} from 'jest-watcher'; @@ -236,7 +236,7 @@ const runWatch = async ( hasDeprecationWarnings: boolean, globalConfig: Config.GlobalConfig, outputStream: NodeJS.WriteStream, - hasteMapInstances: Array, + hasteMapInstances: Array, filter?: Filter, ) => { if (hasDeprecationWarnings) { diff --git a/packages/jest-core/src/lib/createContext.ts b/packages/jest-core/src/lib/createContext.ts index be1a75061dd9..29baed127705 100644 --- a/packages/jest-core/src/lib/createContext.ts +++ b/packages/jest-core/src/lib/createContext.ts @@ -7,12 +7,14 @@ import type {TestContext} from '@jest/test-result'; import type {Config} from '@jest/types'; -import type {HasteMapObject} from 'jest-haste-map'; +import type {IHasteFS, IModuleMap} from 'jest-haste-map'; import Runtime from 'jest-runtime'; +type HasteContext = {hasteFS: IHasteFS; moduleMap: IModuleMap}; + export default function createContext( config: Config.ProjectConfig, - {hasteFS, moduleMap}: HasteMapObject, + {hasteFS, moduleMap}: HasteContext, ): TestContext { return { config, diff --git a/packages/jest-core/src/watch.ts b/packages/jest-core/src/watch.ts index d1b84c7635bc..84fbd3e2a69e 100644 --- a/packages/jest-core/src/watch.ts +++ b/packages/jest-core/src/watch.ts @@ -12,10 +12,7 @@ import exit = require('exit'); import slash = require('slash'); import type {TestContext} from '@jest/test-result'; import type {Config} from '@jest/types'; -import type { - ChangeEvent as HasteChangeEvent, - default as HasteMap, -} from 'jest-haste-map'; +import type {IHasteMap as HasteMap} from 'jest-haste-map'; import {formatExecError} from 'jest-message-util'; import { isInteractive, @@ -241,31 +238,28 @@ export default async function watch( emitFileChange(); hasteMapInstances.forEach((hasteMapInstance, index) => { - hasteMapInstance.on( - 'change', - ({eventsQueue, hasteFS, moduleMap}: HasteChangeEvent) => { - const validPaths = eventsQueue.filter(({filePath}) => - isValidPath(globalConfig, filePath), - ); + hasteMapInstance.on('change', ({eventsQueue, hasteFS, moduleMap}) => { + const validPaths = eventsQueue.filter(({filePath}) => + isValidPath(globalConfig, filePath), + ); - if (validPaths.length) { - const context = (contexts[index] = createContext( - contexts[index].config, - {hasteFS, moduleMap}, - )); - - activePlugin = null; - - searchSources = searchSources.slice(); - searchSources[index] = { - context, - searchSource: new SearchSource(context), - }; - emitFileChange(); - startRun(globalConfig); - } - }, - ); + if (validPaths.length) { + const context = (contexts[index] = createContext( + contexts[index].config, + {hasteFS, moduleMap}, + )); + + activePlugin = null; + + searchSources = searchSources.slice(); + searchSources[index] = { + context, + searchSource: new SearchSource(context), + }; + emitFileChange(); + startRun(globalConfig); + } + }); }); if (!hasExitListener) { diff --git a/packages/jest-haste-map/src/HasteFS.ts b/packages/jest-haste-map/src/HasteFS.ts index 56fd2792067c..3fc1144c976c 100644 --- a/packages/jest-haste-map/src/HasteFS.ts +++ b/packages/jest-haste-map/src/HasteFS.ts @@ -8,9 +8,9 @@ import {globsToMatcher, replacePathSepForGlob} from 'jest-util'; import H from './constants'; import * as fastPath from './lib/fast_path'; -import type {FileData} from './types'; +import type {FileData, IHasteFS} from './types'; -export default class HasteFS { +export default class HasteFS implements IHasteFS { private readonly _rootDir: string; private readonly _files: FileData; diff --git a/packages/jest-haste-map/src/ModuleMap.ts b/packages/jest-haste-map/src/ModuleMap.ts index fea276a6f325..59fd7760115b 100644 --- a/packages/jest-haste-map/src/ModuleMap.ts +++ b/packages/jest-haste-map/src/ModuleMap.ts @@ -19,7 +19,7 @@ import type { const EMPTY_OBJ: Record = {}; const EMPTY_MAP = new Map(); -export default class ModuleMap implements IModuleMap { +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 c3f65c495ef4..812a4cbdb700 100644 --- a/packages/jest-haste-map/src/index.ts +++ b/packages/jest-haste-map/src/index.ts @@ -34,6 +34,8 @@ import type { FileMetaData, HasteMapStatic, HasteRegExp, + IHasteMap, + IModuleMap, InternalHasteMap, HasteMap as InternalHasteMapObject, MockData, @@ -109,11 +111,12 @@ type Watcher = { type HasteWorker = typeof import('./worker'); -export type {default as FS} from './HasteFS'; -export {default as ModuleMap} from './ModuleMap'; +export const ModuleMap = HasteModuleMap as { + create: (rootPath: string) => IModuleMap; +}; export type { - ChangeEvent, - HasteMap as HasteMapObject, + IHasteFS, + IHasteMap, IModuleMap, SerializableModuleMap, } from './types'; @@ -210,7 +213,7 @@ function invariant(condition: unknown, message?: string): asserts condition { * Worker processes can directly access the cache through `HasteMap.read()`. * */ -export default class HasteMap extends EventEmitter { +class HasteMap extends EventEmitter implements IHasteMap { private _buildPromise: Promise | null = null; private _cachePath = ''; private _changeInterval?: ReturnType; @@ -227,7 +230,7 @@ export default class HasteMap extends EventEmitter { return HasteMap; } - static async create(options: Options): Promise { + static async create(options: Options): Promise { if (options.hasteMapModulePath) { const CustomHasteMap = require(options.hasteMapModulePath); return new CustomHasteMap(options); @@ -1143,3 +1146,11 @@ function copy>(object: T): T { function copyMap(input: Map): Map { return new Map(input); } + +// Export the smallest API surface required by Jest +type IJestHasteMap = HasteMapStatic & { + create(options: Options): Promise; + getStatic(config: Config.ProjectConfig): HasteMapStatic; +}; +const JestHasteMap: IJestHasteMap = HasteMap; +export default JestHasteMap; diff --git a/packages/jest-haste-map/src/types.ts b/packages/jest-haste-map/src/types.ts index 934526d218b2..c56c950534c1 100644 --- a/packages/jest-haste-map/src/types.ts +++ b/packages/jest-haste-map/src/types.ts @@ -39,6 +39,24 @@ export interface IModuleMap { toJSON(): S; } +export interface IHasteFS { + exists(path: string): boolean; + getAbsoluteFileIterator(): Iterable; + getAllFiles(): Array; + getDependencies(file: string): Array | null; + getSize(path: string): number | null; + matchFiles(pattern: RegExp | string): Array; + matchFilesWithGlob( + globs: ReadonlyArray, + root: string | null, + ): Set; +} + +export interface IHasteMap { + on(eventType: 'change', handler: (event: ChangeEvent) => void): void; + build(): Promise<{hasteFS: IHasteFS; moduleMap: IModuleMap}>; +} + export type HasteMapStatic = { getCacheFilePath( tmpdir: string, diff --git a/packages/jest-resolve-dependencies/src/index.ts b/packages/jest-resolve-dependencies/src/index.ts index 21ba04ac3115..58ea8b26481b 100644 --- a/packages/jest-resolve-dependencies/src/index.ts +++ b/packages/jest-resolve-dependencies/src/index.ts @@ -6,7 +6,7 @@ */ import * as path from 'path'; -import type {FS as HasteFS} from 'jest-haste-map'; +import type {IHasteFS} from 'jest-haste-map'; import type {ResolveModuleConfig, default as Resolver} from 'jest-resolve'; import {SnapshotResolver, isSnapshotPath} from 'jest-snapshot'; @@ -20,13 +20,13 @@ export type ResolvedModule = { * to retrieve a list of all transitive inverse dependencies. */ export class DependencyResolver { - private _hasteFS: HasteFS; + private _hasteFS: IHasteFS; private _resolver: Resolver; private _snapshotResolver: SnapshotResolver; constructor( resolver: Resolver, - hasteFS: HasteFS, + hasteFS: IHasteFS, snapshotResolver: SnapshotResolver, ) { this._resolver = resolver; diff --git a/packages/jest-resolve/src/__tests__/resolve.test.ts b/packages/jest-resolve/src/__tests__/resolve.test.ts index eb6d3c3cd7d3..d04caec49c27 100644 --- a/packages/jest-resolve/src/__tests__/resolve.test.ts +++ b/packages/jest-resolve/src/__tests__/resolve.test.ts @@ -9,7 +9,7 @@ import * as path from 'path'; import * as fs from 'graceful-fs'; import {sync as resolveSync} from 'resolve'; -import {ModuleMap} from 'jest-haste-map'; +import {IModuleMap, ModuleMap} from 'jest-haste-map'; import userResolver from '../__mocks__/userResolver'; import userResolverAsync from '../__mocks__/userResolverAsync'; import defaultResolver from '../defaultResolver'; @@ -358,7 +358,7 @@ describe('findNodeModuleAsync', () => { }); describe('resolveModule', () => { - let moduleMap: ModuleMap; + let moduleMap: IModuleMap; beforeEach(() => { moduleMap = ModuleMap.create('/'); }); @@ -463,7 +463,7 @@ describe('resolveModule', () => { }); describe('resolveModuleAsync', () => { - let moduleMap: ModuleMap; + let moduleMap: IModuleMap; beforeEach(() => { moduleMap = ModuleMap.create('/'); }); @@ -626,7 +626,7 @@ describe('nodeModulesPaths', () => { describe('Resolver.getModulePaths() -> nodeModulesPaths()', () => { const _path = path; - let moduleMap: ModuleMap; + let moduleMap: IModuleMap; beforeEach(() => { jest.resetModules(); diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 4e9358d24f8a..6d7ee64ea5a3 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -48,7 +48,7 @@ import { shouldInstrument, } from '@jest/transform'; import type {Config, Global} from '@jest/types'; -import HasteMap, {IModuleMap} from 'jest-haste-map'; +import HasteMap, {IHasteMap, IModuleMap} from 'jest-haste-map'; import {formatStackTrace, separateMessageFromStack} from 'jest-message-util'; import type {MockMetadata, ModuleMocker} from 'jest-mock'; import {escapePathForRegex} from 'jest-regex-util'; @@ -330,7 +330,7 @@ export default class Runtime { static createHasteMap( config: Config.ProjectConfig, options?: HasteMapOptions, - ): Promise { + ): Promise { const ignorePatternParts = [ ...config.modulePathIgnorePatterns, ...(options && options.watch ? config.watchPathIgnorePatterns : []), diff --git a/packages/jest-snapshot/src/index.ts b/packages/jest-snapshot/src/index.ts index adf570b82476..ca054ca79595 100644 --- a/packages/jest-snapshot/src/index.ts +++ b/packages/jest-snapshot/src/index.ts @@ -8,7 +8,7 @@ import * as fs from 'graceful-fs'; import type {Config} from '@jest/types'; import type {MatcherFunctionWithContext} from 'expect'; -import type {FS as HasteFS} from 'jest-haste-map'; +import type {IHasteFS} from 'jest-haste-map'; import { BOLD_WEIGHT, EXPECTED_COLOR, @@ -110,11 +110,11 @@ function stripAddedIndentation(inlineSnapshot: string) { return inlineSnapshot; } -const fileExists = (filePath: string, hasteFS: HasteFS): boolean => +const fileExists = (filePath: string, hasteFS: IHasteFS): boolean => hasteFS.exists(filePath) || fs.existsSync(filePath); export const cleanup = ( - hasteFS: HasteFS, + hasteFS: IHasteFS, update: Config.SnapshotUpdateState, snapshotResolver: SnapshotResolver, testPathIgnorePatterns?: Config.ProjectConfig['testPathIgnorePatterns'], diff --git a/packages/jest-test-result/src/types.ts b/packages/jest-test-result/src/types.ts index 752ee750c730..bbc6eeaf5114 100644 --- a/packages/jest-test-result/src/types.ts +++ b/packages/jest-test-result/src/types.ts @@ -9,7 +9,7 @@ import type {V8Coverage} from 'collect-v8-coverage'; import type {CoverageMap, CoverageMapData} from 'istanbul-lib-coverage'; import type {ConsoleBuffer} from '@jest/console'; import type {Config, TestResult, TransformTypes} from '@jest/types'; -import type {FS as HasteFS, ModuleMap} from 'jest-haste-map'; +import type {IHasteFS, IModuleMap} from 'jest-haste-map'; import type Resolver from 'jest-resolve'; export interface RuntimeTransformResult extends TransformTypes.TransformResult { @@ -187,8 +187,8 @@ export type Test = { export type TestContext = { config: Config.ProjectConfig; - hasteFS: HasteFS; - moduleMap: ModuleMap; + hasteFS: IHasteFS; + moduleMap: IModuleMap; resolver: Resolver; };