Files
babel/scripts/babel-plugin-toggle-boolean-flag/plugin.cjs
T
Nicolò RibaudoandGitHub 603de86d78 Add logic to materialize Babel 8 in source (#17605)
* Add logic to materialize Bbael 8 in source

* Avoid empty blocks

* Remove Babel 7 vs Babel 8 ts comments

* Flatten object spread
2025-11-25 07:13:14 +01:00

213 lines
6.6 KiB
JavaScript

module.exports = 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>
*
* @param {import("@babel/core")} pluginAPI
* @returns {import("@babel/core").PluginObject}
*/
function pluginToggleBooleanFlag({ types: t, template }, { name, value }) {
if (typeof value !== "boolean" && value !== null) {
throw new Error(`.value must be a boolean, or null`);
}
function parseExpression(source) {
// eslint-disable-next-line regexp/no-useless-assertions
return template.expression(source, { placeholderPattern: /(?!.)./ })({});
}
/**
* @param {import("@babel/core").types.Expression} test
*/
function evaluate(test, value) {
const res = {
replace: replacement => ({ replacement, value: null, unrelated: false }),
value: value => ({ 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.replacement(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: {
"IfStatement|ConditionalExpression"(path) {
if (value === null) return;
let test = path.get("test");
// 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];
}
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;
/** @type {null | import("@babel/core").NodePath<import("@babel/core").types.ImportAttribute>} */
const ifAttribute = path
.get("attributes")
.find(attr => attr.node.key.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.binaryExpression(
"||",
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();
}
},
},
};
}