You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
197 lines
4.9 KiB
197 lines
4.9 KiB
/**
|
|
* 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
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
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};
|
|
|
|
function constantFoldingPlugin(context: {
|
|
types: Types,
|
|
traverse: Traverse,
|
|
...
|
|
}): PluginObj<State> {
|
|
const t = context.types;
|
|
const {isVariableDeclarator} = 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>,
|
|
state: {safe: boolean},
|
|
) => {
|
|
state.safe = false;
|
|
};
|
|
|
|
path.traverse(
|
|
{
|
|
CallExpression: unsafe,
|
|
AssignmentExpression: 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,
|
|
> = {
|
|
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,
|
|
> = {
|
|
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 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,
|
|
> = {
|
|
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,
|
|
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,
|
|
LogicalExpression,
|
|
Program: {...Program}, // Babel mutates objects passed.
|
|
UnaryExpression: Expression,
|
|
};
|
|
|
|
return {visitor};
|
|
}
|
|
|
|
module.exports = constantFoldingPlugin;
|