first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

40
node_modules/metro-transform-plugins/package.json generated vendored Normal file
View File

@@ -0,0 +1,40 @@
{
"name": "metro-transform-plugins",
"version": "0.83.3",
"description": "🚇 Transform plugins for Metro.",
"main": "src/index.js",
"exports": {
".": "./src/index.js",
"./package.json": "./package.json",
"./private/*": "./src/*.js"
},
"repository": {
"type": "git",
"url": "git@github.com:facebook/metro.git"
},
"scripts": {
"prepare-release": "test -d build && rm -rf src.real && mv src src.real && mv build src",
"cleanup-release": "test ! -e build && mv src build && mv src.real src"
},
"license": "MIT",
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/generator": "^7.25.0",
"@babel/template": "^7.25.0",
"@babel/traverse": "^7.25.3",
"flow-enums-runtime": "^0.0.6",
"nullthrows": "^1.1.1"
},
"devDependencies": {
"@babel/code-frame": "^7.24.7",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
"@babel/plugin-transform-flow-strip-types": "^7.25.2",
"@babel/plugin-transform-modules-commonjs": "^7.24.8",
"@babel/types": "^7.25.2",
"babel-plugin-tester": "^6.0.1",
"metro": "0.83.3"
},
"engines": {
"node": ">=20.19.4"
}
}

View File

@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = addParamsToDefineCall;
function addParamsToDefineCall(code, ...paramsToAdd) {
const index = code.lastIndexOf(")");
const params = paramsToAdd.map((param) =>
param !== undefined ? JSON.stringify(param) : "undefined",
);
return code.slice(0, index) + "," + params.join(",") + code.slice(index);
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/
/**
* Simple way of adding additional parameters to the end of the define calls.
*
* This is used to add extra information to the generaic compiled modules (like
* the dependencyMap object or the list of inverse dependencies).
*/
export default function addParamsToDefineCall(
code: string,
...paramsToAdd: Array<mixed>
): string {
const index = code.lastIndexOf(')');
const params = paramsToAdd.map(param =>
// Distinguish between `undefined` and `null` - `undefined` is not JSON.
// eslint-disable-next-line lint/strictly-null
param !== undefined ? JSON.stringify(param) : 'undefined',
);
return code.slice(0, index) + ',' + params.join(',') + code.slice(index);
}

View File

@@ -0,0 +1,160 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = constantFoldingPlugin;
function constantFoldingPlugin(context) {
const t = context.types;
const { isLiteral, isVariableDeclarator, isUnaryExpression } = t;
const traverse = context.traverse;
const evaluate = function (path) {
const state = {
safe: true,
};
const unsafe = (path, state) => {
state.safe = false;
};
if (isUnaryExpression(path.node) && path.node.operator === "void") {
if (isLiteral(path.node.argument)) {
return {
confident: true,
value: undefined,
};
}
return {
confident: false,
value: null,
};
}
path.traverse(
{
AssignmentExpression: unsafe,
CallExpression: unsafe,
OptionalCallExpression: unsafe,
},
state,
);
try {
if (!state.safe) {
return {
confident: false,
value: null,
};
}
const evaluated = path.evaluate();
return {
confident: evaluated.confident,
value: evaluated.value,
};
} catch {
return {
confident: false,
value: null,
};
}
};
const FunctionDeclaration = {
exit(path, state) {
const binding =
path.node.id != null && path.scope.parent.getBinding(path.node.id.name);
if (binding && !binding.referenced) {
state.stripped = true;
path.remove();
}
},
};
const FunctionExpression = {
exit(path, state) {
const parentPath = path.parentPath;
const parentNode = parentPath?.node;
if (isVariableDeclarator(parentNode) && parentNode.id.name != null) {
const binding = parentPath?.scope.getBinding(parentNode.id.name);
if (binding && !binding.referenced) {
state.stripped = true;
parentPath?.remove();
}
}
},
};
const Conditional = {
exit(path, state) {
const node = path.node;
const result = evaluate(path.get("test"));
if (result.confident) {
state.stripped = true;
if (result.value || node.alternate) {
path.replaceWith(result.value ? node.consequent : node.alternate);
} else if (!result.value) {
path.remove();
}
}
},
};
const Expression = {
exit(path) {
const result = evaluate(path);
if (result.confident) {
path.replaceWith(t.valueToNode(result.value));
path.skip();
}
},
};
const LogicalExpression = {
exit(path) {
const node = path.node;
const result = evaluate(path.get("left"));
if (result.confident) {
const value = result.value;
switch (node.operator) {
case "||":
path.replaceWith(value ? node.left : node.right);
break;
case "&&":
path.replaceWith(value ? node.right : node.left);
break;
case "??":
path.replaceWith(value == null ? node.right : node.left);
break;
}
}
},
};
const Program = {
enter(path, state) {
state.stripped = false;
},
exit(path, state) {
path.traverse(
{
ArrowFunctionExpression: FunctionExpression,
ConditionalExpression: Conditional,
FunctionDeclaration,
FunctionExpression,
IfStatement: Conditional,
},
state,
);
if (state.stripped) {
traverse.cache.clearScope();
path.scope.crawl();
Program.enter(path, state);
path.traverse(visitor, {
stripped: false,
});
Program.exit(path, state);
}
},
};
const visitor = {
BinaryExpression: Expression,
LogicalExpression,
Program: {
...Program,
},
UnaryExpression: Expression,
};
return {
visitor,
};
}

View File

@@ -0,0 +1,222 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
* @oncall react_native
*/
import type {PluginObj} from '@babel/core';
import type {NodePath, VisitNode, Visitor} from '@babel/traverse';
import typeof Traverse from '@babel/traverse';
// This is only a typeof import, no runtime dependency exists
// eslint-disable-next-line import/no-extraneous-dependencies
import typeof * as Types from '@babel/types';
type State = {stripped: boolean};
export default function constantFoldingPlugin(context: {
types: Types,
traverse: Traverse,
...
}): PluginObj<State> {
const t = context.types;
const {isLiteral, isVariableDeclarator, isUnaryExpression} = t;
const traverse = context.traverse;
const evaluate = function (path: NodePath<>): {
confident: boolean,
value: mixed,
} {
const state = {safe: true};
const unsafe = (
path:
| NodePath<BabelNodeAssignmentExpression>
| NodePath<BabelNodeCallExpression>
| NodePath<BabelNodeOptionalCallExpression>,
state: {safe: boolean},
) => {
state.safe = false;
};
if (isUnaryExpression(path.node) && path.node.operator === 'void') {
// Void expressions always evaluate to undefined but would rarely be used
// to express a constant (with the exception of `void 0`). More often,
// they are used to discard the value of a side-effectful expression, so
// are unsafe to fold. Conservatively, evaluate to undefined only if the
// argument is a literal.
if (isLiteral(path.node.argument)) {
return {confident: true, value: undefined};
}
return {confident: false, value: null};
}
path.traverse(
{
AssignmentExpression: unsafe,
CallExpression: unsafe,
/**
* This will mark `foo?.()` as unsafe, so it is not replaced with `undefined` down the line.
*
* We saw this case in the wild, where the unary expression `void foo?.()` was replaced with `undefined`
* resulting in the expression call being skipped.
*/
OptionalCallExpression: unsafe,
},
state,
);
try {
if (!state.safe) {
return {confident: false, value: null};
}
const evaluated = path.evaluate();
return {confident: evaluated.confident, value: evaluated.value};
} catch {
return {confident: false, value: null};
}
};
const FunctionDeclaration = {
exit(path: NodePath<BabelNodeFunctionDeclaration>, state: State): void {
const binding =
path.node.id != null && path.scope.parent.getBinding(path.node.id.name);
if (binding && !binding.referenced) {
state.stripped = true;
path.remove();
}
},
};
const FunctionExpression: VisitNode<
BabelNodeFunctionExpression | BabelNodeArrowFunctionExpression,
State,
> =
// $FlowFixMe[incompatible-type]
{
exit(path, state) {
const parentPath = path.parentPath;
const parentNode = parentPath?.node;
if (isVariableDeclarator(parentNode) && parentNode.id.name != null) {
const binding = parentPath?.scope.getBinding(parentNode.id.name);
if (binding && !binding.referenced) {
state.stripped = true;
parentPath?.remove();
}
}
},
};
const Conditional: VisitNode<
BabelNodeIfStatement | BabelNodeConditionalExpression,
State,
> =
// $FlowFixMe[incompatible-type]
{
exit(path, state): void {
const node = path.node;
const result = evaluate(path.get('test'));
if (result.confident) {
state.stripped = true;
if (result.value || node.alternate) {
// $FlowFixMe[incompatible-type]
// $FlowFixMe[sketchy-null-mixed] Flow error uncovered by typing Babel more strictly
path.replaceWith(result.value ? node.consequent : node.alternate);
} else if (!result.value) {
path.remove();
}
}
},
};
const Expression: VisitNode<
BabelNodeUnaryExpression | BabelNodeBinaryExpression,
State,
> =
// $FlowFixMe[incompatible-type]
{
exit(path) {
const result = evaluate(path);
if (result.confident) {
path.replaceWith(t.valueToNode(result.value));
path.skip();
}
},
};
const LogicalExpression = {
exit(path: NodePath<BabelNodeLogicalExpression>) {
const node = path.node;
const result = evaluate(path.get('left'));
if (result.confident) {
const value = result.value;
switch (node.operator) {
case '||':
path.replaceWith(value ? node.left : node.right);
break;
case '&&':
path.replaceWith(value ? node.right : node.left);
break;
case '??':
path.replaceWith(value == null ? node.right : node.left);
break;
}
}
},
};
const Program = {
enter(path: NodePath<BabelNodeProgram>, state: State): void {
state.stripped = false;
},
exit(path: NodePath<BabelNodeProgram>, state: State): void {
path.traverse(
{
ArrowFunctionExpression: FunctionExpression,
ConditionalExpression: Conditional,
// $FlowFixMe[incompatible-type]
FunctionDeclaration,
FunctionExpression,
IfStatement: Conditional,
},
state,
);
if (state.stripped) {
traverse.cache.clearScope();
path.scope.crawl();
// Re-traverse all program, if we removed any blocks. Manually re-call
// enter and exit, because traversing a Program node won't call them.
Program.enter(path, state);
path.traverse(visitor, {stripped: false});
Program.exit(path, state);
}
},
};
const visitor: Visitor<State> = {
BinaryExpression: Expression,
// $FlowFixMe[incompatible-type]
LogicalExpression,
Program: {...Program}, // Babel mutates objects passed.
UnaryExpression: Expression,
};
return {visitor};
}

View File

@@ -0,0 +1,432 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = importExportPlugin;
var _template = _interopRequireDefault(require("@babel/template"));
var _nullthrows = _interopRequireDefault(require("nullthrows"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const importTemplate = _template.default.statement(`
var LOCAL = IMPORT(FILE);
`);
const importNamedTemplate = _template.default.statement(`
var LOCAL = require(FILE).REMOTE;
`);
const importSideEffectTemplate = _template.default.statement(`
require(FILE);
`);
const exportAllTemplate = _template.default.statements(`
var REQUIRED = require(FILE);
for (var KEY in REQUIRED) {
exports[KEY] = REQUIRED[KEY];
}
`);
const exportTemplate = _template.default.statement(`
exports.REMOTE = LOCAL;
`);
const esModuleExportTemplate = _template.default.statement(`
Object.defineProperty(exports, '__esModule', {value: true});
`);
const resolveTemplate = _template.default.expression(`
require.resolve(NODE)
`);
function resolvePath(node, resolve) {
if (!resolve) {
return node;
}
return resolveTemplate({
NODE: node,
});
}
function withLocation(node, loc) {
if (Array.isArray(node)) {
return node.map((n) => withLocation(n, loc));
}
if (!node.loc) {
return {
...node,
loc,
};
}
return node;
}
function importExportPlugin({ types: t }) {
const { isDeclaration, isVariableDeclaration } = t;
return {
visitor: {
ExportAllDeclaration(path, state) {
state.exportAll.push({
file: path.node.source.value,
loc: path.node.loc,
});
path.remove();
},
ExportDefaultDeclaration(path, state) {
const declaration = path.node.declaration;
const id =
declaration.id || path.scope.generateUidIdentifier("default");
declaration.id = id;
const loc = path.node.loc;
state.exportDefault.push({
local: id.name,
loc,
});
if (isDeclaration(declaration)) {
path.insertBefore(withLocation(declaration, loc));
} else {
path.insertBefore(
withLocation(
t.variableDeclaration("var", [
t.variableDeclarator(id, declaration),
]),
loc,
),
);
}
path.remove();
},
ExportNamedDeclaration(path, state) {
if (path.node.exportKind && path.node.exportKind !== "value") {
return;
}
const declaration = path.node.declaration;
const loc = path.node.loc;
if (declaration) {
if (isVariableDeclaration(declaration)) {
declaration.declarations.forEach((d) => {
switch (d.id.type) {
case "ObjectPattern":
{
const properties = d.id.properties;
properties.forEach((p) => {
const name = p.key.name;
state.exportNamed.push({
local: name,
remote: name,
loc,
});
});
}
break;
case "ArrayPattern":
{
const elements = d.id.elements;
elements.forEach((e) => {
const name = e.name;
state.exportNamed.push({
local: name,
remote: name,
loc,
});
});
}
break;
default:
{
const name = d.id.name;
state.exportNamed.push({
local: name,
remote: name,
loc,
});
}
break;
}
});
} else {
const id = declaration.id || path.scope.generateUidIdentifier();
const name = id.name;
declaration.id = id;
state.exportNamed.push({
local: name,
remote: name,
loc,
});
}
path.insertBefore(declaration);
}
const specifiers = path.node.specifiers;
if (specifiers) {
specifiers.forEach((s) => {
const local = s.local;
const remote = s.exported;
if (remote.type === "StringLiteral") {
throw path.buildCodeFrameError(
"Module string names are not supported",
);
}
if (path.node.source) {
const temp = path.scope.generateUidIdentifier(local.name);
if (local.name === "default") {
path.insertBefore(
withLocation(
importTemplate({
IMPORT: t.cloneNode(state.importDefault),
FILE: resolvePath(
t.cloneNode((0, _nullthrows.default)(path.node.source)),
state.opts.resolve,
),
LOCAL: temp,
}),
loc,
),
);
state.exportNamed.push({
local: temp.name,
remote: remote.name,
loc,
});
} else if (remote.name === "default") {
path.insertBefore(
withLocation(
importNamedTemplate({
FILE: resolvePath(
t.cloneNode((0, _nullthrows.default)(path.node.source)),
state.opts.resolve,
),
LOCAL: temp,
REMOTE: local,
}),
loc,
),
);
state.exportDefault.push({
local: temp.name,
loc,
});
} else {
path.insertBefore(
withLocation(
importNamedTemplate({
FILE: resolvePath(
t.cloneNode((0, _nullthrows.default)(path.node.source)),
state.opts.resolve,
),
LOCAL: temp,
REMOTE: local,
}),
loc,
),
);
state.exportNamed.push({
local: temp.name,
remote: remote.name,
loc,
});
}
} else {
if (remote.name === "default") {
state.exportDefault.push({
local: local.name,
loc,
});
} else {
state.exportNamed.push({
local: local.name,
remote: remote.name,
loc,
});
}
}
});
}
path.remove();
},
ImportDeclaration(path, state) {
if (path.node.importKind && path.node.importKind !== "value") {
return;
}
const file = path.node.source;
const specifiers = path.node.specifiers;
const loc = path.node.loc;
if (!specifiers.length) {
state.imports.push({
node: withLocation(
importSideEffectTemplate({
FILE: resolvePath(t.cloneNode(file), state.opts.resolve),
}),
loc,
),
});
} else {
let sharedModuleImport;
let sharedModuleVariableDeclaration = null;
if (
specifiers.filter(
(s) =>
s.type === "ImportSpecifier" &&
(s.imported.type === "StringLiteral" ||
s.imported.name !== "default"),
).length > 1
) {
sharedModuleImport =
path.scope.generateUidIdentifierBasedOnNode(file);
sharedModuleVariableDeclaration = withLocation(
t.variableDeclaration("var", [
t.variableDeclarator(
t.cloneNode(sharedModuleImport),
t.callExpression(t.identifier("require"), [
resolvePath(t.cloneNode(file), state.opts.resolve),
]),
),
]),
loc,
);
state.imports.push({
node: sharedModuleVariableDeclaration,
});
}
specifiers.forEach((s) => {
const imported = s.imported;
const local = s.local;
switch (s.type) {
case "ImportNamespaceSpecifier":
state.imports.push({
node: withLocation(
importTemplate({
IMPORT: t.cloneNode(state.importAll),
FILE: resolvePath(t.cloneNode(file), state.opts.resolve),
LOCAL: t.cloneNode(local),
}),
loc,
),
});
break;
case "ImportDefaultSpecifier":
state.imports.push({
node: withLocation(
importTemplate({
IMPORT: t.cloneNode(state.importDefault),
FILE: resolvePath(t.cloneNode(file), state.opts.resolve),
LOCAL: t.cloneNode(local),
}),
loc,
),
});
break;
case "ImportSpecifier":
if (imported.name === "default") {
state.imports.push({
node: withLocation(
importTemplate({
IMPORT: t.cloneNode(state.importDefault),
FILE: resolvePath(
t.cloneNode(file),
state.opts.resolve,
),
LOCAL: t.cloneNode(local),
}),
loc,
),
});
} else if (sharedModuleVariableDeclaration != null) {
sharedModuleVariableDeclaration.declarations.push(
withLocation(
t.variableDeclarator(
t.cloneNode(local),
t.memberExpression(
t.cloneNode(sharedModuleImport),
t.cloneNode(imported),
),
),
loc,
),
);
} else {
state.imports.push({
node: withLocation(
importNamedTemplate({
FILE: resolvePath(
t.cloneNode(file),
state.opts.resolve,
),
LOCAL: t.cloneNode(local),
REMOTE: t.cloneNode(imported),
}),
loc,
),
});
}
break;
default:
throw new TypeError("Unknown import type: " + s.type);
}
});
}
path.remove();
},
Program: {
enter(path, state) {
state.exportAll = [];
state.exportDefault = [];
state.exportNamed = [];
state.imports = [];
state.importAll = t.identifier(state.opts.importAll);
state.importDefault = t.identifier(state.opts.importDefault);
["module", "global", "exports", "require"].forEach((name) =>
path.scope.rename(name),
);
},
exit(path, state) {
const body = path.node.body;
state.imports.reverse().forEach((e) => {
body.unshift(e.node);
});
state.exportDefault.forEach((e) => {
body.push(
withLocation(
exportTemplate({
LOCAL: t.identifier(e.local),
REMOTE: t.identifier("default"),
}),
e.loc,
),
);
});
state.exportAll.forEach((e) => {
body.push(
...withLocation(
exportAllTemplate({
FILE: resolvePath(
t.stringLiteral(e.file),
state.opts.resolve,
),
REQUIRED: path.scope.generateUidIdentifier(e.file),
KEY: path.scope.generateUidIdentifier("key"),
}),
e.loc,
),
);
});
state.exportNamed.forEach((e) => {
body.push(
withLocation(
exportTemplate({
LOCAL: t.identifier(e.local),
REMOTE: t.identifier(e.remote),
}),
e.loc,
),
);
});
if (
state.exportDefault.length ||
state.exportAll.length ||
state.exportNamed.length
) {
body.unshift(esModuleExportTemplate());
if (state.opts.out) {
state.opts.out.isESModule = true;
}
} else if (state.opts.out) {
state.opts.out.isESModule = false;
}
},
},
},
};
}

View File

@@ -0,0 +1,594 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/
import type {PluginObj} from '@babel/core';
import type {NodePath} from '@babel/traverse';
import type {
ExportNamedDeclaration,
ImportDeclaration,
Node,
Program,
Statement,
} from '@babel/types';
// Type only dependency. This is not a runtime dependency
// eslint-disable-next-line import/no-extraneous-dependencies
import typeof * as Types from '@babel/types';
import template from '@babel/template';
import nullthrows from 'nullthrows';
export type Options = $ReadOnly<{
importDefault: string,
importAll: string,
resolve: boolean,
out?: {isESModule: boolean, ...},
}>;
type State = {
exportAll: Array<{file: string, loc: ?BabelSourceLocation, ...}>,
exportDefault: Array<{local: string, loc: ?BabelSourceLocation, ...}>,
exportNamed: Array<{
local: string,
remote: string,
loc: ?BabelSourceLocation,
...
}>,
imports: Array<{node: Statement}>,
importDefault: BabelNode,
importAll: BabelNode,
opts: Options,
...
};
/**
* Produces a Babel template that transforms an "import * as x from ..." or an
* "import x from ..." call into a "const x = importAll(...)" call with the
* corresponding id in it.
*/
const importTemplate = template.statement(`
var LOCAL = IMPORT(FILE);
`);
/**
* Produces a Babel template that transforms an "import {x as y} from ..." into
* "const y = require(...).x" call with the corresponding id in it.
*/
const importNamedTemplate = template.statement(`
var LOCAL = require(FILE).REMOTE;
`);
/**
* Produces a Babel template that transforms an "import ..." into
* "require(...)", which is considered a side-effect call.
*/
const importSideEffectTemplate = template.statement(`
require(FILE);
`);
/**
* Produces an "export all" template that traverses all exported symbols and
* re-exposes them.
*/
const exportAllTemplate = template.statements(`
var REQUIRED = require(FILE);
for (var KEY in REQUIRED) {
exports[KEY] = REQUIRED[KEY];
}
`);
/**
* Produces a "named export" or "default export" template to export a single
* symbol.
*/
const exportTemplate = template.statement(`
exports.REMOTE = LOCAL;
`);
/**
* Flags the exported module as a transpiled ES module. Needs to be kept in 1:1
* compatibility with Babel.
*/
const esModuleExportTemplate = template.statement(`
Object.defineProperty(exports, '__esModule', {value: true});
`);
/**
* Resolution template in case it is requested.
*/
const resolveTemplate = template.expression(`
require.resolve(NODE)
`);
/**
* Enforces the resolution of a path to a fully-qualified one, if set.
*/
function resolvePath<TNode: Node>(
node: TNode,
resolve: boolean,
): BabelNodeExpression | TNode {
if (!resolve) {
return node;
}
return resolveTemplate({
NODE: node,
});
}
declare function withLocation<TNode: BabelNode>(
node: TNode,
loc: ?BabelSourceLocation,
): TNode;
// eslint-disable-next-line no-redeclare
declare function withLocation<TNode: BabelNode>(
node: $ReadOnlyArray<TNode>,
loc: ?BabelSourceLocation,
): Array<TNode>;
// eslint-disable-next-line no-redeclare
function withLocation(
node: BabelNode | $ReadOnlyArray<BabelNode>,
loc: ?BabelNodeSourceLocation,
): Array<BabelNode> | BabelNode {
if (Array.isArray(node)) {
return node.map(n => withLocation(n, loc));
}
if (!node.loc) {
return {...node, loc};
}
return node;
}
export default function importExportPlugin({
types: t,
}: {
types: Types,
...
}): PluginObj<State> {
const {isDeclaration, isVariableDeclaration} = t;
return {
visitor: {
ExportAllDeclaration(
path: NodePath<BabelNodeExportAllDeclaration>,
state: State,
): void {
state.exportAll.push({
file: path.node.source.value,
loc: path.node.loc,
});
path.remove();
},
ExportDefaultDeclaration(
path: NodePath<BabelNodeExportDefaultDeclaration>,
state: State,
): void {
const declaration = path.node.declaration;
const id =
declaration.id || path.scope.generateUidIdentifier('default');
// $FlowFixMe[prop-missing] Flow error uncovered by typing Babel more strictly
declaration.id = id;
const loc = path.node.loc;
state.exportDefault.push({
local: id.name,
loc,
});
if (isDeclaration(declaration)) {
path.insertBefore(withLocation(declaration, loc));
} else {
path.insertBefore(
withLocation(
t.variableDeclaration('var', [
t.variableDeclarator(id, declaration),
]),
loc,
),
);
}
path.remove();
},
ExportNamedDeclaration(
path: NodePath<ExportNamedDeclaration>,
state: State,
): void {
if (path.node.exportKind && path.node.exportKind !== 'value') {
return;
}
const declaration = path.node.declaration;
const loc = path.node.loc;
if (declaration) {
if (isVariableDeclaration(declaration)) {
declaration.declarations.forEach(d => {
switch (d.id.type) {
case 'ObjectPattern':
{
const properties = d.id.properties;
properties.forEach(p => {
// $FlowFixMe[incompatible-use] Flow error uncovered by typing Babel more strictly
// $FlowFixMe[prop-missing]
const name = p.key.name;
// $FlowFixMe[incompatible-type]
state.exportNamed.push({local: name, remote: name, loc});
});
}
break;
case 'ArrayPattern':
{
const elements = d.id.elements;
elements.forEach(e => {
// $FlowFixMe[incompatible-use] Flow error uncovered by typing Babel more strictly
// $FlowFixMe[prop-missing]
const name = e.name;
// $FlowFixMe[incompatible-type]
state.exportNamed.push({local: name, remote: name, loc});
});
}
break;
default:
{
const name = d.id.name;
// $FlowFixMe[incompatible-type]
state.exportNamed.push({local: name, remote: name, loc});
}
break;
}
});
} else {
const id = declaration.id || path.scope.generateUidIdentifier();
const name = id.name;
// $FlowFixMe[incompatible-type] Flow error uncovered by typing Babel more strictly
// $FlowFixMe[prop-missing]
declaration.id = id;
// $FlowFixMe[incompatible-type]
state.exportNamed.push({local: name, remote: name, loc});
}
path.insertBefore(declaration);
}
const specifiers = path.node.specifiers;
if (specifiers) {
specifiers.forEach(s => {
const local = s.local;
const remote = s.exported;
if (remote.type === 'StringLiteral') {
// https://babeljs.io/docs/en/babel-plugin-syntax-module-string-names
throw path.buildCodeFrameError<$FlowFixMe>(
'Module string names are not supported',
);
}
if (path.node.source) {
// $FlowFixMe[incompatible-use]
const temp = path.scope.generateUidIdentifier(local.name);
// $FlowFixMe[incompatible-type]
// $FlowFixMe[incompatible-use]
if (local.name === 'default') {
path.insertBefore(
withLocation(
importTemplate({
IMPORT: t.cloneNode(state.importDefault),
FILE: resolvePath(
t.cloneNode(nullthrows(path.node.source)),
state.opts.resolve,
),
LOCAL: temp,
}),
loc,
),
);
state.exportNamed.push({
local: temp.name,
remote: remote.name,
loc,
});
} else if (remote.name === 'default') {
path.insertBefore(
withLocation(
importNamedTemplate({
FILE: resolvePath(
t.cloneNode(nullthrows(path.node.source)),
state.opts.resolve,
),
LOCAL: temp,
REMOTE: local,
}),
loc,
),
);
state.exportDefault.push({local: temp.name, loc});
} else {
path.insertBefore(
withLocation(
importNamedTemplate({
FILE: resolvePath(
t.cloneNode(nullthrows(path.node.source)),
state.opts.resolve,
),
LOCAL: temp,
REMOTE: local,
}),
loc,
),
);
state.exportNamed.push({
local: temp.name,
remote: remote.name,
loc,
});
}
} else {
if (remote.name === 'default') {
// $FlowFixMe[incompatible-use]
state.exportDefault.push({local: local.name, loc});
} else {
state.exportNamed.push({
// $FlowFixMe[incompatible-use]
local: local.name,
remote: remote.name,
loc,
});
}
}
});
}
path.remove();
},
ImportDeclaration(path: NodePath<ImportDeclaration>, state: State): void {
if (path.node.importKind && path.node.importKind !== 'value') {
return;
}
const file = path.node.source;
const specifiers = path.node.specifiers;
const loc = path.node.loc;
if (!specifiers.length) {
state.imports.push({
node: withLocation(
importSideEffectTemplate({
FILE: resolvePath(t.cloneNode(file), state.opts.resolve),
}),
loc,
),
});
} else {
let sharedModuleImport;
let sharedModuleVariableDeclaration = null;
if (
specifiers.filter(
s =>
s.type === 'ImportSpecifier' &&
(s.imported.type === 'StringLiteral' ||
s.imported.name !== 'default'),
).length > 1
) {
sharedModuleImport =
path.scope.generateUidIdentifierBasedOnNode(file);
sharedModuleVariableDeclaration = withLocation(
t.variableDeclaration('var', [
t.variableDeclarator(
t.cloneNode(sharedModuleImport),
t.callExpression(t.identifier('require'), [
resolvePath(t.cloneNode(file), state.opts.resolve),
]),
),
]),
loc,
);
state.imports.push({node: sharedModuleVariableDeclaration});
}
specifiers.forEach(s => {
const imported = s.imported;
const local = s.local;
switch (s.type) {
case 'ImportNamespaceSpecifier':
state.imports.push({
node: withLocation(
importTemplate({
IMPORT: t.cloneNode(state.importAll),
FILE: resolvePath(t.cloneNode(file), state.opts.resolve),
LOCAL: t.cloneNode(local),
}),
loc,
),
});
break;
case 'ImportDefaultSpecifier':
state.imports.push({
node: withLocation(
importTemplate({
IMPORT: t.cloneNode(state.importDefault),
FILE: resolvePath(t.cloneNode(file), state.opts.resolve),
LOCAL: t.cloneNode(local),
}),
loc,
),
});
break;
case 'ImportSpecifier':
// $FlowFixMe[incompatible-type]
// $FlowFixMe[incompatible-use]
if (imported.name === 'default') {
state.imports.push({
node: withLocation(
importTemplate({
IMPORT: t.cloneNode(state.importDefault),
FILE: resolvePath(
t.cloneNode(file),
state.opts.resolve,
),
LOCAL: t.cloneNode(local),
}),
loc,
),
});
} else if (sharedModuleVariableDeclaration != null) {
sharedModuleVariableDeclaration.declarations.push(
withLocation(
t.variableDeclarator(
t.cloneNode(local),
t.memberExpression(
t.cloneNode(sharedModuleImport),
// $FlowFixMe[incompatible-type]
t.cloneNode(imported),
),
),
loc,
),
);
} else {
state.imports.push({
node: withLocation(
importNamedTemplate({
FILE: resolvePath(
t.cloneNode(file),
state.opts.resolve,
),
LOCAL: t.cloneNode(local),
REMOTE: t.cloneNode(imported),
}),
loc,
),
});
}
break;
default:
throw new TypeError('Unknown import type: ' + s.type);
}
});
}
path.remove();
},
Program: {
enter(path: NodePath<Program>, state: State): void {
state.exportAll = [];
state.exportDefault = [];
state.exportNamed = [];
state.imports = [];
state.importAll = t.identifier(state.opts.importAll);
state.importDefault = t.identifier(state.opts.importDefault);
// Rename declarations at module scope that might otherwise conflict
// with arguments we inject into the module factory.
// Note that it isn't necessary to rename importAll/importDefault
// because Metro already uses generateUid to generate unused names.
['module', 'global', 'exports', 'require'].forEach(name =>
path.scope.rename(name),
);
},
exit(path: NodePath<Program>, state: State): void {
const body = path.node.body;
// state.imports = [node1, node2, node3, ...nodeN]
state.imports.reverse().forEach((e: {node: Statement}) => {
// import nodes are added to the top of the program body
body.unshift(e.node);
});
state.exportDefault.forEach(
(e: {local: string, loc: ?BabelSourceLocation, ...}) => {
body.push(
withLocation(
exportTemplate({
LOCAL: t.identifier(e.local),
REMOTE: t.identifier('default'),
}),
e.loc,
),
);
},
);
state.exportAll.forEach(
(e: {file: string, loc: ?BabelSourceLocation, ...}) => {
body.push(
// $FlowFixMe[incompatible-call]
...withLocation(
exportAllTemplate({
FILE: resolvePath(
t.stringLiteral(e.file),
state.opts.resolve,
),
REQUIRED: path.scope.generateUidIdentifier(e.file),
KEY: path.scope.generateUidIdentifier('key'),
}),
e.loc,
),
);
},
);
state.exportNamed.forEach(
(e: {
local: string,
remote: string,
loc: ?BabelSourceLocation,
...
}) => {
body.push(
withLocation(
exportTemplate({
LOCAL: t.identifier(e.local),
REMOTE: t.identifier(e.remote),
}),
e.loc,
),
);
},
);
if (
state.exportDefault.length ||
state.exportAll.length ||
state.exportNamed.length
) {
body.unshift(esModuleExportTemplate());
if (state.opts.out) {
state.opts.out.isESModule = true;
}
} else if (state.opts.out) {
state.opts.out.isESModule = false;
}
},
},
},
};
}

30
node_modules/metro-transform-plugins/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,30 @@
"use strict";
module.exports = {
get addParamsToDefineCall() {
return require("./addParamsToDefineCall").default;
},
get constantFoldingPlugin() {
return require("./constant-folding-plugin").default;
},
get importExportPlugin() {
return require("./import-export-plugin").default;
},
get inlinePlugin() {
return require("./inline-plugin").default;
},
get inlineRequiresPlugin() {
return require("./inline-requires-plugin").default;
},
get normalizePseudoGlobals() {
return require("./normalizePseudoGlobals").default;
},
getTransformPluginCacheKeyFiles: () => [
require.resolve(__filename),
require.resolve("./constant-folding-plugin"),
require.resolve("./import-export-plugin"),
require.resolve("./inline-plugin"),
require.resolve("./inline-requires-plugin"),
require.resolve("./normalizePseudoGlobals"),
],
};

69
node_modules/metro-transform-plugins/src/index.js.flow generated vendored Normal file
View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
/* eslint-disable import/no-commonjs */
'use strict';
import typeof ConstantFoldingPlugin from './constant-folding-plugin';
import typeof ImportExportPlugin from './import-export-plugin';
import typeof InlinePlugin from './inline-plugin';
import typeof InlineRequiresPlugin from './inline-requires-plugin';
import typeof NormalizePseudoGlobalsFn from './normalizePseudoGlobals';
export type {Options as ImportExportPluginOptions} from './import-export-plugin';
export type {Options as InlinePluginOptions} from './inline-plugin';
export type {PluginOptions as InlineRequiresPluginOptions} from './inline-requires-plugin';
type TransformPlugins = {
addParamsToDefineCall(string, ...Array<mixed>): string,
constantFoldingPlugin: ConstantFoldingPlugin,
importExportPlugin: ImportExportPlugin,
inlinePlugin: InlinePlugin,
inlineRequiresPlugin: InlineRequiresPlugin,
normalizePseudoGlobals: NormalizePseudoGlobalsFn,
getTransformPluginCacheKeyFiles(): $ReadOnlyArray<string>,
};
module.exports = ({
// $FlowFixMe[unsafe-getters-setters]
get addParamsToDefineCall() {
return require('./addParamsToDefineCall').default;
},
// $FlowFixMe[unsafe-getters-setters]
get constantFoldingPlugin() {
return require('./constant-folding-plugin').default;
},
// $FlowFixMe[unsafe-getters-setters]
get importExportPlugin() {
return require('./import-export-plugin').default;
},
// $FlowFixMe[unsafe-getters-setters]
get inlinePlugin() {
return require('./inline-plugin').default;
},
// $FlowFixMe[unsafe-getters-setters]
get inlineRequiresPlugin() {
return require('./inline-requires-plugin').default;
},
// $FlowFixMe[unsafe-getters-setters]
get normalizePseudoGlobals() {
return require('./normalizePseudoGlobals').default;
},
getTransformPluginCacheKeyFiles: () => [
require.resolve(__filename),
require.resolve('./constant-folding-plugin'),
require.resolve('./import-export-plugin'),
require.resolve('./inline-plugin'),
require.resolve('./inline-requires-plugin'),
require.resolve('./normalizePseudoGlobals'),
],
}: TransformPlugins);

View File

@@ -0,0 +1,133 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = inlinePlugin;
var _createInlinePlatformChecks = _interopRequireDefault(
require("./utils/createInlinePlatformChecks"),
);
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const env = {
name: "env",
};
const nodeEnv = {
name: "NODE_ENV",
};
const processId = {
name: "process",
};
const dev = {
name: "__DEV__",
};
function inlinePlugin({ types: t }, options) {
const {
isAssignmentExpression,
isIdentifier,
isMemberExpression,
isObjectExpression,
isObjectMethod,
isObjectProperty,
isSpreadElement,
isStringLiteral,
} = t;
const { isPlatformNode, isPlatformSelectNode } = (0,
_createInlinePlatformChecks.default)(t, options.requireName ?? "require");
function isGlobal(binding) {
return !binding;
}
const isFlowDeclared = (binding) => t.isDeclareVariable(binding.path);
function isGlobalOrFlowDeclared(binding) {
return !binding || isFlowDeclared(binding);
}
const isLeftHandSideOfAssignmentExpression = (node, parent) =>
isAssignmentExpression(parent) && parent.left === node;
const isProcessEnvNodeEnv = (node, scope) =>
isIdentifier(node.property, nodeEnv) &&
isMemberExpression(node.object) &&
isIdentifier(node.object.property, env) &&
isIdentifier(node.object.object, processId) &&
isGlobal(scope.getBinding(processId.name));
const isDev = (node, parent, scope) =>
isIdentifier(node, dev) &&
isGlobalOrFlowDeclared(scope.getBinding(dev.name));
function findProperty(objectExpression, key, fallback) {
let value = null;
for (const p of objectExpression.properties) {
if (!isObjectProperty(p) && !isObjectMethod(p)) {
continue;
}
if (
(isIdentifier(p.key) && p.key.name === key) ||
(isStringLiteral(p.key) && p.key.value === key)
) {
if (isObjectProperty(p)) {
value = p.value;
break;
} else if (isObjectMethod(p)) {
value = t.toExpression(p);
break;
}
}
}
return value ?? fallback();
}
function hasStaticProperties(objectExpression) {
return objectExpression.properties.every((p) => {
if (p.computed === true || isSpreadElement(p)) {
return false;
}
if (isObjectMethod(p) && p.kind !== "method") {
return false;
}
return isIdentifier(p.key) || isStringLiteral(p.key);
});
}
return {
visitor: {
ReferencedIdentifier(path, state) {
if (!state.opts.dev && isDev(path.node, path.parent, path.scope)) {
path.replaceWith(t.booleanLiteral(state.opts.dev));
}
},
MemberExpression(path, state) {
const node = path.node;
const scope = path.scope;
const opts = state.opts;
if (!isLeftHandSideOfAssignmentExpression(node, path.parent)) {
if (
opts.inlinePlatform &&
isPlatformNode(node, scope, !!opts.isWrapped)
) {
path.replaceWith(t.stringLiteral(opts.platform));
} else if (!opts.dev && isProcessEnvNodeEnv(node, scope)) {
path.replaceWith(
t.stringLiteral(opts.dev ? "development" : "production"),
);
}
}
},
CallExpression(path, state) {
const node = path.node;
const scope = path.scope;
const arg = node.arguments[0];
const opts = state.opts;
if (
opts.inlinePlatform &&
isPlatformSelectNode(node, scope, !!opts.isWrapped) &&
isObjectExpression(arg)
) {
if (hasStaticProperties(arg)) {
const fallback = () =>
findProperty(arg, "native", () =>
findProperty(arg, "default", () => t.identifier("undefined")),
);
path.replaceWith(findProperty(arg, opts.platform, fallback));
}
}
},
},
};
}

View File

@@ -0,0 +1,177 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/
import type {PluginObj} from '@babel/core';
import type {Binding, NodePath, Scope} from '@babel/traverse';
import type {
CallExpression,
MemberExpression,
Node,
ObjectExpression,
} from '@babel/types';
// type only import. No runtime dependency
// eslint-disable-next-line import/no-extraneous-dependencies
import typeof * as Types from '@babel/types';
import createInlinePlatformChecks from './utils/createInlinePlatformChecks';
export type Options = $ReadOnly<{
dev: boolean,
inlinePlatform: boolean,
isWrapped: boolean,
requireName?: string,
platform: string,
}>;
type State = {opts: Options};
const env = {name: 'env'};
const nodeEnv = {name: 'NODE_ENV'};
const processId = {name: 'process'};
const dev = {name: '__DEV__'};
export default function inlinePlugin(
{types: t}: {types: Types},
options: Options,
): PluginObj<State> {
const {
isAssignmentExpression,
isIdentifier,
isMemberExpression,
isObjectExpression,
isObjectMethod,
isObjectProperty,
isSpreadElement,
isStringLiteral,
} = t;
const {isPlatformNode, isPlatformSelectNode} = createInlinePlatformChecks(
t,
options.requireName ?? 'require',
);
function isGlobal(binding: ?Binding): boolean {
return !binding;
}
const isFlowDeclared = (binding: Binding) =>
t.isDeclareVariable(binding.path);
function isGlobalOrFlowDeclared(binding: ?Binding): boolean {
return !binding || isFlowDeclared(binding);
}
const isLeftHandSideOfAssignmentExpression = (
node: Node,
parent: Node,
): boolean => isAssignmentExpression(parent) && parent.left === node;
const isProcessEnvNodeEnv = (node: MemberExpression, scope: Scope): boolean =>
isIdentifier(node.property, nodeEnv) &&
isMemberExpression(node.object) &&
isIdentifier(node.object.property, env) &&
isIdentifier(node.object.object, processId) &&
isGlobal(scope.getBinding(processId.name));
const isDev = (node: Node, parent: Node, scope: Scope): boolean =>
isIdentifier(node, dev) &&
isGlobalOrFlowDeclared(scope.getBinding(dev.name));
function findProperty(
objectExpression: ObjectExpression,
key: string,
fallback: () => Node,
): Node {
let value = null;
for (const p of objectExpression.properties) {
if (!isObjectProperty(p) && !isObjectMethod(p)) {
continue;
}
if (
(isIdentifier(p.key) && p.key.name === key) ||
(isStringLiteral(p.key) && p.key.value === key)
) {
if (isObjectProperty(p)) {
value = p.value;
break;
} else if (isObjectMethod(p)) {
value = t.toExpression(p);
break;
}
}
}
return value ?? fallback();
}
function hasStaticProperties(objectExpression: ObjectExpression): boolean {
return objectExpression.properties.every(p => {
if (p.computed === true || isSpreadElement(p)) {
return false;
}
if (isObjectMethod(p) && p.kind !== 'method') {
return false;
}
return isIdentifier(p.key) || isStringLiteral(p.key);
});
}
return {
visitor: {
ReferencedIdentifier(path: NodePath<Node>, state: State): void {
if (!state.opts.dev && isDev(path.node, path.parent, path.scope)) {
path.replaceWith(t.booleanLiteral(state.opts.dev));
}
},
MemberExpression(path: NodePath<MemberExpression>, state: State): void {
const node = path.node;
const scope = path.scope;
const opts = state.opts;
if (!isLeftHandSideOfAssignmentExpression(node, path.parent)) {
if (
opts.inlinePlatform &&
isPlatformNode(node, scope, !!opts.isWrapped)
) {
path.replaceWith(t.stringLiteral(opts.platform));
} else if (!opts.dev && isProcessEnvNodeEnv(node, scope)) {
path.replaceWith(
t.stringLiteral(opts.dev ? 'development' : 'production'),
);
}
}
},
CallExpression(path: NodePath<CallExpression>, state: State): void {
const node = path.node;
const scope = path.scope;
const arg = node.arguments[0];
const opts = state.opts;
if (
opts.inlinePlatform &&
isPlatformSelectNode(node, scope, !!opts.isWrapped) &&
isObjectExpression(arg)
) {
if (hasStaticProperties(arg)) {
const fallback = () =>
findProperty(arg, 'native', () =>
findProperty(arg, 'default', () => t.identifier('undefined')),
);
path.replaceWith(findProperty(arg, opts.platform, fallback));
}
}
},
},
};
}

View File

@@ -0,0 +1,345 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _default = ({ types: t, traverse }) => ({
name: "inline-requires",
visitor: {
Program: {
enter() {},
exit(path, state) {
const ignoredRequires = new Set();
const inlineableCalls = new Set(["require"]);
const nonMemoizedModules = new Set();
let memoizeCalls = false;
const opts = state.opts;
if (opts != null) {
opts.ignoredRequires?.forEach((name) => ignoredRequires.add(name));
opts.inlineableCalls?.forEach((name) => inlineableCalls.add(name));
opts.nonMemoizedModules?.forEach((name) =>
nonMemoizedModules.add(name),
);
memoizeCalls = opts.memoizeCalls ?? false;
}
const programNode = path.scope.block;
if (programNode.type !== "Program") {
return;
}
path.scope.crawl();
path.traverse(
{
CallExpression(path, state) {
const parseResult =
parseInlineableAlias(path, state) ||
parseInlineableMemberAlias(path, state);
if (parseResult == null) {
return;
}
const { declarationPath, moduleName, requireFnName } =
parseResult;
const maybeInit = declarationPath.node.init;
const name = declarationPath.node.id
? declarationPath.node.id.name
: null;
const binding =
name == null ? null : declarationPath.scope.getBinding(name);
if (
maybeInit == null ||
!t.isExpression(maybeInit) ||
binding == null ||
binding.constantViolations.length > 0
) {
return;
}
const init = maybeInit;
const initPath = declarationPath.get("init");
if (Array.isArray(initPath)) {
return;
}
const initLoc = getNearestLocFromPath(initPath);
deleteLocation(init);
traverse(init, {
noScope: true,
enter: (path) => deleteLocation(path.node),
});
let thrown = false;
const memoVarName = parseResult.identifierName;
let hasMemoVar = false;
if (
memoizeCalls &&
binding.referencePaths.length > 0 &&
!nonMemoizedModules.has(moduleName)
) {
const varInitStmt = t.variableDeclaration("var", [
t.variableDeclarator(t.identifier(memoVarName)),
]);
declarationPath.remove();
hasMemoVar = addStmtToBlock(programNode, varInitStmt, 0);
}
function getMemoOrCallExpr() {
const refExpr = t.cloneDeep(init);
refExpr.METRO_INLINE_REQUIRES_INIT_LOC = initLoc;
return t.logicalExpression(
"||",
t.identifier(memoVarName),
t.assignmentExpression(
"=",
t.identifier(memoVarName),
refExpr,
),
);
}
const scopesWithInlinedRequire = new Set();
for (const referencePath of binding.referencePaths) {
excludeMemberAssignment(moduleName, referencePath, state);
try {
referencePath.scope.rename(requireFnName);
if (hasMemoVar) {
referencePath.scope.rename(memoVarName);
if (!isDirectlyEnclosedByBlock(t, referencePath)) {
referencePath.replaceWith(getMemoOrCallExpr());
continue;
}
if (scopesWithInlinedRequire.has(referencePath.scope)) {
referencePath.replaceWith(t.identifier(memoVarName));
} else {
referencePath.replaceWith(getMemoOrCallExpr());
scopesWithInlinedRequire.add(referencePath.scope);
}
} else {
const refExpr = t.cloneDeep(init);
refExpr.METRO_INLINE_REQUIRES_INIT_LOC = initLoc;
referencePath.replaceWith(refExpr);
}
} catch (error) {
thrown = true;
}
}
if (!thrown && declarationPath.node != null) {
declarationPath.remove();
}
},
},
{
ignoredRequires,
inlineableCalls,
membersAssigned: new Map(),
},
);
},
},
},
});
exports.default = _default;
function excludeMemberAssignment(moduleName, referencePath, state) {
const assignment = referencePath.parentPath?.parent;
if (assignment?.type !== "AssignmentExpression") {
return;
}
const left = assignment.left;
if (left.type !== "MemberExpression" || left.object !== referencePath.node) {
return;
}
const memberPropertyName = getMemberPropertyName(left);
if (memberPropertyName == null) {
return;
}
let membersAssigned = state.membersAssigned.get(moduleName);
if (membersAssigned == null) {
membersAssigned = new Set();
state.membersAssigned.set(moduleName, membersAssigned);
}
membersAssigned.add(memberPropertyName);
}
function isExcludedMemberAssignment(moduleName, memberPropertyName, state) {
const excludedAliases = state.membersAssigned.get(moduleName);
return excludedAliases != null && excludedAliases.has(memberPropertyName);
}
function getMemberPropertyName(node) {
if (node.property.type === "Identifier") {
return node.property.name;
}
if (node.property.type === "StringLiteral") {
return node.property.value;
}
return null;
}
function deleteLocation(node) {
delete node.start;
delete node.end;
delete node.loc;
}
function parseInlineableAlias(path, state) {
const module = getInlineableModule(path, state);
if (module == null) {
return null;
}
const { moduleName, requireFnName } = module;
const parentPath = path.parentPath;
if (parentPath == null) {
return null;
}
const grandParentPath = parentPath.parentPath;
if (grandParentPath == null) {
return null;
}
if (path.parent.type !== "VariableDeclarator") {
return null;
}
const variableDeclarator = path.parent;
if (variableDeclarator.id.type !== "Identifier") {
return null;
}
const identifier = variableDeclarator.id;
const isValid =
parentPath.parent.type === "VariableDeclaration" &&
grandParentPath.parent.type === "Program";
return !isValid || parentPath.node == null
? null
: {
declarationPath: parentPath,
moduleName,
requireFnName,
identifierName: identifier.name,
};
}
function parseInlineableMemberAlias(path, state) {
const module = getInlineableModule(path, state);
if (module == null) {
return null;
}
const { moduleName, requireFnName } = module;
const parent = path.parent;
const parentPath = path.parentPath;
if (parentPath == null) {
return null;
}
const grandParentPath = parentPath.parentPath;
if (grandParentPath == null) {
return null;
}
if (parent.type !== "MemberExpression") {
return null;
}
const memberExpression = parent;
if (parentPath.parent.type !== "VariableDeclarator") {
return null;
}
const variableDeclarator = parentPath.parent;
if (variableDeclarator.id.type !== "Identifier") {
return null;
}
const identifier = variableDeclarator.id;
if (
grandParentPath.parent.type !== "VariableDeclaration" ||
grandParentPath.parentPath?.parent.type !== "Program" ||
grandParentPath.node == null
) {
return null;
}
const memberPropertyName = getMemberPropertyName(memberExpression);
return memberPropertyName == null ||
isExcludedMemberAssignment(moduleName, memberPropertyName, state)
? null
: {
declarationPath: grandParentPath,
moduleName,
requireFnName,
identifierName: identifier.name,
};
}
function getInlineableModule(path, state) {
const node = path.node;
const isInlineable =
node.type === "CallExpression" &&
node.callee.type === "Identifier" &&
state.inlineableCalls.has(node.callee.name) &&
node["arguments"].length >= 1;
if (!isInlineable) {
return null;
}
let moduleName =
node["arguments"][0].type === "StringLiteral"
? node["arguments"][0].value
: null;
if (moduleName == null) {
const callNode = node["arguments"][0];
if (
callNode.type === "CallExpression" &&
callNode.callee.type === "MemberExpression" &&
callNode.callee.object.type === "Identifier"
) {
const callee = callNode.callee;
moduleName =
callee.object.type === "Identifier" &&
state.inlineableCalls.has(callee.object.name) &&
callee.property.type === "Identifier" &&
callee.property.name === "resolve" &&
callNode["arguments"].length >= 1 &&
callNode["arguments"][0].type === "StringLiteral"
? callNode["arguments"][0].value
: null;
}
}
const fnName = node.callee.name;
if (fnName == null) {
return null;
}
const isRequireInScope = path.scope.getBinding(fnName) != null;
return moduleName == null ||
state.ignoredRequires.has(moduleName) ||
moduleName.startsWith("@babel/runtime/") ||
isRequireInScope
? null
: {
moduleName,
requireFnName: fnName,
};
}
function getNearestLocFromPath(path) {
let current = path;
while (current && !current.node.loc) {
current = current.parentPath;
}
return current?.node.loc;
}
function isBranch(t, node) {
return (
t.isIfStatement(node) ||
t.isLogicalExpression(node) ||
t.isConditionalExpression(node) ||
t.isSwitchStatement(node) ||
t.isSwitchCase(node) ||
t.isForStatement(node) ||
t.isForInStatement(node) ||
t.isForOfStatement(node) ||
t.isWhileStatement(node)
);
}
function isDirectlyEnclosedByBlock(t, path) {
let curPath = path;
while (curPath) {
if (isBranch(t, curPath.node)) {
return false;
}
if (t.isBlockStatement(curPath.node)) {
return true;
}
curPath = curPath.parentPath;
}
return true;
}
function addStmtToBlock(block, stmt, idx) {
const scopeBody = block.body;
if (Array.isArray(scopeBody)) {
scopeBody.splice(idx, 0, stmt);
return true;
} else if (scopeBody && Array.isArray(scopeBody.body)) {
scopeBody.body.splice(idx, 0, stmt);
return true;
} else {
return false;
}
}

View File

@@ -0,0 +1,497 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict
*/
import type {PluginObj} from '@babel/core';
import typeof * as Babel from '@babel/core';
import type {NodePath, Scope} from '@babel/traverse';
import type {Program} from '@babel/types';
type Types = Babel['types'];
export type PluginOptions = $ReadOnly<{
ignoredRequires?: $ReadOnlyArray<string>,
inlineableCalls?: $ReadOnlyArray<string>,
nonMemoizedModules?: $ReadOnlyArray<string>,
memoizeCalls?: boolean,
}>;
export type State = {
opts?: PluginOptions,
ignoredRequires: Set<string>,
inlineableCalls: Set<string>,
membersAssigned: Map<string, Set<string>>,
...
};
/**
* This transform inlines top-level require(...) aliases with to enable lazy
* loading of dependencies. It is able to inline both single references and
* child property references.
*
* For instance:
* var Foo = require('foo');
* f(Foo);
*
* Will be transformed into:
* f(require('foo'));
*
* When the assigment expression has a property access, it will be inlined too,
* keeping the property. For instance:
* var Bar = require('foo').bar;
* g(Bar);
*
* Will be transformed into:
* g(require('foo').bar);
*
* Destructuring also works the same way. For instance:
* const {Baz} = require('foo');
* h(Baz);
*
* Is also successfully inlined into:
* g(require('foo').Baz);
*/
export default ({types: t, traverse}: Babel): PluginObj<State> => ({
name: 'inline-requires',
visitor: {
Program: {
enter() {},
exit(path: NodePath<Program>, state: State): void {
const ignoredRequires = new Set<string>();
const inlineableCalls = new Set(['require']);
const nonMemoizedModules = new Set<string>();
let memoizeCalls = false;
const opts = state.opts;
if (opts != null) {
opts.ignoredRequires?.forEach(name => ignoredRequires.add(name));
opts.inlineableCalls?.forEach(name => inlineableCalls.add(name));
opts.nonMemoizedModules?.forEach(name =>
nonMemoizedModules.add(name),
);
memoizeCalls = opts.memoizeCalls ?? false;
}
const programNode = path.scope.block;
if (programNode.type !== 'Program') {
return;
}
path.scope.crawl();
path.traverse<State>(
{
CallExpression(path, state) {
const parseResult =
parseInlineableAlias(path, state) ||
parseInlineableMemberAlias(path, state);
if (parseResult == null) {
return;
}
const {declarationPath, moduleName, requireFnName} = parseResult;
const maybeInit = declarationPath.node.init;
const name = declarationPath.node.id
? declarationPath.node.id.name
: null;
const binding =
name == null ? null : declarationPath.scope.getBinding(name);
if (
maybeInit == null ||
!t.isExpression(maybeInit) ||
binding == null ||
binding.constantViolations.length > 0
) {
return;
}
const init: BabelNodeExpression = maybeInit;
const initPath = declarationPath.get('init');
if (Array.isArray(initPath)) {
return;
}
const initLoc = getNearestLocFromPath(initPath);
deleteLocation(init);
traverse(init, {
noScope: true,
enter: path => deleteLocation(path.node),
});
let thrown = false;
const memoVarName = parseResult.identifierName;
// Whether the module has a "var foo" at program scope, used to
// store the result of a require call if memoizeCalls is true.
let hasMemoVar = false;
if (
memoizeCalls &&
// Don't add a var init statement if there are no references to
// the lvalue of the require assignment.
binding.referencePaths.length > 0 &&
// Some modules should never be memoized even though they
// may be inlined.
!nonMemoizedModules.has(moduleName)
) {
// create var init statement
const varInitStmt = t.variableDeclaration('var', [
t.variableDeclarator(t.identifier(memoVarName)),
]);
// Must remove the declaration path
declarationPath.remove();
hasMemoVar = addStmtToBlock(programNode, varInitStmt, 0);
}
function getMemoOrCallExpr() {
const refExpr = t.cloneDeep(init);
// $FlowFixMe[prop-missing]
refExpr.METRO_INLINE_REQUIRES_INIT_LOC = initLoc;
return t.logicalExpression(
'||',
t.identifier(memoVarName),
t.assignmentExpression(
'=',
t.identifier(memoVarName),
refExpr,
),
);
}
const scopesWithInlinedRequire = new Set<Scope>();
for (const referencePath of binding.referencePaths) {
excludeMemberAssignment(moduleName, referencePath, state);
try {
referencePath.scope.rename(requireFnName);
if (hasMemoVar) {
referencePath.scope.rename(memoVarName);
// Swap the local reference with (v || v = require(m)),
// unless it is directly enclosed.
if (!isDirectlyEnclosedByBlock(t, referencePath)) {
referencePath.replaceWith(getMemoOrCallExpr());
continue;
}
// if the current scope already has a (v || v = require(m))
// expression for module m, use identifier reference v
// instead. Else use the full (v || v = require(m)) and
// register the current scope for subsequent references.
if (scopesWithInlinedRequire.has(referencePath.scope)) {
referencePath.replaceWith(t.identifier(memoVarName));
} else {
referencePath.replaceWith(getMemoOrCallExpr());
scopesWithInlinedRequire.add(referencePath.scope);
}
} else {
const refExpr = t.cloneDeep(init);
// $FlowFixMe[prop-missing]
refExpr.METRO_INLINE_REQUIRES_INIT_LOC = initLoc;
referencePath.replaceWith(refExpr);
}
} catch (error) {
thrown = true;
}
}
// If a replacement failed (e.g. replacing a type annotation),
// avoid removing the initial require just to be safe.
if (!thrown && declarationPath.node != null) {
declarationPath.remove();
}
},
},
{
ignoredRequires,
inlineableCalls,
membersAssigned: new Map(),
},
);
},
},
},
});
function excludeMemberAssignment(
moduleName: string,
referencePath: NodePath<>,
state: State,
) {
const assignment: ?BabelNode = referencePath.parentPath?.parent;
if (assignment?.type !== 'AssignmentExpression') {
return;
}
const left = assignment.left;
if (left.type !== 'MemberExpression' || left.object !== referencePath.node) {
return;
}
const memberPropertyName = getMemberPropertyName(left);
if (memberPropertyName == null) {
return;
}
let membersAssigned = state.membersAssigned.get(moduleName);
if (membersAssigned == null) {
membersAssigned = new Set();
state.membersAssigned.set(moduleName, membersAssigned);
}
membersAssigned.add(memberPropertyName);
}
function isExcludedMemberAssignment(
moduleName: string,
memberPropertyName: string,
state: State,
) {
const excludedAliases = state.membersAssigned.get(moduleName);
return excludedAliases != null && excludedAliases.has(memberPropertyName);
}
function getMemberPropertyName(node: BabelNodeMemberExpression): ?string {
if (node.property.type === 'Identifier') {
return node.property.name;
}
if (node.property.type === 'StringLiteral') {
return node.property.value;
}
return null;
}
function deleteLocation(node: BabelNode) {
delete node.start;
delete node.end;
delete node.loc;
}
function parseInlineableAlias(
path: NodePath<BabelNodeCallExpression>,
state: State,
): ?{
declarationPath: NodePath<BabelNode>,
moduleName: string,
requireFnName: string,
identifierName: string,
} {
const module = getInlineableModule(path, state);
if (module == null) {
return null;
}
const {moduleName, requireFnName} = module;
const parentPath = path.parentPath;
if (parentPath == null) {
return null;
}
const grandParentPath = parentPath.parentPath;
if (grandParentPath == null) {
return null;
}
if (path.parent.type !== 'VariableDeclarator') {
return null;
}
const variableDeclarator = path.parent;
if (variableDeclarator.id.type !== 'Identifier') {
return null;
}
const identifier = variableDeclarator.id;
const isValid =
parentPath.parent.type === 'VariableDeclaration' &&
grandParentPath.parent.type === 'Program';
return !isValid || parentPath.node == null
? null
: {
declarationPath: parentPath,
moduleName,
requireFnName,
identifierName: identifier.name,
};
}
function parseInlineableMemberAlias(
path: NodePath<BabelNodeCallExpression>,
state: State,
): ?{
declarationPath: NodePath<BabelNode>,
moduleName: string,
requireFnName: string,
identifierName: string,
} {
const module = getInlineableModule(path, state);
if (module == null) {
return null;
}
const {moduleName, requireFnName} = module;
const parent = path.parent;
const parentPath = path.parentPath;
if (parentPath == null) {
return null;
}
const grandParentPath = parentPath.parentPath;
if (grandParentPath == null) {
return null;
}
if (parent.type !== 'MemberExpression') {
return null;
}
const memberExpression: BabelNodeMemberExpression = parent;
if (parentPath.parent.type !== 'VariableDeclarator') {
return null;
}
const variableDeclarator = parentPath.parent;
if (variableDeclarator.id.type !== 'Identifier') {
return null;
}
const identifier = variableDeclarator.id;
if (
grandParentPath.parent.type !== 'VariableDeclaration' ||
grandParentPath.parentPath?.parent.type !== 'Program' ||
grandParentPath.node == null
) {
return null;
}
const memberPropertyName = getMemberPropertyName(memberExpression);
return memberPropertyName == null ||
isExcludedMemberAssignment(moduleName, memberPropertyName, state)
? null
: {
declarationPath: grandParentPath,
moduleName,
requireFnName,
identifierName: identifier.name,
};
}
function getInlineableModule(
path: NodePath<BabelNodeCallExpression>,
state: State,
): ?{moduleName: string, requireFnName: string} {
const node = path.node;
const isInlineable =
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
state.inlineableCalls.has(node.callee.name) &&
node['arguments'].length >= 1;
if (!isInlineable) {
return null;
}
// require('foo');
let moduleName =
node['arguments'][0].type === 'StringLiteral'
? node['arguments'][0].value
: null;
// require(require.resolve('foo'));
if (moduleName == null) {
const callNode = node['arguments'][0];
if (
callNode.type === 'CallExpression' &&
callNode.callee.type === 'MemberExpression' &&
callNode.callee.object.type === 'Identifier'
) {
const callee = callNode.callee;
moduleName =
callee.object.type === 'Identifier' &&
state.inlineableCalls.has(callee.object.name) &&
callee.property.type === 'Identifier' &&
callee.property.name === 'resolve' &&
callNode['arguments'].length >= 1 &&
callNode['arguments'][0].type === 'StringLiteral'
? callNode['arguments'][0].value
: null;
}
}
// Check if require is in any parent scope
const fnName = node.callee.name;
if (fnName == null) {
return null;
}
const isRequireInScope = path.scope.getBinding(fnName) != null;
return moduleName == null ||
state.ignoredRequires.has(moduleName) ||
moduleName.startsWith('@babel/runtime/') ||
isRequireInScope
? null
: {moduleName, requireFnName: fnName};
}
function getNearestLocFromPath(path: NodePath<>): ?BabelSourceLocation {
let current: ?(NodePath<> | NodePath<BabelNode>) = path;
while (current && !current.node.loc) {
current = current.parentPath;
}
return current?.node.loc;
}
// check if a node is a branch
function isBranch(t: Types, node: BabelNode) {
return (
t.isIfStatement(node) ||
t.isLogicalExpression(node) ||
t.isConditionalExpression(node) ||
t.isSwitchStatement(node) ||
t.isSwitchCase(node) ||
t.isForStatement(node) ||
t.isForInStatement(node) ||
t.isForOfStatement(node) ||
t.isWhileStatement(node)
);
}
function isDirectlyEnclosedByBlock(t: Types, path: NodePath<BabelNode>) {
let curPath: ?NodePath<BabelNode> = path;
while (curPath) {
if (isBranch(t, curPath.node)) {
return false;
}
if (t.isBlockStatement(curPath.node)) {
return true;
}
curPath = curPath.parentPath;
}
return true;
}
// insert statement to the beginning of the scope block
function addStmtToBlock(
block: BabelNodeProgram,
stmt: BabelNodeStatement,
idx: number,
): boolean {
const scopeBody = block.body;
if (Array.isArray(scopeBody)) {
// if the code is inside global scope
scopeBody.splice(idx, 0, stmt);
return true;
} else if (scopeBody && Array.isArray(scopeBody.body)) {
// if the code is inside function scope
scopeBody.body.splice(idx, 0, stmt);
return true;
} else {
return false;
}
}

View File

@@ -0,0 +1,81 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = normalizePseudoglobals;
var _traverse = _interopRequireDefault(require("@babel/traverse"));
var _nullthrows = _interopRequireDefault(require("nullthrows"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function normalizePseudoglobals(ast, options) {
const reservedNames = new Set(options?.reservedNames ?? []);
const renamedParamNames = [];
(0, _traverse.default)(ast, {
Program(path) {
const params = path.get("body.0.expression.arguments.0.params");
const body = path.get("body.0.expression.arguments.0.body");
if (!body || Array.isArray(body) || !Array.isArray(params)) {
path.stop();
return;
}
const pseudoglobals = params
.map((path) => path.node.name)
.filter((name) => !reservedNames.has(name));
const usedShortNames = new Set();
const namePairs = pseudoglobals.map((fullName) => [
fullName,
getShortName(fullName, usedShortNames),
]);
for (const [fullName, shortName] of namePairs) {
if (reservedNames.has(shortName)) {
throw new ReferenceError(
"Could not reserve the identifier " +
shortName +
" because it is the short name for " +
fullName,
);
}
renamedParamNames.push(rename(fullName, shortName, body.scope));
}
path.stop();
},
});
return renamedParamNames;
}
function getShortName(fullName, usedNames) {
const regexp = /^[^A-Za-z]*([A-Za-z])|([A-Z])[a-z]|([A-Z])[A-Z]+$/g;
let match;
while ((match = regexp.exec(fullName))) {
const name = (match[1] || match[2] || match[3] || "").toLowerCase();
if (!name) {
throw new ReferenceError(
"Could not identify any valid name for " + fullName,
);
}
if (!usedNames.has(name)) {
usedNames.add(name);
return name;
}
}
throw new ReferenceError(
`Unable to determine short name for ${fullName}. The variables are not unique: ${Array.from(usedNames).join(", ")}`,
);
}
function rename(fullName, shortName, scope) {
let unusedName = shortName;
if (
scope.hasLabel(shortName) ||
scope.hasBinding(shortName) ||
scope.hasGlobal(shortName) ||
scope.hasReference(shortName)
) {
unusedName = scope.generateUid(shortName);
const programScope = scope.getProgramParent();
(0, _nullthrows.default)(programScope.references)[shortName] = true;
(0, _nullthrows.default)(programScope.uids)[shortName] = true;
}
scope.rename(fullName, unusedName);
return unusedName;
}

View File

@@ -0,0 +1,126 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/
import type {NodePath, Scope} from '@babel/traverse';
import type {Program} from '@babel/types';
import traverse from '@babel/traverse';
import nullthrows from 'nullthrows';
export type Options = {
reservedNames: $ReadOnlyArray<string>,
};
export default function normalizePseudoglobals(
ast: BabelNode,
options?: Options,
): $ReadOnlyArray<string> {
const reservedNames = new Set<
| void
| string
| BabelNodeIdentifier
| BabelNodeJSXIdentifier
| BabelNodeJSXMemberExpression
| BabelNodeJSXNamespacedName,
>(options?.reservedNames ?? []);
const renamedParamNames = [];
traverse(ast, {
Program(path: NodePath<Program>): void {
const params = path.get('body.0.expression.arguments.0.params');
const body = path.get('body.0.expression.arguments.0.body');
if (!body || Array.isArray(body) || !Array.isArray(params)) {
path.stop();
return;
}
const pseudoglobals: Array<string> = params
.map(path => path.node.name)
// $FlowFixMe[incompatible-type] Flow error uncovered by typing Babel more strictly
.filter(name => !reservedNames.has(name));
const usedShortNames = new Set<string>();
const namePairs: Array<[string, string]> = pseudoglobals.map(fullName => [
fullName,
getShortName(fullName, usedShortNames),
]);
for (const [fullName, shortName] of namePairs) {
if (reservedNames.has(shortName)) {
throw new ReferenceError(
'Could not reserve the identifier ' +
shortName +
' because it is the short name for ' +
fullName,
);
}
renamedParamNames.push(rename(fullName, shortName, body.scope));
}
path.stop();
},
});
return renamedParamNames;
}
function getShortName(fullName: string, usedNames: Set<string>): string {
// Try finding letters that are semantically relatable to the name
// of the variable given. For instance, in XMLHttpRequest, it will
// first match "X", then "H", then "R".
const regexp = /^[^A-Za-z]*([A-Za-z])|([A-Z])[a-z]|([A-Z])[A-Z]+$/g;
let match;
while ((match = regexp.exec(fullName))) {
const name = (match[1] || match[2] || match[3] || '').toLowerCase();
if (!name) {
throw new ReferenceError(
'Could not identify any valid name for ' + fullName,
);
}
if (!usedNames.has(name)) {
usedNames.add(name);
return name;
}
}
throw new ReferenceError(
`Unable to determine short name for ${fullName}. The variables are not unique: ${Array.from(
usedNames,
).join(', ')}`,
);
}
function rename(fullName: string, shortName: string, scope: Scope): string {
let unusedName = shortName;
// `generateUid` generates a name of the form name_ even if there was no conflict which we don't want.
// Check if the desired name was never used and in that case proceed and only use `generateUid` if there's a
// name clash.
if (
scope.hasLabel(shortName) ||
scope.hasBinding(shortName) ||
scope.hasGlobal(shortName) ||
scope.hasReference(shortName)
) {
unusedName = scope.generateUid(shortName);
const programScope = scope.getProgramParent();
nullthrows(programScope.references)[shortName] = true;
nullthrows(programScope.uids)[shortName] = true;
}
scope.rename(fullName, unusedName);
return unusedName;
}

View File

@@ -0,0 +1,133 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = createInlinePlatformChecks;
const importMap = new Map([["ReactNative", "react-native"]]);
function createInlinePlatformChecks(t, requireName = "require") {
const {
isIdentifier,
isStringLiteral,
isNumericLiteral,
isMemberExpression,
isCallExpression,
} = t;
const isPlatformNode = (node, scope, isWrappedModule) =>
isPlatformOS(node, scope, isWrappedModule) ||
isReactPlatformOS(node, scope, isWrappedModule);
const isPlatformSelectNode = (node, scope, isWrappedModule) =>
isPlatformSelect(node, scope, isWrappedModule) ||
isReactPlatformSelect(node, scope, isWrappedModule);
const isPlatformOS = (node, scope, isWrappedModule) =>
isIdentifier(node.property, {
name: "OS",
}) &&
isImportOrGlobal(
node.object,
scope,
[
{
name: "Platform",
},
],
isWrappedModule,
);
const isReactPlatformOS = (node, scope, isWrappedModule) =>
isIdentifier(node.property, {
name: "OS",
}) &&
isMemberExpression(node.object) &&
isIdentifier(node.object.property, {
name: "Platform",
}) &&
isImportOrGlobal(
node.object.object,
scope,
[
{
name: "React",
},
{
name: "ReactNative",
},
],
isWrappedModule,
);
const isPlatformSelect = (node, scope, isWrappedModule) =>
isMemberExpression(node.callee) &&
isIdentifier(node.callee.property, {
name: "select",
}) &&
isImportOrGlobal(
node.callee.object,
scope,
[
{
name: "Platform",
},
],
isWrappedModule,
);
const isReactPlatformSelect = (node, scope, isWrappedModule) =>
isMemberExpression(node.callee) &&
isIdentifier(node.callee.property, {
name: "select",
}) &&
isMemberExpression(node.callee.object) &&
isIdentifier(node.callee.object.property, {
name: "Platform",
}) &&
isImportOrGlobal(
node.callee.object.object,
scope,
[
{
name: "React",
},
{
name: "ReactNative",
},
],
isWrappedModule,
);
const isRequireCall = (node, dependencyId, scope) =>
isCallExpression(node) &&
isIdentifier(node.callee, {
name: requireName,
}) &&
checkRequireArgs(node.arguments, dependencyId);
const isImport = (node, scope, patterns) =>
patterns.some((pattern) => {
const importName = importMap.get(pattern.name) || pattern.name;
return isRequireCall(node, importName, scope);
});
const isImportOrGlobal = (node, scope, patterns, isWrappedModule) => {
const identifier = patterns.find((pattern) => isIdentifier(node, pattern));
return (
(!!identifier &&
isToplevelBinding(
scope.getBinding(identifier.name),
isWrappedModule,
)) ||
isImport(node, scope, patterns)
);
};
const checkRequireArgs = (args, dependencyId) => {
const pattern = t.stringLiteral(dependencyId);
return (
isStringLiteral(args[0], pattern) ||
(isMemberExpression(args[0]) &&
isNumericLiteral(args[0].property) &&
isStringLiteral(args[1], pattern))
);
};
const isToplevelBinding = (binding, isWrappedModule) =>
!binding ||
!binding.scope.parent ||
(isWrappedModule && !binding.scope.parent.parent);
return {
isPlatformNode,
isPlatformSelectNode,
};
}

View File

@@ -0,0 +1,184 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/
import type {Scope} from '@babel/traverse';
import type {CallExpression, MemberExpression} from '@babel/types';
// Type only import. No runtime dependency
// eslint-disable-next-line import/no-extraneous-dependencies
import typeof * as Types from '@babel/types';
const importMap = new Map([['ReactNative', 'react-native']]);
type PlatformChecks = {
isPlatformNode: (
node: MemberExpression,
scope: Scope,
isWrappedModule: boolean,
) => boolean,
isPlatformSelectNode: (
node: CallExpression,
scope: Scope,
isWrappedModule: boolean,
) => boolean,
};
export default function createInlinePlatformChecks(
t: Types,
requireName: string = 'require',
): PlatformChecks {
const {
isIdentifier,
isStringLiteral,
isNumericLiteral,
isMemberExpression,
isCallExpression,
} = t;
const isPlatformNode = (
node: MemberExpression,
scope: Scope,
isWrappedModule: boolean,
): boolean =>
isPlatformOS(node, scope, isWrappedModule) ||
isReactPlatformOS(node, scope, isWrappedModule);
const isPlatformSelectNode = (
node: CallExpression,
scope: Scope,
isWrappedModule: boolean,
): boolean =>
isPlatformSelect(node, scope, isWrappedModule) ||
isReactPlatformSelect(node, scope, isWrappedModule);
const isPlatformOS = (
node: MemberExpression,
scope: Scope,
isWrappedModule: boolean,
): boolean =>
isIdentifier(node.property, {name: 'OS'}) &&
isImportOrGlobal(node.object, scope, [{name: 'Platform'}], isWrappedModule);
const isReactPlatformOS = (
node: MemberExpression,
scope: Scope,
isWrappedModule: boolean,
): boolean =>
isIdentifier(node.property, {name: 'OS'}) &&
isMemberExpression(node.object) &&
isIdentifier(node.object.property, {name: 'Platform'}) &&
isImportOrGlobal(
// $FlowFixMe[incompatible-type]
node.object.object,
scope,
[{name: 'React'}, {name: 'ReactNative'}],
isWrappedModule,
);
const isPlatformSelect = (
node: CallExpression,
scope: Scope,
isWrappedModule: boolean,
): boolean =>
isMemberExpression(node.callee) &&
isIdentifier(node.callee.property, {name: 'select'}) &&
isImportOrGlobal(
// $FlowFixMe[incompatible-type]
node.callee.object,
scope,
[{name: 'Platform'}],
isWrappedModule,
);
const isReactPlatformSelect = (
node: CallExpression,
scope: Scope,
isWrappedModule: boolean,
): boolean =>
isMemberExpression(node.callee) &&
isIdentifier(node.callee.property, {name: 'select'}) &&
isMemberExpression(node.callee.object) &&
isIdentifier(node.callee.object.property, {name: 'Platform'}) &&
isImportOrGlobal(
// $FlowFixMe[incompatible-type]
// $FlowFixMe[incompatible-use]
node.callee.object.object,
scope,
[{name: 'React'}, {name: 'ReactNative'}],
isWrappedModule,
);
const isRequireCall = (
node: BabelNodeExpression,
dependencyId: string,
scope: Scope,
): boolean =>
isCallExpression(node) &&
isIdentifier(node.callee, {name: requireName}) &&
checkRequireArgs(node.arguments, dependencyId);
const isImport = (
node: BabelNodeExpression,
scope: Scope,
patterns: Array<{name: string}>,
): boolean =>
patterns.some((pattern: {name: string}) => {
const importName = importMap.get(pattern.name) || pattern.name;
return isRequireCall(node, importName, scope);
});
const isImportOrGlobal = (
node: BabelNodeExpression,
scope: Scope,
patterns: Array<{name: string}>,
isWrappedModule: boolean,
): boolean => {
const identifier = patterns.find((pattern: {name: string}) =>
isIdentifier(node, pattern),
);
return (
(!!identifier &&
isToplevelBinding(
scope.getBinding(identifier.name),
isWrappedModule,
)) ||
isImport(node, scope, patterns)
);
};
const checkRequireArgs = (
args: Array<
| BabelNodeExpression
| BabelNodeSpreadElement
| BabelNodeArgumentPlaceholder,
>,
dependencyId: string,
): boolean => {
const pattern = t.stringLiteral(dependencyId);
return (
isStringLiteral(args[0], pattern) ||
(isMemberExpression(args[0]) &&
isNumericLiteral(args[0].property) &&
isStringLiteral(args[1], pattern))
);
};
const isToplevelBinding = (
binding: void | $FlowFixMe,
isWrappedModule: boolean,
): boolean =>
!binding ||
!binding.scope.parent ||
(isWrappedModule && !binding.scope.parent.parent);
return {
isPlatformNode,
isPlatformSelectNode,
};
}