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.
This commit is contained in:
Lauren Tan
2022-10-14 12:10:55 -04:00
parent 58100e3d60
commit 101ee3a440
4 changed files with 28 additions and 4 deletions
+19 -2
View File
@@ -6,15 +6,15 @@
*/
import { PluginOptions } from "@babel/core";
import { hasOwnProperty } from "./Common/utils";
import {
createCompilerFlags,
CompilerFlags,
createCompilerFlags,
parseCompilerFlags,
} from "./CompilerFlags";
import { isOutputKind, OutputKind } from "./CompilerOutputs";
import { Logger, noopLogger } from "./Logger";
import { PassName } from "./Pass";
import { hasOwnProperty } from "./Common/utils";
export type CompilerOptions = {
outputKinds: OutputKind[];
@@ -38,6 +38,11 @@ export type CompilerOptions = {
* By default, logs are disabled.
*/
logger: Logger;
/**
* Capitalized identifier names that can be used in call expressions.
*/
allowedCapitalizedUserFunctions: Set<string>;
};
/**
@@ -98,6 +103,17 @@ export function parseCompilerOptions(
}
resOpts.logger = logger;
}
if (hasOwnProperty(inputOpts, "allowedCapitalizedUserFunctions")) {
const allowedCapitalizedUserFunctions =
inputOpts.allowedCapitalizedUserFunctions;
if (
typeof allowedCapitalizedUserFunctions !== "object" ||
!(allowedCapitalizedUserFunctions instanceof Set)
) {
throw `Invalid value for 'allowedCapitalizedUserFunctions': ${allowedCapitalizedUserFunctions}`;
}
resOpts.allowedCapitalizedUserFunctions = allowedCapitalizedUserFunctions;
}
return resOpts;
}
@@ -111,5 +127,6 @@ export function createCompilerOptions(): CompilerOptions {
stopPass: PassName.JSGen,
optIn: false,
logger: noopLogger,
allowedCapitalizedUserFunctions: new Set(),
};
}
@@ -23,7 +23,7 @@ export default {
run,
};
const ALLOWED_CAPITALIZED_FUNCTIONS = new Set([
const ALLOWED_CAPITALIZED_STDLIB_FUNCTIONS = new Set([
"AggregateError",
"Array",
"BigInt",
@@ -58,7 +58,10 @@ export function run(
const callee = path.get("callee");
if (t.isIdentifier(callee.node)) {
const name = callee.node.name;
if (ALLOWED_CAPITALIZED_FUNCTIONS.has(name)) {
if (
ALLOWED_CAPITALIZED_STDLIB_FUNCTIONS.has(name) ||
context.opts.allowedCapitalizedUserFunctions.has(name)
) {
return;
}
// Allow `Module().method()`;
@@ -64,6 +64,7 @@ describe("CompilerOptions", () => {
optIn: true,
stopPass: PassName.JSGen,
logger: noopLogger,
allowedCapitalizedUserFunctions: new Set(),
};
expect(parseCompilerOptions(fullInput)).toEqual(fullInput);
});
@@ -116,6 +116,9 @@ describe("React Forget", () => {
stopPass,
flags,
logger: createArrayLogger(logs),
allowedCapitalizedUserFunctions: new Set([
"ReactForgetSecretInternals",
]),
},
compileOptions
);