diff --git a/compiler/forget/src/Babel/BabelPlugin.ts b/compiler/forget/src/Babel/BabelPlugin.ts
index 10f2122749..005d0f85ff 100644
--- a/compiler/forget/src/Babel/BabelPlugin.ts
+++ b/compiler/forget/src/Babel/BabelPlugin.ts
@@ -7,40 +7,9 @@
///
-import type * as BabelCore from "@babel/core";
import jsx from "@babel/plugin-syntax-jsx";
-import * as t from "@babel/types";
-import {
- CompilerError,
- CompilerErrorDetail,
- ErrorSeverity,
-} from "../CompilerError";
-import { compile } from "../CompilerPipeline";
-import { GeneratedSource } from "../HIR";
-import { addInstrumentForget } from "./InstrumentForgetBabelPlugin";
-import {
- ExternalFunction,
- PluginOptions,
- parsePluginOptions,
-} from "./PluginOptions";
-
-type BabelPluginPass = {
- opts: PluginOptions;
- filename: string | null;
-};
-
-function hasUseForgetDirective(directive: t.Directive): boolean {
- return directive.value.value === "use forget";
-}
-
-function hasAnyUseForgetDirectives(directives: t.Directive[]): boolean {
- for (const directive of directives) {
- if (hasUseForgetDirective(directive)) {
- return true;
- }
- }
- return false;
-}
+import type * as BabelCore from "@babel/core";
+import { compileProgram } from "../CompilerEntrypoint";
/**
* The React Forget Babel Plugin
@@ -50,150 +19,6 @@ function hasAnyUseForgetDirectives(directives: t.Directive[]): boolean {
export default function ReactForgetBabelPlugin(
_babel: typeof BabelCore
): BabelCore.PluginObj {
- let hasForgetCompiledCode: boolean = false;
-
- function visitFn(
- fn: BabelCore.NodePath,
- pass: BabelPluginPass
- ): void {
- try {
- const compiled = compile(fn, pass.opts.environment);
- if (fn.node.id == null) {
- CompilerError.invariant(
- "FunctionDeclaration must have a name",
- fn.node.loc ?? GeneratedSource
- );
- }
- const originalIdent = fn.node.id;
-
- if (pass.opts.gating != null) {
- // Rename existing function
- fn.node.id = addSuffix(fn.node.id, "_uncompiled");
-
- // Rename and append compiled function
- if (compiled.id == null) {
- CompilerError.invariant(
- "FunctionDeclaration must produce a name",
- fn.node.loc ?? GeneratedSource
- );
- }
- compiled.id = addSuffix(compiled.id, "_forget");
- const compiledFn = fn.insertAfter(compiled)[0];
- compiledFn.skip();
-
- // Build and append gating test
- compiledFn.insertAfter(
- buildGatingTest({
- originalFnDecl: fn,
- compiledIdent: compiled.id,
- originalIdent,
- gating: pass.opts.gating,
- })
- );
- if (pass.opts.instrumentForget != null) {
- const gatingIdentifierName =
- pass.opts.instrumentForget.gating.importSpecifierName;
- const instrumentFnName =
- pass.opts.instrumentForget.instrumentFn.importSpecifierName;
- addInstrumentForget(
- fn,
- originalIdent.name,
- gatingIdentifierName,
- instrumentFnName
- );
- addInstrumentForget(
- compiledFn,
- originalIdent.name,
- gatingIdentifierName,
- instrumentFnName
- );
- }
- } else {
- fn.replaceWith(compiled);
- if (pass.opts.instrumentForget != null) {
- const gatingIdentifierName =
- pass.opts.instrumentForget.gating.importSpecifierName;
- const instrumentFnName =
- pass.opts.instrumentForget.instrumentFn.importSpecifierName;
- addInstrumentForget(
- fn,
- originalIdent.name,
- gatingIdentifierName,
- instrumentFnName
- );
- }
- }
-
- hasForgetCompiledCode = true;
- } catch (err) {
- if (pass.opts.logger && err) {
- pass.opts.logger.logEvent("err", err);
- }
- /** Always throw if the flag is enabled, otherwise we only throw if the error is critical
- * (eg an invariant is broken, meaning the compiler may be buggy). See
- * {@link CompilerError.isCritical} for mappings.
- * */
- if (
- pass.opts.panicOnBailout ||
- !(err instanceof CompilerError) ||
- (err instanceof CompilerError && err.isCritical())
- ) {
- throw err;
- } else {
- if (pass.opts.isDev) {
- log(err, pass.filename ?? null);
- }
- }
- } finally {
- // We are generating a new FunctionDeclaration node, so we must skip over it or this
- // traversal will loop infinitely.
- fn.skip();
- }
- }
-
- const visitor = {
- FunctionDeclaration(
- fn: BabelCore.NodePath,
- pass: BabelPluginPass
- ): void {
- if (!shouldCompile(fn, pass)) {
- return;
- }
-
- visitFn(fn, pass);
- },
-
- ArrowFunctionExpression(
- fn: BabelCore.NodePath,
- pass: BabelPluginPass
- ): void {
- if (!shouldCompile(fn, pass)) {
- return;
- }
-
- const loweredFn = buildFunctionDeclaration(fn);
- if (loweredFn instanceof CompilerError) {
- const error = loweredFn;
-
- const options = parsePluginOptions(pass.opts);
- if (options.logger != null) {
- options.logger.logEvent("err", error);
- }
-
- if (options.panicOnBailout || error.isCritical()) {
- throw error;
- } else {
- if (pass.opts.isDev) {
- log(error, pass.filename);
- }
- }
- return;
- }
-
- visitFn(loweredFn, pass);
- },
- };
-
return {
name: "react-forget",
inherits: jsx,
@@ -201,347 +26,9 @@ export default function ReactForgetBabelPlugin(
// Note: Babel does some "smart" merging of visitors across plugins, so even if A is inserted
// 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): void {
- const options = parsePluginOptions(pass.opts);
-
- const violations = [];
- const fileComments = pass.file.ast.comments;
- let fileHasUseForgetDirective = false;
- if (Array.isArray(fileComments)) {
- for (const comment of fileComments) {
- if (
- /eslint-disable(-next-line)? react-hooks\/(exhaustive-deps|rules-of-hooks)/.test(
- comment.value
- )
- ) {
- violations.push(comment);
- }
- }
- }
-
- if (violations.length > 0) {
- path.traverse({
- Directive(path) {
- if (hasUseForgetDirective(path.node)) {
- fileHasUseForgetDirective = true;
- }
- },
- });
-
- const reason = `One or more React eslint rules is disabled`;
- const error = new CompilerError();
- for (const violation of violations) {
- if (options.logger != null) {
- options.logger.logEvent("err", {
- reason,
- filename: pass.filename,
- violation,
- });
- }
-
- error.pushErrorDetail(
- new CompilerErrorDetail({
- reason,
- description: violation.value.trim(),
- severity: ErrorSeverity.UnsafeInput,
- codeframe: null,
- loc: violation.loc ?? null,
- })
- );
- }
-
- if (fileHasUseForgetDirective) {
- if (options.panicOnBailout || error.isCritical()) {
- throw error;
- } else {
- if (options.isDev) {
- log(error, pass.filename ?? null);
- }
- }
- }
-
- return;
- }
-
- path.traverse(visitor, {
- ...pass,
- opts: { ...pass.opts, ...options },
- filename: pass.filename ?? null,
- });
-
- // If there isn't already an import of * as React, insert it so useMemoCache doesn't
- // throw
- if (hasForgetCompiledCode) {
- let didInsertUseMemoCache = false;
- let hasExistingReactImport = false;
- path.traverse({
- CallExpression(callExprPath) {
- const callee = callExprPath.get("callee");
- const args = callExprPath.get("arguments");
- if (
- callee.isIdentifier() &&
- callee.node.name === "useMemoCache" &&
- args.length === 1 &&
- args[0].isNumericLiteral()
- ) {
- didInsertUseMemoCache = true;
- }
- },
- ImportDeclaration(importDeclPath) {
- if (isNonNamespacedImportOfReact(importDeclPath)) {
- hasExistingReactImport = true;
- }
- },
- });
- // If Forget did successfully compile inject/update an import of
- // `import {unstable_useMemoCache as useMemoCache} from 'react'` and rename
- // `React.unstable_useMemoCache(n)` to `useMemoCache(n)`;
- if (didInsertUseMemoCache) {
- if (hasExistingReactImport) {
- let didUpdateImport = false;
- path.traverse({
- ImportDeclaration(importDeclPath) {
- if (isNonNamespacedImportOfReact(importDeclPath)) {
- importDeclPath.pushContainer(
- "specifiers",
- t.importSpecifier(
- t.identifier("useMemoCache"),
- t.identifier("unstable_useMemoCache")
- )
- );
- didUpdateImport = true;
- }
- },
- });
- if (didUpdateImport === false) {
- throw new Error(
- "Expected an ImportDeclaration of react in order to update ImportSpecifiers with useMemoCache"
- );
- }
- } else {
- path.unshiftContainer(
- "body",
- t.importDeclaration(
- [
- t.importSpecifier(
- t.identifier("useMemoCache"),
- t.identifier("unstable_useMemoCache")
- ),
- ],
- t.stringLiteral("react")
- )
- );
- }
- }
- // TODO: check for duplicate import specifiers
- if (options.gating != null) {
- path.unshiftContainer(
- "body",
- buildImportForExternalFunction(options.gating)
- );
- }
- if (options.instrumentForget != null) {
- path.unshiftContainer(
- "body",
- buildImportForExternalFunction(options.instrumentForget.gating)
- );
- path.unshiftContainer(
- "body",
- buildImportForExternalFunction(
- options.instrumentForget.instrumentFn
- )
- );
- }
- }
+ Program(prog, pass): void {
+ compileProgram(prog, pass);
},
},
};
}
-
-function shouldCompile(
- fn: BabelCore.NodePath,
- pass: BabelPluginPass
-): boolean {
- if (pass.opts.enableOnlyOnUseForgetDirective) {
- const body = fn.get("body");
- if (!body.isBlockStatement()) {
- return false;
- }
- if (!hasAnyUseForgetDirectives(body.node.directives)) {
- return false;
- }
- }
-
- if (fn.scope.getProgramParent() !== fn.scope.parent) {
- return false;
- }
-
- return true;
-}
-
-function log(error: CompilerError, filename: string | null): void {
- const filenameStr = filename ? `in ${filename}` : "";
- console.log(
- error.details
- .map(
- (e) =>
- `[ReactForget] Skipping compilation of component ${filenameStr}: ${e.printErrorMessage()}`
- )
- .join("\n")
- );
-}
-
-function makeError(
- reason: string,
- loc: t.SourceLocation | null
-): CompilerError {
- const error = new CompilerError();
- error.pushErrorDetail(
- new CompilerErrorDetail({
- reason,
- description: null,
- severity: ErrorSeverity.InvalidInput,
- codeframe: null,
- loc,
- })
- );
- return error;
-}
-
-function buildFunctionDeclaration(
- fn: BabelCore.NodePath
-): BabelCore.NodePath | CompilerError {
- if (!fn.parentPath.isVariableDeclarator()) {
- return makeError(
- "ArrowFunctionExpression must be declared in variable declaration",
- fn.node.loc ?? null
- );
- }
- const variableDeclarator = fn.parentPath;
-
- if (!variableDeclarator.parentPath.isVariableDeclaration()) {
- return makeError(
- "ArrowFunctionExpression must be a single declaration",
- fn.node.loc ?? null
- );
- }
- const variableDeclaration = variableDeclarator.parentPath;
-
- const id = variableDeclarator.get("id");
- if (!id.isIdentifier()) {
- return makeError(
- "ArrowFunctionExpression must have an id",
- fn.node.loc ?? null
- );
- }
-
- const rewrittenFn = variableDeclaration.replaceWith(
- t.functionDeclaration(
- id.node,
- fn.node.params,
- buildBlockStatement(fn),
- fn.node.generator,
- fn.node.async
- )
- )[0];
- fn.skip();
- return rewrittenFn;
-}
-
-function buildBlockStatement(
- fn: BabelCore.NodePath
-): t.BlockStatement {
- const body = fn.get("body");
- if (body.isExpression()) {
- const wrappedBody = body.replaceWith(
- t.blockStatement([t.returnStatement(body.node)])
- )[0];
- body.skip();
-
- return wrappedBody.node;
- }
-
- if (!body.isBlockStatement()) {
- CompilerError.invariant(
- "Body must be a BlockStatement",
- body.node.loc ?? GeneratedSource
- );
- }
- return body.node;
-}
-
-type GatingTestOptions = {
- originalFnDecl: BabelCore.NodePath;
- compiledIdent: t.Identifier;
- originalIdent: t.Identifier;
- gating: ExternalFunction;
-};
-function buildGatingTest({
- originalFnDecl,
- compiledIdent,
- originalIdent,
- gating,
-}: GatingTestOptions): t.Node | t.Node[] {
- const testVarDecl = t.variableDeclaration("const", [
- t.variableDeclarator(
- originalIdent,
- t.conditionalExpression(
- t.callExpression(buildSpecifierIdent(gating), []),
- compiledIdent,
- originalFnDecl.node.id!
- )
- ),
- ]);
-
- // Re-export new declaration
- const parent = originalFnDecl.parentPath;
- if (t.isExportDefaultDeclaration(parent)) {
- // Re-add uncompiled function
- parent.replaceWith(originalFnDecl)[0].skip();
-
- // Add test and synthesize new export
- return [testVarDecl, t.exportDefaultDeclaration(originalIdent)];
- } else if (t.isExportNamedDeclaration(parent)) {
- // Re-add uncompiled function
- parent.replaceWith(originalFnDecl)[0].skip();
-
- // Add and export test
- return t.exportNamedDeclaration(testVarDecl);
- }
-
- // Just add the test, no need for re-export
- return testVarDecl;
-}
-
-function addSuffix(id: t.Identifier, suffix: string): t.Identifier {
- return t.identifier(`${id.name}${suffix}`);
-}
-
-function buildImportForExternalFunction(
- gating: ExternalFunction
-): t.ImportDeclaration {
- const specifierIdent = buildSpecifierIdent(gating);
- return t.importDeclaration(
- [t.importSpecifier(specifierIdent, specifierIdent)],
- t.stringLiteral(gating.source)
- );
-}
-
-function buildSpecifierIdent(gating: ExternalFunction): t.Identifier {
- return t.identifier(gating.importSpecifierName);
-}
-
-/**
- * Matches `import { ... } from 'react';`
- * but not `import * as React from 'react';`
- */
-function isNonNamespacedImportOfReact(
- importDeclPath: BabelCore.NodePath
-): boolean {
- return (
- importDeclPath.get("source").node.value === "react" &&
- importDeclPath
- .get("specifiers")
- .every((specifier) => specifier.isImportSpecifier())
- );
-}
diff --git a/compiler/forget/src/Babel/InstrumentForgetBabelPlugin.ts b/compiler/forget/src/Babel/InstrumentForgetBabelPlugin.ts
deleted file mode 100644
index 12c79d62bf..0000000000
--- a/compiler/forget/src/Babel/InstrumentForgetBabelPlugin.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * 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 { NodePath } from "@babel/core";
-import * as t from "@babel/types";
-import "./BabelPlugin";
-
-export function addInstrumentForget(
- fn: NodePath,
- fnName: string,
- gatingIdentifierName: string,
- instrumentFnName: string
-): void {
- const fnBody = fn.get("body");
- // Technically, this is a conditional hook call. However, we expect
- // __DEV__ and gatingIdentifier to be runtime constants
- const testExpr: t.Node = t.ifStatement(
- t.logicalExpression(
- "&&",
- t.identifier("__DEV__"),
- t.identifier(gatingIdentifierName)
- ),
- t.expressionStatement(
- t.callExpression(t.identifier(instrumentFnName), [
- t.stringLiteral(fnName),
- ])
- )
- );
- fnBody.unshiftContainer("body", testExpr);
-}
diff --git a/compiler/forget/src/Babel/RunReactForgetBabelPlugin.ts b/compiler/forget/src/Babel/RunReactForgetBabelPlugin.ts
index b904fad9a2..9feb00a767 100644
--- a/compiler/forget/src/Babel/RunReactForgetBabelPlugin.ts
+++ b/compiler/forget/src/Babel/RunReactForgetBabelPlugin.ts
@@ -11,7 +11,7 @@ import * as parser from "@babel/parser";
import invariant from "invariant";
import prettier from "prettier";
import ReactForgetBabelPlugin from "./BabelPlugin";
-import { PluginOptions } from "./PluginOptions";
+import type { PluginOptions } from "../CompilerOptions";
type ReactForgetBabelPluginResult = {
ast: BabelCore.BabelFileResult["ast"];
diff --git a/compiler/forget/src/CompilerEntrypoint.ts b/compiler/forget/src/CompilerEntrypoint.ts
index 217a0ebebc..e64c25fe91 100644
--- a/compiler/forget/src/CompilerEntrypoint.ts
+++ b/compiler/forget/src/CompilerEntrypoint.ts
@@ -5,8 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
-import type * as BabelCore from "@babel/core";
-import jsx from "@babel/plugin-syntax-jsx";
+import { NodePath, PluginPass } from "@babel/core";
import * as t from "@babel/types";
import {
CompilerError,
@@ -20,7 +19,7 @@ import {
ExternalFunction,
PluginOptions,
parsePluginOptions,
-} from "./Babel/PluginOptions";
+} from "./CompilerOptions";
type BabelPluginPass = {
opts: PluginOptions;
@@ -40,18 +39,14 @@ function hasAnyUseForgetDirectives(directives: t.Directive[]): boolean {
return false;
}
-/**
- * The React Forget Babel Plugin
- * @param {*} _babel
- * @returns
- */
-export default function ReactForgetBabelPlugin(
- _babel: typeof BabelCore
-): BabelCore.PluginObj {
+export function compileProgram(
+ program: NodePath,
+ pass: PluginPass
+): void {
let hasForgetCompiledCode: boolean = false;
function visitFn(
- fn: BabelCore.NodePath,
+ fn: NodePath,
pass: BabelPluginPass
): void {
try {
@@ -151,7 +146,7 @@ export default function ReactForgetBabelPlugin(
const visitor = {
FunctionDeclaration(
- fn: BabelCore.NodePath,
+ fn: NodePath,
pass: BabelPluginPass
): void {
if (!shouldCompile(fn, pass)) {
@@ -162,7 +157,7 @@ export default function ReactForgetBabelPlugin(
},
ArrowFunctionExpression(
- fn: BabelCore.NodePath,
+ fn: NodePath,
pass: BabelPluginPass
): void {
if (!shouldCompile(fn, pass)) {
@@ -192,172 +187,159 @@ export default function ReactForgetBabelPlugin(
},
};
- return {
- name: "react-forget",
- inherits: jsx,
- visitor: {
- // Note: Babel does some "smart" merging of visitors across plugins, so even if A is inserted
- // 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): void {
- const options = parsePluginOptions(pass.opts);
+ const options = parsePluginOptions(pass.opts);
- const violations = [];
- const fileComments = pass.file.ast.comments;
- let fileHasUseForgetDirective = false;
- if (Array.isArray(fileComments)) {
- for (const comment of fileComments) {
- if (
- /eslint-disable(-next-line)? react-hooks\/(exhaustive-deps|rules-of-hooks)/.test(
- comment.value
- )
- ) {
- violations.push(comment);
- }
- }
- }
+ const violations = [];
+ const fileComments = pass.file.ast.comments;
+ let fileHasUseForgetDirective = false;
+ if (Array.isArray(fileComments)) {
+ for (const comment of fileComments) {
+ if (
+ /eslint-disable(-next-line)? react-hooks\/(exhaustive-deps|rules-of-hooks)/.test(
+ comment.value
+ )
+ ) {
+ violations.push(comment);
+ }
+ }
+ }
- if (violations.length > 0) {
- path.traverse({
- Directive(path) {
- if (hasUseForgetDirective(path.node)) {
- fileHasUseForgetDirective = true;
- }
- },
- });
-
- const reason = `One or more React eslint rules is disabled`;
- const error = new CompilerError();
- for (const violation of violations) {
- if (options.logger != null) {
- options.logger.logEvent("err", {
- reason,
- filename: pass.filename,
- violation,
- });
- }
-
- error.pushErrorDetail(
- new CompilerErrorDetail({
- reason,
- description: violation.value.trim(),
- severity: ErrorSeverity.UnsafeInput,
- codeframe: null,
- loc: violation.loc ?? null,
- })
- );
- }
-
- if (fileHasUseForgetDirective) {
- if (options.panicOnBailout || error.isCritical()) {
- throw error;
- } else {
- if (options.isDev) {
- log(error, pass.filename ?? null);
- }
- }
- }
-
- return;
- }
-
- path.traverse(visitor, {
- ...pass,
- opts: { ...pass.opts, ...options },
- filename: pass.filename ?? null,
- });
-
- // If there isn't already an import of * as React, insert it so useMemoCache doesn't
- // throw
- if (hasForgetCompiledCode) {
- let didInsertUseMemoCache = false;
- let hasExistingReactImport = false;
- path.traverse({
- CallExpression(callExprPath) {
- const callee = callExprPath.get("callee");
- const args = callExprPath.get("arguments");
- if (
- callee.isIdentifier() &&
- callee.node.name === "useMemoCache" &&
- args.length === 1 &&
- args[0].isNumericLiteral()
- ) {
- didInsertUseMemoCache = true;
- }
- },
- ImportDeclaration(importDeclPath) {
- if (isNonNamespacedImportOfReact(importDeclPath)) {
- hasExistingReactImport = true;
- }
- },
- });
- // If Forget did successfully compile inject/update an import of
- // `import {unstable_useMemoCache as useMemoCache} from 'react'` and rename
- // `React.unstable_useMemoCache(n)` to `useMemoCache(n)`;
- if (didInsertUseMemoCache) {
- if (hasExistingReactImport) {
- let didUpdateImport = false;
- path.traverse({
- ImportDeclaration(importDeclPath) {
- if (isNonNamespacedImportOfReact(importDeclPath)) {
- importDeclPath.pushContainer(
- "specifiers",
- t.importSpecifier(
- t.identifier("useMemoCache"),
- t.identifier("unstable_useMemoCache")
- )
- );
- didUpdateImport = true;
- }
- },
- });
- if (didUpdateImport === false) {
- throw new Error(
- "Expected an ImportDeclaration of react in order to update ImportSpecifiers with useMemoCache"
- );
- }
- } else {
- path.unshiftContainer(
- "body",
- t.importDeclaration(
- [
- t.importSpecifier(
- t.identifier("useMemoCache"),
- t.identifier("unstable_useMemoCache")
- ),
- ],
- t.stringLiteral("react")
- )
- );
- }
- }
- // TODO: check for duplicate import specifiers
- if (options.gating != null) {
- path.unshiftContainer(
- "body",
- buildImportForExternalFunction(options.gating)
- );
- }
- if (options.instrumentForget != null) {
- path.unshiftContainer(
- "body",
- buildImportForExternalFunction(options.instrumentForget.gating)
- );
- path.unshiftContainer(
- "body",
- buildImportForExternalFunction(
- options.instrumentForget.instrumentFn
- )
- );
- }
+ if (violations.length > 0) {
+ program.traverse({
+ Directive(directive) {
+ if (hasUseForgetDirective(directive.node)) {
+ fileHasUseForgetDirective = true;
}
},
- },
- };
+ });
+
+ const reason = `One or more React eslint rules is disabled`;
+ const error = new CompilerError();
+ for (const violation of violations) {
+ if (options.logger != null) {
+ options.logger.logEvent("err", {
+ reason,
+ filename: pass.filename,
+ violation,
+ });
+ }
+
+ error.pushErrorDetail(
+ new CompilerErrorDetail({
+ reason,
+ description: violation.value.trim(),
+ severity: ErrorSeverity.UnsafeInput,
+ codeframe: null,
+ loc: violation.loc ?? null,
+ })
+ );
+ }
+
+ if (fileHasUseForgetDirective) {
+ if (options.panicOnBailout || error.isCritical()) {
+ throw error;
+ } else {
+ if (options.isDev) {
+ log(error, pass.filename ?? null);
+ }
+ }
+ }
+
+ return;
+ }
+
+ program.traverse(visitor, {
+ ...pass,
+ opts: { ...pass.opts, ...options },
+ filename: pass.filename ?? null,
+ });
+
+ // If there isn't already an import of * as React, insert it so useMemoCache doesn't
+ // throw
+ if (hasForgetCompiledCode) {
+ let didInsertUseMemoCache = false;
+ let hasExistingReactImport = false;
+ program.traverse({
+ CallExpression(callExprPath) {
+ const callee = callExprPath.get("callee");
+ const args = callExprPath.get("arguments");
+ if (
+ callee.isIdentifier() &&
+ callee.node.name === "useMemoCache" &&
+ args.length === 1 &&
+ args[0].isNumericLiteral()
+ ) {
+ didInsertUseMemoCache = true;
+ }
+ },
+ ImportDeclaration(importDeclPath) {
+ if (isNonNamespacedImportOfReact(importDeclPath)) {
+ hasExistingReactImport = true;
+ }
+ },
+ });
+ // If Forget did successfully compile inject/update an import of
+ // `import {unstable_useMemoCache as useMemoCache} from 'react'` and rename
+ // `React.unstable_useMemoCache(n)` to `useMemoCache(n)`;
+ if (didInsertUseMemoCache) {
+ if (hasExistingReactImport) {
+ let didUpdateImport = false;
+ program.traverse({
+ ImportDeclaration(importDeclPath) {
+ if (isNonNamespacedImportOfReact(importDeclPath)) {
+ importDeclPath.pushContainer(
+ "specifiers",
+ t.importSpecifier(
+ t.identifier("useMemoCache"),
+ t.identifier("unstable_useMemoCache")
+ )
+ );
+ didUpdateImport = true;
+ }
+ },
+ });
+ if (didUpdateImport === false) {
+ throw new Error(
+ "Expected an ImportDeclaration of react in order to update ImportSpecifiers with useMemoCache"
+ );
+ }
+ } else {
+ program.unshiftContainer(
+ "body",
+ t.importDeclaration(
+ [
+ t.importSpecifier(
+ t.identifier("useMemoCache"),
+ t.identifier("unstable_useMemoCache")
+ ),
+ ],
+ t.stringLiteral("react")
+ )
+ );
+ }
+ }
+ // TODO: check for duplicate import specifiers
+ if (options.gating != null) {
+ program.unshiftContainer(
+ "body",
+ buildImportForExternalFunction(options.gating)
+ );
+ }
+ if (options.instrumentForget != null) {
+ program.unshiftContainer(
+ "body",
+ buildImportForExternalFunction(options.instrumentForget.gating)
+ );
+ program.unshiftContainer(
+ "body",
+ buildImportForExternalFunction(options.instrumentForget.instrumentFn)
+ );
+ }
+ }
}
function shouldCompile(
- fn: BabelCore.NodePath,
+ fn: NodePath,
pass: BabelPluginPass
): boolean {
if (pass.opts.enableOnlyOnUseForgetDirective) {
@@ -407,8 +389,8 @@ function makeError(
}
function buildFunctionDeclaration(
- fn: BabelCore.NodePath
-): BabelCore.NodePath | CompilerError {
+ fn: NodePath
+): NodePath | CompilerError {
if (!fn.parentPath.isVariableDeclarator()) {
return makeError(
"ArrowFunctionExpression must be declared in variable declaration",
@@ -447,7 +429,7 @@ function buildFunctionDeclaration(
}
function buildBlockStatement(
- fn: BabelCore.NodePath
+ fn: NodePath
): t.BlockStatement {
const body = fn.get("body");
if (body.isExpression()) {
@@ -469,7 +451,7 @@ function buildBlockStatement(
}
type GatingTestOptions = {
- originalFnDecl: BabelCore.NodePath;
+ originalFnDecl: NodePath;
compiledIdent: t.Identifier;
originalIdent: t.Identifier;
gating: ExternalFunction;
@@ -534,7 +516,7 @@ function buildSpecifierIdent(gating: ExternalFunction): t.Identifier {
* but not `import * as React from 'react';`
*/
function isNonNamespacedImportOfReact(
- importDeclPath: BabelCore.NodePath
+ importDeclPath: NodePath
): boolean {
return (
importDeclPath.get("source").node.value === "react" &&
diff --git a/compiler/forget/src/Babel/PluginOptions.ts b/compiler/forget/src/CompilerOptions.ts
similarity index 98%
rename from compiler/forget/src/Babel/PluginOptions.ts
rename to compiler/forget/src/CompilerOptions.ts
index 37145c2019..8db4c4f789 100644
--- a/compiler/forget/src/Babel/PluginOptions.ts
+++ b/compiler/forget/src/CompilerOptions.ts
@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
-import { EnvironmentConfig } from "../HIR/Environment";
+import { EnvironmentConfig } from "./HIR/Environment";
export type ExternalFunction = {
/**