From a5a86c302aef71111be15816cbdb37ec98d6921a Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Wed, 31 May 2023 09:38:28 -0700 Subject: [PATCH] Validate against ref access in render --- .../packages/snap/src/compiler-worker.ts | 5 + compiler/forget/src/CompilerPipeline.ts | 4 + compiler/forget/src/HIR/Environment.ts | 10 ++ .../src/HIR/ValidateNoRefAccesInRender.ts | 104 ++++++++++++++++++ compiler/forget/src/HIR/index.ts | 1 + ...invalid-access-ref-during-render.expect.md | 25 +++++ .../error.invalid-access-ref-during-render.js | 6 + ...ror.invalid-assign-hook-to-local.expect.md | 6 +- .../error.invalid-assign-hook-to-local.js | 6 +- ...ror.invalid-pass-ref-to-function.expect.md | 20 ++++ .../error.invalid-pass-ref-to-function.js | 5 + ...d-set-and-read-ref-during-render.expect.md | 20 ++++ ...invalid-set-and-read-ref-during-render.js} | 0 ...-current-aliased-no-added-to-dep.expect.md | 3 +- .../ref-current-aliased-no-added-to-dep.js | 1 + ...rrent-aliased-not-added-to-dep-2.expect.md | 3 +- .../ref-current-aliased-not-added-to-dep-2.js | 1 + ...f-current-field-not-added-to-dep.expect.md | 3 +- .../ref-current-field-not-added-to-dep.js | 1 + .../ref-current-not-added-to-dep-2.expect.md | 3 +- .../ref-current-not-added-to-dep-2.js | 1 + .../fixtures/compiler/ref-in-effect.expect.md | 58 ++++++++++ .../fixtures/compiler/ref-in-effect.js | 10 ++ .../compiler/useRef-mutable.expect.md | 23 ---- 24 files changed, 286 insertions(+), 33 deletions(-) create mode 100644 compiler/forget/src/HIR/ValidateNoRefAccesInRender.ts create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.js create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/error.invalid-pass-ref-to-function.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/error.invalid-pass-ref-to-function.js create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/error.invalid-set-and-read-ref-during-render.expect.md rename compiler/forget/src/__tests__/fixtures/compiler/{useRef-mutable.js => error.invalid-set-and-read-ref-during-render.js} (100%) create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/ref-in-effect.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/compiler/ref-in-effect.js delete mode 100644 compiler/forget/src/__tests__/fixtures/compiler/useRef-mutable.expect.md diff --git a/compiler/forget/packages/snap/src/compiler-worker.ts b/compiler/forget/packages/snap/src/compiler-worker.ts index 2ad5c170f2..08f2ba2a93 100644 --- a/compiler/forget/packages/snap/src/compiler-worker.ts +++ b/compiler/forget/packages/snap/src/compiler-worker.ts @@ -96,6 +96,7 @@ export async function compile( let enableAssumeHooksFollowRulesOfReact = false; let enableTreatHooksAsFunctions = true; let disableAllMemoization = false; + let validateRefAccessDuringRender = true; if (firstLine.indexOf("@forgetDirective") !== -1) { enableOnlyOnUseForgetDirective = true; } @@ -132,6 +133,9 @@ export async function compile( if (firstLine.indexOf("@disableAllMemoization true") !== -1) { disableAllMemoization = true; } + if (firstLine.indexOf("@validateRefAccessDuringRender false") !== -1) { + validateRefAccessDuringRender = false; + } const language = parseLanguage(firstLine); @@ -154,6 +158,7 @@ export async function compile( inlineUseMemo: true, memoizeJsxElements, validateHooksUsage: true, + validateRefAccessDuringRender, }, logger: null, gating, diff --git a/compiler/forget/src/CompilerPipeline.ts b/compiler/forget/src/CompilerPipeline.ts index cedd57b480..2eaa74a933 100644 --- a/compiler/forget/src/CompilerPipeline.ts +++ b/compiler/forget/src/CompilerPipeline.ts @@ -14,6 +14,7 @@ import { ReactiveFunction, validateConsistentIdentifiers, validateHooksUsage, + validateNoRefAccessInRender, validateTerminalSuccessors, validateUnconditionalHooks, } from "./HIR"; @@ -93,6 +94,9 @@ export function* run( inferTypes(hir); yield log({ kind: "hir", name: "InferTypes", value: hir }); + if (env.validateRefAccessDuringRender) { + validateNoRefAccessInRender(hir); + } if (env.validateHooksUsage) { validateHooksUsage(hir); const conditionalHooksResult = validateUnconditionalHooks(hir).unwrap(); diff --git a/compiler/forget/src/HIR/Environment.ts b/compiler/forget/src/HIR/Environment.ts index 8b575edbb2..f6188d78a2 100644 --- a/compiler/forget/src/HIR/Environment.ts +++ b/compiler/forget/src/HIR/Environment.ts @@ -67,6 +67,13 @@ export type EnvironmentConfig = Partial<{ */ validateHooksUsage: boolean; + /** + * Validate that ref values (`ref.current`) are not accessed during render. + * + * Defaults to false + */ + validateRefAccessDuringRender: boolean; + /** * Enable inlining of `useMemo()` function expressions so that they can be more optimally * compiled. @@ -119,6 +126,7 @@ export class Environment { #nextIdentifer: number = 0; #nextBlock: number = 0; validateHooksUsage: boolean; + validateRefAccessDuringRender: boolean; enableFunctionCallSignatureOptimizations: boolean; enableAssumeHooksFollowRulesOfReact: boolean; enableTreatHooksAsFunctions: boolean; @@ -154,6 +162,8 @@ export class Environment { this.#globals = DEFAULT_GLOBALS; } this.validateHooksUsage = config?.validateHooksUsage ?? false; + this.validateRefAccessDuringRender = + config?.validateRefAccessDuringRender ?? false; this.enableFunctionCallSignatureOptimizations = config?.enableFunctionCallSignatureOptimizations ?? false; this.enableAssumeHooksFollowRulesOfReact = diff --git a/compiler/forget/src/HIR/ValidateNoRefAccesInRender.ts b/compiler/forget/src/HIR/ValidateNoRefAccesInRender.ts new file mode 100644 index 0000000000..21d84e04c5 --- /dev/null +++ b/compiler/forget/src/HIR/ValidateNoRefAccesInRender.ts @@ -0,0 +1,104 @@ +/** + * 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. + */ + +import { + CompilerError, + CompilerErrorDetail, + ErrorSeverity, +} from "../CompilerError"; +import { HIRFunction, Place, isRefValueType, isUseRefType } from "./HIR"; +import { printPlace } from "./PrintHIR"; +import { eachInstructionValueOperand, eachTerminalOperand } from "./visitors"; + +/** + * Validates that ref values (the `current` property) are not accessed during render. + * This validation is conservative and only rejects accesses of known ref values: + * + * ```javascript + * // ERROR + * const ref = useRef(); + * ref.current; + * + * const ref = useRef(); + * foo(ref); // may access .current + * + * // ALLOWED + * const ref = useHookThatReturnsRef(); + * ref.current; + * ``` + * + * In the future we may reject more cases, based on either object names (`fooRef.current` is likely a ref) + * or based on property name alone (`foo.current` might be a ref). + */ +export function validateNoRefAccessInRender(fn: HIRFunction): void { + const error = new CompilerError(); + + for (const [, block] of fn.body.blocks) { + for (const instr of block.instructions) { + switch (instr.value.kind) { + case "FunctionExpression": { + // For now we assume *all* function expressions are safe, eventually we can + // be more precise and disallow ref access in functions that may be called + // during render + break; + } + case "CallExpression": + case "NewExpression": { + for (const operand of eachInstructionValueOperand(instr.value)) { + validateNonRefValue(error, operand); + validateNonRefObject(error, operand); + } + break; + } + default: { + for (const operand of eachInstructionValueOperand(instr.value)) { + validateNonRefValue(error, operand); + } + } + } + } + for (const operand of eachTerminalOperand(block.terminal)) { + validateNonRefValue(error, operand); + } + } + + if (error.hasErrors()) { + throw error; + } +} + +// Check that the operand's type is not that of useRef().current (the ref's current value) +function validateNonRefValue(error: CompilerError, operand: Place): void { + if (isRefValueType(operand.identifier)) { + error.pushErrorDetail( + new CompilerErrorDetail({ + codeframe: null, + description: `Cannot access ref value at ${printPlace(operand)}`, + loc: typeof operand.loc !== "symbol" ? operand.loc : null, + reason: + "Ref values (the `current` property) may not be accessed during render", + severity: ErrorSeverity.InvalidInput, + }) + ); + } +} + +// Check that the operand's type is not that of useRef() return value (the ref container) +function validateNonRefObject(error: CompilerError, operand: Place): void { + if (isUseRefType(operand.identifier)) { + error.pushErrorDetail( + new CompilerErrorDetail({ + codeframe: null, + description: `Cannot access ref object at ${printPlace(operand)}`, + loc: typeof operand.loc !== "symbol" ? operand.loc : null, + reason: + "Ref values may not be passed to functions because they could read the ref value (`current` property) during render", + severity: ErrorSeverity.InvalidInput, + }) + ); + } +} diff --git a/compiler/forget/src/HIR/index.ts b/compiler/forget/src/HIR/index.ts index 47e296ed83..609e88ff7b 100644 --- a/compiler/forget/src/HIR/index.ts +++ b/compiler/forget/src/HIR/index.ts @@ -19,5 +19,6 @@ export { mergeConsecutiveBlocks } from "./MergeConsecutiveBlocks"; export { printFunction, printHIR } from "./PrintHIR"; export { validateConsistentIdentifiers } from "./ValidateConsistentIdentifiers"; export { validateHooksUsage } from "./ValidateHooksUsage"; +export { validateNoRefAccessInRender } from "./ValidateNoRefAccesInRender"; export { validateTerminalSuccessors } from "./ValidateTerminalSuccessors"; export { validateUnconditionalHooks } from "./ValidateUnconditionalHooks"; diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md new file mode 100644 index 0000000000..8a816009a2 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md @@ -0,0 +1,25 @@ + +## Input + +```javascript +// @debug +function Component(props) { + const ref = useRef(null); + const value = ref.current; + return value; +} + +``` + + +## Error + +``` +[ReactForget] InvalidInput: Ref values (the `current` property) may not be accessed during render. Cannot access ref value at $20:TObject (4:4) + +[ReactForget] InvalidInput: Ref values (the `current` property) may not be accessed during render. Cannot access ref value at value$21:TObject (5:5) + +[ReactForget] InvalidInput: Ref values (the `current` property) may not be accessed during render. Cannot access ref value at $23:TObject (5:5) +``` + + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.js b/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.js new file mode 100644 index 0000000000..0be1f1047a --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.js @@ -0,0 +1,6 @@ +// @debug +function Component(props) { + const ref = useRef(null); + const value = ref.current; + return value; +} diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-assign-hook-to-local.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-assign-hook-to-local.expect.md index 398153b4a5..e1cbf724a4 100644 --- a/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-assign-hook-to-local.expect.md +++ b/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-assign-hook-to-local.expect.md @@ -3,9 +3,9 @@ ```javascript function Component(props) { - const x = useRef; - const ref = x(null); - return ref.current; + const x = useState; + const state = x(null); + return state[0]; } ``` diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-assign-hook-to-local.js b/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-assign-hook-to-local.js index ab676997a8..886860e4de 100644 --- a/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-assign-hook-to-local.js +++ b/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-assign-hook-to-local.js @@ -1,5 +1,5 @@ function Component(props) { - const x = useRef; - const ref = x(null); - return ref.current; + const x = useState; + const state = x(null); + return state[0]; } diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-pass-ref-to-function.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-pass-ref-to-function.expect.md new file mode 100644 index 0000000000..f90ca6a5e6 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-pass-ref-to-function.expect.md @@ -0,0 +1,20 @@ + +## Input + +```javascript +function Component(props) { + const ref = useRef(null); + const x = foo(ref); + return x.current; +} + +``` + + +## Error + +``` +[ReactForget] InvalidInput: Ref values may not be passed to functions because they could read the ref value (`current` property) during render. Cannot access ref object at $22:TObject (3:3) +``` + + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-pass-ref-to-function.js b/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-pass-ref-to-function.js new file mode 100644 index 0000000000..ce93fc174c --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-pass-ref-to-function.js @@ -0,0 +1,5 @@ +function Component(props) { + const ref = useRef(null); + const x = foo(ref); + return x.current; +} diff --git a/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-set-and-read-ref-during-render.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-set-and-read-ref-during-render.expect.md new file mode 100644 index 0000000000..8cfd73d10c --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-set-and-read-ref-during-render.expect.md @@ -0,0 +1,20 @@ + +## Input + +```javascript +function Component(props) { + const ref = useRef(null); + ref.current = props.value; + return ref.current; +} + +``` + + +## Error + +``` +[ReactForget] InvalidInput: Ref values (the `current` property) may not be accessed during render. Cannot access ref value at $25:TObject (4:4) +``` + + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/useRef-mutable.js b/compiler/forget/src/__tests__/fixtures/compiler/error.invalid-set-and-read-ref-during-render.js similarity index 100% rename from compiler/forget/src/__tests__/fixtures/compiler/useRef-mutable.js rename to compiler/forget/src/__tests__/fixtures/compiler/error.invalid-set-and-read-ref-during-render.js diff --git a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-no-added-to-dep.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-no-added-to-dep.expect.md index a68e34a230..351873f98a 100644 --- a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-no-added-to-dep.expect.md +++ b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-no-added-to-dep.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @validateRefAccessDuringRender false function VideoTab() { const ref = useRef(); const t = ref.current; @@ -17,7 +18,7 @@ function VideoTab() { ## Code ```javascript -import { unstable_useMemoCache as useMemoCache } from "react"; +import { unstable_useMemoCache as useMemoCache } from "react"; // @validateRefAccessDuringRender false function VideoTab() { const $ = useMemoCache(3); const ref = useRef(); diff --git a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-no-added-to-dep.js b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-no-added-to-dep.js index 4ee957e987..e11fb47247 100644 --- a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-no-added-to-dep.js +++ b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-no-added-to-dep.js @@ -1,3 +1,4 @@ +// @validateRefAccessDuringRender false function VideoTab() { const ref = useRef(); const t = ref.current; diff --git a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-not-added-to-dep-2.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-not-added-to-dep-2.expect.md index b5ec176278..4a142eb308 100644 --- a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-not-added-to-dep-2.expect.md +++ b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-not-added-to-dep-2.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @validateRefAccessDuringRender false function Foo({ a }) { const ref = useRef(); const val = ref.current; @@ -15,7 +16,7 @@ function Foo({ a }) { ## Code ```javascript -import { unstable_useMemoCache as useMemoCache } from "react"; +import { unstable_useMemoCache as useMemoCache } from "react"; // @validateRefAccessDuringRender false function Foo(t21) { const $ = useMemoCache(4); const { a } = t21; diff --git a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-not-added-to-dep-2.js b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-not-added-to-dep-2.js index 1bdd040c67..92d7decf20 100644 --- a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-not-added-to-dep-2.js +++ b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-aliased-not-added-to-dep-2.js @@ -1,3 +1,4 @@ +// @validateRefAccessDuringRender false function Foo({ a }) { const ref = useRef(); const val = ref.current; diff --git a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-field-not-added-to-dep.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-field-not-added-to-dep.expect.md index 7a630ff951..bb151446f6 100644 --- a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-field-not-added-to-dep.expect.md +++ b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-field-not-added-to-dep.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @validateRefAccessDuringRender false function VideoTab() { const ref = useRef(); let x = () => { @@ -16,7 +17,7 @@ function VideoTab() { ## Code ```javascript -import { unstable_useMemoCache as useMemoCache } from "react"; +import { unstable_useMemoCache as useMemoCache } from "react"; // @validateRefAccessDuringRender false function VideoTab() { const $ = useMemoCache(3); const ref = useRef(); diff --git a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-field-not-added-to-dep.js b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-field-not-added-to-dep.js index 53d4489d8c..fd5c43cdba 100644 --- a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-field-not-added-to-dep.js +++ b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-field-not-added-to-dep.js @@ -1,3 +1,4 @@ +// @validateRefAccessDuringRender false function VideoTab() { const ref = useRef(); let x = () => { diff --git a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-not-added-to-dep-2.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-not-added-to-dep-2.expect.md index 3edc16158e..bb197280f3 100644 --- a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-not-added-to-dep-2.expect.md +++ b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-not-added-to-dep-2.expect.md @@ -2,6 +2,7 @@ ## Input ```javascript +// @validateRefAccessDuringRender false function Foo({ a }) { const ref = useRef(); const x = { a, val: ref.current }; @@ -14,7 +15,7 @@ function Foo({ a }) { ## Code ```javascript -import { unstable_useMemoCache as useMemoCache } from "react"; +import { unstable_useMemoCache as useMemoCache } from "react"; // @validateRefAccessDuringRender false function Foo(t18) { const $ = useMemoCache(4); const { a } = t18; diff --git a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-not-added-to-dep-2.js b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-not-added-to-dep-2.js index 80ced49a9d..a31a11fd3e 100644 --- a/compiler/forget/src/__tests__/fixtures/compiler/ref-current-not-added-to-dep-2.js +++ b/compiler/forget/src/__tests__/fixtures/compiler/ref-current-not-added-to-dep-2.js @@ -1,3 +1,4 @@ +// @validateRefAccessDuringRender false function Foo({ a }) { const ref = useRef(); const x = { a, val: ref.current }; diff --git a/compiler/forget/src/__tests__/fixtures/compiler/ref-in-effect.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/ref-in-effect.expect.md new file mode 100644 index 0000000000..1eb8fb418b --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/ref-in-effect.expect.md @@ -0,0 +1,58 @@ + +## Input + +```javascript +function Component(props) { + const ref = useRef(null); + const onChange = (e) => { + ref.current = e.target.value; + }; + useEffect(() => { + console.log(ref.current); + }); + return ; +} + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; +function Component(props) { + const $ = useMemoCache(4); + const ref = useRef(null); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = (e) => { + ref.current = e.target.value; + }; + $[0] = t0; + } else { + t0 = $[0]; + } + const onChange = t0; + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = () => { + console.log(ref.current); + }; + $[1] = t1; + } else { + t1 = $[1]; + } + useEffect(t1); + const c_2 = $[2] !== onChange; + let t2; + if (c_2) { + t2 = ; + $[2] = onChange; + $[3] = t2; + } else { + t2 = $[3]; + } + return t2; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/compiler/ref-in-effect.js b/compiler/forget/src/__tests__/fixtures/compiler/ref-in-effect.js new file mode 100644 index 0000000000..b26e06c529 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/compiler/ref-in-effect.js @@ -0,0 +1,10 @@ +function Component(props) { + const ref = useRef(null); + const onChange = (e) => { + ref.current = e.target.value; + }; + useEffect(() => { + console.log(ref.current); + }); + return ; +} diff --git a/compiler/forget/src/__tests__/fixtures/compiler/useRef-mutable.expect.md b/compiler/forget/src/__tests__/fixtures/compiler/useRef-mutable.expect.md deleted file mode 100644 index 6ba63442e1..0000000000 --- a/compiler/forget/src/__tests__/fixtures/compiler/useRef-mutable.expect.md +++ /dev/null @@ -1,23 +0,0 @@ - -## Input - -```javascript -function Component(props) { - const ref = useRef(null); - ref.current = props.value; - return ref.current; -} - -``` - -## Code - -```javascript -function Component(props) { - const ref = useRef(null); - ref.current = props.value; - return ref.current; -} - -``` - \ No newline at end of file