4.3 KB133 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import type {RepoRelativePath} from './types';
9
10import {atom, useAtomValue} from 'jotai';
11import {useMemo} from 'react';
12import {LRU} from 'shared/LRU';
13import serverAPI from './ClientToServerAPI';
14import {t} from './i18n';
15import {writeAtom} from './jotaiUtils';
16import {GeneratedStatus} from './types';
17import {registerDisposable} from './utils';
18
19export const generatedFileCache = new LRU<RepoRelativePath, GeneratedStatus>(1500);
20
21/** To avoid sending multiple redundant fetch requests, also save which files are being fetched right now */
22const currentlyFetching = new Set<RepoRelativePath>();
23
24/**
25 * Generated files are cached in `generatedFileCache` LRU.
26 * For historical reasons, the files are not an atom.
27 * In order to allow rerender dependencies when we update file statuses,
28 * store a generation index in recoil.
29 * This state should generally be used through useGeneratedFileStatus helpers.
30 */
31const generatedFileGeneration = atom<number>(0);
32
33registerDisposable(
34 currentlyFetching,
35 serverAPI.onMessageOfType('fetchedGeneratedStatuses', event => {
36 for (const [path, status] of Object.entries(event.results)) {
37 generatedFileCache.set(path, status);
38 currentlyFetching.delete(path);
39 }
40 writeAtom(generatedFileGeneration, old => old + 1);
41 }),
42 import.meta.hot,
43);
44
45export function useGeneratedFileStatus(path: RepoRelativePath): GeneratedStatus {
46 useAtomValue(generatedFileGeneration); // update if we get new statuses
47 const found = generatedFileCache.get(path);
48 if (found == null) {
49 fetchMissingGeneratedFileStatuses([path]);
50 return GeneratedStatus.Manual;
51 }
52 return found;
53}
54
55export function getGeneratedFilesFrom(paths: ReadonlyArray<RepoRelativePath>) {
56 return Object.fromEntries(
57 paths.map(path => [path, generatedFileCache.get(path) ?? GeneratedStatus.Manual]),
58 );
59}
60
61export function useGeneratedFileStatuses(
62 paths: ReadonlyArray<RepoRelativePath>,
63): Record<RepoRelativePath, GeneratedStatus> {
64 const generation = useAtomValue(generatedFileGeneration); // update if we get new statuses
65
66 fetchMissingGeneratedFileStatuses(paths);
67
68 return useMemo(() => {
69 return getGeneratedFilesFrom(paths);
70 // eslint-disable-next-line react-hooks/exhaustive-deps
71 }, [generation, paths]);
72}
73
74export function getCachedGeneratedFileStatuses(
75 paths: ReadonlyArray<RepoRelativePath>,
76): Record<RepoRelativePath, GeneratedStatus | undefined> {
77 return Object.fromEntries(paths.map(path => [path, generatedFileCache.get(path)]));
78}
79
80/**
81 * Hint that this set of files are being used, any files missing from the generated files cache
82 * should be checked on the server.
83 * No-op if all files already in the cache.
84 */
85export function fetchMissingGeneratedFileStatuses(files: ReadonlyArray<RepoRelativePath>) {
86 let changed = false;
87 const notCached = files
88 .filter(file => generatedFileCache.get(file) == null && !currentlyFetching.has(file))
89 .filter(path => {
90 const isGeneratedTestedFromPath =
91 path.indexOf('__generated__') !== -1 || path.indexOf('/generated/') !== -1;
92 if (isGeneratedTestedFromPath) {
93 generatedFileCache.set(path, GeneratedStatus.Generated);
94 changed = true;
95 }
96 return !isGeneratedTestedFromPath;
97 });
98 if (changed) {
99 writeAtom(generatedFileGeneration, old => old + 1);
100 }
101 if (notCached.length > 0) {
102 for (const file of notCached) {
103 currentlyFetching.add(file);
104 }
105 serverAPI.postMessage({
106 type: 'fetchGeneratedStatuses',
107 paths: notCached,
108 });
109 }
110}
111
112export function generatedStatusToLabel(status: GeneratedStatus | undefined): string {
113 if (status === GeneratedStatus.Generated) {
114 return 'generated';
115 } else if (status === GeneratedStatus.PartiallyGenerated) {
116 return 'partial';
117 } else {
118 return 'manual';
119 }
120}
121
122export function generatedStatusDescription(
123 status: GeneratedStatus | undefined,
124): string | undefined {
125 if (status === GeneratedStatus.Generated) {
126 return t('This file is generated');
127 } else if (status === GeneratedStatus.PartiallyGenerated) {
128 return t('This file is partially generated');
129 } else {
130 return undefined;
131 }
132}
133