PHP WebShell
Текущая директория: /usr/lib/node_modules/bitgo/node_modules/metro-file-map/src/plugins
Просмотр файла: HastePlugin.js.flow
/**
* 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 {
Console,
DuplicatesIndex,
DuplicatesSet,
FileMapDelta,
FileMapPlugin,
FileMapPluginInitOptions,
FileMetadata,
HasteConflict,
HasteMap,
HasteMapItem,
HasteMapItemMetadata,
HTypeValue,
Path,
PerfLogger,
} from '../flow-types';
import H from '../constants';
import {RootPathUtils} from '../lib/RootPathUtils';
import {chainComparators, compareStrings} from '../lib/sorting';
import {DuplicateHasteCandidatesError} from './haste/DuplicateHasteCandidatesError';
import getPlatformExtension from './haste/getPlatformExtension';
import {HasteConflictsError} from './haste/HasteConflictsError';
import path from 'path';
const EMPTY_OBJ: $ReadOnly<{[string]: HasteMapItemMetadata}> = {};
const EMPTY_MAP: $ReadOnlyMap<string, DuplicatesSet> = new Map();
// Periodically yield to the event loop to allow parallel I/O, etc.
// Based on 200k files taking up to 800ms => max 40ms between yields.
const YIELD_EVERY_NUM_HASTE_FILES = 10000;
type HasteMapOptions = $ReadOnly<{
console?: ?Console,
enableHastePackages: boolean,
perfLogger: ?PerfLogger,
platforms: $ReadOnlySet<string>,
rootDir: Path,
failValidationOnConflicts: boolean,
}>;
export default class HastePlugin implements HasteMap, FileMapPlugin<null> {
+name = 'haste';
+#rootDir: Path;
+#map: Map<string, HasteMapItem> = new Map();
+#duplicates: DuplicatesIndex = new Map();
+#console: ?Console;
+#enableHastePackages: boolean;
+#perfLogger: ?PerfLogger;
+#pathUtils: RootPathUtils;
+#platforms: $ReadOnlySet<string>;
+#failValidationOnConflicts: boolean;
constructor(options: HasteMapOptions) {
this.#console = options.console ?? null;
this.#enableHastePackages = options.enableHastePackages;
this.#perfLogger = options.perfLogger;
this.#platforms = options.platforms;
this.#rootDir = options.rootDir;
this.#pathUtils = new RootPathUtils(options.rootDir);
this.#failValidationOnConflicts = options.failValidationOnConflicts;
}
async initialize({files}: FileMapPluginInitOptions<null>): Promise<void> {
this.#perfLogger?.point('constructHasteMap_start');
let hasteFiles = 0;
for (const {baseName, canonicalPath, metadata} of files.metadataIterator({
// Symlinks and node_modules are never Haste modules or packages.
includeNodeModules: false,
includeSymlinks: false,
})) {
if (metadata[H.ID]) {
this.setModule(metadata[H.ID], [
canonicalPath,
this.#enableHastePackages && baseName === 'package.json'
? H.PACKAGE
: H.MODULE,
]);
if (++hasteFiles % YIELD_EVERY_NUM_HASTE_FILES === 0) {
await new Promise(setImmediate);
}
}
}
this.#perfLogger?.point('constructHasteMap_end');
this.#perfLogger?.annotate({int: {hasteFiles}});
}
getSerializableSnapshot(): null {
// Haste is not serialised, but built from traversing the file metadata
// on each run. This turns out to have comparable performance to
// serialisation, at least when Haste is dense, and makes for a much
// smaller cache.
return null;
}
getModule(
name: string,
platform?: ?string,
supportsNativePlatform?: ?boolean,
type?: ?HTypeValue,
): ?Path {
const module = this._getModuleMetadata(
name,
platform,
!!supportsNativePlatform,
);
/* $FlowFixMe[invalid-compare] Error discovered during Constant Condition
* roll out. See https://fburl.com/workplace/4oq3zi07. */
if (module && module[H.TYPE] === (type ?? H.MODULE)) {
const modulePath = module[H.PATH];
return modulePath && this.#pathUtils.normalToAbsolute(modulePath);
}
return null;
}
getPackage(
name: string,
platform: ?string,
_supportsNativePlatform?: ?boolean,
): ?Path {
return this.getModule(name, platform, null, H.PACKAGE);
}
/**
* When looking up a module's data, we walk through each eligible platform for
* the query. For each platform, we want to check if there are known
* duplicates for that name+platform pair. The duplication logic normally
* removes elements from the `map` object, but we want to check upfront to be
* extra sure. If metadata exists both in the `duplicates` object and the
* `map`, this would be a bug.
*/
_getModuleMetadata(
name: string,
platform: ?string,
supportsNativePlatform: boolean,
): HasteMapItemMetadata | null {
const map = this.#map.get(name) || EMPTY_OBJ;
const dupMap = this.#duplicates.get(name) || EMPTY_MAP;
if (platform != null) {
this._assertNoDuplicates(
name,
platform,
supportsNativePlatform,
dupMap.get(platform),
);
if (map[platform] != null) {
return map[platform];
}
}
if (supportsNativePlatform) {
this._assertNoDuplicates(
name,
H.NATIVE_PLATFORM,
supportsNativePlatform,
dupMap.get(H.NATIVE_PLATFORM),
);
if (map[H.NATIVE_PLATFORM]) {
return map[H.NATIVE_PLATFORM];
}
}
this._assertNoDuplicates(
name,
H.GENERIC_PLATFORM,
supportsNativePlatform,
dupMap.get(H.GENERIC_PLATFORM),
);
if (map[H.GENERIC_PLATFORM]) {
return map[H.GENERIC_PLATFORM];
}
return null;
}
_assertNoDuplicates(
name: string,
platform: string,
supportsNativePlatform: boolean,
relativePathSet: ?DuplicatesSet,
): void {
if (relativePathSet == null) {
return;
}
const duplicates = new Map<string, number>();
for (const [relativePath, type] of relativePathSet) {
const duplicatePath = this.#pathUtils.normalToAbsolute(relativePath);
duplicates.set(duplicatePath, type);
}
throw new DuplicateHasteCandidatesError(
name,
platform,
supportsNativePlatform,
duplicates,
);
}
async bulkUpdate(delta: FileMapDelta): Promise<void> {
// Process removals first so that moves aren't treated as duplicates.
for (const [normalPath, metadata] of delta.removed) {
this.onRemovedFile(normalPath, metadata);
}
for (const [normalPath, metadata] of delta.addedOrModified) {
this.onNewOrModifiedFile(normalPath, metadata);
}
}
onNewOrModifiedFile(relativeFilePath: string, fileMetadata: FileMetadata) {
const id = fileMetadata[H.ID] || null; // Empty string indicates no module
if (id == null) {
return;
}
const module: HasteMapItemMetadata = [
relativeFilePath,
this.#enableHastePackages &&
path.basename(relativeFilePath) === 'package.json'
? H.PACKAGE
: H.MODULE,
];
this.setModule(id, module);
}
setModule(id: string, module: HasteMapItemMetadata) {
let hasteMapItem = this.#map.get(id);
if (!hasteMapItem) {
// $FlowFixMe[unclear-type] - Add type coverage
hasteMapItem = (Object.create(null): any);
this.#map.set(id, hasteMapItem);
}
const platform =
getPlatformExtension(module[H.PATH], this.#platforms) ||
H.GENERIC_PLATFORM;
const existingModule = hasteMapItem[platform];
if (existingModule && existingModule[H.PATH] !== module[H.PATH]) {
if (this.#console) {
this.#console.warn(
[
'metro-file-map: Haste module naming collision: ' + id,
' The following files share their name; please adjust your hasteImpl:',
' * <rootDir>' + path.sep + existingModule[H.PATH],
' * <rootDir>' + path.sep + module[H.PATH],
'',
].join('\n'),
);
}
// We do NOT want consumers to use a module that is ambiguous.
delete hasteMapItem[platform];
if (Object.keys(hasteMapItem).length === 0) {
this.#map.delete(id);
}
let dupsByPlatform = this.#duplicates.get(id);
if (dupsByPlatform == null) {
dupsByPlatform = new Map();
this.#duplicates.set(id, dupsByPlatform);
}
const dups = new Map([
[module[H.PATH], module[H.TYPE]],
[existingModule[H.PATH], existingModule[H.TYPE]],
]);
dupsByPlatform.set(platform, dups);
return;
}
const dupsByPlatform = this.#duplicates.get(id);
if (dupsByPlatform != null) {
const dups = dupsByPlatform.get(platform);
if (dups != null) {
dups.set(module[H.PATH], module[H.TYPE]);
}
return;
}
hasteMapItem[platform] = module;
}
onRemovedFile(relativeFilePath: string, fileMetadata: FileMetadata) {
const moduleName = fileMetadata[H.ID] || null; // Empty string indicates no module
if (moduleName == null) {
return;
}
const platform =
getPlatformExtension(relativeFilePath, this.#platforms) ||
H.GENERIC_PLATFORM;
const hasteMapItem = this.#map.get(moduleName);
if (hasteMapItem != null) {
delete hasteMapItem[platform];
if (Object.keys(hasteMapItem).length === 0) {
this.#map.delete(moduleName);
} else {
this.#map.set(moduleName, hasteMapItem);
}
}
this._recoverDuplicates(moduleName, relativeFilePath);
}
assertValid(): void {
if (!this.#failValidationOnConflicts) {
return;
}
const conflicts = this.computeConflicts();
if (conflicts.length > 0) {
throw new HasteConflictsError(conflicts);
}
}
/**
* This function should be called when the file under `filePath` is removed
* or changed. When that happens, we want to figure out if that file was
* part of a group of files that had the same ID. If it was, we want to
* remove it from the group. Furthermore, if there is only one file
* remaining in the group, then we want to restore that single file as the
* correct resolution for its ID, and cleanup the duplicates index.
*/
_recoverDuplicates(moduleName: string, relativeFilePath: string) {
let dupsByPlatform = this.#duplicates.get(moduleName);
if (dupsByPlatform == null) {
return;
}
const platform =
getPlatformExtension(relativeFilePath, this.#platforms) ||
H.GENERIC_PLATFORM;
let dups = dupsByPlatform.get(platform);
if (dups == null) {
return;
}
dupsByPlatform = new Map(dupsByPlatform);
this.#duplicates.set(moduleName, dupsByPlatform);
dups = new Map(dups);
dupsByPlatform.set(platform, dups);
dups.delete(relativeFilePath);
if (dups.size !== 1) {
return;
}
const uniqueModule = dups.entries().next().value;
if (!uniqueModule) {
return;
}
let dedupMap: ?HasteMapItem = this.#map.get(moduleName);
if (dedupMap == null) {
dedupMap = (Object.create(null): HasteMapItem);
this.#map.set(moduleName, dedupMap);
}
dedupMap[platform] = uniqueModule;
dupsByPlatform.delete(platform);
if (dupsByPlatform.size === 0) {
this.#duplicates.delete(moduleName);
}
}
computeConflicts(): Array<HasteConflict> {
const conflicts: Array<HasteConflict> = [];
// Add literal duplicates tracked in the #duplicates map
for (const [id, dupsByPlatform] of this.#duplicates.entries()) {
for (const [platform, conflictingModules] of dupsByPlatform) {
conflicts.push({
id,
platform: platform === H.GENERIC_PLATFORM ? null : platform,
absolutePaths: [...conflictingModules.keys()]
.map(modulePath => this.#pathUtils.normalToAbsolute(modulePath))
// Sort for ease of testing
.sort(),
type: 'duplicate',
});
}
}
// Add cases of "shadowing at a distance": a module with a platform suffix and
// a module with a lower priority platform suffix (or no suffix), in different
// directories.
for (const [id, data] of this.#map) {
const conflictPaths = new Set<string>();
const basePaths = [];
for (const basePlatform of [H.NATIVE_PLATFORM, H.GENERIC_PLATFORM]) {
if (data[basePlatform] == null) {
continue;
}
const basePath = data[basePlatform][0];
basePaths.push(basePath);
const basePathDir = path.dirname(basePath);
// Find all platforms that can shadow basePlatform
// Given that X.(specific platform).js > x.native.js > X.js
// and basePlatform is either 'native' or generic (no platform).
for (const platform of Object.keys(data)) {
if (
platform === basePlatform ||
platform === H.GENERIC_PLATFORM /* lowest priority */
) {
continue;
}
const platformPath = data[platform][0];
if (path.dirname(platformPath) !== basePathDir) {
conflictPaths.add(platformPath);
}
}
}
if (conflictPaths.size) {
conflicts.push({
id,
platform: null,
absolutePaths: [...new Set([...conflictPaths, ...basePaths])]
.map(modulePath => this.#pathUtils.normalToAbsolute(modulePath))
// Sort for ease of testing
.sort(),
type: 'shadowing',
});
}
}
// Sort for ease of testing
conflicts.sort(
chainComparators(
(a, b) => compareStrings(a.type, b.type),
(a, b) => compareStrings(a.id, b.id),
(a, b) => compareStrings(a.platform, b.platform),
),
);
return conflicts;
}
getCacheKey(): string {
return JSON.stringify([
this.#enableHastePackages,
[...this.#platforms].sort(),
]);
}
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!