chore: add mobile app

This commit is contained in:
2026-02-07 22:33:02 +01:00
parent db34640acc
commit e89384ad4e
29327 changed files with 3886944 additions and 12 deletions
+358
View File
@@ -0,0 +1,358 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _constants = _interopRequireDefault(require("../constants"));
var _RootPathUtils = require("../lib/RootPathUtils");
var _sorting = require("../lib/sorting");
var _DuplicateHasteCandidatesError = require("./haste/DuplicateHasteCandidatesError");
var _getPlatformExtension = _interopRequireDefault(
require("./haste/getPlatformExtension"),
);
var _HasteConflictsError = require("./haste/HasteConflictsError");
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const EMPTY_OBJ = {};
const EMPTY_MAP = new Map();
const YIELD_EVERY_NUM_HASTE_FILES = 10000;
class HastePlugin {
name = "haste";
#rootDir;
#map = new Map();
#duplicates = new Map();
#console;
#enableHastePackages;
#perfLogger;
#pathUtils;
#platforms;
#failValidationOnConflicts;
constructor(options) {
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.RootPathUtils(options.rootDir);
this.#failValidationOnConflicts = options.failValidationOnConflicts;
}
async initialize({ files }) {
this.#perfLogger?.point("constructHasteMap_start");
let hasteFiles = 0;
for (const { baseName, canonicalPath, metadata } of files.metadataIterator({
includeNodeModules: false,
includeSymlinks: false,
})) {
if (metadata[_constants.default.ID]) {
this.setModule(metadata[_constants.default.ID], [
canonicalPath,
this.#enableHastePackages && baseName === "package.json"
? _constants.default.PACKAGE
: _constants.default.MODULE,
]);
if (++hasteFiles % YIELD_EVERY_NUM_HASTE_FILES === 0) {
await new Promise(setImmediate);
}
}
}
this.#perfLogger?.point("constructHasteMap_end");
this.#perfLogger?.annotate({
int: {
hasteFiles,
},
});
}
getSerializableSnapshot() {
return null;
}
getModule(name, platform, supportsNativePlatform, type) {
const module = this._getModuleMetadata(
name,
platform,
!!supportsNativePlatform,
);
if (
module &&
module[_constants.default.TYPE] === (type ?? _constants.default.MODULE)
) {
const modulePath = module[_constants.default.PATH];
return modulePath && this.#pathUtils.normalToAbsolute(modulePath);
}
return null;
}
getPackage(name, platform, _supportsNativePlatform) {
return this.getModule(name, platform, null, _constants.default.PACKAGE);
}
_getModuleMetadata(name, platform, supportsNativePlatform) {
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,
_constants.default.NATIVE_PLATFORM,
supportsNativePlatform,
dupMap.get(_constants.default.NATIVE_PLATFORM),
);
if (map[_constants.default.NATIVE_PLATFORM]) {
return map[_constants.default.NATIVE_PLATFORM];
}
}
this._assertNoDuplicates(
name,
_constants.default.GENERIC_PLATFORM,
supportsNativePlatform,
dupMap.get(_constants.default.GENERIC_PLATFORM),
);
if (map[_constants.default.GENERIC_PLATFORM]) {
return map[_constants.default.GENERIC_PLATFORM];
}
return null;
}
_assertNoDuplicates(name, platform, supportsNativePlatform, relativePathSet) {
if (relativePathSet == null) {
return;
}
const duplicates = new Map();
for (const [relativePath, type] of relativePathSet) {
const duplicatePath = this.#pathUtils.normalToAbsolute(relativePath);
duplicates.set(duplicatePath, type);
}
throw new _DuplicateHasteCandidatesError.DuplicateHasteCandidatesError(
name,
platform,
supportsNativePlatform,
duplicates,
);
}
async bulkUpdate(delta) {
for (const [normalPath, metadata] of delta.removed) {
this.onRemovedFile(normalPath, metadata);
}
for (const [normalPath, metadata] of delta.addedOrModified) {
this.onNewOrModifiedFile(normalPath, metadata);
}
}
onNewOrModifiedFile(relativeFilePath, fileMetadata) {
const id = fileMetadata[_constants.default.ID] || null;
if (id == null) {
return;
}
const module = [
relativeFilePath,
this.#enableHastePackages &&
_path.default.basename(relativeFilePath) === "package.json"
? _constants.default.PACKAGE
: _constants.default.MODULE,
];
this.setModule(id, module);
}
setModule(id, module) {
let hasteMapItem = this.#map.get(id);
if (!hasteMapItem) {
hasteMapItem = Object.create(null);
this.#map.set(id, hasteMapItem);
}
const platform =
(0, _getPlatformExtension.default)(
module[_constants.default.PATH],
this.#platforms,
) || _constants.default.GENERIC_PLATFORM;
const existingModule = hasteMapItem[platform];
if (
existingModule &&
existingModule[_constants.default.PATH] !==
module[_constants.default.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.default.sep +
existingModule[_constants.default.PATH],
" * <rootDir>" +
_path.default.sep +
module[_constants.default.PATH],
"",
].join("\n"),
);
}
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[_constants.default.PATH], module[_constants.default.TYPE]],
[
existingModule[_constants.default.PATH],
existingModule[_constants.default.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[_constants.default.PATH],
module[_constants.default.TYPE],
);
}
return;
}
hasteMapItem[platform] = module;
}
onRemovedFile(relativeFilePath, fileMetadata) {
const moduleName = fileMetadata[_constants.default.ID] || null;
if (moduleName == null) {
return;
}
const platform =
(0, _getPlatformExtension.default)(relativeFilePath, this.#platforms) ||
_constants.default.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() {
if (!this.#failValidationOnConflicts) {
return;
}
const conflicts = this.computeConflicts();
if (conflicts.length > 0) {
throw new _HasteConflictsError.HasteConflictsError(conflicts);
}
}
_recoverDuplicates(moduleName, relativeFilePath) {
let dupsByPlatform = this.#duplicates.get(moduleName);
if (dupsByPlatform == null) {
return;
}
const platform =
(0, _getPlatformExtension.default)(relativeFilePath, this.#platforms) ||
_constants.default.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 = this.#map.get(moduleName);
if (dedupMap == null) {
dedupMap = Object.create(null);
this.#map.set(moduleName, dedupMap);
}
dedupMap[platform] = uniqueModule;
dupsByPlatform.delete(platform);
if (dupsByPlatform.size === 0) {
this.#duplicates.delete(moduleName);
}
}
computeConflicts() {
const conflicts = [];
for (const [id, dupsByPlatform] of this.#duplicates.entries()) {
for (const [platform, conflictingModules] of dupsByPlatform) {
conflicts.push({
id,
platform:
platform === _constants.default.GENERIC_PLATFORM ? null : platform,
absolutePaths: [...conflictingModules.keys()]
.map((modulePath) => this.#pathUtils.normalToAbsolute(modulePath))
.sort(),
type: "duplicate",
});
}
}
for (const [id, data] of this.#map) {
const conflictPaths = new Set();
const basePaths = [];
for (const basePlatform of [
_constants.default.NATIVE_PLATFORM,
_constants.default.GENERIC_PLATFORM,
]) {
if (data[basePlatform] == null) {
continue;
}
const basePath = data[basePlatform][0];
basePaths.push(basePath);
const basePathDir = _path.default.dirname(basePath);
for (const platform of Object.keys(data)) {
if (
platform === basePlatform ||
platform === _constants.default.GENERIC_PLATFORM
) {
continue;
}
const platformPath = data[platform][0];
if (_path.default.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(),
type: "shadowing",
});
}
}
conflicts.sort(
(0, _sorting.chainComparators)(
(a, b) => (0, _sorting.compareStrings)(a.type, b.type),
(a, b) => (0, _sorting.compareStrings)(a.id, b.id),
(a, b) => (0, _sorting.compareStrings)(a.platform, b.platform),
),
);
return conflicts;
}
getCacheKey() {
return JSON.stringify([
this.#enableHastePackages,
[...this.#platforms].sort(),
]);
}
}
exports.default = HastePlugin;
@@ -0,0 +1,460 @@
/**
* 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(),
]);
}
}
+179
View File
@@ -0,0 +1,179 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = exports.CACHE_VERSION = void 0;
var _normalizePathSeparatorsToPosix = _interopRequireDefault(
require("../lib/normalizePathSeparatorsToPosix"),
);
var _normalizePathSeparatorsToSystem = _interopRequireDefault(
require("../lib/normalizePathSeparatorsToSystem"),
);
var _RootPathUtils = require("../lib/RootPathUtils");
var _getMockName = _interopRequireDefault(require("./mocks/getMockName"));
var _nullthrows = _interopRequireDefault(require("nullthrows"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const CACHE_VERSION = (exports.CACHE_VERSION = 2);
class MockPlugin {
name = "mocks";
#mocksPattern;
#raw;
#rootDir;
#pathUtils;
#console;
#throwOnModuleCollision;
constructor({
console,
mocksPattern,
rawMockMap = {
mocks: new Map(),
duplicates: new Map(),
version: CACHE_VERSION,
},
rootDir,
throwOnModuleCollision,
}) {
this.#mocksPattern = mocksPattern;
if (rawMockMap.version !== CACHE_VERSION) {
throw new Error("Incompatible state passed to MockPlugin");
}
this.#raw = rawMockMap;
this.#rootDir = rootDir;
this.#console = console;
this.#pathUtils = new _RootPathUtils.RootPathUtils(rootDir);
this.#throwOnModuleCollision = throwOnModuleCollision;
}
async initialize({ files, pluginState }) {
if (pluginState != null && pluginState.version === this.#raw.version) {
this.#raw = pluginState;
} else {
await this.bulkUpdate({
addedOrModified: [
...files.metadataIterator({
includeNodeModules: false,
includeSymlinks: false,
}),
].map(({ canonicalPath, metadata }) => [canonicalPath, metadata]),
removed: [],
});
}
}
getMockModule(name) {
const mockPosixRelativePath =
this.#raw.mocks.get(name) || this.#raw.mocks.get(name + "/index");
if (typeof mockPosixRelativePath !== "string") {
return null;
}
return this.#pathUtils.normalToAbsolute(
(0, _normalizePathSeparatorsToSystem.default)(mockPosixRelativePath),
);
}
async bulkUpdate(delta) {
for (const [relativeFilePath] of delta.removed) {
this.onRemovedFile(relativeFilePath);
}
for (const [relativeFilePath] of delta.addedOrModified) {
this.onNewOrModifiedFile(relativeFilePath);
}
}
onNewOrModifiedFile(relativeFilePath) {
const absoluteFilePath = this.#pathUtils.normalToAbsolute(relativeFilePath);
if (!this.#mocksPattern.test(absoluteFilePath)) {
return;
}
const mockName = (0, _getMockName.default)(absoluteFilePath);
const posixRelativePath = (0, _normalizePathSeparatorsToPosix.default)(
relativeFilePath,
);
const existingMockPosixPath = this.#raw.mocks.get(mockName);
if (existingMockPosixPath != null) {
if (existingMockPosixPath !== posixRelativePath) {
let duplicates = this.#raw.duplicates.get(mockName);
if (duplicates == null) {
duplicates = new Set([existingMockPosixPath, posixRelativePath]);
this.#raw.duplicates.set(mockName, duplicates);
} else {
duplicates.add(posixRelativePath);
}
this.#console.warn(this.#getMessageForDuplicates(mockName, duplicates));
}
}
this.#raw.mocks.set(mockName, posixRelativePath);
}
onRemovedFile(relativeFilePath) {
const absoluteFilePath = this.#pathUtils.normalToAbsolute(relativeFilePath);
if (!this.#mocksPattern.test(absoluteFilePath)) {
return;
}
const mockName = (0, _getMockName.default)(absoluteFilePath);
const duplicates = this.#raw.duplicates.get(mockName);
if (duplicates != null) {
const posixRelativePath = (0, _normalizePathSeparatorsToPosix.default)(
relativeFilePath,
);
duplicates.delete(posixRelativePath);
if (duplicates.size === 1) {
this.#raw.duplicates.delete(mockName);
}
const remaining = (0, _nullthrows.default)(
duplicates.values().next().value,
);
this.#raw.mocks.set(mockName, remaining);
} else {
this.#raw.mocks.delete(mockName);
}
}
getSerializableSnapshot() {
return {
mocks: new Map(this.#raw.mocks),
duplicates: new Map(
[...this.#raw.duplicates].map(([k, v]) => [k, new Set(v)]),
),
version: this.#raw.version,
};
}
assertValid() {
if (!this.#throwOnModuleCollision) {
return;
}
const errors = [];
for (const [mockName, relativePosixPaths] of this.#raw.duplicates) {
errors.push(this.#getMessageForDuplicates(mockName, relativePosixPaths));
}
if (errors.length > 0) {
throw new Error(
`Mock map has ${errors.length} error${errors.length > 1 ? "s" : ""}:\n${errors.join("\n")}`,
);
}
}
#getMessageForDuplicates(mockName, relativePosixPaths) {
return (
"Duplicate manual mock found for `" +
mockName +
"`:\n" +
[...relativePosixPaths]
.map(
(relativePosixPath) =>
" * <rootDir>" +
_path.default.sep +
this.#pathUtils.absoluteToNormal(
(0, _normalizePathSeparatorsToSystem.default)(relativePosixPath),
) +
"\n",
)
.join("")
);
}
getCacheKey() {
return (
this.#mocksPattern.source.replaceAll("\\\\", "\\/") +
"," +
this.#mocksPattern.flags
);
}
}
exports.default = MockPlugin;
@@ -0,0 +1,216 @@
/**
* 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 {
FileMapDelta,
FileMapPlugin,
FileMapPluginInitOptions,
MockMap as IMockMap,
Path,
RawMockMap,
} from '../flow-types';
import normalizePathSeparatorsToPosix from '../lib/normalizePathSeparatorsToPosix';
import normalizePathSeparatorsToSystem from '../lib/normalizePathSeparatorsToSystem';
import {RootPathUtils} from '../lib/RootPathUtils';
import getMockName from './mocks/getMockName';
import nullthrows from 'nullthrows';
import path from 'path';
export const CACHE_VERSION = 2;
export default class MockPlugin implements FileMapPlugin<RawMockMap>, IMockMap {
+name = 'mocks';
+#mocksPattern: RegExp;
#raw: RawMockMap;
+#rootDir: Path;
+#pathUtils: RootPathUtils;
+#console: typeof console;
#throwOnModuleCollision: boolean;
constructor({
console,
mocksPattern,
rawMockMap = {
mocks: new Map(),
duplicates: new Map(),
version: CACHE_VERSION,
},
rootDir,
throwOnModuleCollision,
}: $ReadOnly<{
console: typeof console,
mocksPattern: RegExp,
rawMockMap?: RawMockMap,
rootDir: Path,
throwOnModuleCollision: boolean,
}>) {
this.#mocksPattern = mocksPattern;
if (rawMockMap.version !== CACHE_VERSION) {
throw new Error('Incompatible state passed to MockPlugin');
}
this.#raw = rawMockMap;
this.#rootDir = rootDir;
this.#console = console;
this.#pathUtils = new RootPathUtils(rootDir);
this.#throwOnModuleCollision = throwOnModuleCollision;
}
async initialize({
files,
pluginState,
}: FileMapPluginInitOptions<RawMockMap>): Promise<void> {
if (pluginState != null && pluginState.version === this.#raw.version) {
// Use cached state directly if available
this.#raw = pluginState;
} else {
// Otherwise, traverse all files to rebuild
await this.bulkUpdate({
addedOrModified: [
...files.metadataIterator({
includeNodeModules: false,
includeSymlinks: false,
}),
].map(({canonicalPath, metadata}) => [canonicalPath, metadata]),
removed: [],
});
}
}
getMockModule(name: string): ?Path {
const mockPosixRelativePath =
this.#raw.mocks.get(name) || this.#raw.mocks.get(name + '/index');
if (typeof mockPosixRelativePath !== 'string') {
return null;
}
return this.#pathUtils.normalToAbsolute(
normalizePathSeparatorsToSystem(mockPosixRelativePath),
);
}
async bulkUpdate(delta: FileMapDelta): Promise<void> {
// Process removals first so that moves aren't treated as duplicates.
for (const [relativeFilePath] of delta.removed) {
this.onRemovedFile(relativeFilePath);
}
for (const [relativeFilePath] of delta.addedOrModified) {
this.onNewOrModifiedFile(relativeFilePath);
}
}
onNewOrModifiedFile(relativeFilePath: Path): void {
const absoluteFilePath = this.#pathUtils.normalToAbsolute(relativeFilePath);
if (!this.#mocksPattern.test(absoluteFilePath)) {
return;
}
const mockName = getMockName(absoluteFilePath);
const posixRelativePath = normalizePathSeparatorsToPosix(relativeFilePath);
const existingMockPosixPath = this.#raw.mocks.get(mockName);
if (existingMockPosixPath != null) {
if (existingMockPosixPath !== posixRelativePath) {
let duplicates = this.#raw.duplicates.get(mockName);
if (duplicates == null) {
duplicates = new Set([existingMockPosixPath, posixRelativePath]);
this.#raw.duplicates.set(mockName, duplicates);
} else {
duplicates.add(posixRelativePath);
}
this.#console.warn(this.#getMessageForDuplicates(mockName, duplicates));
}
}
// If there are duplicates and we don't throw, the latest mock wins.
// This is to preserve backwards compatibility, but it's unpredictable.
this.#raw.mocks.set(mockName, posixRelativePath);
}
onRemovedFile(relativeFilePath: Path): void {
const absoluteFilePath = this.#pathUtils.normalToAbsolute(relativeFilePath);
if (!this.#mocksPattern.test(absoluteFilePath)) {
return;
}
const mockName = getMockName(absoluteFilePath);
const duplicates = this.#raw.duplicates.get(mockName);
if (duplicates != null) {
const posixRelativePath =
normalizePathSeparatorsToPosix(relativeFilePath);
duplicates.delete(posixRelativePath);
if (duplicates.size === 1) {
this.#raw.duplicates.delete(mockName);
}
// Set the mock to a remaining duplicate. Should never be empty.
const remaining = nullthrows(duplicates.values().next().value);
this.#raw.mocks.set(mockName, remaining);
} else {
this.#raw.mocks.delete(mockName);
}
}
getSerializableSnapshot(): RawMockMap {
return {
mocks: new Map(this.#raw.mocks),
duplicates: new Map(
[...this.#raw.duplicates].map(([k, v]) => [k, new Set(v)]),
),
version: this.#raw.version,
};
}
assertValid(): void {
if (!this.#throwOnModuleCollision) {
return;
}
// Throw an aggregate error for each duplicate.
const errors = [];
for (const [mockName, relativePosixPaths] of this.#raw.duplicates) {
errors.push(this.#getMessageForDuplicates(mockName, relativePosixPaths));
}
if (errors.length > 0) {
throw new Error(
`Mock map has ${errors.length} error${errors.length > 1 ? 's' : ''}:\n${errors.join('\n')}`,
);
}
}
#getMessageForDuplicates(
mockName: string,
relativePosixPaths: $ReadOnlySet<string>,
): string {
return (
'Duplicate manual mock found for `' +
mockName +
'`:\n' +
[...relativePosixPaths]
.map(
relativePosixPath =>
' * <rootDir>' +
path.sep +
this.#pathUtils.absoluteToNormal(
normalizePathSeparatorsToSystem(relativePosixPath),
) +
'\n',
)
.join('')
);
}
getCacheKey(): string {
return (
this.#mocksPattern.source.replaceAll('\\\\', '\\/') +
',' +
this.#mocksPattern.flags
);
}
}
@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.DuplicateHasteCandidatesError = void 0;
var _constants = _interopRequireDefault(require("../../constants"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
class DuplicateHasteCandidatesError extends Error {
constructor(name, platform, supportsNativePlatform, duplicatesSet) {
const platformMessage = getPlatformMessage(platform);
super(
`The name \`${name}\` was looked up in the Haste module map. It ` +
"cannot be resolved, because there exists several different " +
"files, or packages, that provide a module for " +
`that particular name and platform. ${platformMessage} You must ` +
"delete or exclude files until there remains only one of these:\n\n" +
Array.from(duplicatesSet)
.map(
([dupFilePath, dupFileType]) =>
` * \`${dupFilePath}\` (${getTypeMessage(dupFileType)})\n`,
)
.sort()
.join(""),
);
this.hasteName = name;
this.platform = platform;
this.supportsNativePlatform = supportsNativePlatform;
this.duplicatesSet = duplicatesSet;
}
}
exports.DuplicateHasteCandidatesError = DuplicateHasteCandidatesError;
function getPlatformMessage(platform) {
if (platform === _constants.default.GENERIC_PLATFORM) {
return "The platform is generic (no extension).";
}
return `The platform extension is \`${platform}\`.`;
}
function getTypeMessage(type) {
switch (type) {
case _constants.default.MODULE:
return "module";
case _constants.default.PACKAGE:
return "package";
}
return "unknown";
}
@@ -0,0 +1,65 @@
/**
* 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 {DuplicatesSet} from '../../flow-types';
import H from '../../constants';
export class DuplicateHasteCandidatesError extends Error {
hasteName: string;
platform: string | null;
supportsNativePlatform: boolean;
duplicatesSet: DuplicatesSet;
constructor(
name: string,
platform: string,
supportsNativePlatform: boolean,
duplicatesSet: DuplicatesSet,
) {
const platformMessage = getPlatformMessage(platform);
super(
`The name \`${name}\` was looked up in the Haste module map. It ` +
'cannot be resolved, because there exists several different ' +
'files, or packages, that provide a module for ' +
`that particular name and platform. ${platformMessage} You must ` +
'delete or exclude files until there remains only one of these:\n\n' +
Array.from(duplicatesSet)
.map(
([dupFilePath, dupFileType]) =>
` * \`${dupFilePath}\` (${getTypeMessage(dupFileType)})\n`,
)
.sort()
.join(''),
);
this.hasteName = name;
this.platform = platform;
this.supportsNativePlatform = supportsNativePlatform;
this.duplicatesSet = duplicatesSet;
}
}
function getPlatformMessage(platform: string) {
if (platform === H.GENERIC_PLATFORM) {
return 'The platform is generic (no extension).';
}
return `The platform extension is \`${platform}\`.`;
}
function getTypeMessage(type: number) {
switch (type) {
case H.MODULE:
return 'module';
case H.PACKAGE:
return 'package';
}
return 'unknown';
}
@@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.HasteConflictsError = void 0;
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
class HasteConflictsError extends Error {
#conflicts;
constructor(conflicts) {
super(
`Found ${conflicts.length} Haste conflict(s). Haste module IDs must be globally unique in the codebase.`,
);
this.#conflicts = conflicts;
}
getDetailedMessage(pathsRelativeToRoot) {
const messages = [];
const conflicts = this.#conflicts;
if (conflicts.some((conflict) => conflict.type === "duplicate")) {
messages.push(
'Advice: Resolve conflicts of type "duplicate" by renaming one or both of the conflicting modules, or by excluding conflicting paths from Haste.',
);
}
if (conflicts.some((conflict) => conflict.type === "shadowing")) {
messages.push(
'Advice: Resolve conflicts of type "shadowing" by moving the modules to the same folder, or by excluding conflicting paths from Haste.',
);
}
let index = 0;
for (const conflict of conflicts) {
const itemHeader = index + 1 + ". ";
const indent = " ".repeat(itemHeader.length + 2);
messages.push(
"\n" +
itemHeader +
conflict.id +
(conflict.platform != null ? `.${conflict.platform}` : "") +
` (${conflict.type})`,
);
for (const modulePath of conflict.absolutePaths) {
messages.push(
indent +
(pathsRelativeToRoot != null
? _path.default.relative(pathsRelativeToRoot, modulePath)
: modulePath),
);
}
++index;
}
return messages.join("\n");
}
}
exports.HasteConflictsError = HasteConflictsError;
@@ -0,0 +1,62 @@
/**
* 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 {HasteConflict} from '../../flow-types';
import path from 'path';
export class HasteConflictsError extends Error {
#conflicts: $ReadOnlyArray<HasteConflict>;
constructor(conflicts: $ReadOnlyArray<HasteConflict>) {
super(
`Found ${conflicts.length} Haste conflict(s). Haste module IDs must be globally unique in the codebase.`,
);
this.#conflicts = conflicts;
}
getDetailedMessage(pathsRelativeToRoot: ?string): string {
const messages: Array<string> = [];
const conflicts = this.#conflicts;
if (conflicts.some(conflict => conflict.type === 'duplicate')) {
messages.push(
'Advice: Resolve conflicts of type "duplicate" by renaming one or both of the conflicting modules, or by excluding conflicting paths from Haste.',
);
}
if (conflicts.some(conflict => conflict.type === 'shadowing')) {
messages.push(
'Advice: Resolve conflicts of type "shadowing" by moving the modules to the same folder, or by excluding conflicting paths from Haste.',
);
}
let index = 0;
for (const conflict of conflicts) {
const itemHeader = index + 1 + '. ';
const indent = ' '.repeat(itemHeader.length + 2);
messages.push(
'\n' +
itemHeader +
conflict.id +
(conflict.platform != null ? `.${conflict.platform}` : '') +
` (${conflict.type})`,
);
for (const modulePath of conflict.absolutePaths) {
messages.push(
indent +
(pathsRelativeToRoot != null
? path.relative(pathsRelativeToRoot, modulePath)
: modulePath),
);
}
++index;
}
return messages.join('\n');
}
}
@@ -0,0 +1,73 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.computeHasteConflicts = computeHasteConflicts;
var _constants = _interopRequireDefault(require("../../constants"));
var _sorting = require("../../lib/sorting");
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function computeHasteConflicts({ duplicates, map, rootDir }) {
const conflicts = [];
for (const [id, dupsByPlatform] of duplicates.entries()) {
for (const [platform, conflictingModules] of dupsByPlatform) {
conflicts.push({
id,
platform:
platform === _constants.default.GENERIC_PLATFORM ? null : platform,
absolutePaths: [...conflictingModules.keys()]
.map((modulePath) => _path.default.resolve(rootDir, modulePath))
.sort(),
type: "duplicate",
});
}
}
for (const [id, data] of map) {
const conflictPaths = new Set();
const basePaths = [];
for (const basePlatform of [
_constants.default.NATIVE_PLATFORM,
_constants.default.GENERIC_PLATFORM,
]) {
if (data[basePlatform] == null) {
continue;
}
const basePath = data[basePlatform][0];
basePaths.push(basePath);
const basePathDir = _path.default.dirname(basePath);
for (const platform of Object.keys(data)) {
if (
platform === basePlatform ||
platform === _constants.default.GENERIC_PLATFORM
) {
continue;
}
const platformPath = data[platform][0];
if (_path.default.dirname(platformPath) !== basePathDir) {
conflictPaths.add(platformPath);
}
}
}
if (conflictPaths.size) {
conflicts.push({
id,
platform: null,
absolutePaths: [...new Set([...conflictPaths, ...basePaths])]
.map((modulePath) => _path.default.resolve(rootDir, modulePath))
.sort(),
type: "shadowing",
});
}
}
conflicts.sort(
(0, _sorting.chainComparators)(
(a, b) => (0, _sorting.compareStrings)(a.type, b.type),
(a, b) => (0, _sorting.compareStrings)(a.id, b.id),
(a, b) => (0, _sorting.compareStrings)(a.platform, b.platform),
),
);
return conflicts;
}
@@ -0,0 +1,105 @@
/**
* 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
*/
import type {HasteMapItem} from '../../flow-types';
import H from '../../constants';
import {chainComparators, compareStrings} from '../../lib/sorting';
import path from 'path';
type Conflict = {
id: string,
platform: string | null,
absolutePaths: Array<string>,
type: 'duplicate' | 'shadowing',
};
export function computeHasteConflicts({
duplicates,
map,
rootDir,
}: $ReadOnly<{
duplicates: $ReadOnlyMap<
string,
$ReadOnlyMap<string, $ReadOnlyMap<string, number>>,
>,
map: $ReadOnlyMap<string, HasteMapItem>,
rootDir: string,
}>): Array<Conflict> {
const conflicts: Array<Conflict> = [];
// Add duplicates reported by metro-file-map
for (const [id, dupsByPlatform] of duplicates.entries()) {
for (const [platform, conflictingModules] of dupsByPlatform) {
conflicts.push({
id,
platform: platform === H.GENERIC_PLATFORM ? null : platform,
absolutePaths: [...conflictingModules.keys()]
.map(modulePath => path.resolve(rootDir, 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 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 => path.resolve(rootDir, 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;
}
@@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = getPlatformExtension;
function getPlatformExtension(file, platforms) {
const last = file.lastIndexOf(".");
const secondToLast = file.lastIndexOf(".", last - 1);
if (secondToLast === -1) {
return null;
}
const platform = file.substring(secondToLast + 1, last);
return platforms.has(platform) ? platform : null;
}
@@ -0,0 +1,23 @@
/**
* 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.
*
* @format
* @flow strict
*/
// Extract platform extension: index.ios.js -> ios
export default function getPlatformExtension(
file: string,
platforms: $ReadOnlySet<string>,
): ?string {
const last = file.lastIndexOf('.');
const secondToLast = file.lastIndexOf('.', last - 1);
if (secondToLast === -1) {
return null;
}
const platform = file.substring(secondToLast + 1, last);
return platforms.has(platform) ? platform : null;
}
@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var path = _interopRequireWildcard(require("path"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
const MOCKS_PATTERN = path.sep + "__mocks__" + path.sep;
const getMockName = (filePath) => {
const mockPath = filePath.split(MOCKS_PATTERN)[1];
return mockPath
.substring(0, mockPath.lastIndexOf(path.extname(mockPath)))
.replaceAll("\\", "/");
};
var _default = (exports.default = getMockName);
@@ -0,0 +1,22 @@
/**
* 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.
*
* @format
* @flow strict
*/
import * as path from 'path';
const MOCKS_PATTERN = path.sep + '__mocks__' + path.sep;
const getMockName = (filePath: string): string => {
const mockPath = filePath.split(MOCKS_PATTERN)[1];
return mockPath
.substring(0, mockPath.lastIndexOf(path.extname(mockPath)))
.replaceAll('\\', '/');
};
export default getMockName;