From 79653b0691f43c8df3e88a1fbc8ac05caaa64e3d Mon Sep 17 00:00:00 2001 From: Rob Hogan Date: Fri, 28 Aug 2026 15:25:02 +0100 Subject: [PATCH] Back out "Add createStaticCrawler" Original commit changeset: 3d0f468f29da This is an AI slop API I think I accidentally left published when I left Meta, and was landed alongside some other work. There's no need for a helper like this to exist in core and even if there were, this isn't the right shape. - "Static file" isn't really a thing - the crawler API is meant to allow for virtual file systems, they're not necessarily static. - Needlessly takes its input in a different shape to the output and converts (essentially most of the function) - expensive on large file sets. - `Reflect.set` to avoid a Flow error.. .*what*. This has never been released, so we can still remove it as non-breaking. Changelog: Internal Test plan: CI --- packages/metro-file-map/API.md | 11 - .../src/__tests__/createStaticCrawler-test.js | 193 ------------------ .../metro-file-map/src/createStaticCrawler.js | 97 --------- packages/metro-file-map/src/index.js | 2 - 4 files changed, 303 deletions(-) delete mode 100644 packages/metro-file-map/src/__tests__/createStaticCrawler-test.js delete mode 100644 packages/metro-file-map/src/createStaticCrawler.js diff --git a/packages/metro-file-map/API.md b/packages/metro-file-map/API.md index fb3e28117c..6be5878bee 100644 --- a/packages/metro-file-map/API.md +++ b/packages/metro-file-map/API.md @@ -85,8 +85,6 @@ export type CrawlerOptions = { export type CrawlResult = {changedFiles: FileData; removedFiles: Set; clocks: WatchmanClocks} | {changedFiles: FileData; removedFiles: Set}; -export function createStaticCrawler($$PARAM_0$$: StaticCrawlerOptions): CrawlerFactory; - export type DependencyExtractor = { extract: (content: string, absoluteFilePath: string, defaultExtractor?: DependencyExtractor['extract']) => Set; getCacheKey: () => string; @@ -261,15 +259,6 @@ export class NoopCacheManager implements CacheManager { write(): Promise; } -export type StaticCrawlerOptions = Readonly<{ - files: ReadonlyArray; -}>; - -export type StaticFile = Readonly<{ - path: string; - pluginData?: null | undefined | Readonly<{[pluginName: string]: unknown}>; -}>; - export type WatcherStatus = | { type: 'watchman_slow_command'; diff --git a/packages/metro-file-map/src/__tests__/createStaticCrawler-test.js b/packages/metro-file-map/src/__tests__/createStaticCrawler-test.js deleted file mode 100644 index c49179a3f5..0000000000 --- a/packages/metro-file-map/src/__tests__/createStaticCrawler-test.js +++ /dev/null @@ -1,193 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict-local - * @format - * @oncall react_native - */ - -import type {Crawler, CrawlerOptions, InputOptions, StaticFile} from '../index'; -import typeof * as AbstractWatcherModule from '../watchers/AbstractWatcher'; - -import FileMap, { - HastePlugin, - NoopCacheManager, - createStaticCrawler, -} from '../index'; -import {FileProcessor} from '../lib/FileProcessor'; -import * as path from 'node:path'; - -// Hoisted above the imports by jest, so the backend class has to be built -// inside the factory. -function mockCreateWatcherBackend() { - const {AbstractWatcher} = jest.requireActual( - '../watchers/AbstractWatcher', - ); - return class MockWatcherBackend extends AbstractWatcher { - static isSupported(): boolean { - return true; - } - }; -} - -jest.mock('../watchers/FallbackWatcher', () => mockCreateWatcherBackend()); -jest.mock('../watchers/NativeWatcher', () => mockCreateWatcherBackend()); -jest.mock('../watchers/WatchmanWatcher', () => mockCreateWatcherBackend()); - -jest.mock('../crawlers/watchman', () => () => { - throw new Error('watchmanCrawl must not be called'); -}); -jest.mock('../crawlers/node', () => () => { - throw new Error('nodeCrawl must not be called'); -}); - -const rootDir = path.join(path.sep, 'project'); -const p = (...parts: Array) => path.join(rootDir, ...parts); - -function createFileMap( - files: Array, - overrides?: Partial, -): {fileMap: FileMap, hastePlugin: HastePlugin} { - const hastePlugin = new HastePlugin({ - enableHastePackages: true, - failValidationOnConflicts: false, - hasteImplModulePath: null, - perfLogger: null, - platforms: new Set(['ios', 'android', 'native']), - rootDir, - }); - const fileMap = new FileMap({ - cacheManagerFactory: () => new NoopCacheManager(), - crawlerFactory: createStaticCrawler({files}), - extensions: ['js', 'json'], - healthCheck: {enabled: false, filePrefix: '', interval: 0, timeout: 0}, - maxWorkers: 1, - plugins: [hastePlugin], - retainAllFiles: true, - rootDir, - roots: [rootDir], - useWatchman: false, - watch: false, - ...overrides, - }); - return {fileMap, hastePlugin}; -} - -describe('createStaticCrawler', () => { - test('builds a FileSystem from the supplied listing', async () => { - const {fileMap} = createFileMap([ - {path: path.join('src', 'index.js')}, - {path: path.join('src', 'nested', 'other.js')}, - {path: p('absolute.js')}, - ]); - - const {fileSystem} = await fileMap.build(); - - expect(fileSystem.exists(p('src', 'index.js'))).toBe(true); - expect(fileSystem.exists(p('src', 'nested', 'other.js'))).toBe(true); - expect(fileSystem.exists(p('absolute.js'))).toBe(true); - expect(fileSystem.exists(p('src', 'missing.js'))).toBe(false); - expect(fileSystem.lookup(p('src'))).toMatchObject({ - exists: true, - type: 'd', - }); - }); - - test('populates HastePlugin from per-file plugin data', async () => { - const {fileMap, hastePlugin} = createFileMap([ - {path: path.join('src', 'Thing.js'), pluginData: {haste: 'Thing'}}, - {path: path.join('src', 'Thing.ios.js'), pluginData: {haste: 'Thing'}}, - {path: path.join('src', 'NoHaste.js')}, - {path: path.join('pkg', 'package.json'), pluginData: {haste: 'HastePkg'}}, - ]); - - await fileMap.build(); - - expect(hastePlugin.getModule('Thing', null, false)).toBe( - p('src', 'Thing.js'), - ); - expect(hastePlugin.getModule('Thing', 'ios', false)).toBe( - p('src', 'Thing.ios.js'), - ); - expect(hastePlugin.getModule('NoHaste', null, false)).toBeNull(); - expect(hastePlugin.getPackage('HastePkg', null, false)).toBe( - p('pkg', 'package.json'), - ); - expect(hastePlugin.getModuleNameByPath(p('src', 'Thing.js'))).toBe('Thing'); - }); - - test('does not process any file contents', async () => { - const processBatch = jest.spyOn(FileProcessor.prototype, 'processBatch'); - const {fileMap} = createFileMap([ - {path: path.join('src', 'index.js'), pluginData: {haste: 'Index'}}, - ]); - - await fileMap.build(); - - expect(processBatch).toHaveBeenCalledTimes(1); - expect(processBatch.mock.calls[0][0]).toEqual([]); - processBatch.mockRestore(); - }); - - test('supplied plugin data is registered as-is, including under node_modules', async () => { - // Unlike a crawl, where HastePlugin's worker filter excludes node_modules, - // data supplied here is taken at face value. Callers must filter for - // themselves. - const {fileMap, hastePlugin} = createFileMap([ - { - path: path.join('node_modules', 'pkg', 'Thing.js'), - pluginData: {haste: 'Thing'}, - }, - ]); - - await fileMap.build(); - - expect(hastePlugin.getModule('Thing', null, false)).toBe( - p('node_modules', 'pkg', 'Thing.js'), - ); - }); - - test('reports the same listing on every crawl', async () => { - // The crawler is invoked again for a recrawl, so the listing must survive - // more than one pass - a single-use iterable would report nothing the - // second time. - const staticFactory = createStaticCrawler({ - files: [ - {path: path.join('src', 'Thing.js'), pluginData: {haste: 'Thing'}}, - ], - }); - let inner: ?Crawler = null; - let seenOptions: ?CrawlerOptions = null; - const {fileMap} = createFileMap([], { - crawlerFactory: factoryOptions => { - const crawl = staticFactory(factoryOptions); - inner = crawl; - return options => { - seenOptions = options; - return crawl(options); - }; - }, - }); - - const {fileSystem} = await fileMap.build(); - expect(fileSystem.exists(p('src', 'Thing.js'))).toBe(true); - - // Re-invoke exactly as a recrawl would. - if (inner == null || seenOptions == null) { - throw new Error('crawler was not invoked'); - } - const second = await inner(seenOptions); - expect([...second.changedFiles.keys()]).toEqual([ - path.join('src', 'Thing.js'), - ]); - }); - - test('end() is safe when not watching', async () => { - const {fileMap} = createFileMap([{path: path.join('src', 'index.js')}]); - await fileMap.build(); - await expect(fileMap.end()).resolves.toBeUndefined(); - }); -}); diff --git a/packages/metro-file-map/src/createStaticCrawler.js b/packages/metro-file-map/src/createStaticCrawler.js deleted file mode 100644 index ece92bd1f1..0000000000 --- a/packages/metro-file-map/src/createStaticCrawler.js +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict-local - * @format - * @oncall react_native - */ - -import type {CrawlerFactory, FileData, FileMetadata} from './flow-types'; - -import {RootPathUtils} from './lib/RootPathUtils'; -import * as path from 'node:path'; - -export type StaticFile = Readonly<{ - /** - * An absolute path, or a path relative to the `FileMap`'s `rootDir`, using - * system separators. - */ - path: string, - - /** - * Per-file plugin data, keyed by plugin `name` - e.g. `{haste: 'MyModule'}` - * for `HastePlugin`. Entries for plugins that aren't registered on the - * `FileMap`, or that declare no worker, are ignored. - * - * NOTE: Unlike a filesystem crawl, where plugin data is computed by a worker - * subject to that plugin's own `filter`, data supplied here is taken as-is. - * In particular `HastePlugin` will register Haste IDs given for files under - * `node_modules`, which it would never do when crawling. Callers are - * responsible for only supplying data they want registered. - */ - pluginData?: ?Readonly<{[pluginName: string]: unknown}>, -}>; - -export type StaticCrawlerOptions = Readonly<{ - /** - * An array rather than an `Iterable`, because the returned crawler walks it - * on every crawl - including a `recrawl` in watch mode - and a single-use - * iterable would report an empty listing on the second pass. - */ - files: ReadonlyArray, -}>; - -/** - * A `CrawlerFactory` backed by a known set of files, for consumers that already - * have a complete file listing and per-file plugin data (e.g. Haste IDs), and - * therefore need neither a filesystem crawl nor the `FileProcessor`. - * - * Files are reported as already visited (`H.VISITED`), never symlinks, with - * unknown mtime, zero size and no SHA-1, so `metro-file-map` performs no file - * reads and starts no workers for them. - * - * The listing is fixed, so every crawl reports the same set and never reports - * removals. Watching such a `FileMap` is possible but pointless, since nothing - * the crawler reports can ever change. - */ -export default function createStaticCrawler({ - files, -}: StaticCrawlerOptions): CrawlerFactory { - return ({pluginDataIndices}) => - async ({rootDir}) => { - const pathUtils = new RootPathUtils(rootDir); - const changedFiles: FileData = new Map(); - - for (const file of files) { - const metadata: FileMetadata = [ - /* mtime */ null, - /* size */ 0, - /* visited */ 1, - /* sha1 */ null, - /* symlink */ 0, - ]; - const pluginData = file.pluginData; - if (pluginData != null) { - for (const [pluginName, dataIdx] of pluginDataIndices) { - const value = pluginData[pluginName]; - if (value != null) { - // `FileMetadata` is a tuple type, so Flow rejects a write at an - // index only known at runtime. Slots are allocated by `FileMap`. - Reflect.set(metadata, dataIdx, value); - } - } - } - changedFiles.set( - path.isAbsolute(file.path) - ? pathUtils.absoluteToNormal(file.path) - : pathUtils.relativeToNormal(file.path), - metadata, - ); - } - - return {changedFiles, removedFiles: new Set()}; - }; -} diff --git a/packages/metro-file-map/src/index.js b/packages/metro-file-map/src/index.js index b9aae47699..7fdfbd8523 100644 --- a/packages/metro-file-map/src/index.js +++ b/packages/metro-file-map/src/index.js @@ -141,8 +141,6 @@ type InternalEnqueuedEvent = Readonly< export {DiskCacheManager} from './cache/DiskCacheManager'; export {NoopCacheManager} from './cache/NoopCacheManager'; -export {default as createStaticCrawler} from './createStaticCrawler'; -export type {StaticFile, StaticCrawlerOptions} from './createStaticCrawler'; export {default as DependencyPlugin} from './plugins/DependencyPlugin'; export type {DependencyPluginOptions} from './plugins/DependencyPlugin'; export {DuplicateHasteCandidatesError} from './plugins/haste/DuplicateHasteCandidatesError';