Pass environment options through babel plugin
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.
Joe Savona committed
Feb 16, 2023 at 14:24 UTC
0c72eed413d0c4a484d5f8bc122c5d2d231952b3
6 files changed
+70
-56
compiler/forget/src/Babel/BabelPlugin.ts
+4
-4
@@ -12,11 +12,11 @@ import generate from "@babel/generator";
12
import jsx from "@babel/plugin-syntax-jsx";
13
import * as t from "@babel/types";
14
import prettier from "prettier";
15
-import { CompilerFlags, parseCompilerFlags } from "../CompilerFlags";
15
import { compile } from "../CompilerPipeline";
16
+import { parsePluginOptions, PluginOptions } from "./PluginOptions";
17
18
type BabelPluginPass = {
19
- opts: CompilerFlags;
19
+ opts: PluginOptions;
20
};
21
22
/**
@@ -47,7 +47,7 @@ export default function ReactForgetBabelPlugin(
47
if (fn.scope.getProgramParent() !== fn.scope.parent) {
48
return;
49
}
50
- const ast = compile(fn, null);
50
+ const ast = compile(fn, pass.opts.environment);
51
52
// We are generating a new FunctionDeclaration node, so we must skip over it or this
53
// traversal will loop infinitely.
@@ -73,7 +73,7 @@ export default function ReactForgetBabelPlugin(
73
// prior to B, if A does not have a Program visitor and B does, B will run first. We always
74
// want Forget to run true to source as possible.
75
Program(path, pass) {
76
- const flags = parseCompilerFlags(pass.opts);
76
+ const flags = parsePluginOptions(pass.opts);
77
path.traverse(visitor, {
78
...pass,
79
opts: { ...pass.opts, ...flags },
compiler/forget/src/Babel/PluginOptions.ts
new
+45
@@ -0,0 +1,45 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+import { EnvironmentOptions } from "../HIR/Environment";
9
+
10
+export type PluginOptions = {
11
+ /**
12
+ * Enable to make Forget only compile functions containing the 'use forget' directive.
13
+ */
14
+ enableOnlyOnUseForgetDirective: boolean;
15
+
16
+ environment: EnvironmentOptions | null;
17
+};
18
+
19
+export const defaultOptions: PluginOptions = {
20
+ enableOnlyOnUseForgetDirective: false,
21
+ environment: null,
22
+} as const;
23
+
24
+export function parsePluginOptions(obj: unknown): PluginOptions {
25
+ if (obj == null || typeof obj !== "object") {
26
+ return defaultOptions;
27
+ }
28
+ const invalidOptions: Array<string> = [];
29
+ let parsedOptions: Partial<PluginOptions> = Object.create(null);
30
+ for (const [key, value] of Object.entries(obj)) {
31
+ if (isCompilerFlag(key)) {
32
+ parsedOptions[key] = value;
33
+ } else {
34
+ invalidOptions.push(key);
35
+ }
36
+ }
37
+ if (invalidOptions.length > 0) {
38
+ console.error(`Unexpected React Forget compiler flags: ${invalidOptions}`);
39
+ }
40
+ return { ...defaultOptions, ...parsedOptions };
41
+}
42
+
43
+function isCompilerFlag(s: string): s is keyof typeof defaultOptions {
44
+ return Object.prototype.hasOwnProperty.call(defaultOptions, s);
45
+}
compiler/forget/src/Babel/RunReactForgetBabelPlugin.ts
+4
-2
@@ -4,6 +4,7 @@ import * as parser from "@babel/parser";
4
import invariant from "invariant";
5
import prettier from "prettier";
6
import ReactForgetBabelPlugin from "./BabelPlugin";
7
+import { PluginOptions } from "./PluginOptions";
8
9
type ReactForgetBabelPluginResult = {
10
ast: BabelCore.BabelFileResult["ast"];
@@ -14,7 +15,8 @@ type ReactForgetBabelPluginResult = {
15
export default function runReactForgetBabelPlugin(
16
text: string,
17
file: string,
17
- language: "flow" | "typescript"
18
+ language: "flow" | "typescript",
19
+ options: PluginOptions | null
20
): ReactForgetBabelPluginResult {
21
const ast = parser.parse(text, {
22
sourceFilename: file,
@@ -25,7 +27,7 @@ export default function runReactForgetBabelPlugin(
27
filename: file,
28
highlightCode: false,
29
retainLines: true,
28
- plugins: [ReactForgetBabelPlugin],
30
+ plugins: [[ReactForgetBabelPlugin, options]],
31
sourceType: "module",
32
});
33
invariant(
compiler/forget/src/CompilerFlags.ts
deleted
-40
@@ -1,40 +0,0 @@
1
-/**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- */
7
-
8
-export type CompilerFlags = {
9
- /**
10
- * Enable to make Forget only compile functions containing the 'use forget' directive.
11
- */
12
- enableOnlyOnUseForgetDirective: boolean;
13
-};
14
-
15
-export const defaultFlags: CompilerFlags = {
16
- enableOnlyOnUseForgetDirective: false,
17
-} as const;
18
-
19
-export function parseCompilerFlags(obj: unknown): CompilerFlags {
20
- if (obj == null || typeof obj !== "object") {
21
- return defaultFlags;
22
- }
23
- const invalidFlags: Array<string> = [];
24
- let parsedFlags: Partial<CompilerFlags> = Object.create(null);
25
- for (const [key, value] of Object.entries(obj)) {
26
- if (isCompilerFlag(key)) {
27
- parsedFlags[key] = value;
28
- } else {
29
- invalidFlags.push(key);
30
- }
31
- }
32
- if (invalidFlags.length > 0) {
33
- console.error(`Unexpected React Forget compiler flags: ${invalidFlags}`);
34
- }
35
- return { ...defaultFlags, ...parsedFlags };
36
-}
37
-
38
-function isCompilerFlag(s: string): s is keyof typeof defaultFlags {
39
- return Object.prototype.hasOwnProperty.call(defaultFlags, s);
40
-}
compiler/forget/src/HIR/Hooks.ts
-9
@@ -26,15 +26,6 @@ export const BUILTIN_HOOKS: Map<string, Hook> = new Map([
26
valueKind: ValueKind.Mutable,
27
},
28
],
29
- [
30
- "useFreeze",
31
- {
32
- kind: "Ref",
33
- name: "useFreeze",
34
- effectKind: Effect.Freeze,
35
- valueKind: ValueKind.Frozen,
36
- },
37
- ],
29
[
30
"useMemo",
31
{
compiler/forget/src/__tests__/hir-test.ts
+17
-1
@@ -10,6 +10,7 @@
10
import { wasmFolder } from "@hpcc-js/wasm";
11
import path from "path";
12
import runReactForgetBabelPlugin from "../Babel/RunReactForgetBabelPlugin";
13
+import { Effect, ValueKind } from "../HIR";
14
import { toggleLogging } from "../Utils/logger";
15
import generateTestsFromFixtures from "./test-utils/generateTestsFromFixtures";
16
@@ -60,7 +61,22 @@ describe("React Forget (HIR version)", () => {
61
}
62
try {
63
items.push({
63
- js: runReactForgetBabelPlugin(input, file, language).code,
64
+ js: runReactForgetBabelPlugin(input, file, language, {
65
+ enableOnlyOnUseForgetDirective: false,
66
+ environment: {
67
+ customHooks: new Map([
68
+ [
69
+ "useFreeze",
70
+ {
71
+ name: "useFreeze",
72
+ kind: "Custom",
73
+ valueKind: ValueKind.Frozen,
74
+ effectKind: Effect.Freeze,
75
+ },
76
+ ],
77
+ ]),
78
+ },
79
+ }).code,
80
});
81
} catch (e) {
82
error = e;