From 3125d6d383b5864e1c8107c2fb95785afa7a0a13 Mon Sep 17 00:00:00 2001 From: Rob Hogan <2590098+robhogan@users.noreply.github.com> Date: Tue, 10 Dec 2024 18:07:24 -0400 Subject: [PATCH] Extract more mock handling into MockMap --- packages/metro-file-map/src/index.js | 71 +++++++--------------- packages/metro-file-map/src/lib/MockMap.js | 60 +++++++++++++++++- 2 files changed, 81 insertions(+), 50 deletions(-) diff --git a/packages/metro-file-map/src/index.js b/packages/metro-file-map/src/index.js index daf7b98447..2de979afe5 100644 --- a/packages/metro-file-map/src/index.js +++ b/packages/metro-file-map/src/index.js @@ -31,8 +31,6 @@ import type { Path, PerfLogger, PerfLoggerFactory, - RawMockMap, - ReadOnlyRawMockMap, WatchmanClocks, WorkerMetadata, } from './flow-types'; @@ -40,9 +38,7 @@ import type {IJestWorker} from 'jest-worker'; import {DiskCacheManager} from './cache/DiskCacheManager'; import H from './constants'; -import getMockName from './getMockName'; import checkWatchmanCapabilities from './lib/checkWatchmanCapabilities'; -import {DuplicateError} from './lib/DuplicateError'; import MockMapImpl from './lib/MockMap'; import MutableHasteMap from './lib/MutableHasteMap'; import normalizePathSeparatorsToSystem from './lib/normalizePathSeparatorsToSystem'; @@ -380,13 +376,20 @@ export default class FileMap extends EventEmitter { this._constructHasteMap(fileSystem), ]); + const mockMap = new MockMapImpl({ + console: this._console, + rawMockMap: mocks, + rootDir, + throwOnModuleCollision: this._options.throwOnModuleCollision, + }); + // Update `fileSystem`, `hasteMap` and `mocks` based on the file delta. - await this._applyFileDelta(fileSystem, hasteMap, mocks, fileDelta); + await this._applyFileDelta(fileSystem, hasteMap, mockMap, fileDelta); await this._takeSnapshotAndPersist( fileSystem, fileDelta.clocks ?? new Map(), - mocks, + mockMap, fileDelta.changedFiles, fileDelta.removedFiles, ); @@ -396,11 +399,11 @@ export default class FileMap extends EventEmitter { fileDelta.removedFiles.size, ); - await this._watch(fileSystem, hasteMap, mocks); + await this._watch(fileSystem, hasteMap, mockMap); return { fileSystem, hasteMap, - mockMap: new MockMapImpl({rootDir, rawMockMap: mocks}), + mockMap, }; })(); } @@ -521,7 +524,7 @@ export default class FileMap extends EventEmitter { */ _processFile( hasteMap: MutableHasteMap, - mockMap: RawMockMap, + mockMap: MockMapImpl, filePath: Path, fileMetadata: FileMetaData, workerOptions?: {forceInBand?: ?boolean, perfLogger?: ?PerfLogger}, @@ -542,8 +545,6 @@ export default class FileMap extends EventEmitter { const rootDir = this._options.rootDir; - const relativeFilePath = this._pathUtils.absoluteToNormal(filePath); - const computeSha1 = this._options.computeSha1 && fileMetadata[H.SHA1] == null; @@ -611,33 +612,7 @@ export default class FileMap extends EventEmitter { this._options.mocksPattern && this._options.mocksPattern.test(filePath) ) { - const mockPath = getMockName(filePath); - const existingMockPath = mockMap.get(mockPath); - - if (existingMockPath != null) { - const secondMockPath = this._pathUtils.absoluteToNormal(filePath); - if (existingMockPath !== secondMockPath) { - const method = this._options.throwOnModuleCollision - ? 'error' - : 'warn'; - - this._console[method]( - [ - 'metro-file-map: duplicate manual mock found: ' + mockPath, - ' The following files share their name; please delete one of them:', - ' * ' + path.sep + existingMockPath, - ' * ' + path.sep + secondMockPath, - '', - ].join('\n'), - ); - - if (this._options.throwOnModuleCollision) { - throw new DuplicateError(existingMockPath, secondMockPath); - } - } - } - - mockMap.set(mockPath, relativeFilePath); + mockMap.addMockModule(filePath); } return this._getWorker(workerOptions) @@ -656,7 +631,7 @@ export default class FileMap extends EventEmitter { async _applyFileDelta( fileSystem: MutableFileSystem, hasteMap: MutableHasteMap, - mockMap: RawMockMap, + mockMap: MockMapImpl, delta: $ReadOnly<{ changedFiles: FileData, removedFiles: $ReadOnlySet, @@ -758,7 +733,7 @@ export default class FileMap extends EventEmitter { async _takeSnapshotAndPersist( fileSystem: FileSystem, clocks: WatchmanClocks, - mockMap: ReadOnlyRawMockMap, + mockMap: MockMapImpl, changed: FileData, removed: Set, ) { @@ -767,7 +742,7 @@ export default class FileMap extends EventEmitter { { fileSystemData: fileSystem.getSerializableSnapshot(), clocks: new Map(clocks), - mocks: new Map(mockMap), + mocks: mockMap.getSerializableSnapshot(), }, {changed, removed}, ); @@ -809,7 +784,7 @@ export default class FileMap extends EventEmitter { _removeIfExists( fileSystem: MutableFileSystem, hasteMap: MutableHasteMap, - mockMap: RawMockMap, + mockMap: MockMapImpl, relativeFilePath: Path, ) { const fileMetadata = fileSystem.remove(relativeFilePath); @@ -824,16 +799,13 @@ export default class FileMap extends EventEmitter { hasteMap.removeModule(moduleName, relativeFilePath); if (this._options.mocksPattern) { - const absoluteFilePath = path.join( - this._options.rootDir, - normalizePathSeparatorsToSystem(relativeFilePath), - ); + const absoluteFilePath = + this._pathUtils.normalToAbsolute(relativeFilePath); if ( this._options.mocksPattern && this._options.mocksPattern.test(absoluteFilePath) ) { - const mockName = getMockName(absoluteFilePath); - mockMap.delete(mockName); + mockMap.deleteMockModule(absoluteFilePath); } } } @@ -844,7 +816,7 @@ export default class FileMap extends EventEmitter { async _watch( fileSystem: MutableFileSystem, hasteMap: MutableHasteMap, - mockMap: RawMockMap, + mockMap: MockMapImpl, ): Promise { this._startupPerfLogger?.point('watch_start'); if (!this._options.watch) { @@ -856,6 +828,7 @@ export default class FileMap extends EventEmitter { // all files, even changes to node_modules. this._options.throwOnModuleCollision = false; hasteMap.setThrowOnModuleCollision(false); + mockMap.setThrowOnModuleCollision(false); this._options.retainAllFiles = true; const hasWatchedExtension = (filePath: string) => diff --git a/packages/metro-file-map/src/lib/MockMap.js b/packages/metro-file-map/src/lib/MockMap.js index 395ffa85b8..059f7975af 100644 --- a/packages/metro-file-map/src/lib/MockMap.js +++ b/packages/metro-file-map/src/lib/MockMap.js @@ -11,21 +11,79 @@ import type {MockMap as IMockMap, Path, RawMockMap} from '../flow-types'; +import getMockName from '../getMockName'; +import {DuplicateError} from './DuplicateError'; import {RootPathUtils} from './RootPathUtils'; +import path from 'path'; export default class MockMap implements IMockMap { +#raw: RawMockMap; +#rootDir: Path; +#pathUtils: RootPathUtils; + +#console: typeof console; + #throwOnModuleCollision: boolean; - constructor({rawMockMap, rootDir}: {rawMockMap: RawMockMap, rootDir: Path}) { + constructor({ + console, + rawMockMap, + rootDir, + throwOnModuleCollision, + }: { + console: typeof console, + rawMockMap: RawMockMap, + rootDir: Path, + throwOnModuleCollision: boolean, + }) { this.#raw = rawMockMap; this.#rootDir = rootDir; + this.#console = console; this.#pathUtils = new RootPathUtils(rootDir); + this.#throwOnModuleCollision = throwOnModuleCollision; } getMockModule(name: string): ?Path { const mockPath = this.#raw.get(name) || this.#raw.get(name + '/index'); return mockPath != null ? this.#pathUtils.normalToAbsolute(mockPath) : null; } + + addMockModule(absoluteFilePath: Path): void { + const mockName = getMockName(absoluteFilePath); + const existingMockPath = this.#raw.get(mockName); + const newMockPath = this.#pathUtils.absoluteToNormal(absoluteFilePath); + + if (existingMockPath != null) { + if (existingMockPath !== newMockPath) { + const method = this.#throwOnModuleCollision ? 'error' : 'warn'; + + this.#console[method]( + [ + 'metro-file-map: duplicate manual mock found: ' + mockName, + ' The following files share their name; please delete one of them:', + ' * ' + path.sep + existingMockPath, + ' * ' + path.sep + newMockPath, + '', + ].join('\n'), + ); + + if (this.#throwOnModuleCollision) { + throw new DuplicateError(existingMockPath, newMockPath); + } + } + } + + this.#raw.set(mockName, newMockPath); + } + + deleteMockModule(absoluteFilePath: Path): void { + const mockName = getMockName(absoluteFilePath); + this.#raw.delete(mockName); + } + + setThrowOnModuleCollision(throwOnModuleCollision: boolean): void { + this.#throwOnModuleCollision = throwOnModuleCollision; + } + + getSerializableSnapshot(): RawMockMap { + return new Map(this.#raw); + } }