@samitouri / QOS-React-2 / commits / d6ff65ecc7

Add runtime validation for ExternalFunction

Use zod to do runtime validation and throw if incorrect. This PR only adds validation for ExternalFunction, will validate other options in follow on PRs.

Sathya Gunasekaran committed Nov 7, 2023 at 11:04 UTC d6ff65ecc707af73a1775aa10e88b18049721d67
4 files changed +73 -26
compiler/packages/babel-plugin-react-forget/package.json
+3 -1
@@ -31,7 +31,9 @@
31 "@babel/types": "^7.19.0",
32 "chalk": "4",
33 "invariant": "^2.2.4",
34 - "pretty-format": "^24"
34 + "pretty-format": "^24",
35 + "zod": "^3.22.4",
36 + "zod-validation-error": "^2.1.0"
37 },
38 "devDependencies": {
39 "@babel/core": "^7.2.0",
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Options.ts
+30 -15
@@ -6,24 +6,39 @@
6 */
7
8 import * as t from "@babel/types";
9 -import { CompilerErrorDetailOptions } from "../CompilerError";
9 +import { z } from "zod";
10 +import { CompilerError, CompilerErrorDetailOptions } from "../CompilerError";
11 import { PartialEnvironmentConfig } from "../HIR/Environment";
12
12 -export type ExternalFunction = {
13 - /**
14 - * Source for the imported module that exports the `importSpecifierName` functions
15 - */
16 - source: string;
17 - /**
18 - * Unique name for the feature flag test condition, eg `isForgetEnabled_ProjectName`
19 - */
20 - importSpecifierName: string;
21 -};
13 +import { fromZodError } from "zod-validation-error";
14
23 -export type InstrumentForgetOptions = {
24 - gating: ExternalFunction;
25 - instrumentFn: ExternalFunction;
26 -};
15 +export const ExternalFunctionSchema = z.object({
16 + // Source for the imported module that exports the `importSpecifierName` functions
17 + source: z.string(),
18 +
19 + // Unique name for the feature flag test condition, eg `isForgetEnabled_ProjectName`
20 + importSpecifierName: z.string(),
21 +});
22 +
23 +export function tryParseExternalFunction(
24 + maybeExternalFunction: any
25 +): ExternalFunction {
26 + const externalFunction = ExternalFunctionSchema.safeParse(
27 + maybeExternalFunction
28 + );
29 + if (externalFunction.success) {
30 + return externalFunction.data;
31 + }
32 +
33 + CompilerError.invalidConfig({
34 + reason: `${fromZodError(externalFunction.error)}`,
35 + description: "Update Forget config to fix the error",
36 + loc: null,
37 + suggestions: null,
38 + });
39 +}
40 +
41 +export type ExternalFunction = z.infer<typeof ExternalFunctionSchema>;
42
43 export type PanicThresholdOptions =
44 // Any errors will panic the compiler by throwing an exception, which will
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Program.ts
+30 -10
@@ -19,7 +19,12 @@ import { assertExhaustive } from "../Utils/utils";
19 import { insertGatedFunctionDeclaration } from "./Gating";
20 import { addImportsToProgram, updateUseMemoCacheImport } from "./Imports";
21 import { addInstrumentForget } from "./Instrumentation";
22 -import { ExternalFunction, PluginOptions, parsePluginOptions } from "./Options";
22 +import {
23 + ExternalFunction,
24 + PluginOptions,
25 + parsePluginOptions,
26 + tryParseExternalFunction,
27 +} from "./Options";
28 import { compileFn } from "./Pipeline";
29
30 export type CompilerPass = {
@@ -340,16 +345,31 @@ export function compileProgram(
345 }
346
347 const externalFunctions: ExternalFunction[] = [];
343 - // TODO: check for duplicate import specifiers
344 - if (options.gating != null) {
345 - externalFunctions.push(options.gating);
346 - }
347 - if (options.instrumentForget != null) {
348 - externalFunctions.push(options.instrumentForget);
349 - }
350 - if (options.environment?.enableEmitFreeze != null) {
351 - externalFunctions.push(options.environment.enableEmitFreeze);
348 + try {
349 + // TODO: check for duplicate import specifiers
350 + if (options.gating != null) {
351 + const gating = tryParseExternalFunction(options.gating);
352 + externalFunctions.push(gating);
353 + }
354 +
355 + if (options.instrumentForget != null) {
356 + const instrumentForget = tryParseExternalFunction(
357 + options.instrumentForget
358 + );
359 + externalFunctions.push(instrumentForget);
360 + }
361 +
362 + if (options.environment?.enableEmitFreeze != null) {
363 + const enableEmitFreeze = tryParseExternalFunction(
364 + options.environment.enableEmitFreeze
365 + );
366 + externalFunctions.push(enableEmitFreeze);
367 + }
368 + } catch (err) {
369 + handleError(pass, null, err);
370 + return;
371 }
372 +
373 addImportsToProgram(program, externalFunctions);
374 }
375
compiler/yarn.lock
+10
@@ -12355,3 +12355,13 @@ yocto-queue@^0.1.0:
12355 version "0.1.0"
12356 resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
12357 integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
12358 +
12359 +zod-validation-error@^2.1.0:
12360 + version "2.1.0"
12361 + resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-2.1.0.tgz#208eac75237dfed47c0018d2fe8fd03501bfc9ac"
12362 + integrity sha512-VJh93e2wb4c3tWtGgTa0OF/dTt/zoPCPzXq4V11ZjxmEAFaPi/Zss1xIZdEB5RD8GD00U0/iVXgqkF77RV7pdQ==
12363 +
12364 +zod@^3.22.4:
12365 + version "3.22.4"
12366 + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
12367 + integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==