@samitouri / QOS-React / commits / b98ee34f45

Delegate BabelPlugin to CompilerEntrypoint

Moves the existing logic in BabelPlugin to its own files

Lauren Tan committed Jun 2, 2023 at 13:06 UTC b98ee34f4562ac37471e125a78b111bfc4eb92fb
5 files changed +159 -724
compiler/forget/src/Babel/BabelPlugin.ts
+4 -517
@@ -7,40 +7,9 @@
7
8 /// <reference path="./plugin-syntax-jsx.d.ts" />
9
10 -import type * as BabelCore from "@babel/core";
10 import jsx from "@babel/plugin-syntax-jsx";
12 -import * as t from "@babel/types";
13 -import {
14 - CompilerError,
15 - CompilerErrorDetail,
16 - ErrorSeverity,
17 -} from "../CompilerError";
18 -import { compile } from "../CompilerPipeline";
19 -import { GeneratedSource } from "../HIR";
20 -import { addInstrumentForget } from "./InstrumentForgetBabelPlugin";
21 -import {
22 - ExternalFunction,
23 - PluginOptions,
24 - parsePluginOptions,
25 -} from "./PluginOptions";
26 -
27 -type BabelPluginPass = {
28 - opts: PluginOptions;
29 - filename: string | null;
30 -};
31 -
32 -function hasUseForgetDirective(directive: t.Directive): boolean {
33 - return directive.value.value === "use forget";
34 -}
35 -
36 -function hasAnyUseForgetDirectives(directives: t.Directive[]): boolean {
37 - for (const directive of directives) {
38 - if (hasUseForgetDirective(directive)) {
39 - return true;
40 - }
41 - }
42 - return false;
43 -}
11 +import type * as BabelCore from "@babel/core";
12 +import { compileProgram } from "../CompilerEntrypoint";
13
14 /**
15 * The React Forget Babel Plugin
@@ -50,150 +19,6 @@ function hasAnyUseForgetDirectives(directives: t.Directive[]): boolean {
19 export default function ReactForgetBabelPlugin(
20 _babel: typeof BabelCore
21 ): BabelCore.PluginObj {
53 - let hasForgetCompiledCode: boolean = false;
54 -
55 - function visitFn(
56 - fn: BabelCore.NodePath<t.FunctionDeclaration>,
57 - pass: BabelPluginPass
58 - ): void {
59 - try {
60 - const compiled = compile(fn, pass.opts.environment);
61 - if (fn.node.id == null) {
62 - CompilerError.invariant(
63 - "FunctionDeclaration must have a name",
64 - fn.node.loc ?? GeneratedSource
65 - );
66 - }
67 - const originalIdent = fn.node.id;
68 -
69 - if (pass.opts.gating != null) {
70 - // Rename existing function
71 - fn.node.id = addSuffix(fn.node.id, "_uncompiled");
72 -
73 - // Rename and append compiled function
74 - if (compiled.id == null) {
75 - CompilerError.invariant(
76 - "FunctionDeclaration must produce a name",
77 - fn.node.loc ?? GeneratedSource
78 - );
79 - }
80 - compiled.id = addSuffix(compiled.id, "_forget");
81 - const compiledFn = fn.insertAfter(compiled)[0];
82 - compiledFn.skip();
83 -
84 - // Build and append gating test
85 - compiledFn.insertAfter(
86 - buildGatingTest({
87 - originalFnDecl: fn,
88 - compiledIdent: compiled.id,
89 - originalIdent,
90 - gating: pass.opts.gating,
91 - })
92 - );
93 - if (pass.opts.instrumentForget != null) {
94 - const gatingIdentifierName =
95 - pass.opts.instrumentForget.gating.importSpecifierName;
96 - const instrumentFnName =
97 - pass.opts.instrumentForget.instrumentFn.importSpecifierName;
98 - addInstrumentForget(
99 - fn,
100 - originalIdent.name,
101 - gatingIdentifierName,
102 - instrumentFnName
103 - );
104 - addInstrumentForget(
105 - compiledFn,
106 - originalIdent.name,
107 - gatingIdentifierName,
108 - instrumentFnName
109 - );
110 - }
111 - } else {
112 - fn.replaceWith(compiled);
113 - if (pass.opts.instrumentForget != null) {
114 - const gatingIdentifierName =
115 - pass.opts.instrumentForget.gating.importSpecifierName;
116 - const instrumentFnName =
117 - pass.opts.instrumentForget.instrumentFn.importSpecifierName;
118 - addInstrumentForget(
119 - fn,
120 - originalIdent.name,
121 - gatingIdentifierName,
122 - instrumentFnName
123 - );
124 - }
125 - }
126 -
127 - hasForgetCompiledCode = true;
128 - } catch (err) {
129 - if (pass.opts.logger && err) {
130 - pass.opts.logger.logEvent("err", err);
131 - }
132 - /** Always throw if the flag is enabled, otherwise we only throw if the error is critical
133 - * (eg an invariant is broken, meaning the compiler may be buggy). See
134 - * {@link CompilerError.isCritical} for mappings.
135 - * */
136 - if (
137 - pass.opts.panicOnBailout ||
138 - !(err instanceof CompilerError) ||
139 - (err instanceof CompilerError && err.isCritical())
140 - ) {
141 - throw err;
142 - } else {
143 - if (pass.opts.isDev) {
144 - log(err, pass.filename ?? null);
145 - }
146 - }
147 - } finally {
148 - // We are generating a new FunctionDeclaration node, so we must skip over it or this
149 - // traversal will loop infinitely.
150 - fn.skip();
151 - }
152 - }
153 -
154 - const visitor = {
155 - FunctionDeclaration(
156 - fn: BabelCore.NodePath<t.FunctionDeclaration>,
157 - pass: BabelPluginPass
158 - ): void {
159 - if (!shouldCompile(fn, pass)) {
160 - return;
161 - }
162 -
163 - visitFn(fn, pass);
164 - },
165 -
166 - ArrowFunctionExpression(
167 - fn: BabelCore.NodePath<t.ArrowFunctionExpression>,
168 - pass: BabelPluginPass
169 - ): void {
170 - if (!shouldCompile(fn, pass)) {
171 - return;
172 - }
173 -
174 - const loweredFn = buildFunctionDeclaration(fn);
175 - if (loweredFn instanceof CompilerError) {
176 - const error = loweredFn;
177 -
178 - const options = parsePluginOptions(pass.opts);
179 - if (options.logger != null) {
180 - options.logger.logEvent("err", error);
181 - }
182 -
183 - if (options.panicOnBailout || error.isCritical()) {
184 - throw error;
185 - } else {
186 - if (pass.opts.isDev) {
187 - log(error, pass.filename);
188 - }
189 - }
190 - return;
191 - }
192 -
193 - visitFn(loweredFn, pass);
194 - },
195 - };
196 -
22 return {
23 name: "react-forget",
24 inherits: jsx,
@@ -201,347 +26,9 @@ export default function ReactForgetBabelPlugin(
26 // Note: Babel does some "smart" merging of visitors across plugins, so even if A is inserted
27 // prior to B, if A does not have a Program visitor and B does, B will run first. We always
28 // want Forget to run true to source as possible.
204 - Program(path, pass): void {
205 - const options = parsePluginOptions(pass.opts);
206 -
207 - const violations = [];
208 - const fileComments = pass.file.ast.comments;
209 - let fileHasUseForgetDirective = false;
210 - if (Array.isArray(fileComments)) {
211 - for (const comment of fileComments) {
212 - if (
213 - /eslint-disable(-next-line)? react-hooks\/(exhaustive-deps|rules-of-hooks)/.test(
214 - comment.value
215 - )
216 - ) {
217 - violations.push(comment);
218 - }
219 - }
220 - }
221 -
222 - if (violations.length > 0) {
223 - path.traverse({
224 - Directive(path) {
225 - if (hasUseForgetDirective(path.node)) {
226 - fileHasUseForgetDirective = true;
227 - }
228 - },
229 - });
230 -
231 - const reason = `One or more React eslint rules is disabled`;
232 - const error = new CompilerError();
233 - for (const violation of violations) {
234 - if (options.logger != null) {
235 - options.logger.logEvent("err", {
236 - reason,
237 - filename: pass.filename,
238 - violation,
239 - });
240 - }
241 -
242 - error.pushErrorDetail(
243 - new CompilerErrorDetail({
244 - reason,
245 - description: violation.value.trim(),
246 - severity: ErrorSeverity.UnsafeInput,
247 - codeframe: null,
248 - loc: violation.loc ?? null,
249 - })
250 - );
251 - }
252 -
253 - if (fileHasUseForgetDirective) {
254 - if (options.panicOnBailout || error.isCritical()) {
255 - throw error;
256 - } else {
257 - if (options.isDev) {
258 - log(error, pass.filename ?? null);
259 - }
260 - }
261 - }
262 -
263 - return;
264 - }
265 -
266 - path.traverse(visitor, {
267 - ...pass,
268 - opts: { ...pass.opts, ...options },
269 - filename: pass.filename ?? null,
270 - });
271 -
272 - // If there isn't already an import of * as React, insert it so useMemoCache doesn't
273 - // throw
274 - if (hasForgetCompiledCode) {
275 - let didInsertUseMemoCache = false;
276 - let hasExistingReactImport = false;
277 - path.traverse({
278 - CallExpression(callExprPath) {
279 - const callee = callExprPath.get("callee");
280 - const args = callExprPath.get("arguments");
281 - if (
282 - callee.isIdentifier() &&
283 - callee.node.name === "useMemoCache" &&
284 - args.length === 1 &&
285 - args[0].isNumericLiteral()
286 - ) {
287 - didInsertUseMemoCache = true;
288 - }
289 - },
290 - ImportDeclaration(importDeclPath) {
291 - if (isNonNamespacedImportOfReact(importDeclPath)) {
292 - hasExistingReactImport = true;
293 - }
294 - },
295 - });
296 - // If Forget did successfully compile inject/update an import of
297 - // `import {unstable_useMemoCache as useMemoCache} from 'react'` and rename
298 - // `React.unstable_useMemoCache(n)` to `useMemoCache(n)`;
299 - if (didInsertUseMemoCache) {
300 - if (hasExistingReactImport) {
301 - let didUpdateImport = false;
302 - path.traverse({
303 - ImportDeclaration(importDeclPath) {
304 - if (isNonNamespacedImportOfReact(importDeclPath)) {
305 - importDeclPath.pushContainer(
306 - "specifiers",
307 - t.importSpecifier(
308 - t.identifier("useMemoCache"),
309 - t.identifier("unstable_useMemoCache")
310 - )
311 - );
312 - didUpdateImport = true;
313 - }
314 - },
315 - });
316 - if (didUpdateImport === false) {
317 - throw new Error(
318 - "Expected an ImportDeclaration of react in order to update ImportSpecifiers with useMemoCache"
319 - );
320 - }
321 - } else {
322 - path.unshiftContainer(
323 - "body",
324 - t.importDeclaration(
325 - [
326 - t.importSpecifier(
327 - t.identifier("useMemoCache"),
328 - t.identifier("unstable_useMemoCache")
329 - ),
330 - ],
331 - t.stringLiteral("react")
332 - )
333 - );
334 - }
335 - }
336 - // TODO: check for duplicate import specifiers
337 - if (options.gating != null) {
338 - path.unshiftContainer(
339 - "body",
340 - buildImportForExternalFunction(options.gating)
341 - );
342 - }
343 - if (options.instrumentForget != null) {
344 - path.unshiftContainer(
345 - "body",
346 - buildImportForExternalFunction(options.instrumentForget.gating)
347 - );
348 - path.unshiftContainer(
349 - "body",
350 - buildImportForExternalFunction(
351 - options.instrumentForget.instrumentFn
352 - )
353 - );
354 - }
355 - }
29 + Program(prog, pass): void {
30 + compileProgram(prog, pass);
31 },
32 },
33 };
34 }
360 -
361 -function shouldCompile(
362 - fn: BabelCore.NodePath<t.FunctionDeclaration | t.ArrowFunctionExpression>,
363 - pass: BabelPluginPass
364 -): boolean {
365 - if (pass.opts.enableOnlyOnUseForgetDirective) {
366 - const body = fn.get("body");
367 - if (!body.isBlockStatement()) {
368 - return false;
369 - }
370 - if (!hasAnyUseForgetDirectives(body.node.directives)) {
371 - return false;
372 - }
373 - }
374 -
375 - if (fn.scope.getProgramParent() !== fn.scope.parent) {
376 - return false;
377 - }
378 -
379 - return true;
380 -}
381 -
382 -function log(error: CompilerError, filename: string | null): void {
383 - const filenameStr = filename ? `in ${filename}` : "";
384 - console.log(
385 - error.details
386 - .map(
387 - (e) =>
388 - `[ReactForget] Skipping compilation of component ${filenameStr}: ${e.printErrorMessage()}`
389 - )
390 - .join("\n")
391 - );
392 -}
393 -
394 -function makeError(
395 - reason: string,
396 - loc: t.SourceLocation | null
397 -): CompilerError {
398 - const error = new CompilerError();
399 - error.pushErrorDetail(
400 - new CompilerErrorDetail({
401 - reason,
402 - description: null,
403 - severity: ErrorSeverity.InvalidInput,
404 - codeframe: null,
405 - loc,
406 - })
407 - );
408 - return error;
409 -}
410 -
411 -function buildFunctionDeclaration(
412 - fn: BabelCore.NodePath<t.ArrowFunctionExpression>
413 -): BabelCore.NodePath<t.FunctionDeclaration> | CompilerError {
414 - if (!fn.parentPath.isVariableDeclarator()) {
415 - return makeError(
416 - "ArrowFunctionExpression must be declared in variable declaration",
417 - fn.node.loc ?? null
418 - );
419 - }
420 - const variableDeclarator = fn.parentPath;
421 -
422 - if (!variableDeclarator.parentPath.isVariableDeclaration()) {
423 - return makeError(
424 - "ArrowFunctionExpression must be a single declaration",
425 - fn.node.loc ?? null
426 - );
427 - }
428 - const variableDeclaration = variableDeclarator.parentPath;
429 -
430 - const id = variableDeclarator.get("id");
431 - if (!id.isIdentifier()) {
432 - return makeError(
433 - "ArrowFunctionExpression must have an id",
434 - fn.node.loc ?? null
435 - );
436 - }
437 -
438 - const rewrittenFn = variableDeclaration.replaceWith(
439 - t.functionDeclaration(
440 - id.node,
441 - fn.node.params,
442 - buildBlockStatement(fn),
443 - fn.node.generator,
444 - fn.node.async
445 - )
446 - )[0];
447 - fn.skip();
448 - return rewrittenFn;
449 -}
450 -
451 -function buildBlockStatement(
452 - fn: BabelCore.NodePath<t.ArrowFunctionExpression>
453 -): t.BlockStatement {
454 - const body = fn.get("body");
455 - if (body.isExpression()) {
456 - const wrappedBody = body.replaceWith(
457 - t.blockStatement([t.returnStatement(body.node)])
458 - )[0];
459 - body.skip();
460 -
461 - return wrappedBody.node;
462 - }
463 -
464 - if (!body.isBlockStatement()) {
465 - CompilerError.invariant(
466 - "Body must be a BlockStatement",
467 - body.node.loc ?? GeneratedSource
468 - );
469 - }
470 - return body.node;
471 -}
472 -
473 -type GatingTestOptions = {
474 - originalFnDecl: BabelCore.NodePath<t.FunctionDeclaration>;
475 - compiledIdent: t.Identifier;
476 - originalIdent: t.Identifier;
477 - gating: ExternalFunction;
478 -};
479 -function buildGatingTest({
480 - originalFnDecl,
481 - compiledIdent,
482 - originalIdent,
483 - gating,
484 -}: GatingTestOptions): t.Node | t.Node[] {
485 - const testVarDecl = t.variableDeclaration("const", [
486 - t.variableDeclarator(
487 - originalIdent,
488 - t.conditionalExpression(
489 - t.callExpression(buildSpecifierIdent(gating), []),
490 - compiledIdent,
491 - originalFnDecl.node.id!
492 - )
493 - ),
494 - ]);
495 -
496 - // Re-export new declaration
497 - const parent = originalFnDecl.parentPath;
498 - if (t.isExportDefaultDeclaration(parent)) {
499 - // Re-add uncompiled function
500 - parent.replaceWith(originalFnDecl)[0].skip();
501 -
502 - // Add test and synthesize new export
503 - return [testVarDecl, t.exportDefaultDeclaration(originalIdent)];
504 - } else if (t.isExportNamedDeclaration(parent)) {
505 - // Re-add uncompiled function
506 - parent.replaceWith(originalFnDecl)[0].skip();
507 -
508 - // Add and export test
509 - return t.exportNamedDeclaration(testVarDecl);
510 - }
511 -
512 - // Just add the test, no need for re-export
513 - return testVarDecl;
514 -}
515 -
516 -function addSuffix(id: t.Identifier, suffix: string): t.Identifier {
517 - return t.identifier(`${id.name}${suffix}`);
518 -}
519 -
520 -function buildImportForExternalFunction(
521 - gating: ExternalFunction
522 -): t.ImportDeclaration {
523 - const specifierIdent = buildSpecifierIdent(gating);
524 - return t.importDeclaration(
525 - [t.importSpecifier(specifierIdent, specifierIdent)],
526 - t.stringLiteral(gating.source)
527 - );
528 -}
529 -
530 -function buildSpecifierIdent(gating: ExternalFunction): t.Identifier {
531 - return t.identifier(gating.importSpecifierName);
532 -}
533 -
534 -/**
535 - * Matches `import { ... } from 'react';`
536 - * but not `import * as React from 'react';`
537 - */
538 -function isNonNamespacedImportOfReact(
539 - importDeclPath: BabelCore.NodePath<t.ImportDeclaration>
540 -): boolean {
541 - return (
542 - importDeclPath.get("source").node.value === "react" &&
543 - importDeclPath
544 - .get("specifiers")
545 - .every((specifier) => specifier.isImportSpecifier())
546 - );
547 -}
compiler/forget/src/Babel/InstrumentForgetBabelPlugin.ts deleted
-34
@@ -1,34 +0,0 @@
1 -/**
2 - * Copyright (c) Meta Platforms, Inc. and 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 { NodePath } from "@babel/core";
9 -import * as t from "@babel/types";
10 -import "./BabelPlugin";
11 -
12 -export function addInstrumentForget(
13 - fn: NodePath<t.FunctionDeclaration>,
14 - fnName: string,
15 - gatingIdentifierName: string,
16 - instrumentFnName: string
17 -): void {
18 - const fnBody = fn.get("body");
19 - // Technically, this is a conditional hook call. However, we expect
20 - // __DEV__ and gatingIdentifier to be runtime constants
21 - const testExpr: t.Node = t.ifStatement(
22 - t.logicalExpression(
23 - "&&",
24 - t.identifier("__DEV__"),
25 - t.identifier(gatingIdentifierName)
26 - ),
27 - t.expressionStatement(
28 - t.callExpression(t.identifier(instrumentFnName), [
29 - t.stringLiteral(fnName),
30 - ])
31 - )
32 - );
33 - fnBody.unshiftContainer("body", testExpr);
34 -}
compiler/forget/src/Babel/RunReactForgetBabelPlugin.ts
+1 -1
@@ -11,7 +11,7 @@ import * as parser from "@babel/parser";
11 import invariant from "invariant";
12 import prettier from "prettier";
13 import ReactForgetBabelPlugin from "./BabelPlugin";
14 -import { PluginOptions } from "./PluginOptions";
14 +import type { PluginOptions } from "../CompilerOptions";
15
16 type ReactForgetBabelPluginResult = {
17 ast: BabelCore.BabelFileResult["ast"];
compiler/forget/src/CompilerEntrypoint.ts
+153 -171
@@ -5,8 +5,7 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import type * as BabelCore from "@babel/core";
9 -import jsx from "@babel/plugin-syntax-jsx";
8 +import { NodePath, PluginPass } from "@babel/core";
9 import * as t from "@babel/types";
10 import {
11 CompilerError,
@@ -20,7 +19,7 @@ import {
19 ExternalFunction,
20 PluginOptions,
21 parsePluginOptions,
23 -} from "./Babel/PluginOptions";
22 +} from "./CompilerOptions";
23
24 type BabelPluginPass = {
25 opts: PluginOptions;
@@ -40,18 +39,14 @@ function hasAnyUseForgetDirectives(directives: t.Directive[]): boolean {
39 return false;
40 }
41
43 -/**
44 - * The React Forget Babel Plugin
45 - * @param {*} _babel
46 - * @returns
47 - */
48 -export default function ReactForgetBabelPlugin(
49 - _babel: typeof BabelCore
50 -): BabelCore.PluginObj {
42 +export function compileProgram(
43 + program: NodePath<t.Program>,
44 + pass: PluginPass
45 +): void {
46 let hasForgetCompiledCode: boolean = false;
47
48 function visitFn(
54 - fn: BabelCore.NodePath<t.FunctionDeclaration>,
49 + fn: NodePath<t.FunctionDeclaration>,
50 pass: BabelPluginPass
51 ): void {
52 try {
@@ -151,7 +146,7 @@ export default function ReactForgetBabelPlugin(
146
147 const visitor = {
148 FunctionDeclaration(
154 - fn: BabelCore.NodePath<t.FunctionDeclaration>,
149 + fn: NodePath<t.FunctionDeclaration>,
150 pass: BabelPluginPass
151 ): void {
152 if (!shouldCompile(fn, pass)) {
@@ -162,7 +157,7 @@ export default function ReactForgetBabelPlugin(
157 },
158
159 ArrowFunctionExpression(
165 - fn: BabelCore.NodePath<t.ArrowFunctionExpression>,
160 + fn: NodePath<t.ArrowFunctionExpression>,
161 pass: BabelPluginPass
162 ): void {
163 if (!shouldCompile(fn, pass)) {
@@ -192,172 +187,159 @@ export default function ReactForgetBabelPlugin(
187 },
188 };
189
195 - return {
196 - name: "react-forget",
197 - inherits: jsx,
198 - visitor: {
199 - // Note: Babel does some "smart" merging of visitors across plugins, so even if A is inserted
200 - // prior to B, if A does not have a Program visitor and B does, B will run first. We always
201 - // want Forget to run true to source as possible.
202 - Program(path, pass): void {
203 - const options = parsePluginOptions(pass.opts);
204 -
205 - const violations = [];
206 - const fileComments = pass.file.ast.comments;
207 - let fileHasUseForgetDirective = false;
208 - if (Array.isArray(fileComments)) {
209 - for (const comment of fileComments) {
210 - if (
211 - /eslint-disable(-next-line)? react-hooks\/(exhaustive-deps|rules-of-hooks)/.test(
212 - comment.value
213 - )
214 - ) {
215 - violations.push(comment);
216 - }
217 - }
218 - }
190 + const options = parsePluginOptions(pass.opts);
191
220 - if (violations.length > 0) {
221 - path.traverse({
222 - Directive(path) {
223 - if (hasUseForgetDirective(path.node)) {
224 - fileHasUseForgetDirective = true;
225 - }
226 - },
227 - });
228 -
229 - const reason = `One or more React eslint rules is disabled`;
230 - const error = new CompilerError();
231 - for (const violation of violations) {
232 - if (options.logger != null) {
233 - options.logger.logEvent("err", {
234 - reason,
235 - filename: pass.filename,
236 - violation,
237 - });
238 - }
192 + const violations = [];
193 + const fileComments = pass.file.ast.comments;
194 + let fileHasUseForgetDirective = false;
195 + if (Array.isArray(fileComments)) {
196 + for (const comment of fileComments) {
197 + if (
198 + /eslint-disable(-next-line)? react-hooks\/(exhaustive-deps|rules-of-hooks)/.test(
199 + comment.value
200 + )
201 + ) {
202 + violations.push(comment);
203 + }
204 + }
205 + }
206
240 - error.pushErrorDetail(
241 - new CompilerErrorDetail({
242 - reason,
243 - description: violation.value.trim(),
244 - severity: ErrorSeverity.UnsafeInput,
245 - codeframe: null,
246 - loc: violation.loc ?? null,
247 - })
248 - );
249 - }
207 + if (violations.length > 0) {
208 + program.traverse({
209 + Directive(directive) {
210 + if (hasUseForgetDirective(directive.node)) {
211 + fileHasUseForgetDirective = true;
212 + }
213 + },
214 + });
215 +
216 + const reason = `One or more React eslint rules is disabled`;
217 + const error = new CompilerError();
218 + for (const violation of violations) {
219 + if (options.logger != null) {
220 + options.logger.logEvent("err", {
221 + reason,
222 + filename: pass.filename,
223 + violation,
224 + });
225 + }
226
251 - if (fileHasUseForgetDirective) {
252 - if (options.panicOnBailout || error.isCritical()) {
253 - throw error;
254 - } else {
255 - if (options.isDev) {
256 - log(error, pass.filename ?? null);
257 - }
258 - }
259 - }
227 + error.pushErrorDetail(
228 + new CompilerErrorDetail({
229 + reason,
230 + description: violation.value.trim(),
231 + severity: ErrorSeverity.UnsafeInput,
232 + codeframe: null,
233 + loc: violation.loc ?? null,
234 + })
235 + );
236 + }
237
261 - return;
238 + if (fileHasUseForgetDirective) {
239 + if (options.panicOnBailout || error.isCritical()) {
240 + throw error;
241 + } else {
242 + if (options.isDev) {
243 + log(error, pass.filename ?? null);
244 }
245 + }
246 + }
247
264 - path.traverse(visitor, {
265 - ...pass,
266 - opts: { ...pass.opts, ...options },
267 - filename: pass.filename ?? null,
268 - });
248 + return;
249 + }
250
270 - // If there isn't already an import of * as React, insert it so useMemoCache doesn't
271 - // throw
272 - if (hasForgetCompiledCode) {
273 - let didInsertUseMemoCache = false;
274 - let hasExistingReactImport = false;
275 - path.traverse({
276 - CallExpression(callExprPath) {
277 - const callee = callExprPath.get("callee");
278 - const args = callExprPath.get("arguments");
279 - if (
280 - callee.isIdentifier() &&
281 - callee.node.name === "useMemoCache" &&
282 - args.length === 1 &&
283 - args[0].isNumericLiteral()
284 - ) {
285 - didInsertUseMemoCache = true;
286 - }
287 - },
288 - ImportDeclaration(importDeclPath) {
289 - if (isNonNamespacedImportOfReact(importDeclPath)) {
290 - hasExistingReactImport = true;
291 - }
292 - },
293 - });
294 - // If Forget did successfully compile inject/update an import of
295 - // `import {unstable_useMemoCache as useMemoCache} from 'react'` and rename
296 - // `React.unstable_useMemoCache(n)` to `useMemoCache(n)`;
297 - if (didInsertUseMemoCache) {
298 - if (hasExistingReactImport) {
299 - let didUpdateImport = false;
300 - path.traverse({
301 - ImportDeclaration(importDeclPath) {
302 - if (isNonNamespacedImportOfReact(importDeclPath)) {
303 - importDeclPath.pushContainer(
304 - "specifiers",
305 - t.importSpecifier(
306 - t.identifier("useMemoCache"),
307 - t.identifier("unstable_useMemoCache")
308 - )
309 - );
310 - didUpdateImport = true;
311 - }
312 - },
313 - });
314 - if (didUpdateImport === false) {
315 - throw new Error(
316 - "Expected an ImportDeclaration of react in order to update ImportSpecifiers with useMemoCache"
317 - );
318 - }
319 - } else {
320 - path.unshiftContainer(
321 - "body",
322 - t.importDeclaration(
323 - [
324 - t.importSpecifier(
325 - t.identifier("useMemoCache"),
326 - t.identifier("unstable_useMemoCache")
327 - ),
328 - ],
329 - t.stringLiteral("react")
251 + program.traverse(visitor, {
252 + ...pass,
253 + opts: { ...pass.opts, ...options },
254 + filename: pass.filename ?? null,
255 + });
256 +
257 + // If there isn't already an import of * as React, insert it so useMemoCache doesn't
258 + // throw
259 + if (hasForgetCompiledCode) {
260 + let didInsertUseMemoCache = false;
261 + let hasExistingReactImport = false;
262 + program.traverse({
263 + CallExpression(callExprPath) {
264 + const callee = callExprPath.get("callee");
265 + const args = callExprPath.get("arguments");
266 + if (
267 + callee.isIdentifier() &&
268 + callee.node.name === "useMemoCache" &&
269 + args.length === 1 &&
270 + args[0].isNumericLiteral()
271 + ) {
272 + didInsertUseMemoCache = true;
273 + }
274 + },
275 + ImportDeclaration(importDeclPath) {
276 + if (isNonNamespacedImportOfReact(importDeclPath)) {
277 + hasExistingReactImport = true;
278 + }
279 + },
280 + });
281 + // If Forget did successfully compile inject/update an import of
282 + // `import {unstable_useMemoCache as useMemoCache} from 'react'` and rename
283 + // `React.unstable_useMemoCache(n)` to `useMemoCache(n)`;
284 + if (didInsertUseMemoCache) {
285 + if (hasExistingReactImport) {
286 + let didUpdateImport = false;
287 + program.traverse({
288 + ImportDeclaration(importDeclPath) {
289 + if (isNonNamespacedImportOfReact(importDeclPath)) {
290 + importDeclPath.pushContainer(
291 + "specifiers",
292 + t.importSpecifier(
293 + t.identifier("useMemoCache"),
294 + t.identifier("unstable_useMemoCache")
295 )
296 );
297 + didUpdateImport = true;
298 }
333 - }
334 - // TODO: check for duplicate import specifiers
335 - if (options.gating != null) {
336 - path.unshiftContainer(
337 - "body",
338 - buildImportForExternalFunction(options.gating)
339 - );
340 - }
341 - if (options.instrumentForget != null) {
342 - path.unshiftContainer(
343 - "body",
344 - buildImportForExternalFunction(options.instrumentForget.gating)
345 - );
346 - path.unshiftContainer(
347 - "body",
348 - buildImportForExternalFunction(
349 - options.instrumentForget.instrumentFn
350 - )
351 - );
352 - }
299 + },
300 + });
301 + if (didUpdateImport === false) {
302 + throw new Error(
303 + "Expected an ImportDeclaration of react in order to update ImportSpecifiers with useMemoCache"
304 + );
305 }
354 - },
355 - },
356 - };
306 + } else {
307 + program.unshiftContainer(
308 + "body",
309 + t.importDeclaration(
310 + [
311 + t.importSpecifier(
312 + t.identifier("useMemoCache"),
313 + t.identifier("unstable_useMemoCache")
314 + ),
315 + ],
316 + t.stringLiteral("react")
317 + )
318 + );
319 + }
320 + }
321 + // TODO: check for duplicate import specifiers
322 + if (options.gating != null) {
323 + program.unshiftContainer(
324 + "body",
325 + buildImportForExternalFunction(options.gating)
326 + );
327 + }
328 + if (options.instrumentForget != null) {
329 + program.unshiftContainer(
330 + "body",
331 + buildImportForExternalFunction(options.instrumentForget.gating)
332 + );
333 + program.unshiftContainer(
334 + "body",
335 + buildImportForExternalFunction(options.instrumentForget.instrumentFn)
336 + );
337 + }
338 + }
339 }
340
341 function shouldCompile(
360 - fn: BabelCore.NodePath<t.FunctionDeclaration | t.ArrowFunctionExpression>,
342 + fn: NodePath<t.FunctionDeclaration | t.ArrowFunctionExpression>,
343 pass: BabelPluginPass
344 ): boolean {
345 if (pass.opts.enableOnlyOnUseForgetDirective) {
@@ -407,8 +389,8 @@ function makeError(
389 }
390
391 function buildFunctionDeclaration(
410 - fn: BabelCore.NodePath<t.ArrowFunctionExpression>
411 -): BabelCore.NodePath<t.FunctionDeclaration> | CompilerError {
392 + fn: NodePath<t.ArrowFunctionExpression>
393 +): NodePath<t.FunctionDeclaration> | CompilerError {
394 if (!fn.parentPath.isVariableDeclarator()) {
395 return makeError(
396 "ArrowFunctionExpression must be declared in variable declaration",
@@ -447,7 +429,7 @@ function buildFunctionDeclaration(
429 }
430
431 function buildBlockStatement(
450 - fn: BabelCore.NodePath<t.ArrowFunctionExpression>
432 + fn: NodePath<t.ArrowFunctionExpression>
433 ): t.BlockStatement {
434 const body = fn.get("body");
435 if (body.isExpression()) {
@@ -469,7 +451,7 @@ function buildBlockStatement(
451 }
452
453 type GatingTestOptions = {
472 - originalFnDecl: BabelCore.NodePath<t.FunctionDeclaration>;
454 + originalFnDecl: NodePath<t.FunctionDeclaration>;
455 compiledIdent: t.Identifier;
456 originalIdent: t.Identifier;
457 gating: ExternalFunction;
@@ -534,7 +516,7 @@ function buildSpecifierIdent(gating: ExternalFunction): t.Identifier {
516 * but not `import * as React from 'react';`
517 */
518 function isNonNamespacedImportOfReact(
537 - importDeclPath: BabelCore.NodePath<t.ImportDeclaration>
519 + importDeclPath: NodePath<t.ImportDeclaration>
520 ): boolean {
521 return (
522 importDeclPath.get("source").node.value === "react" &&
compiler/forget/src/CompilerOptions.ts renamed
+1 -1
@@ -5,7 +5,7 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import { EnvironmentConfig } from "../HIR/Environment";
8 +import { EnvironmentConfig } from "./HIR/Environment";
9
10 export type ExternalFunction = {
11 /**