From f5d99baf8e64530473fac5fe0dd2bd489f3bf8fc Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 4 Mar 2024 19:00:48 -0500 Subject: [PATCH] Add config option to ignore 'use no forget' directives This will let us test without taking into account the existing 'use no forget' directives to better understand what validations we may need to build. There are two other files that look for this directive that do not seem to take compiler options: 1. https://github.com/facebook/react-forget/blob/408617ec8a5caa815f61d4204cb3fec0775593ca/react/scripts/babel/transform-forget.js#L25 2. https://github.com/facebook/react-forget/blob/408617ec8a5caa815f61d4204cb3fec0775593ca/packages/babel-plugin-react-forget/scripts/jest/makeTransform.ts#L119 Do I need to do anything for those? --- .../src/Entrypoint/Options.ts | 5 ++ .../src/Entrypoint/Program.ts | 18 ++++--- .../compiler/ignore-use-no-forget.expect.md | 53 +++++++++++++++++++ .../fixtures/compiler/ignore-use-no-forget.js | 11 ++++ compiler/packages/snap/src/compiler.ts | 30 ++++++----- 5 files changed, 99 insertions(+), 18 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ignore-use-no-forget.expect.md create mode 100644 compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ignore-use-no-forget.js diff --git a/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Options.ts index 54beb4e7fc..74d71bc82d 100644 --- a/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Options.ts @@ -104,6 +104,10 @@ export type PluginOptions = { eslintSuppressionRules?: Array | null | undefined; flowSuppressions: boolean; + /* + * Ignore 'use no forget' annotations. Helpful during testing but should not be used in production. + */ + ignoreUseNoForget: boolean; }; const CompilationModeSchema = z.enum([ @@ -172,6 +176,7 @@ export const defaultOptions: PluginOptions = { enableUseMemoCachePolyfill: false, eslintSuppressionRules: null, flowSuppressions: false, + ignoreUseNoForget: false, } as const; export function parsePluginOptions(obj: unknown): PluginOptions { diff --git a/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts index 17fee03884..2d5a362d87 100644 --- a/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts @@ -51,13 +51,15 @@ function findDirectiveEnablingMemoization( } function findDirectiveDisablingMemoization( - directives: t.Directive[] + directives: t.Directive[], + options: PluginOptions ): t.Directive | null { for (const directive of directives) { const directiveValue = directive.value.value; if ( - directiveValue === "use no forget" || - directiveValue === "use no memo" + (directiveValue === "use no forget" || + directiveValue === "use no memo") && + !options.ignoreUseNoForget ) { return directive; } @@ -197,12 +199,15 @@ export function compileProgram( program: NodePath, pass: CompilerPass ): void { + const options = parsePluginOptions(pass.opts); + // Top level "use no forget", skip this file entirely - if (findDirectiveDisablingMemoization(program.node.directives) != null) { + if ( + findDirectiveDisablingMemoization(program.node.directives, options) != null + ) { return; } - const options = parsePluginOptions(pass.opts); const environment = parseEnvironmentConfig(pass.opts.environment ?? {}); /* @@ -400,7 +405,8 @@ function getReactFunctionType( if (fn.node.body.type === "BlockStatement") { // Opt-outs disable compilation regardless of mode const useNoForget = findDirectiveDisablingMemoization( - fn.node.body.directives + fn.node.body.directives, + pass.opts ); if (useNoForget != null) { pass.opts.logger?.logEvent(pass.filename, { diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ignore-use-no-forget.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ignore-use-no-forget.expect.md new file mode 100644 index 0000000000..56de5f68fc --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ignore-use-no-forget.expect.md @@ -0,0 +1,53 @@ + +## Input + +```javascript +// @ignoreUseNoForget +function Component(prop) { + "use no forget"; + const result = prop.x.toFixed(); + return
{result}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ x: 1 }], +}; + +``` + +## Code + +```javascript +import { unstable_useMemoCache as useMemoCache } from "react"; // @ignoreUseNoForget +function Component(prop) { + const $ = useMemoCache(4); + let t0; + if ($[0] !== prop.x) { + t0 = prop.x.toFixed(); + $[0] = prop.x; + $[1] = t0; + } else { + t0 = $[1]; + } + const result = t0; + let t1; + if ($[2] !== result) { + t1 =
{result}
; + $[2] = result; + $[3] = t1; + } else { + t1 = $[3]; + } + return t1; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ x: 1 }], +}; + +``` + +### Eval output +(kind: ok)
1
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ignore-use-no-forget.js b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ignore-use-no-forget.js new file mode 100644 index 0000000000..6b3c8fe7d5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/ignore-use-no-forget.js @@ -0,0 +1,11 @@ +// @ignoreUseNoForget +function Component(prop) { + "use no forget"; + const result = prop.x.toFixed(); + return
{result}
; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ x: 1 }], +}; diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index 7c111f51f9..6665c16c2b 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -108,6 +108,11 @@ function makePluginOptions( flowSuppressions = true; } + let ignoreUseNoForget: boolean = false; + if (firstLine.includes("@ignoreUseNoForget")) { + ignoreUseNoForget = true; + } + const hookPatternMatch = /@hookPattern:"([^"]+)"/.exec(firstLine); if ( hookPatternMatch && @@ -168,6 +173,7 @@ function makePluginOptions( enableUseMemoCachePolyfill, eslintSuppressionRules, flowSuppressions, + ignoreUseNoForget }; } @@ -205,18 +211,18 @@ function getEvaluatorPresets( presets.push( language === "typescript" ? [ - "@babel/preset-typescript", - { - /** - * onlyRemoveTypeImports needs to be set as fbt imports - * would otherwise be removed by this pass. - * https://github.com/facebook/fbt/issues/49 - * https://github.com/facebook/sfbt/issues/72 - * https://dev.to/retyui/how-to-add-support-typescript-for-fbt-an-internationalization-framework-3lo0 - */ - onlyRemoveTypeImports: true, - }, - ] + "@babel/preset-typescript", + { + /** + * onlyRemoveTypeImports needs to be set as fbt imports + * would otherwise be removed by this pass. + * https://github.com/facebook/fbt/issues/49 + * https://github.com/facebook/sfbt/issues/72 + * https://dev.to/retyui/how-to-add-support-typescript-for-fbt-an-internationalization-framework-3lo0 + */ + onlyRemoveTypeImports: true, + }, + ] : "@babel/preset-flow" );