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" );