UNPKG

@babel/plugin-proposal-do-expressions

Version:
392 lines (389 loc) 17.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _helperPluginUtils = require("@babel/helper-plugin-utils"); var _utils = require("./utils.js"); var _default = exports.default = (0, _helperPluginUtils.declare)(api => { var _api$assumption; api.assertVersion(7); const { types: t } = api; const noDocumentAll = (_api$assumption = api.assumption("noDocumentAll")) != null ? _api$assumption : false; return { name: "proposal-do-expressions", manipulateOptions: (_, parser) => parser.plugins.push("doExpressions"), visitor: { DoExpression: { exit(path) { if (path.node.async) { return; } const controlFlowState = (0, _utils.collectControlFlowStatements)(path); if (controlFlowState.returnPath || controlFlowState.break.size || controlFlowState.continue.size) { transformDoExpressionWithControlFlowStatements(path); } else { const body = path.node.body.body; if (body.length) { path.replaceExpressionWithStatements(body); } else { path.replaceWith(path.scope.buildUndefinedNode()); } } } } } }; function transformDoExpressionWithControlFlowStatements(doExprPath) { const doAncestors = new WeakSet(); let path = doExprPath; while (path) { if (!path.node) return; doAncestors.add(path.node); if (path.isFunction()) { let body = path.get("body"); if (path.isArrowFunctionExpression()) { if (body.isExpression() && doAncestors.has(body.node)) { [body] = body.replaceWith(t.blockStatement([t.returnStatement(body.node)])); flattenStatement(body.get("body")[0]); } } let foundDoExpression = false; const deferredPatterns = []; const deferredTemps = []; for (const param of path.get("params")) { const actualParam = param.isRestElement() ? param.get("argument") : param.isTSParameterProperty() ? param.get("parameter") : param; foundDoExpression || (foundDoExpression = doAncestors.has(actualParam.node)); if (foundDoExpression && !isLValSideEffectFree(actualParam)) { const pattern = actualParam.node; const uid = body.scope.generateUidIdentifier("do"); actualParam.replaceWith(t.cloneNode(uid)); deferredPatterns.push(pattern); deferredTemps.push(uid); } } if (deferredPatterns.length) { let blockBody; if (body.isBlockStatement()) { blockBody = body; } else { [blockBody] = body.replaceWith(t.blockStatement([t.returnStatement(body.node)])); } blockBody.unshiftContainer("body", t.variableDeclaration("var", [t.variableDeclarator(t.arrayPattern(deferredPatterns), t.arrayExpression(deferredTemps))])); flattenStatement(blockBody.get("body")[0]); } return; } if (path.isStatement()) { flattenStatement(path); return; } path = path.parentPath; } throw new Error("Internal error: DoExpression must be in a statement. This is a Babel bug, please report it."); function flattenStatement(path) { switch (path.type) { case "VariableDeclaration": { const statements = []; for (const decl of path.get("declarations")) { const init = decl.get("init"); const id = decl.get("id"); if (doAncestors.has(init.node)) { statements.push(...flattenExpression(init)); } if (doAncestors.has(id.node)) { statements.push(...flattenLVal(id, init.node, path.node.kind)); } else { statements.push(t.variableDeclaration(path.node.kind, [decl.node])); } } path.replaceWithMultiple(statements); break; } case "ForStatement": { const body = []; const test = path.get("test"); if (doAncestors.has(test.node)) { body.push(...flattenExpression(test), t.ifStatement(t.unaryExpression("!", test.node), t.breakStatement())); test.remove(); } body.push(path.node.body); const update = path.get("update"); if (doAncestors.has(update.node)) { body.push(...flattenExpression(update, { discardResult: true })); update.remove(); } path.set("body", t.blockStatement(body)); const init = path.get("init"); if (doAncestors.has(init.node)) { const initNode = init.isExpression() ? t.expressionStatement(init.node) : init.node; init.remove(); const [newPath] = path.replaceWith(t.blockStatement([initNode, path.node])); flattenStatement(newPath.get("body")[0]); } break; } case "ForInStatement": case "ForOfStatement": { const left = path.get("left"); if (doAncestors.has(left.node)) { const body = path.get("body"); const uid = body.scope.generateDeclaredUidIdentifier("do"); if (left.isVariableDeclaration()) { const init = left.get("declarations")[0].get("init"); if (init.node) { throw init.buildCodeFrameError("Complex variable declaration in for-in with do expression is not currently supported"); } init.replaceWith(t.cloneNode(uid)); const [newBody] = body.replaceWith(t.blockStatement([left.node, body.node])); flattenStatement(newBody.get("body")[0]); } else { body.replaceWith(t.blockStatement([...flattenLVal(left, t.cloneNode(uid), null), body.node])); } left.replaceWith(uid); } const right = path.get("right"); if (doAncestors.has(right.node)) { path.replaceWithMultiple([...flattenExpression(right), path.node]); } break; } case "WhileStatement": { const test = path.get("test"); path.set("body", t.blockStatement([...flattenExpression(test), t.ifStatement(t.unaryExpression("!", test.node), t.breakStatement()), path.node.body])); test.replaceWith(t.booleanLiteral(true)); break; } case "DoWhileStatement": { const test = path.get("test"); path.set("body", t.blockStatement([path.node.body, ...flattenExpression(test), t.ifStatement(t.unaryExpression("!", test.node), t.breakStatement())])); test.replaceWith(t.booleanLiteral(true)); break; } case "ExpressionStatement": path.replaceWithMultiple(flattenExpression(path.get("expression"), { discardResult: true })); break; default: path.insertBefore(flattenByTraverse(path)); } } function flattenExpression(path, opts = {}) { if (isTopLevelCopyable(path)) { return flattenByTraverse(path, opts.flattenTrailing); } const hasDoExpression = doAncestors.has(path.node); if (hasDoExpression) { if (path.isDoExpression()) { const body = path.get("body"); if (!opts.discardResult) { const completions = body.getCompletionRecords(true).filter(completion => completion.isExpressionStatement()); if (completions.length) { const uid = body.scope.generateDeclaredUidIdentifier("do"); for (const completion of completions) { completion.replaceWith(t.assignmentExpression("=", t.cloneNode(uid), completion.node.expression)); } path.replaceWith(uid); } else { path.replaceWith(path.scope.buildUndefinedNode()); } } return [body.node]; } else if (path.isAssignmentExpression()) { const left = path.get("left"); const right = path.get("right"); if (doAncestors.has(left.node)) { if (path.node.operator !== "=") { throw path.buildCodeFrameError("Do expression inside complex assignment expression is not currently supported."); } const uid = path.scope.generateDeclaredUidIdentifier("do"); path.replaceWith(uid); return [...flattenExpression(right), ...flattenLVal(left, right.node, null)]; } } else if (path.isLogicalExpression()) { const operator = path.node.operator; const left = path.get("left"); const right = path.get("right"); const statements = [...flattenExpression(left), t.ifStatement(operator === "&&" ? t.cloneNode(left.node) : operator === "||" ? t.unaryExpression("!", t.cloneNode(left.node)) : t.binaryExpression("==", t.cloneNode(left.node), t.nullLiteral()), t.blockStatement(flattenExpression(right)))]; path.replaceWith(t.logicalExpression(path.node.operator, left.node, right.node)); return statements; } else if (path.isConditionalExpression()) { const test = path.get("test"); const alternate = path.get("alternate"); const consequent = path.get("consequent"); const statements = [...flattenExpression(test), t.ifStatement(t.cloneNode(test.node), t.blockStatement(flattenExpression(consequent)), t.blockStatement(flattenExpression(alternate)))]; path.replaceWith(t.conditionalExpression(test.node, consequent.node, alternate.node)); return statements; } else if (path.isOptionalMemberExpression() && path.node.computed) { const object = path.get("object"); const property = path.get("property"); const uid = path.scope.generateDeclaredUidIdentifier("do"); path.replaceWith(uid); return [...flattenExpression(object), t.ifStatement(buildOptionalChainChecker(object.node), t.blockStatement([...flattenExpression(property), t.expressionStatement(t.assignmentExpression("=", t.cloneNode(uid), t.memberExpression(object.node, property.node, true)))]))]; } else if (path.isOptionalCallExpression()) { const callee = path.get("callee"); const calleeStatements = flattenExpression(callee); const [newPath] = path.replaceWith(t.callExpression(t.cloneNode(callee.node), path.node.arguments)); const callStatements = flattenCallExpression(newPath, opts.discardResult); return [...calleeStatements, t.ifStatement(buildOptionalChainChecker(callee.node), t.blockStatement(callStatements))]; } else if (path.isCallExpression()) { return flattenCallExpression(path, opts.discardResult); } } if (hasDoExpression) { return [...flattenByTraverse(path, opts.flattenTrailing), intoTempVariable(path, opts.discardResult)]; } else { return [intoTempVariable(path, opts.discardResult)]; } } function flattenCallExpression(path, discardResult) { const callee = path.get("callee"); let thisArgument; const statements = []; if (callee.isMemberExpression()) { thisArgument = callee.get("object"); if (!thisArgument.isSuper()) { statements.push(...flattenExpression(thisArgument)); } } statements.push(...flattenExpression(callee)); for (const arg of path.get("arguments")) { if (arg.isSpreadElement()) { const uid = path.scope.generateDeclaredUidIdentifier("do"); statements.push(...flattenExpression(arg.get("argument")), t.expressionStatement(t.assignmentExpression("=", t.cloneNode(uid), t.arrayExpression([arg.node])))); arg.replaceWith(t.spreadElement(uid)); } else { statements.push(...flattenExpression(arg)); } } if (thisArgument) { path.replaceWith(t.callExpression(t.memberExpression(path.node.callee, t.identifier("call")), [thisArgument.isSuper() ? t.thisExpression() : t.cloneNode(thisArgument.node), ...path.node.arguments])); return [...statements, intoTempVariable(path, discardResult)]; } else { path.replaceWith(t.callExpression(callee.node, path.node.arguments)); } return [...statements, intoTempVariable(path, discardResult)]; } function flattenLVal(path, init, declare) { switch (path.type) { case "ObjectPattern": { (0, _utils.wrapDoExpressionInIIFE)(path); } case "Identifier": { if (declare) { return [t.variableDeclaration(declare, [t.variableDeclarator(path.node, init)])]; } else { return [t.expressionStatement(t.assignmentExpression("=", path.node, init))]; } } case "MemberExpression": { return [...flattenByTraverse(path), t.expressionStatement(t.assignmentExpression("=", path.node, init))]; } case "AssignmentPattern": { const left = path.get("left"); const right = path.get("right"); if (init) { const uid = path.scope.generateDeclaredUidIdentifier("do"); return [t.expressionStatement(t.assignmentExpression("=", t.cloneNode(uid), init)), t.ifStatement(t.binaryExpression("===", t.cloneNode(uid), t.buildUndefinedNode()), t.blockStatement([...flattenExpression(right), t.expressionStatement(t.assignmentExpression("=", t.cloneNode(uid), right.node))])), ...flattenLVal(left, uid, declare)]; } else { return flattenLVal(path.get("left"), right.node, declare); } } case "ArrayPattern": { const elements = []; const statements = []; for (const element of path.get("elements")) { if (!element.type || isLValSideEffectFree(element)) { elements.push(element.node); continue; } const uid = path.scope.generateDeclaredUidIdentifier("do"); if (element.isRestElement()) { elements.push(t.restElement(t.cloneNode(uid))); statements.push(...flattenLVal(element.get("argument"), uid, declare)); } else { elements.push(t.cloneNode(uid)); statements.push(...flattenLVal(element, uid, declare)); } } return [t.expressionStatement(t.assignmentExpression("=", t.arrayPattern(elements), init)), ...statements]; } default: { throw path.buildCodeFrameError(`Do expression inside ${path.type} is not currently supported`); } } } function flattenByTraverse(path, flattenTrailing) { const expressions = []; path.traverse({ Statement(path) { path.skip(); }, Expression(path) { expressions.push(path); path.skip(); } }); let lastDoExpression; if (!flattenTrailing) { while (expressions.length) { const path = expressions.pop(); if (doAncestors.has(path.node)) { lastDoExpression = path; break; } } } const statements = []; for (const path of expressions) { statements.push(...flattenExpression(path, { flattenTrailing: true })); } if (lastDoExpression) { statements.push(...flattenExpression(lastDoExpression)); } return statements; } function intoTempVariable(path, discardResult) { if (discardResult) { return t.expressionStatement(path.node); } else { const uid = path.scope.generateDeclaredUidIdentifier("do"); const statement = t.expressionStatement(t.assignmentExpression("=", t.cloneNode(uid), path.node)); path.replaceWith(uid); return statement; } } } function buildOptionalChainChecker(node) { if (noDocumentAll) { return t.binaryExpression("!=", t.cloneNode(node), t.nullLiteral()); } else { return t.logicalExpression("&&", t.binaryExpression("!==", t.cloneNode(node), t.nullLiteral()), t.binaryExpression("!==", t.cloneNode(node), t.buildUndefinedNode())); } } }); function isTopLevelCopyable(path) { return path.isPureish() || path.isBinaryExpression() || path.isParenthesizedExpression() || path.isSequenceExpression() || path.isUnaryExpression() && path.node.operator !== "throw" && path.node.operator !== "delete"; } function isLValSideEffectFree(path) { return path.isIdentifier() || path.isAssignmentPattern() && isLValSideEffectFree(path.get("left")) && path.get("right").isPureish() || path.isRestElement() && isLValSideEffectFree(path.get("argument")); } //# sourceMappingURL=index.js.map