@samitouri / QOS-React / commits / 101ee3a440

Allow capitalized function identifiers to be allowlisted

The current allowlist for capitalized function identifiers only allows stdlib JS modules. This PR introduces a new compiler option to allow passing in a set of allowed capitalized user function identifiers. It's a hack (in the absence of a type system) to let us check that capitalized function calls are only possible for identifiers that aren't bound to a React component. I considered adding a mechanism in fixtures to configure compiler options per-fixture, but opted to keep it simple for now and special case a `ReactForgetSecretInternals` identifier as one allowed user function in transform tests.

Lauren Tan committed Oct 14, 2022 at 12:10 UTC 101ee3a440e01a90787d294e57287c4f8de6286a
4 files changed +28 -4
compiler/forget/src/CompilerOptions.ts
+19 -2
@@ -6,15 +6,15 @@
6 */
7
8 import { PluginOptions } from "@babel/core";
9 +import { hasOwnProperty } from "./Common/utils";
10 import {
10 - createCompilerFlags,
11 CompilerFlags,
12 + createCompilerFlags,
13 parseCompilerFlags,
14 } from "./CompilerFlags";
15 import { isOutputKind, OutputKind } from "./CompilerOutputs";
16 import { Logger, noopLogger } from "./Logger";
17 import { PassName } from "./Pass";
17 -import { hasOwnProperty } from "./Common/utils";
18
19 export type CompilerOptions = {
20 outputKinds: OutputKind[];
@@ -38,6 +38,11 @@ export type CompilerOptions = {
38 * By default, logs are disabled.
39 */
40 logger: Logger;
41 +
42 + /**
43 + * Capitalized identifier names that can be used in call expressions.
44 + */
45 + allowedCapitalizedUserFunctions: Set<string>;
46 };
47
48 /**
@@ -98,6 +103,17 @@ export function parseCompilerOptions(
103 }
104 resOpts.logger = logger;
105 }
106 + if (hasOwnProperty(inputOpts, "allowedCapitalizedUserFunctions")) {
107 + const allowedCapitalizedUserFunctions =
108 + inputOpts.allowedCapitalizedUserFunctions;
109 + if (
110 + typeof allowedCapitalizedUserFunctions !== "object" ||
111 + !(allowedCapitalizedUserFunctions instanceof Set)
112 + ) {
113 + throw `Invalid value for 'allowedCapitalizedUserFunctions': ${allowedCapitalizedUserFunctions}`;
114 + }
115 + resOpts.allowedCapitalizedUserFunctions = allowedCapitalizedUserFunctions;
116 + }
117 return resOpts;
118 }
119
@@ -111,5 +127,6 @@ export function createCompilerOptions(): CompilerOptions {
127 stopPass: PassName.JSGen,
128 optIn: false,
129 logger: noopLogger,
130 + allowedCapitalizedUserFunctions: new Set(),
131 };
132 }
compiler/forget/src/MiddleEnd/SketchyCodeCheck.ts
+5 -2
@@ -23,7 +23,7 @@ export default {
23 run,
24 };
25
26 -const ALLOWED_CAPITALIZED_FUNCTIONS = new Set([
26 +const ALLOWED_CAPITALIZED_STDLIB_FUNCTIONS = new Set([
27 "AggregateError",
28 "Array",
29 "BigInt",
@@ -58,7 +58,10 @@ export function run(
58 const callee = path.get("callee");
59 if (t.isIdentifier(callee.node)) {
60 const name = callee.node.name;
61 - if (ALLOWED_CAPITALIZED_FUNCTIONS.has(name)) {
61 + if (
62 + ALLOWED_CAPITALIZED_STDLIB_FUNCTIONS.has(name) ||
63 + context.opts.allowedCapitalizedUserFunctions.has(name)
64 + ) {
65 return;
66 }
67 // Allow `Module().method()`;
compiler/forget/src/__tests__/CompilerOptions-test.ts
+1
@@ -64,6 +64,7 @@ describe("CompilerOptions", () => {
64 optIn: true,
65 stopPass: PassName.JSGen,
66 logger: noopLogger,
67 + allowedCapitalizedUserFunctions: new Set(),
68 };
69 expect(parseCompilerOptions(fullInput)).toEqual(fullInput);
70 });
compiler/forget/src/__tests__/transform-test.ts
+3
@@ -116,6 +116,9 @@ describe("React Forget", () => {
116 stopPass,
117 flags,
118 logger: createArrayLogger(logs),
119 + allowedCapitalizedUserFunctions: new Set([
120 + "ReactForgetSecretInternals",
121 + ]),
122 },
123 compileOptions
124 );