1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 */78import * as React from 'react';910const {useRef, useEffect, isValidElement} = React;11const ReactSecretInternals =12 //@ts-ignore13 React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE ??14 //@ts-ignore15 React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;1617type MemoCache = Array<number | typeof $empty>;1819const $empty = Symbol.for('react.memo_cache_sentinel');2021// Re-export React.c if present, otherwise fallback to the userspace polyfill for versions of React22// < 19.23export const c =24 // @ts-expect-error25 typeof React.__COMPILER_RUNTIME?.c === 'function'26 ? // @ts-expect-error27 React.__COMPILER_RUNTIME.c28 : function c(size: number) {29 return React.useMemo<Array<unknown>>(() => {30 const $ = new Array(size);31 for (let ii = 0; ii < size; ii++) {32 $[ii] = $empty;33 }34 // This symbol is added to tell the react devtools that this array is from35 // useMemoCache.36 // @ts-ignore37 $[$empty] = true;38 return $;39 }, []);40 };4142const LazyGuardDispatcher: {[key: string]: (...args: Array<any>) => any} = {};43[44 'readContext',45 'useCallback',46 'useContext',47 'useEffect',48 'useImperativeHandle',49 'useInsertionEffect',50 'useLayoutEffect',51 'useMemo',52 'useReducer',53 'useRef',54 'useState',55 'useDebugValue',56 'useDeferredValue',57 'useTransition',58 'useMutableSource',59 'useSyncExternalStore',60 'useId',61 'unstable_isNewReconciler',62 'getCacheSignal',63 'getCacheForType',64 'useCacheRefresh',65].forEach(name => {66 LazyGuardDispatcher[name] = () => {67 throw new Error(68 `[React] Unexpected React hook call (${name}) from a React compiled function. ` +69 "Check that all hooks are called directly and named according to convention ('use[A-Z]') ",70 );71 };72});7374let originalDispatcher: unknown = null;7576// Allow guards are not emitted for useMemoCache77LazyGuardDispatcher['useMemoCache'] = (count: number) => {78 if (originalDispatcher == null) {79 throw new Error(80 'React Compiler internal invariant violation: unexpected null dispatcher',81 );82 } else {83 return (originalDispatcher as any).useMemoCache(count);84 }85};8687enum GuardKind {88 PushGuardContext = 0,89 PopGuardContext = 1,90 PushExpectHook = 2,91 PopExpectHook = 3,92}9394function setCurrent(newDispatcher: any) {95 ReactSecretInternals.ReactCurrentDispatcher.current = newDispatcher;96 return ReactSecretInternals.ReactCurrentDispatcher.current;97}9899const guardFrames: Array<unknown> = [];100101/**102 * When `enableEmitHookGuards` is set, this does runtime validation103 * of the no-conditional-hook-calls rule.104 * As React Compiler needs to statically understand which calls to move out of105 * conditional branches (i.e. React Compiler cannot memoize the results of hook106 * calls), its understanding of "the rules of React" are more restrictive.107 * This validation throws on unsound inputs at runtime.108 *109 * Components should only be invoked through React as React Compiler could memoize110 * the call to AnotherComponent, introducing conditional hook calls in its111 * compiled output.112 * ```js113 * function Invalid(props) {114 * const myJsx = AnotherComponent(props);115 * return <div> { myJsx } </div>;116 * }117 *118 * Hooks must be named as hooks.119 * ```js120 * const renamedHook = useState;121 * function Invalid() {122 * const [state, setState] = renamedHook(0);123 * }124 * ```125 *126 * Hooks must be directly called.127 * ```128 * function call(fn) {129 * return fn();130 * }131 * function Invalid() {132 * const result = call(useMyHook);133 * }134 * ```135 */136export function $dispatcherGuard(kind: GuardKind) {137 const curr = ReactSecretInternals.ReactCurrentDispatcher.current;138 if (kind === GuardKind.PushGuardContext) {139 // Push before checking invariant or errors140 guardFrames.push(curr);141142 if (guardFrames.length === 1) {143 // save if we're the first guard on the stack144 originalDispatcher = curr;145 }146147 if (curr === LazyGuardDispatcher) {148 throw new Error(149 `[React] Unexpected call to custom hook or component from a React compiled function. ` +150 "Check that (1) all hooks are called directly and named according to convention ('use[A-Z]') " +151 'and (2) components are returned as JSX instead of being directly invoked.',152 );153 }154 setCurrent(LazyGuardDispatcher);155 } else if (kind === GuardKind.PopGuardContext) {156 // Pop before checking invariant or errors157 const lastFrame = guardFrames.pop();158159 if (lastFrame == null) {160 throw new Error(161 'React Compiler internal error: unexpected null in guard stack',162 );163 }164 if (guardFrames.length === 0) {165 originalDispatcher = null;166 }167 setCurrent(lastFrame);168 } else if (kind === GuardKind.PushExpectHook) {169 // ExpectHooks could be nested, so we save the current dispatcher170 // for the matching PopExpectHook to restore.171 guardFrames.push(curr);172 setCurrent(originalDispatcher);173 } else if (kind === GuardKind.PopExpectHook) {174 const lastFrame = guardFrames.pop();175 if (lastFrame == null) {176 throw new Error(177 'React Compiler internal error: unexpected null in guard stack',178 );179 }180 setCurrent(lastFrame);181 } else {182 throw new Error('React Compiler internal error: unreachable block' + kind);183 }184}185186export function $reset($: MemoCache) {187 for (let ii = 0; ii < $.length; ii++) {188 $[ii] = $empty;189 }190}191192export function $makeReadOnly() {193 throw new Error('TODO: implement $makeReadOnly in react-compiler-runtime');194}195196/**197 * Instrumentation to count rerenders in React components198 */199export const renderCounterRegistry: Map<200 string,201 Set<{count: number}>202> = new Map();203export function clearRenderCounterRegistry() {204 for (const counters of renderCounterRegistry.values()) {205 counters.forEach(counter => {206 counter.count = 0;207 });208 }209}210211function registerRenderCounter(name: string, val: {count: number}) {212 let counters = renderCounterRegistry.get(name);213 if (counters == null) {214 counters = new Set();215 renderCounterRegistry.set(name, counters);216 }217 counters.add(val);218}219220function removeRenderCounter(name: string, val: {count: number}): void {221 const counters = renderCounterRegistry.get(name);222 if (counters == null) {223 return;224 }225 counters.delete(val);226}227228export function useRenderCounter(name: string): void {229 const val = useRef<{count: number}>(null);230231 if (val.current != null) {232 val.current.count += 1;233 }234 useEffect(() => {235 // Not counting initial render shouldn't be a problem236 if (val.current == null) {237 const counter = {count: 0};238 registerRenderCounter(name, counter);239 // @ts-ignore240 val.current = counter;241 }242 return () => {243 if (val.current !== null) {244 removeRenderCounter(name, val.current);245 }246 };247 });248}249250const seenErrors = new Set();251252export function $structuralCheck(253 oldValue: any,254 newValue: any,255 variableName: string,256 fnName: string,257 kind: string,258 loc: string,259): void {260 function error(l: string, r: string, path: string, depth: number) {261 const str = `${fnName}:${loc} [${kind}] ${variableName}${path} changed from ${l} to ${r} at depth ${depth}`;262 if (seenErrors.has(str)) {263 return;264 }265 seenErrors.add(str);266 console.error(str);267 }268 const depthLimit = 2;269 function recur(oldValue: any, newValue: any, path: string, depth: number) {270 if (depth > depthLimit) {271 return;272 } else if (oldValue === newValue) {273 return;274 } else if (typeof oldValue !== typeof newValue) {275 error(`type ${typeof oldValue}`, `type ${typeof newValue}`, path, depth);276 } else if (typeof oldValue === 'object') {277 const oldArray = Array.isArray(oldValue);278 const newArray = Array.isArray(newValue);279 if (oldValue === null && newValue !== null) {280 error('null', `type ${typeof newValue}`, path, depth);281 } else if (newValue === null) {282 error(`type ${typeof oldValue}`, 'null', path, depth);283 } else if (oldValue instanceof Map) {284 if (!(newValue instanceof Map)) {285 error(`Map instance`, `other value`, path, depth);286 } else if (oldValue.size !== newValue.size) {287 error(288 `Map instance with size ${oldValue.size}`,289 `Map instance with size ${newValue.size}`,290 path,291 depth,292 );293 } else {294 for (const [k, v] of oldValue) {295 if (!newValue.has(k)) {296 error(297 `Map instance with key ${k}`,298 `Map instance without key ${k}`,299 path,300 depth,301 );302 } else {303 recur(v, newValue.get(k), `${path}.get(${k})`, depth + 1);304 }305 }306 }307 } else if (newValue instanceof Map) {308 error('other value', `Map instance`, path, depth);309 } else if (oldValue instanceof Set) {310 if (!(newValue instanceof Set)) {311 error(`Set instance`, `other value`, path, depth);312 } else if (oldValue.size !== newValue.size) {313 error(314 `Set instance with size ${oldValue.size}`,315 `Set instance with size ${newValue.size}`,316 path,317 depth,318 );319 } else {320 for (const v of newValue) {321 if (!oldValue.has(v)) {322 error(323 `Set instance without element ${v}`,324 `Set instance with element ${v}`,325 path,326 depth,327 );328 }329 }330 }331 } else if (newValue instanceof Set) {332 error('other value', `Set instance`, path, depth);333 } else if (oldArray || newArray) {334 if (oldArray !== newArray) {335 error(336 `type ${oldArray ? 'array' : 'object'}`,337 `type ${newArray ? 'array' : 'object'}`,338 path,339 depth,340 );341 } else if (oldValue.length !== newValue.length) {342 error(343 `array with length ${oldValue.length}`,344 `array with length ${newValue.length}`,345 path,346 depth,347 );348 } else {349 for (let ii = 0; ii < oldValue.length; ii++) {350 recur(oldValue[ii], newValue[ii], `${path}[${ii}]`, depth + 1);351 }352 }353 } else if (isValidElement(oldValue) || isValidElement(newValue)) {354 if (isValidElement(oldValue) !== isValidElement(newValue)) {355 error(356 `type ${isValidElement(oldValue) ? 'React element' : 'object'}`,357 `type ${isValidElement(newValue) ? 'React element' : 'object'}`,358 path,359 depth,360 );361 } else if (oldValue.type !== newValue.type) {362 error(363 `React element of type ${oldValue.type}`,364 `React element of type ${newValue.type}`,365 path,366 depth,367 );368 } else {369 recur(370 oldValue.props,371 newValue.props,372 `[props of ${path}]`,373 depth + 1,374 );375 }376 } else {377 for (const key in newValue) {378 if (!(key in oldValue)) {379 error(380 `object without key ${key}`,381 `object with key ${key}`,382 path,383 depth,384 );385 }386 }387 for (const key in oldValue) {388 if (!(key in newValue)) {389 error(390 `object with key ${key}`,391 `object without key ${key}`,392 path,393 depth,394 );395 } else {396 recur(oldValue[key], newValue[key], `${path}.${key}`, depth + 1);397 }398 }399 }400 } else if (typeof oldValue === 'function') {401 // Bail on functions for now402 return;403 } else if (isNaN(oldValue) || isNaN(newValue)) {404 if (isNaN(oldValue) !== isNaN(newValue)) {405 error(406 `${isNaN(oldValue) ? 'NaN' : 'non-NaN value'}`,407 `${isNaN(newValue) ? 'NaN' : 'non-NaN value'}`,408 path,409 depth,410 );411 }412 } else if (oldValue !== newValue) {413 error(oldValue, newValue, path, depth);414 }415 }416 recur(oldValue, newValue, '', 0);417}
Findings
✓ No findings reported for this file.