Skip to content

Commit

Permalink
Open-source Haste conflict computation, remove leaky getRawHasteMap
Browse files Browse the repository at this point in the history
Differential Revision: D66927755
  • Loading branch information
robhogan authored and facebook-github-bot committed Dec 7, 2024
1 parent e15bfd5 commit 9512ae0
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 29 deletions.
17 changes: 8 additions & 9 deletions packages/metro-file-map/src/flow-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,13 @@ export interface MockMap {
getMockModule(name: string): ?Path;
}

export type HasteConflict = {
id: string,
platform: string | null,
absolutePaths: Array<string>,
type: 'duplicate' | 'shadowing',
};

export interface HasteMap {
getModule(
name: string,
Expand All @@ -286,7 +293,7 @@ export interface HasteMap {
_supportsNativePlatform: ?boolean,
): ?Path;

getRawHasteMap(): ReadOnlyRawHasteMap;
computeConflicts(): Array<HasteConflict>;
}

export type HasteMapData = Map<string, HasteMapItem>;
Expand All @@ -307,14 +314,6 @@ export type Path = string;

export type RawMockMap = Map<string, Path>;

export type ReadOnlyRawHasteMap = $ReadOnly<{
duplicates: $ReadOnlyMap<
string,
$ReadOnlyMap<string, $ReadOnlyMap<string, number>>,
>,
map: $ReadOnlyMap<string, HasteMapItem>,
}>;

export type ReadOnlyRawMockMap = $ReadOnlyMap<string, Path>;

export type WatchmanClockSpec =
Expand Down
84 changes: 74 additions & 10 deletions packages/metro-file-map/src/lib/MutableHasteMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,20 @@ import type {
Console,
DuplicatesIndex,
DuplicatesSet,
HasteConflict,
HasteMap,
HasteMapItem,
HasteMapItemMetaData,
HTypeValue,
Path,
ReadOnlyRawHasteMap,
} from '../flow-types';

import H from '../constants';
import {DuplicateError} from './DuplicateError';
import {DuplicateHasteCandidatesError} from './DuplicateHasteCandidatesError';
import getPlatformExtension from './getPlatformExtension';
import {RootPathUtils} from './RootPathUtils';
import {chainComparators, compareStrings} from './sorting';
import path from 'path';

const EMPTY_OBJ: $ReadOnly<{[string]: HasteMapItemMetaData}> = {};
Expand Down Expand Up @@ -82,15 +83,6 @@ export default class MutableHasteMap implements HasteMap {
return this.getModule(name, platform, null, H.PACKAGE);
}

// FIXME: This is only used by Meta-internal validation and should be
// removed or replaced with a less leaky API.
getRawHasteMap(): ReadOnlyRawHasteMap {
return {
duplicates: this.#duplicates,
map: this.#map,
};
}

/**
* 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
Expand Down Expand Up @@ -299,4 +291,76 @@ export default class MutableHasteMap implements HasteMap {
this.#duplicates.delete(moduleName);
}
}

computeConflicts(): Array<HasteConflict> {
const conflicts: Array<HasteConflict> = [];

// Add duplicates reported by metro-file-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;
}
}
35 changes: 35 additions & 0 deletions packages/metro-file-map/src/lib/sorting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* 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
* @format
*/

// Utilities for working with Array.prototype.sort

export function compareStrings(a: null | string, b: null | string): number {
if (a == null) {
return b == null ? 0 : -1;
}
if (b == null) {
return 1;
}
return a.localeCompare(b);
}

export function chainComparators<T>(
...comparators: Array<(a: T, b: T) => number>
): (a: T, b: T) => number {
return (a, b) => {
for (const comparator of comparators) {
const result = comparator(a, b);
if (result !== 0) {
return result;
}
}
return 0;
};
}
18 changes: 8 additions & 10 deletions packages/metro-file-map/types/flow-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,13 @@ export type LookupResult =
type: 'd' | 'f';
};

export type HasteConflict = {
id: string;
platform: string | null;
absolutePaths: Array<string>;
type: 'duplicate' | 'shadowing';
};

export interface HasteMap {
getModule(
name: string,
Expand All @@ -268,7 +275,7 @@ export interface HasteMap {
_supportsNativePlatform: boolean | null,
): Path | null;

getRawHasteMap(): ReadOnlyRawHasteMap;
computeConflicts(): Array<HasteConflict>;
}

export type MockData = Map<string, Path>;
Expand All @@ -287,15 +294,6 @@ export interface MutableFileSystem extends FileSystem {

export type Path = string;

export type ReadOnlyRawHasteMap = Readonly<{
rootDir: Path;
duplicates: ReadonlyMap<
string,
ReadonlyMap<string, ReadonlyMap<string, number>>
>;
map: ReadonlyMap<string, HasteMapItem>;
}>;

export type WatchmanClockSpec =
| string
| Readonly<{scm: Readonly<{'mergebase-with': string}>}>;
Expand Down

0 comments on commit 9512ae0

Please sign in to comment.