mirror of
https://github.com/hanzoai/babel.git
synced 2026-09-24 16:56:45 +00:00
225 lines
6.7 KiB
TypeScript
225 lines
6.7 KiB
TypeScript
import type {
|
|
NodePath,
|
|
PluginAPI,
|
|
PluginObject,
|
|
types as t,
|
|
} from "@babel/core";
|
|
export default pluginToggleBooleanFlag;
|
|
|
|
/**
|
|
* If `value` is null, it will leave the conditions in code. It will remove
|
|
* them in attributes by replacing them with <if true> || <if false>
|
|
*/
|
|
function pluginToggleBooleanFlag(
|
|
{ types: t, template, traverse }: PluginAPI,
|
|
{ name, value }: { name: string; value: boolean | null }
|
|
): PluginObject {
|
|
if (typeof value !== "boolean" && value !== null) {
|
|
throw new Error(`.value must be a boolean, or null`);
|
|
}
|
|
|
|
function parseExpression(source: string) {
|
|
// eslint-disable-next-line regexp/no-useless-assertions
|
|
return template.expression(source, { placeholderPattern: /(?!.)./ })({});
|
|
}
|
|
|
|
function evaluate(test: t.Expression, value: boolean): any {
|
|
const res = {
|
|
replace: (replacement: t.Expression) => ({
|
|
replacement,
|
|
value: null,
|
|
unrelated: false,
|
|
}),
|
|
value: (value: boolean) => ({
|
|
replacement: null,
|
|
value,
|
|
unrelated: false,
|
|
}),
|
|
unrelated: () => ({ replacement: test, value: null, unrelated: true }),
|
|
};
|
|
|
|
if (t.isIdentifier(test, { name }) || t.matchesPattern(test, name)) {
|
|
return res.value(value);
|
|
}
|
|
|
|
if (t.isUnaryExpression(test, { operator: "!" })) {
|
|
const arg = evaluate(test.argument, value);
|
|
return arg.unrelated
|
|
? res.unrelated()
|
|
: arg.replacement
|
|
? res.replace(t.unaryExpression("!", arg.replacement))
|
|
: res.value(!arg.value);
|
|
}
|
|
|
|
if (t.isLogicalExpression(test, { operator: "||" })) {
|
|
const left = evaluate(test.left, value);
|
|
const right = evaluate(test.right, value);
|
|
|
|
if (left.value === true || right.value === true) return res.value(true);
|
|
if (left.value === false && right.value === false) {
|
|
return res.value(false);
|
|
}
|
|
if (left.value === false) return res.replace(right.replacement);
|
|
if (right.value === false) return res.replace(left.replacement);
|
|
if (left.unrelated && right.unrelated) return res.unrelated();
|
|
return res.replace(
|
|
t.logicalExpression("||", left.replacement, right.replacement)
|
|
);
|
|
}
|
|
|
|
if (t.isLogicalExpression(test, { operator: "&&" })) {
|
|
const left = evaluate(test.left, value);
|
|
const right = evaluate(test.right, value);
|
|
|
|
if (left.value === true && right.value === true) return res.value(true);
|
|
if (left.value === false || right.value === false) {
|
|
return res.value(false);
|
|
}
|
|
if (left.value === true) return res.replace(right.replacement);
|
|
if (right.value === true) return res.replace(left.replacement);
|
|
if (left.unrelated && right.unrelated) return res.unrelated();
|
|
return res.replace(
|
|
t.logicalExpression("&&", left.replacement, right.replacement)
|
|
);
|
|
}
|
|
|
|
return res.unrelated();
|
|
}
|
|
|
|
return {
|
|
visitor: traverse.explode({
|
|
"IfStatement|ConditionalExpression"(
|
|
path: NodePath<t.IfStatement | t.ConditionalExpression>
|
|
) {
|
|
if (value === null) return;
|
|
|
|
let test = path.get("test") as NodePath<t.Expression>;
|
|
|
|
// yarn-plugin-conditions injects bool(process.env.BABEL_8_BREAKING)
|
|
// tests, to properly cast the env variable to a boolean.
|
|
if (
|
|
test.isCallExpression() &&
|
|
test.get("callee").isIdentifier({ name: "bool" }) &&
|
|
test.get("arguments").length === 1
|
|
) {
|
|
test = test.get("arguments")[0] as NodePath<t.Expression>;
|
|
}
|
|
|
|
const res = evaluate(test.node, value);
|
|
|
|
if (res.unrelated) return;
|
|
if (res.replacement) {
|
|
path.get("test").replaceWith(res.replacement);
|
|
} else {
|
|
const replacementNode = res.value
|
|
? path.node.consequent
|
|
: path.node.alternate;
|
|
|
|
if (t.isBlockStatement(replacementNode)) {
|
|
path.replaceWithMultiple(replacementNode.body);
|
|
} else if (replacementNode) {
|
|
path.replaceWith(replacementNode);
|
|
} else {
|
|
path.remove();
|
|
}
|
|
}
|
|
},
|
|
LogicalExpression(path) {
|
|
if (value === null) return;
|
|
|
|
const res = evaluate(path.node, value);
|
|
if (res.unrelated) return;
|
|
if (res.replacement) {
|
|
path.replaceWith(res.replacement);
|
|
} else {
|
|
path.replaceWith(t.booleanLiteral(res.value));
|
|
}
|
|
},
|
|
MemberExpression(path) {
|
|
if (value === null) return;
|
|
|
|
if (path.matchesPattern(name)) {
|
|
throw path.buildCodeFrameError("This check could not be stripped.");
|
|
}
|
|
},
|
|
ReferencedIdentifier(path) {
|
|
if (value === null) return;
|
|
|
|
if (path.node.name === name) {
|
|
throw path.buildCodeFrameError("This check could not be stripped.");
|
|
}
|
|
},
|
|
ImportDeclaration(path) {
|
|
if (!path.node.attributes) return;
|
|
|
|
const ifAttribute = path
|
|
.get("attributes")
|
|
.find(attr => (attr.node.key as t.Identifier).name === "if");
|
|
if (ifAttribute == null) {
|
|
return;
|
|
}
|
|
|
|
const condition = parseExpression(ifAttribute.node.value.value);
|
|
|
|
let res;
|
|
res_block: if (value !== null) {
|
|
res = evaluate(condition, value);
|
|
} else {
|
|
const ifTrue = evaluate(condition, true);
|
|
if (ifTrue.unrelated || ifTrue.value === true) {
|
|
res = ifTrue;
|
|
break res_block;
|
|
}
|
|
const ifFalse = evaluate(condition, false);
|
|
if (ifFalse.unrelated) throw new Error("Cannot be unrelated");
|
|
if (ifFalse.value === true) {
|
|
res = ifFalse;
|
|
break res_block;
|
|
}
|
|
|
|
if (ifTrue.value === false) {
|
|
res = ifFalse;
|
|
break res_block;
|
|
}
|
|
if (ifTrue.value === false) {
|
|
res = ifTrue;
|
|
break res_block;
|
|
}
|
|
|
|
if (!ifTrue.replacement && !ifFalse.replacement) {
|
|
throw new Error("Expected two replacements");
|
|
}
|
|
|
|
res = {
|
|
replacement: t.logicalExpression(
|
|
"||",
|
|
ifTrue.replacement,
|
|
ifFalse.replacement
|
|
),
|
|
value: null,
|
|
unrelated: false,
|
|
};
|
|
}
|
|
|
|
if (res.unrelated) return;
|
|
if (res.replacement) {
|
|
ifAttribute
|
|
.get("value")
|
|
.replaceWith(
|
|
t.stringLiteral(
|
|
ifAttribute.toString.call({ node: res.replacement })
|
|
)
|
|
);
|
|
} else if (res.value === true) {
|
|
ifAttribute.remove();
|
|
if (path.node.attributes.length === 0) {
|
|
path.node.attributes = null;
|
|
}
|
|
} else if (res.value === false) {
|
|
path.remove();
|
|
}
|
|
},
|
|
}),
|
|
};
|
|
}
|