addons/isl/src/GeneratedFile.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {RepoRelativePath} from './types';
b69ab319
b69ab3110import {atom, useAtomValue} from 'jotai';
b69ab3111import {useMemo} from 'react';
b69ab3112import {LRU} from 'shared/LRU';
b69ab3113import serverAPI from './ClientToServerAPI';
b69ab3114import {t} from './i18n';
b69ab3115import {writeAtom} from './jotaiUtils';
b69ab3116import {GeneratedStatus} from './types';
b69ab3117import {registerDisposable} from './utils';
b69ab3118
b69ab3119export const generatedFileCache = new LRU<RepoRelativePath, GeneratedStatus>(1500);
b69ab3120
b69ab3121/** To avoid sending multiple redundant fetch requests, also save which files are being fetched right now */
b69ab3122const currentlyFetching = new Set<RepoRelativePath>();
b69ab3123
b69ab3124/**
b69ab3125 * Generated files are cached in `generatedFileCache` LRU.
b69ab3126 * For historical reasons, the files are not an atom.
b69ab3127 * In order to allow rerender dependencies when we update file statuses,
b69ab3128 * store a generation index in recoil.
b69ab3129 * This state should generally be used through useGeneratedFileStatus helpers.
b69ab3130 */
b69ab3131const generatedFileGeneration = atom<number>(0);
b69ab3132
b69ab3133registerDisposable(
b69ab3134 currentlyFetching,
b69ab3135 serverAPI.onMessageOfType('fetchedGeneratedStatuses', event => {
b69ab3136 for (const [path, status] of Object.entries(event.results)) {
b69ab3137 generatedFileCache.set(path, status);
b69ab3138 currentlyFetching.delete(path);
b69ab3139 }
b69ab3140 writeAtom(generatedFileGeneration, old => old + 1);
b69ab3141 }),
b69ab3142 import.meta.hot,
b69ab3143);
b69ab3144
b69ab3145export function useGeneratedFileStatus(path: RepoRelativePath): GeneratedStatus {
b69ab3146 useAtomValue(generatedFileGeneration); // update if we get new statuses
b69ab3147 const found = generatedFileCache.get(path);
b69ab3148 if (found == null) {
b69ab3149 fetchMissingGeneratedFileStatuses([path]);
b69ab3150 return GeneratedStatus.Manual;
b69ab3151 }
b69ab3152 return found;
b69ab3153}
b69ab3154
b69ab3155export function getGeneratedFilesFrom(paths: ReadonlyArray<RepoRelativePath>) {
b69ab3156 return Object.fromEntries(
b69ab3157 paths.map(path => [path, generatedFileCache.get(path) ?? GeneratedStatus.Manual]),
b69ab3158 );
b69ab3159}
b69ab3160
b69ab3161export function useGeneratedFileStatuses(
b69ab3162 paths: ReadonlyArray<RepoRelativePath>,
b69ab3163): Record<RepoRelativePath, GeneratedStatus> {
b69ab3164 const generation = useAtomValue(generatedFileGeneration); // update if we get new statuses
b69ab3165
b69ab3166 fetchMissingGeneratedFileStatuses(paths);
b69ab3167
b69ab3168 return useMemo(() => {
b69ab3169 return getGeneratedFilesFrom(paths);
b69ab3170 // eslint-disable-next-line react-hooks/exhaustive-deps
b69ab3171 }, [generation, paths]);
b69ab3172}
b69ab3173
b69ab3174export function getCachedGeneratedFileStatuses(
b69ab3175 paths: ReadonlyArray<RepoRelativePath>,
b69ab3176): Record<RepoRelativePath, GeneratedStatus | undefined> {
b69ab3177 return Object.fromEntries(paths.map(path => [path, generatedFileCache.get(path)]));
b69ab3178}
b69ab3179
b69ab3180/**
b69ab3181 * Hint that this set of files are being used, any files missing from the generated files cache
b69ab3182 * should be checked on the server.
b69ab3183 * No-op if all files already in the cache.
b69ab3184 */
b69ab3185export function fetchMissingGeneratedFileStatuses(files: ReadonlyArray<RepoRelativePath>) {
b69ab3186 let changed = false;
b69ab3187 const notCached = files
b69ab3188 .filter(file => generatedFileCache.get(file) == null && !currentlyFetching.has(file))
b69ab3189 .filter(path => {
b69ab3190 const isGeneratedTestedFromPath =
b69ab3191 path.indexOf('__generated__') !== -1 || path.indexOf('/generated/') !== -1;
b69ab3192 if (isGeneratedTestedFromPath) {
b69ab3193 generatedFileCache.set(path, GeneratedStatus.Generated);
b69ab3194 changed = true;
b69ab3195 }
b69ab3196 return !isGeneratedTestedFromPath;
b69ab3197 });
b69ab3198 if (changed) {
b69ab3199 writeAtom(generatedFileGeneration, old => old + 1);
b69ab31100 }
b69ab31101 if (notCached.length > 0) {
b69ab31102 for (const file of notCached) {
b69ab31103 currentlyFetching.add(file);
b69ab31104 }
b69ab31105 serverAPI.postMessage({
b69ab31106 type: 'fetchGeneratedStatuses',
b69ab31107 paths: notCached,
b69ab31108 });
b69ab31109 }
b69ab31110}
b69ab31111
b69ab31112export function generatedStatusToLabel(status: GeneratedStatus | undefined): string {
b69ab31113 if (status === GeneratedStatus.Generated) {
b69ab31114 return 'generated';
b69ab31115 } else if (status === GeneratedStatus.PartiallyGenerated) {
b69ab31116 return 'partial';
b69ab31117 } else {
b69ab31118 return 'manual';
b69ab31119 }
b69ab31120}
b69ab31121
b69ab31122export function generatedStatusDescription(
b69ab31123 status: GeneratedStatus | undefined,
b69ab31124): string | undefined {
b69ab31125 if (status === GeneratedStatus.Generated) {
b69ab31126 return t('This file is generated');
b69ab31127 } else if (status === GeneratedStatus.PartiallyGenerated) {
b69ab31128 return t('This file is partially generated');
b69ab31129 } else {
b69ab31130 return undefined;
b69ab31131 }
b69ab31132}