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
);