- Consolidated duplicate UndoManagers to single instance - Fixed connection promise to only resolve on 'connected' status - Fixed WebSocketProvider import (WebsocketProvider) - Added proper doc.destroy() cleanup - Renamed isPresenceInitialized property to avoid conflict Co-Authored-By: Paperclip <noreply@paperclip.ing>
150 lines
4.7 KiB
Plaintext
150 lines
4.7 KiB
Plaintext
/**
|
|
* 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
|
|
* @format
|
|
* @oncall react_native
|
|
*/
|
|
|
|
import type {PackageJson} from 'metro-resolver/private/types';
|
|
|
|
import {readFileSync} from 'fs';
|
|
import {dirname, sep} from 'path';
|
|
|
|
type GetClosestPackageFn = (absoluteFilePath: string) => ?{
|
|
packageJsonPath: string,
|
|
packageRelativePath: string,
|
|
};
|
|
|
|
type PackageForModule = Readonly<{
|
|
packageJson: PackageJson,
|
|
rootPath: string,
|
|
packageRelativePath: string,
|
|
}>;
|
|
|
|
export class PackageCache {
|
|
#getClosestPackage: GetClosestPackageFn;
|
|
#packageCache: Map<
|
|
string,
|
|
{
|
|
rootPath: string,
|
|
packageJson: PackageJson,
|
|
},
|
|
>;
|
|
// Single cache: module path → pre-built result object, or null (no allocation on hit)
|
|
#resultByModulePath: Map<string, PackageForModule | null>;
|
|
// Reverse index for invalidation: package.json path → set of module paths
|
|
#modulePathsByPackagePath: Map<string, Set<string>>;
|
|
// Module paths that resolved to no package.json (null), for invalidation
|
|
#modulePathsWithNoPackage: Set<string>;
|
|
|
|
constructor(options: {getClosestPackage: GetClosestPackageFn, ...}) {
|
|
this.#getClosestPackage = options.getClosestPackage;
|
|
this.#packageCache = new Map();
|
|
this.#resultByModulePath = new Map();
|
|
this.#modulePathsByPackagePath = new Map();
|
|
this.#modulePathsWithNoPackage = new Set();
|
|
}
|
|
|
|
getPackage(filePath: string): Readonly<{
|
|
rootPath: string,
|
|
packageJson: PackageJson,
|
|
}> {
|
|
let cached = this.#packageCache.get(filePath);
|
|
if (cached == null) {
|
|
cached = {
|
|
rootPath: dirname(filePath),
|
|
packageJson: JSON.parse(readFileSync(filePath, 'utf8')),
|
|
};
|
|
this.#packageCache.set(filePath, cached);
|
|
}
|
|
return cached;
|
|
}
|
|
|
|
getPackageForModule(absoluteModulePath: string): ?PackageForModule {
|
|
const cached = this.#resultByModulePath.get(absoluteModulePath);
|
|
|
|
// Distinguish between `null` (positively no closest package) and
|
|
// `undefined` (no cached result yet)
|
|
// eslint-disable-next-line lint/strictly-null
|
|
if (cached !== undefined) {
|
|
return cached;
|
|
}
|
|
|
|
const closest = this.#getClosestPackage(absoluteModulePath);
|
|
if (closest == null) {
|
|
this.#resultByModulePath.set(absoluteModulePath, null);
|
|
this.#modulePathsWithNoPackage.add(absoluteModulePath);
|
|
return null;
|
|
}
|
|
|
|
const packagePath = closest.packageJsonPath;
|
|
|
|
// Track module→package for invalidation
|
|
let modulePaths = this.#modulePathsByPackagePath.get(packagePath);
|
|
if (modulePaths == null) {
|
|
modulePaths = new Set();
|
|
this.#modulePathsByPackagePath.set(packagePath, modulePaths);
|
|
}
|
|
modulePaths.add(absoluteModulePath);
|
|
|
|
const pkg = this.getPackage(packagePath);
|
|
if (pkg == null) {
|
|
return null;
|
|
}
|
|
|
|
// Cache the pre-built result object — no allocation on future hits
|
|
const result: PackageForModule = {
|
|
packageJson: pkg.packageJson,
|
|
packageRelativePath: closest.packageRelativePath,
|
|
rootPath: pkg.rootPath,
|
|
};
|
|
this.#resultByModulePath.set(absoluteModulePath, result);
|
|
return result;
|
|
}
|
|
|
|
invalidate(filePath: string) {
|
|
this.#packageCache.delete(filePath);
|
|
|
|
// Clean up any cached result for this module path (including null).
|
|
// Derive the package.json path from the cached result to clean up the
|
|
// reverse index.
|
|
const cachedResult = this.#resultByModulePath.get(filePath);
|
|
this.#resultByModulePath.delete(filePath);
|
|
this.#modulePathsWithNoPackage.delete(filePath);
|
|
|
|
if (cachedResult != null) {
|
|
const packagePath = cachedResult.rootPath + sep + 'package.json';
|
|
const modules = this.#modulePathsByPackagePath.get(packagePath);
|
|
if (modules != null) {
|
|
modules.delete(filePath);
|
|
if (modules.size === 0) {
|
|
this.#modulePathsByPackagePath.delete(packagePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If filePath is a package.json, invalidate all module lookups pointing to it
|
|
const modulePaths = this.#modulePathsByPackagePath.get(filePath);
|
|
if (modulePaths != null) {
|
|
for (const modulePath of modulePaths) {
|
|
this.#resultByModulePath.delete(modulePath);
|
|
}
|
|
this.#modulePathsByPackagePath.delete(filePath);
|
|
}
|
|
|
|
// If a package.json was created, modified, or deleted, invalidate all
|
|
// null-cached module results, since modules that previously had no
|
|
// enclosing package.json may now resolve to this one.
|
|
if (filePath.endsWith(sep + 'package.json')) {
|
|
for (const modulePath of this.#modulePathsWithNoPackage) {
|
|
this.#resultByModulePath.delete(modulePath);
|
|
}
|
|
this.#modulePathsWithNoPackage.clear();
|
|
}
|
|
}
|
|
}
|