191 lines
6.2 KiB
JavaScript
191 lines
6.2 KiB
JavaScript
const { clone, isPrimitiveNode, parseArgs, parseFile, reparse, saveAst, t, traverse } = require("./shared");
|
|
|
|
function getReplacementTarget(path) {
|
|
let current = path;
|
|
while (current.parentPath.isMemberExpression() && current.key === "object") {
|
|
current = current.parentPath;
|
|
}
|
|
return current;
|
|
}
|
|
|
|
function isUnsafeReplacementTarget(path) {
|
|
const target = getReplacementTarget(path);
|
|
if (target.parentPath.isUpdateExpression() && target.key === "argument") {
|
|
return true;
|
|
}
|
|
if ((target.parentPath.isAssignmentExpression() || target.parentPath.isAssignmentPattern()) && target.key === "left") {
|
|
return true;
|
|
}
|
|
if ((target.parentPath.isForInStatement() || target.parentPath.isForOfStatement()) && target.key === "left") {
|
|
return true;
|
|
}
|
|
if (target.parentPath.isUnaryExpression({ operator: "delete" }) && target.key === "argument") {
|
|
return true;
|
|
}
|
|
if (target.parentPath.isObjectProperty() && target.key === "key" && !target.parent.computed) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function replaceSafely(path, node) {
|
|
if (isUnsafeReplacementTarget(path)) {
|
|
return false;
|
|
}
|
|
path.replaceWith(node);
|
|
return true;
|
|
}
|
|
|
|
function allRefsAfter(binding, position) {
|
|
return binding.referencePaths.every(refPath => typeof refPath.node.start !== "number" || refPath.node.start > position);
|
|
}
|
|
|
|
function inlineLiterals(ast) {
|
|
let changed = false;
|
|
|
|
traverse(ast, {
|
|
MemberExpression(path) {
|
|
if (t.isArrayExpression(path.node.object) && path.node.object.elements.length === 0 && t.isArrayExpression(path.node.property) && path.node.property.elements.length === 0) {
|
|
if (replaceSafely(path, t.identifier("undefined"))) {
|
|
changed = true;
|
|
}
|
|
}
|
|
},
|
|
VariableDeclarator(path) {
|
|
if (!path.get("id").isIdentifier()) {
|
|
return;
|
|
}
|
|
const name = path.node.id.name;
|
|
const init = path.node.init;
|
|
const binding = path.scope.getBinding(name);
|
|
if (!binding) {
|
|
return;
|
|
}
|
|
if (t.isStringLiteral(init) || t.isNumericLiteral(init) || t.isBooleanLiteral(init)) {
|
|
if (!binding.constant) {
|
|
return;
|
|
}
|
|
let replacedAll = binding.referencePaths.length > 0;
|
|
let replacedAny = false;
|
|
binding.referencePaths.forEach(refPath => {
|
|
if (!replaceSafely(refPath, clone(init))) {
|
|
replacedAll = false;
|
|
return;
|
|
}
|
|
replacedAny = true;
|
|
});
|
|
if (replacedAny) {
|
|
changed = true;
|
|
}
|
|
if (replacedAll) {
|
|
path.remove();
|
|
}
|
|
return;
|
|
}
|
|
if (t.isSequenceExpression(init) && init.expressions.length > 0 && init.expressions.every(expr => t.isStringLiteral(expr))) {
|
|
path.get("init").replaceWith(clone(init.expressions[init.expressions.length - 1]));
|
|
changed = true;
|
|
return;
|
|
}
|
|
if (t.isArrayExpression(init) && init.elements.length > 0 && init.elements.every(el => el && isPrimitiveNode(el))) {
|
|
let replacedAll = binding.referencePaths.length > 0;
|
|
let replacedCount = 0;
|
|
binding.referencePaths.forEach(refPath => {
|
|
const parent = refPath.parentPath;
|
|
if (!parent.isMemberExpression({ object: refPath.node }) || !parent.get("property").isNumericLiteral()) {
|
|
replacedAll = false;
|
|
return;
|
|
}
|
|
const index = parent.node.property.value;
|
|
if (!init.elements[index]) {
|
|
replacedAll = false;
|
|
return;
|
|
}
|
|
if (!replaceSafely(parent, clone(init.elements[index]))) {
|
|
replacedAll = false;
|
|
return;
|
|
}
|
|
replacedCount += 1;
|
|
changed = true;
|
|
});
|
|
if (replacedAll && replacedCount > 0) {
|
|
path.remove();
|
|
}
|
|
}
|
|
},
|
|
AssignmentExpression(path) {
|
|
if (!path.get("left").isIdentifier() || path.node.operator !== "=") {
|
|
return;
|
|
}
|
|
if (!path.parentPath.isExpressionStatement()) {
|
|
return;
|
|
}
|
|
const binding = path.scope.getBinding(path.node.left.name);
|
|
if (!binding) {
|
|
return;
|
|
}
|
|
if (typeof path.node.start === "number" && !allRefsAfter(binding, path.node.start)) {
|
|
return;
|
|
}
|
|
if (path.get("right").isStringLiteral() || path.get("right").isNumericLiteral() || path.get("right").isBooleanLiteral()) {
|
|
const right = clone(path.node.right);
|
|
let replacedAll = binding.referencePaths.length > 0;
|
|
let replacedAny = false;
|
|
binding.referencePaths.forEach(refPath => {
|
|
if (!replaceSafely(refPath, clone(right))) {
|
|
replacedAll = false;
|
|
return;
|
|
}
|
|
replacedAny = true;
|
|
});
|
|
if (replacedAny) {
|
|
changed = true;
|
|
}
|
|
if (replacedAll) {
|
|
path.remove();
|
|
}
|
|
return;
|
|
}
|
|
if (path.get("right").isArrayExpression() && path.node.right.elements.every(el => el && isPrimitiveNode(el))) {
|
|
const elements = path.node.right.elements;
|
|
let replacedAll = binding.referencePaths.length > 0;
|
|
let replacedCount = 0;
|
|
binding.referencePaths.forEach(refPath => {
|
|
const parent = refPath.parentPath;
|
|
if (!parent.isMemberExpression({ object: refPath.node }) || !parent.get("property").isNumericLiteral()) {
|
|
replacedAll = false;
|
|
return;
|
|
}
|
|
const index = parent.node.property.value;
|
|
if (!elements[index]) {
|
|
replacedAll = false;
|
|
return;
|
|
}
|
|
if (!replaceSafely(parent, clone(elements[index]))) {
|
|
replacedAll = false;
|
|
return;
|
|
}
|
|
replacedCount += 1;
|
|
changed = true;
|
|
});
|
|
if (replacedAll && replacedCount > 0) {
|
|
path.remove();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return { ast, changed };
|
|
}
|
|
|
|
const { inputPath, outputPath } = parseArgs();
|
|
let ast = parseFile(inputPath);
|
|
let changed = false;
|
|
do {
|
|
({ ast, changed } = inlineLiterals(ast));
|
|
if (changed) {
|
|
ast = reparse(ast);
|
|
}
|
|
} while (changed);
|
|
saveAst(ast, outputPath);
|