import type {PluginObj} from '@babel/core';
import {transformFromAstSync} from '@babel/core';
import generate from '@babel/generator';
import traverse from '@babel/traverse';
import * as t from '@babel/types';
import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/Utils/TestUtils';
import {parseInput} from './compiler.js';
import {PARSE_CONFIG_PRAGMA_IMPORT, BABEL_PLUGIN_SRC} from './constants.js';
type CompileSuccess = {kind: 'success'};
type CompileParseError = {kind: 'parse_error'; message: string};
type CompileErrors = {
kind: 'errors';
errors: Array<{category: string; reason: string; description: string | null}>;
};
type CompileResult = CompileSuccess | CompileParseError | CompileErrors;
function compileAndGetError(
code: string,
filename: string,
language: 'flow' | 'typescript',
sourceType: 'module' | 'script',
plugin: PluginObj,
parseConfigPragmaFn: typeof ParseConfigPragma,
): CompileResult {
let ast: t.File;
try {
ast = parseInput(code, filename, language, sourceType);
} catch (e: unknown) {
return {kind: 'parse_error', message: (e as Error).message};
}
const firstLine = code.substring(0, code.indexOf('\n'));
const config = parseConfigPragmaFn(firstLine, {compilationMode: 'all'});
const options = {
...config,
environment: {
...config.environment,
},
logger: {
logEvent: () => {},
debugLogIRs: () => {},
},
enableReanimatedCheck: false,
};
try {
transformFromAstSync(ast, code, {
filename: '/' + filename,
highlightCode: false,
retainLines: true,
compact: true,
plugins: [[plugin, options]],
sourceType: 'module',
ast: false,
cloneInputAst: true,
configFile: false,
babelrc: false,
});
return {kind: 'success'};
} catch (e: unknown) {
const error = e as Error & {
details?: Array<{
category: string;
reason: string;
description: string | null;
}>;
};
if (error.details && error.details.length > 0) {
return {
kind: 'errors',
errors: error.details.map(detail => ({
category: detail.category,
reason: detail.reason,
description: detail.description,
})),
};
}
return {
kind: 'errors',
errors: [
{
category: error.name ?? 'Error',
reason: error.message,
description: null,
},
],
};
}
}
function errorsMatch(a: CompileErrors, b: CompileResult): boolean {
if (b.kind !== 'errors') {
return false;
}
if (a.errors.length !== b.errors.length) {
return false;
}
for (let i = 0; i < a.errors.length; i++) {
if (
a.errors[i].category !== b.errors[i].category ||
a.errors[i].reason !== b.errors[i].reason ||
a.errors[i].description !== b.errors[i].description
) {
return false;
}
}
return true;
}
function astToCode(ast: t.File): string {
return generate(ast).code;
}
function cloneAst(ast: t.File): t.File {
return t.cloneNode(ast, true);
}
function* removeStatements(ast: t.File): Generator<t.File> {
const statementLocations: Array<{containerIndex: number; stmtIndex: number}> =
[];
let containerIndex = 0;
t.traverseFast(ast, node => {
if (t.isBlockStatement(node) || t.isProgram(node)) {
const body = node.body as t.Statement[];
for (let i = body.length - 1; i >= 0; i--) {
statementLocations.push({containerIndex, stmtIndex: i});
}
containerIndex++;
}
});
for (const {
containerIndex: targetContainerIdx,
stmtIndex,
} of statementLocations) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
t.traverseFast(cloned, node => {
if (modified) return;
if (t.isBlockStatement(node) || t.isProgram(node)) {
if (idx === targetContainerIdx) {
const body = node.body as t.Statement[];
if (stmtIndex < body.length) {
body.splice(stmtIndex, 1);
modified = true;
}
}
idx++;
}
});
if (modified) {
yield cloned;
}
}
}
function* removeCallArguments(ast: t.File): Generator<t.File> {
const callSites: Array<{callIndex: number; argCount: number}> = [];
let callIndex = 0;
t.traverseFast(ast, node => {
if (t.isCallExpression(node) && node.arguments.length > 0) {
callSites.push({callIndex, argCount: node.arguments.length});
callIndex++;
}
});
for (const {callIndex: targetCallIdx, argCount} of callSites) {
for (let argIdx = argCount - 1; argIdx >= 0; argIdx--) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
t.traverseFast(cloned, node => {
if (modified) return;
if (t.isCallExpression(node) && node.arguments.length > 0) {
if (idx === targetCallIdx && argIdx < node.arguments.length) {
node.arguments.splice(argIdx, 1);
modified = true;
}
idx++;
}
});
if (modified) {
yield cloned;
}
}
}
}
function* removeFunctionParameters(ast: t.File): Generator<t.File> {
const funcSites: Array<{funcIndex: number; paramCount: number}> = [];
let funcIndex = 0;
t.traverseFast(ast, node => {
if (t.isFunction(node) && node.params.length > 0) {
funcSites.push({funcIndex, paramCount: node.params.length});
funcIndex++;
}
});
for (const {funcIndex: targetFuncIdx, paramCount} of funcSites) {
for (let paramIdx = paramCount - 1; paramIdx >= 0; paramIdx--) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
t.traverseFast(cloned, node => {
if (modified) return;
if (t.isFunction(node) && node.params.length > 0) {
if (idx === targetFuncIdx && paramIdx < node.params.length) {
node.params.splice(paramIdx, 1);
modified = true;
}
idx++;
}
});
if (modified) {
yield cloned;
}
}
}
}
function* simplifyCallExpressions(ast: t.File): Generator<t.File> {
let callCount = 0;
t.traverseFast(ast, node => {
if (t.isCallExpression(node) && node.arguments.length > 0) {
callCount++;
}
});
for (let targetIdx = 0; targetIdx < callCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
CallExpression(path) {
if (modified) return;
if (path.node.arguments.length > 0 && idx === targetIdx) {
const args = path.node.arguments;
const exprArgs = args.filter((arg): arg is t.Expression =>
t.isExpression(arg),
);
if (exprArgs.length === 0) {
idx++;
return;
}
if (exprArgs.length === 1) {
path.replaceWith(exprArgs[0]);
} else {
path.replaceWith(t.arrayExpression(exprArgs));
}
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < callCount; targetIdx++) {
let argCount = 0;
let currentIdx = 0;
t.traverseFast(ast, node => {
if (t.isCallExpression(node) && node.arguments.length > 0) {
if (currentIdx === targetIdx) {
argCount = node.arguments.length;
}
currentIdx++;
}
});
for (let argIdx = 0; argIdx < argCount; argIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
CallExpression(path) {
if (modified) return;
if (path.node.arguments.length > 0 && idx === targetIdx) {
const arg = path.node.arguments[argIdx];
if (t.isExpression(arg)) {
path.replaceWith(arg);
modified = true;
}
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
}
function* simplifyConditionals(ast: t.File): Generator<t.File> {
let condCount = 0;
t.traverseFast(ast, node => {
if (t.isConditionalExpression(node)) {
condCount++;
}
});
for (let targetIdx = 0; targetIdx < condCount; targetIdx++) {
const cloned = cloneAst(ast);
let modified = false;
let idx = 0;
traverse(cloned, {
ConditionalExpression(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.test);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < condCount; targetIdx++) {
const cloned = cloneAst(ast);
let modified = false;
let idx = 0;
traverse(cloned, {
ConditionalExpression(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.consequent);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < condCount; targetIdx++) {
const cloned = cloneAst(ast);
let modified = false;
let idx = 0;
traverse(cloned, {
ConditionalExpression(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.alternate);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* simplifyLogicalExpressions(ast: t.File): Generator<t.File> {
let logicalCount = 0;
t.traverseFast(ast, node => {
if (t.isLogicalExpression(node)) {
logicalCount++;
}
});
for (let targetIdx = 0; targetIdx < logicalCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
LogicalExpression(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.left);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < logicalCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
LogicalExpression(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.right);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* simplifyOptionalChains(ast: t.File): Generator<t.File> {
let optionalCount = 0;
t.traverseFast(ast, node => {
if (
t.isOptionalMemberExpression(node) ||
t.isOptionalCallExpression(node)
) {
optionalCount++;
}
});
for (let targetIdx = 0; targetIdx < optionalCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
OptionalMemberExpression(path) {
if (modified) return;
if (idx === targetIdx) {
const {object, property, computed} = path.node;
path.replaceWith(t.memberExpression(object, property, computed));
modified = true;
}
idx++;
},
OptionalCallExpression(path) {
if (modified) return;
if (idx === targetIdx) {
const {callee, arguments: args} = path.node;
if (t.isExpression(callee)) {
path.replaceWith(t.callExpression(callee, args));
modified = true;
}
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* simplifyAwaitExpressions(ast: t.File): Generator<t.File> {
let awaitCount = 0;
t.traverseFast(ast, node => {
if (t.isAwaitExpression(node)) {
awaitCount++;
}
});
for (let targetIdx = 0; targetIdx < awaitCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
AwaitExpression(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.argument);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* simplifyIfStatements(ast: t.File): Generator<t.File> {
let ifCount = 0;
t.traverseFast(ast, node => {
if (t.isIfStatement(node)) {
ifCount++;
}
});
for (let targetIdx = 0; targetIdx < ifCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
IfStatement(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(t.expressionStatement(path.node.test));
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < ifCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
IfStatement(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.consequent);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < ifCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
IfStatement(path) {
if (modified) return;
if (idx === targetIdx && path.node.alternate) {
path.replaceWith(path.node.alternate);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* simplifySwitchStatements(ast: t.File): Generator<t.File> {
let switchCount = 0;
t.traverseFast(ast, node => {
if (t.isSwitchStatement(node)) {
switchCount++;
}
});
for (let targetIdx = 0; targetIdx < switchCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
SwitchStatement(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(t.expressionStatement(path.node.discriminant));
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < switchCount; targetIdx++) {
let caseCount = 0;
let currentIdx = 0;
t.traverseFast(ast, node => {
if (t.isSwitchStatement(node)) {
if (currentIdx === targetIdx) {
caseCount = node.cases.length;
}
currentIdx++;
}
});
for (let caseIdx = 0; caseIdx < caseCount; caseIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
SwitchStatement(path) {
if (modified) return;
if (idx === targetIdx) {
const switchCase = path.node.cases[caseIdx];
if (switchCase && switchCase.consequent.length > 0) {
path.replaceWithMultiple(switchCase.consequent);
modified = true;
}
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
}
function* simplifyWhileStatements(ast: t.File): Generator<t.File> {
let whileCount = 0;
t.traverseFast(ast, node => {
if (t.isWhileStatement(node)) {
whileCount++;
}
});
for (let targetIdx = 0; targetIdx < whileCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
WhileStatement(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(t.expressionStatement(path.node.test));
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < whileCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
WhileStatement(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.body);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* simplifyDoWhileStatements(ast: t.File): Generator<t.File> {
let doWhileCount = 0;
t.traverseFast(ast, node => {
if (t.isDoWhileStatement(node)) {
doWhileCount++;
}
});
for (let targetIdx = 0; targetIdx < doWhileCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
DoWhileStatement(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(t.expressionStatement(path.node.test));
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < doWhileCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
DoWhileStatement(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.body);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* simplifyForStatements(ast: t.File): Generator<t.File> {
let forCount = 0;
t.traverseFast(ast, node => {
if (t.isForStatement(node)) {
forCount++;
}
});
for (let targetIdx = 0; targetIdx < forCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
ForStatement(path) {
if (modified) return;
if (idx === targetIdx && path.node.init) {
if (t.isExpression(path.node.init)) {
path.replaceWith(t.expressionStatement(path.node.init));
} else {
path.replaceWith(path.node.init);
}
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < forCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
ForStatement(path) {
if (modified) return;
if (idx === targetIdx && path.node.test) {
path.replaceWith(t.expressionStatement(path.node.test));
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < forCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
ForStatement(path) {
if (modified) return;
if (idx === targetIdx && path.node.update) {
path.replaceWith(t.expressionStatement(path.node.update));
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < forCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
ForStatement(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.body);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* simplifyForInStatements(ast: t.File): Generator<t.File> {
let forInCount = 0;
t.traverseFast(ast, node => {
if (t.isForInStatement(node)) {
forInCount++;
}
});
for (let targetIdx = 0; targetIdx < forInCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
ForInStatement(path) {
if (modified) return;
if (idx === targetIdx) {
const left = path.node.left;
if (t.isExpression(left)) {
path.replaceWith(t.expressionStatement(left));
} else {
path.replaceWith(left);
}
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < forInCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
ForInStatement(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(t.expressionStatement(path.node.right));
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < forInCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
ForInStatement(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.body);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* simplifyForOfStatements(ast: t.File): Generator<t.File> {
let forOfCount = 0;
t.traverseFast(ast, node => {
if (t.isForOfStatement(node)) {
forOfCount++;
}
});
for (let targetIdx = 0; targetIdx < forOfCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
ForOfStatement(path) {
if (modified) return;
if (idx === targetIdx) {
const left = path.node.left;
if (t.isExpression(left)) {
path.replaceWith(t.expressionStatement(left));
} else {
path.replaceWith(left);
}
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < forOfCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
ForOfStatement(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(t.expressionStatement(path.node.right));
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < forOfCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
ForOfStatement(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.body);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* simplifyVariableDeclarations(ast: t.File): Generator<t.File> {
const declaratorSites: Array<{declIndex: number}> = [];
let declIndex = 0;
t.traverseFast(ast, node => {
if (t.isVariableDeclaration(node) && node.kind !== 'const') {
for (const declarator of node.declarations) {
if (declarator.init) {
declaratorSites.push({declIndex});
declIndex++;
}
}
}
});
for (const {declIndex: targetDeclIdx} of declaratorSites) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
t.traverseFast(cloned, node => {
if (modified) return;
if (t.isVariableDeclaration(node) && node.kind !== 'const') {
for (const declarator of node.declarations) {
if (declarator.init) {
if (idx === targetDeclIdx) {
declarator.init = null;
modified = true;
return;
}
idx++;
}
}
}
});
if (modified) {
yield cloned;
}
}
}
function* simplifyTryStatements(ast: t.File): Generator<t.File> {
let tryCount = 0;
t.traverseFast(ast, node => {
if (t.isTryStatement(node)) {
tryCount++;
}
});
for (let targetIdx = 0; targetIdx < tryCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
TryStatement(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.block);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < tryCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
TryStatement(path) {
if (modified) return;
if (idx === targetIdx && path.node.handler) {
path.replaceWith(path.node.handler.body);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < tryCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
TryStatement(path) {
if (modified) return;
if (idx === targetIdx && path.node.finalizer) {
path.replaceWith(path.node.finalizer);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* simplifySingleStatementBlocks(ast: t.File): Generator<t.File> {
let blockCount = 0;
t.traverseFast(ast, node => {
if (t.isBlockStatement(node) && node.body.length === 1) {
blockCount++;
}
});
for (let targetIdx = 0; targetIdx < blockCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
BlockStatement(path) {
if (modified) return;
if (path.node.body.length === 1 && idx === targetIdx) {
if (
t.isFunction(path.parent) ||
t.isCatchClause(path.parent) ||
t.isClassMethod(path.parent) ||
t.isObjectMethod(path.parent) ||
t.isTryStatement(path.parent)
) {
idx++;
return;
}
path.replaceWith(path.node.body[0]);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* removeArrayElements(ast: t.File): Generator<t.File> {
const arraySites: Array<{arrayIndex: number; elementCount: number}> = [];
let arrayIndex = 0;
t.traverseFast(ast, node => {
if (t.isArrayExpression(node) && node.elements.length > 0) {
arraySites.push({arrayIndex, elementCount: node.elements.length});
arrayIndex++;
}
});
for (const {arrayIndex: targetArrayIdx, elementCount} of arraySites) {
for (let elemIdx = elementCount - 1; elemIdx >= 0; elemIdx--) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
t.traverseFast(cloned, node => {
if (modified) return;
if (t.isArrayExpression(node) && node.elements.length > 0) {
if (idx === targetArrayIdx && elemIdx < node.elements.length) {
node.elements.splice(elemIdx, 1);
modified = true;
}
idx++;
}
});
if (modified) {
yield cloned;
}
}
}
}
function* removeJSXAttributes(ast: t.File): Generator<t.File> {
const jsxSites: Array<{jsxIndex: number; attrCount: number}> = [];
let jsxIndex = 0;
t.traverseFast(ast, node => {
if (t.isJSXOpeningElement(node) && node.attributes.length > 0) {
jsxSites.push({jsxIndex, attrCount: node.attributes.length});
jsxIndex++;
}
});
for (const {jsxIndex: targetJsxIdx, attrCount} of jsxSites) {
for (let attrIdx = attrCount - 1; attrIdx >= 0; attrIdx--) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
t.traverseFast(cloned, node => {
if (modified) return;
if (t.isJSXOpeningElement(node) && node.attributes.length > 0) {
if (idx === targetJsxIdx && attrIdx < node.attributes.length) {
node.attributes.splice(attrIdx, 1);
modified = true;
}
idx++;
}
});
if (modified) {
yield cloned;
}
}
}
}
function* removeJSXChildren(ast: t.File): Generator<t.File> {
const jsxSites: Array<{jsxIndex: number; childCount: number}> = [];
let jsxIndex = 0;
t.traverseFast(ast, node => {
if (t.isJSXElement(node) && node.children.length > 0) {
jsxSites.push({jsxIndex, childCount: node.children.length});
jsxIndex++;
}
});
for (const {jsxIndex: targetJsxIdx, childCount} of jsxSites) {
for (let childIdx = childCount - 1; childIdx >= 0; childIdx--) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
t.traverseFast(cloned, node => {
if (modified) return;
if (t.isJSXElement(node) && node.children.length > 0) {
if (idx === targetJsxIdx && childIdx < node.children.length) {
node.children.splice(childIdx, 1);
modified = true;
}
idx++;
}
});
if (modified) {
yield cloned;
}
}
}
}
function* removeJSXFragmentChildren(ast: t.File): Generator<t.File> {
const fragmentSites: Array<{fragIndex: number; childCount: number}> = [];
let fragIndex = 0;
t.traverseFast(ast, node => {
if (t.isJSXFragment(node) && node.children.length > 0) {
fragmentSites.push({fragIndex, childCount: node.children.length});
fragIndex++;
}
});
for (const {fragIndex: targetFragIdx, childCount} of fragmentSites) {
for (let childIdx = childCount - 1; childIdx >= 0; childIdx--) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
t.traverseFast(cloned, node => {
if (modified) return;
if (t.isJSXFragment(node) && node.children.length > 0) {
if (idx === targetFragIdx && childIdx < node.children.length) {
node.children.splice(childIdx, 1);
modified = true;
}
idx++;
}
});
if (modified) {
yield cloned;
}
}
}
}
function* simplifySingleElementArrays(ast: t.File): Generator<t.File> {
let arrayCount = 0;
t.traverseFast(ast, node => {
if (t.isArrayExpression(node) && node.elements.length === 1) {
arrayCount++;
}
});
for (let targetIdx = 0; targetIdx < arrayCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
ArrayExpression(path) {
if (modified) return;
if (path.node.elements.length === 1 && idx === targetIdx) {
const elem = path.node.elements[0];
if (t.isExpression(elem)) {
path.replaceWith(elem);
modified = true;
}
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* simplifySinglePropertyObjects(ast: t.File): Generator<t.File> {
let objectCount = 0;
t.traverseFast(ast, node => {
if (t.isObjectExpression(node) && node.properties.length === 1) {
objectCount++;
}
});
for (let targetIdx = 0; targetIdx < objectCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
ObjectExpression(path) {
if (modified) return;
if (path.node.properties.length === 1 && idx === targetIdx) {
const prop = path.node.properties[0];
if (t.isObjectProperty(prop) && t.isExpression(prop.value)) {
path.replaceWith(prop.value);
modified = true;
}
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < objectCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
ObjectExpression(path) {
if (modified) return;
if (path.node.properties.length === 1 && idx === targetIdx) {
const prop = path.node.properties[0];
if (
t.isObjectProperty(prop) &&
prop.computed &&
t.isExpression(prop.key)
) {
path.replaceWith(prop.key);
modified = true;
}
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* removeObjectProperties(ast: t.File): Generator<t.File> {
const objectSites: Array<{objectIndex: number; propCount: number}> = [];
let objectIndex = 0;
t.traverseFast(ast, node => {
if (t.isObjectExpression(node) && node.properties.length > 0) {
objectSites.push({objectIndex, propCount: node.properties.length});
objectIndex++;
}
});
for (const {objectIndex: targetObjIdx, propCount} of objectSites) {
for (let propIdx = propCount - 1; propIdx >= 0; propIdx--) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
t.traverseFast(cloned, node => {
if (modified) return;
if (t.isObjectExpression(node) && node.properties.length > 0) {
if (idx === targetObjIdx && propIdx < node.properties.length) {
node.properties.splice(propIdx, 1);
modified = true;
}
idx++;
}
});
if (modified) {
yield cloned;
}
}
}
}
function* removeArrayPatternElements(ast: t.File): Generator<t.File> {
const patternSites: Array<{patternIndex: number; elementCount: number}> = [];
let patternIndex = 0;
t.traverseFast(ast, node => {
if (t.isArrayPattern(node) && node.elements.length > 0) {
patternSites.push({patternIndex, elementCount: node.elements.length});
patternIndex++;
}
});
for (const {patternIndex: targetPatternIdx, elementCount} of patternSites) {
for (let elemIdx = elementCount - 1; elemIdx >= 0; elemIdx--) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
t.traverseFast(cloned, node => {
if (modified) return;
if (t.isArrayPattern(node) && node.elements.length > 0) {
if (idx === targetPatternIdx && elemIdx < node.elements.length) {
node.elements.splice(elemIdx, 1);
modified = true;
}
idx++;
}
});
if (modified) {
yield cloned;
}
}
}
}
function* removeObjectPatternProperties(ast: t.File): Generator<t.File> {
const patternSites: Array<{patternIndex: number; propCount: number}> = [];
let patternIndex = 0;
t.traverseFast(ast, node => {
if (t.isObjectPattern(node) && node.properties.length > 0) {
patternSites.push({patternIndex, propCount: node.properties.length});
patternIndex++;
}
});
for (const {patternIndex: targetPatternIdx, propCount} of patternSites) {
for (let propIdx = propCount - 1; propIdx >= 0; propIdx--) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
t.traverseFast(cloned, node => {
if (modified) return;
if (t.isObjectPattern(node) && node.properties.length > 0) {
if (idx === targetPatternIdx && propIdx < node.properties.length) {
node.properties.splice(propIdx, 1);
modified = true;
}
idx++;
}
});
if (modified) {
yield cloned;
}
}
}
}
function* simplifyAssignmentExpressions(ast: t.File): Generator<t.File> {
let assignmentCount = 0;
t.traverseFast(ast, node => {
if (t.isAssignmentExpression(node)) {
assignmentCount++;
}
});
for (let targetIdx = 0; targetIdx < assignmentCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
AssignmentExpression(path) {
if (modified) return;
if (idx === targetIdx) {
const left = path.node.left;
if (t.isExpression(left)) {
path.replaceWith(left);
modified = true;
}
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < assignmentCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
AssignmentExpression(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.right);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* simplifyBinaryExpressions(ast: t.File): Generator<t.File> {
let binaryCount = 0;
t.traverseFast(ast, node => {
if (t.isBinaryExpression(node)) {
binaryCount++;
}
});
for (let targetIdx = 0; targetIdx < binaryCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
BinaryExpression(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.left);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < binaryCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
BinaryExpression(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.right);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function* simplifyMemberExpressions(ast: t.File): Generator<t.File> {
let memberCount = 0;
t.traverseFast(ast, node => {
if (t.isMemberExpression(node)) {
memberCount++;
}
});
for (let targetIdx = 0; targetIdx < memberCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
MemberExpression(path) {
if (modified) return;
if (idx === targetIdx) {
path.replaceWith(path.node.object);
modified = true;
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
for (let targetIdx = 0; targetIdx < memberCount; targetIdx++) {
const cloned = cloneAst(ast);
let idx = 0;
let modified = false;
traverse(cloned, {
MemberExpression(path) {
if (modified) return;
if (idx === targetIdx && path.node.computed) {
const property = path.node.property;
if (t.isExpression(property)) {
path.replaceWith(property);
modified = true;
}
}
idx++;
},
});
if (modified) {
yield cloned;
}
}
}
function collectUniqueIdentifierNames(ast: t.File): Set<string> {
const names = new Set<string>();
t.traverseFast(ast, node => {
if (t.isIdentifier(node)) {
names.add(node.name);
}
});
return names;
}
function renameAllIdentifiers(
ast: t.File,
oldName: string,
newName: string,
): boolean {
let modified = false;
t.traverseFast(ast, node => {
if (t.isIdentifier(node) && node.name === oldName) {
node.name = newName;
modified = true;
}
});
return modified;
}
function* simplifyIdentifiersRemoveOnPrefix(ast: t.File): Generator<t.File> {
const names = collectUniqueIdentifierNames(ast);
for (const name of names) {
if (
name.length > 2 &&
name.startsWith('on') &&
name[2] === name[2].toUpperCase()
) {
const newName = name.slice(2);
if (names.has(newName)) {
continue;
}
const cloned = cloneAst(ast);
if (renameAllIdentifiers(cloned, name, newName)) {
yield cloned;
}
}
}
}
function* simplifyIdentifiersRemoveRefSuffix(ast: t.File): Generator<t.File> {
const names = collectUniqueIdentifierNames(ast);
for (const name of names) {
if (name.length > 3 && name.endsWith('Ref')) {
const newName = name.slice(0, -3);
if (names.has(newName)) {
continue;
}
if (newName.length === 0) {
continue;
}
const cloned = cloneAst(ast);
if (renameAllIdentifiers(cloned, name, newName)) {
yield cloned;
}
}
}
}
function* simplifyIdentifiersRenameRef(ast: t.File): Generator<t.File> {
const names = collectUniqueIdentifierNames(ast);
if (names.has('ref')) {
if (!names.has('ref_')) {
const cloned = cloneAst(ast);
if (renameAllIdentifiers(cloned, 'ref', 'ref_')) {
yield cloned;
}
}
}
}
const simplificationStrategies = [
{name: 'removeStatements', generator: removeStatements},
{name: 'removeCallArguments', generator: removeCallArguments},
{name: 'removeFunctionParameters', generator: removeFunctionParameters},
{name: 'removeArrayElements', generator: removeArrayElements},
{name: 'removeObjectProperties', generator: removeObjectProperties},
{name: 'removeArrayPatternElements', generator: removeArrayPatternElements},
{
name: 'removeObjectPatternProperties',
generator: removeObjectPatternProperties,
},
{name: 'removeJSXAttributes', generator: removeJSXAttributes},
{name: 'removeJSXChildren', generator: removeJSXChildren},
{name: 'removeJSXFragmentChildren', generator: removeJSXFragmentChildren},
{name: 'simplifyCallExpressions', generator: simplifyCallExpressions},
{name: 'simplifyConditionals', generator: simplifyConditionals},
{name: 'simplifyLogicalExpressions', generator: simplifyLogicalExpressions},
{name: 'simplifyBinaryExpressions', generator: simplifyBinaryExpressions},
{
name: 'simplifyAssignmentExpressions',
generator: simplifyAssignmentExpressions,
},
{name: 'simplifySingleElementArrays', generator: simplifySingleElementArrays},
{
name: 'simplifySinglePropertyObjects',
generator: simplifySinglePropertyObjects,
},
{name: 'simplifyMemberExpressions', generator: simplifyMemberExpressions},
{name: 'simplifyOptionalChains', generator: simplifyOptionalChains},
{name: 'simplifyAwaitExpressions', generator: simplifyAwaitExpressions},
{name: 'simplifyIfStatements', generator: simplifyIfStatements},
{name: 'simplifySwitchStatements', generator: simplifySwitchStatements},
{name: 'simplifyWhileStatements', generator: simplifyWhileStatements},
{name: 'simplifyDoWhileStatements', generator: simplifyDoWhileStatements},
{name: 'simplifyForStatements', generator: simplifyForStatements},
{name: 'simplifyForInStatements', generator: simplifyForInStatements},
{name: 'simplifyForOfStatements', generator: simplifyForOfStatements},
{
name: 'simplifyVariableDeclarations',
generator: simplifyVariableDeclarations,
},
{name: 'simplifyTryStatements', generator: simplifyTryStatements},
{
name: 'simplifySingleStatementBlocks',
generator: simplifySingleStatementBlocks,
},
{
name: 'simplifyIdentifiersRemoveOnPrefix',
generator: simplifyIdentifiersRemoveOnPrefix,
},
{
name: 'simplifyIdentifiersRemoveRefSuffix',
generator: simplifyIdentifiersRemoveRefSuffix,
},
{
name: 'simplifyIdentifiersRenameRef',
generator: simplifyIdentifiersRenameRef,
},
];
type MinimizeResult =
| {kind: 'success'}
| {kind: 'minimal'}
| {kind: 'minimized'; source: string};
export function minimize(
input: string,
filename: string,
language: 'flow' | 'typescript',
sourceType: 'module' | 'script',
): MinimizeResult {
const importedCompilerPlugin = require(BABEL_PLUGIN_SRC) as Record<
string,
unknown
>;
const BabelPluginReactCompiler = importedCompilerPlugin[
'default'
] as PluginObj;
const parseConfigPragmaForTests = importedCompilerPlugin[
PARSE_CONFIG_PRAGMA_IMPORT
] as typeof ParseConfigPragma;
const initialResult = compileAndGetError(
input,
filename,
language,
sourceType,
BabelPluginReactCompiler,
parseConfigPragmaForTests,
);
if (initialResult.kind === 'success') {
return {kind: 'success'};
}
if (initialResult.kind === 'parse_error') {
return {kind: 'success'};
}
const targetError = initialResult;
let currentAst = parseInput(input, filename, language, sourceType);
let currentCode = input;
let changed = true;
let iterations = 0;
const maxIterations = 1000;
process.stdout.write('\nMinimizing');
while (changed && iterations < maxIterations) {
changed = false;
iterations++;
for (const strategy of simplificationStrategies) {
const generator = strategy.generator(currentAst);
for (const candidateAst of generator) {
let candidateCode: string;
try {
candidateCode = astToCode(candidateAst);
} catch {
continue;
}
const result = compileAndGetError(
candidateCode,
filename,
language,
sourceType,
BabelPluginReactCompiler,
parseConfigPragmaForTests,
);
if (errorsMatch(targetError, result)) {
currentAst = candidateAst;
currentCode = candidateCode;
changed = true;
process.stdout.write('.');
break;
}
}
if (changed) {
break;
}
}
}
console.log('\n');
if (currentCode === input) {
return {kind: 'minimal'};
}
return {kind: 'minimized', source: currentCode};
}