From 0c72eed413d0c4a484d5f8bc122c5d2d231952b3 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Thu, 16 Feb 2023 14:24:15 -0800 Subject: [PATCH] Pass environment options through babel plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates the babel plugin so that environment options — including custom hook definitions — can be passed in through the plugin: * Renames `CompilerFlags` => `PluginOptions` since they are specific to the babel plugin, and are no longer just flags. * Moves the definition of `useFreeze()` out of the builtin hook list and instead passes it when our unit tests configure the plugin. --- compiler/forget/src/Babel/BabelPlugin.ts | 8 ++-- compiler/forget/src/Babel/PluginOptions.ts | 45 +++++++++++++++++++ .../src/Babel/RunReactForgetBabelPlugin.ts | 6 ++- compiler/forget/src/CompilerFlags.ts | 40 ----------------- compiler/forget/src/HIR/Hooks.ts | 9 ---- compiler/forget/src/__tests__/hir-test.ts | 18 +++++++- 6 files changed, 70 insertions(+), 56 deletions(-) create mode 100644 compiler/forget/src/Babel/PluginOptions.ts delete mode 100644 compiler/forget/src/CompilerFlags.ts diff --git a/compiler/forget/src/Babel/BabelPlugin.ts b/compiler/forget/src/Babel/BabelPlugin.ts index 59a70c4bc6..a01850b9d2 100644 --- a/compiler/forget/src/Babel/BabelPlugin.ts +++ b/compiler/forget/src/Babel/BabelPlugin.ts @@ -12,11 +12,11 @@ import generate from "@babel/generator"; import jsx from "@babel/plugin-syntax-jsx"; import * as t from "@babel/types"; import prettier from "prettier"; -import { CompilerFlags, parseCompilerFlags } from "../CompilerFlags"; import { compile } from "../CompilerPipeline"; +import { parsePluginOptions, PluginOptions } from "./PluginOptions"; type BabelPluginPass = { - opts: CompilerFlags; + opts: PluginOptions; }; /** @@ -47,7 +47,7 @@ export default function ReactForgetBabelPlugin( if (fn.scope.getProgramParent() !== fn.scope.parent) { return; } - const ast = compile(fn, null); + const ast = compile(fn, pass.opts.environment); // We are generating a new FunctionDeclaration node, so we must skip over it or this // traversal will loop infinitely. @@ -73,7 +73,7 @@ export default function ReactForgetBabelPlugin( // prior to B, if A does not have a Program visitor and B does, B will run first. We always // want Forget to run true to source as possible. Program(path, pass) { - const flags = parseCompilerFlags(pass.opts); + const flags = parsePluginOptions(pass.opts); path.traverse(visitor, { ...pass, opts: { ...pass.opts, ...flags }, diff --git a/compiler/forget/src/Babel/PluginOptions.ts b/compiler/forget/src/Babel/PluginOptions.ts new file mode 100644 index 0000000000..6a1aedbc56 --- /dev/null +++ b/compiler/forget/src/Babel/PluginOptions.ts @@ -0,0 +1,45 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { EnvironmentOptions } from "../HIR/Environment"; + +export type PluginOptions = { + /** + * Enable to make Forget only compile functions containing the 'use forget' directive. + */ + enableOnlyOnUseForgetDirective: boolean; + + environment: EnvironmentOptions | null; +}; + +export const defaultOptions: PluginOptions = { + enableOnlyOnUseForgetDirective: false, + environment: null, +} as const; + +export function parsePluginOptions(obj: unknown): PluginOptions { + if (obj == null || typeof obj !== "object") { + return defaultOptions; + } + const invalidOptions: Array = []; + let parsedOptions: Partial = Object.create(null); + for (const [key, value] of Object.entries(obj)) { + if (isCompilerFlag(key)) { + parsedOptions[key] = value; + } else { + invalidOptions.push(key); + } + } + if (invalidOptions.length > 0) { + console.error(`Unexpected React Forget compiler flags: ${invalidOptions}`); + } + return { ...defaultOptions, ...parsedOptions }; +} + +function isCompilerFlag(s: string): s is keyof typeof defaultOptions { + return Object.prototype.hasOwnProperty.call(defaultOptions, s); +} diff --git a/compiler/forget/src/Babel/RunReactForgetBabelPlugin.ts b/compiler/forget/src/Babel/RunReactForgetBabelPlugin.ts index 68cb4efa68..8ad5867ecf 100644 --- a/compiler/forget/src/Babel/RunReactForgetBabelPlugin.ts +++ b/compiler/forget/src/Babel/RunReactForgetBabelPlugin.ts @@ -4,6 +4,7 @@ import * as parser from "@babel/parser"; import invariant from "invariant"; import prettier from "prettier"; import ReactForgetBabelPlugin from "./BabelPlugin"; +import { PluginOptions } from "./PluginOptions"; type ReactForgetBabelPluginResult = { ast: BabelCore.BabelFileResult["ast"]; @@ -14,7 +15,8 @@ type ReactForgetBabelPluginResult = { export default function runReactForgetBabelPlugin( text: string, file: string, - language: "flow" | "typescript" + language: "flow" | "typescript", + options: PluginOptions | null ): ReactForgetBabelPluginResult { const ast = parser.parse(text, { sourceFilename: file, @@ -25,7 +27,7 @@ export default function runReactForgetBabelPlugin( filename: file, highlightCode: false, retainLines: true, - plugins: [ReactForgetBabelPlugin], + plugins: [[ReactForgetBabelPlugin, options]], sourceType: "module", }); invariant( diff --git a/compiler/forget/src/CompilerFlags.ts b/compiler/forget/src/CompilerFlags.ts deleted file mode 100644 index d4b90b94f6..0000000000 --- a/compiler/forget/src/CompilerFlags.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -export type CompilerFlags = { - /** - * Enable to make Forget only compile functions containing the 'use forget' directive. - */ - enableOnlyOnUseForgetDirective: boolean; -}; - -export const defaultFlags: CompilerFlags = { - enableOnlyOnUseForgetDirective: false, -} as const; - -export function parseCompilerFlags(obj: unknown): CompilerFlags { - if (obj == null || typeof obj !== "object") { - return defaultFlags; - } - const invalidFlags: Array = []; - let parsedFlags: Partial = Object.create(null); - for (const [key, value] of Object.entries(obj)) { - if (isCompilerFlag(key)) { - parsedFlags[key] = value; - } else { - invalidFlags.push(key); - } - } - if (invalidFlags.length > 0) { - console.error(`Unexpected React Forget compiler flags: ${invalidFlags}`); - } - return { ...defaultFlags, ...parsedFlags }; -} - -function isCompilerFlag(s: string): s is keyof typeof defaultFlags { - return Object.prototype.hasOwnProperty.call(defaultFlags, s); -} diff --git a/compiler/forget/src/HIR/Hooks.ts b/compiler/forget/src/HIR/Hooks.ts index 26b37a92b0..9ea29c5536 100644 --- a/compiler/forget/src/HIR/Hooks.ts +++ b/compiler/forget/src/HIR/Hooks.ts @@ -26,15 +26,6 @@ export const BUILTIN_HOOKS: Map = new Map([ valueKind: ValueKind.Mutable, }, ], - [ - "useFreeze", - { - kind: "Ref", - name: "useFreeze", - effectKind: Effect.Freeze, - valueKind: ValueKind.Frozen, - }, - ], [ "useMemo", { diff --git a/compiler/forget/src/__tests__/hir-test.ts b/compiler/forget/src/__tests__/hir-test.ts index 13e76ab4de..62d1b160d7 100644 --- a/compiler/forget/src/__tests__/hir-test.ts +++ b/compiler/forget/src/__tests__/hir-test.ts @@ -10,6 +10,7 @@ import { wasmFolder } from "@hpcc-js/wasm"; import path from "path"; import runReactForgetBabelPlugin from "../Babel/RunReactForgetBabelPlugin"; +import { Effect, ValueKind } from "../HIR"; import { toggleLogging } from "../Utils/logger"; import generateTestsFromFixtures from "./test-utils/generateTestsFromFixtures"; @@ -60,7 +61,22 @@ describe("React Forget (HIR version)", () => { } try { items.push({ - js: runReactForgetBabelPlugin(input, file, language).code, + js: runReactForgetBabelPlugin(input, file, language, { + enableOnlyOnUseForgetDirective: false, + environment: { + customHooks: new Map([ + [ + "useFreeze", + { + name: "useFreeze", + kind: "Custom", + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + }, + ], + ]), + }, + }).code, }); } catch (e) { error = e;