@babel/plugin-proposal-explicit-resource-management
Version:
Compile `using` declarations to ES2015
242 lines (239 loc) • 10.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _helperPluginUtils = require("@babel/helper-plugin-utils");
var _core = require("@babel/core");
var _default = exports.default = (0, _helperPluginUtils.declare)(api => {
api.assertVersion("^7.22.0 || >8.0.0-alpha <8.0.0-beta");
const TOP_LEVEL_USING = new Map();
function isUsingDeclaration(node) {
if (!_core.types.isVariableDeclaration(node)) return false;
return node.kind === "using" || node.kind === "await using" || TOP_LEVEL_USING.has(node);
}
const transformUsingDeclarationsVisitor = {
ForOfStatement(path) {
const {
left
} = path.node;
if (!isUsingDeclaration(left)) return;
const {
id
} = left.declarations[0];
const tmpId = path.scope.generateUidIdentifierBasedOnNode(id);
left.declarations[0].id = tmpId;
left.kind = "const";
path.ensureBlock();
path.node.body.body.unshift(_core.types.variableDeclaration("using", [_core.types.variableDeclarator(id, _core.types.cloneNode(tmpId))]));
},
"BlockStatement|StaticBlock"(path, state) {
if (state.availableHelper("usingCtx")) {
let ctx = null;
let needsAwait = false;
for (const node of path.node.body) {
var _ctx;
if (!isUsingDeclaration(node)) continue;
(_ctx = ctx) != null ? _ctx : ctx = path.scope.generateUidIdentifier("usingCtx");
const isAwaitUsing = node.kind === "await using" || TOP_LEVEL_USING.get(node) === 1;
needsAwait || (needsAwait = isAwaitUsing);
if (!TOP_LEVEL_USING.delete(node)) {
node.kind = "const";
}
for (const decl of node.declarations) {
decl.init = _core.types.callExpression(_core.types.memberExpression(_core.types.cloneNode(ctx), isAwaitUsing ? _core.types.identifier("a") : _core.types.identifier("u")), [decl.init]);
}
}
if (!ctx) return;
const disposeCall = _core.types.callExpression(_core.types.memberExpression(_core.types.cloneNode(ctx), _core.types.identifier("d")), []);
const replacement = _core.template.statement.ast`
try {
var ${_core.types.cloneNode(ctx)} = ${state.addHelper("usingCtx")}();
${path.node.body}
} catch (_) {
${_core.types.cloneNode(ctx)}.e = _;
} finally {
${needsAwait ? _core.types.awaitExpression(disposeCall) : disposeCall}
}
`;
_core.types.inherits(replacement, path.node);
const {
parentPath
} = path;
if (parentPath.isFunction() || parentPath.isTryStatement() || parentPath.isCatchClause()) {
path.replaceWith(_core.types.blockStatement([replacement]));
} else if (path.isStaticBlock()) {
path.node.body = [replacement];
} else {
path.replaceWith(replacement);
}
} else {
let stackId = null;
let needsAwait = false;
for (const node of path.node.body) {
var _stackId;
if (!isUsingDeclaration(node)) continue;
(_stackId = stackId) != null ? _stackId : stackId = path.scope.generateUidIdentifier("stack");
const isAwaitUsing = node.kind === "await using" || TOP_LEVEL_USING.get(node) === 1;
needsAwait || (needsAwait = isAwaitUsing);
if (!TOP_LEVEL_USING.delete(node)) {
node.kind = "const";
}
node.declarations.forEach(decl => {
const args = [_core.types.cloneNode(stackId), decl.init];
if (isAwaitUsing) args.push(_core.types.booleanLiteral(true));
decl.init = _core.types.callExpression(state.addHelper("using"), args);
});
}
if (!stackId) return;
const errorId = path.scope.generateUidIdentifier("error");
const hasErrorId = path.scope.generateUidIdentifier("hasError");
let disposeCall = _core.types.callExpression(state.addHelper("dispose"), [_core.types.cloneNode(stackId), _core.types.cloneNode(errorId), _core.types.cloneNode(hasErrorId)]);
if (needsAwait) disposeCall = _core.types.awaitExpression(disposeCall);
const replacement = _core.template.statement.ast`
try {
var ${stackId} = [];
${path.node.body}
} catch (_) {
var ${errorId} = _;
var ${hasErrorId} = true;
} finally {
${disposeCall}
}
`;
_core.types.inherits(replacement.block, path.node);
const {
parentPath
} = path;
if (parentPath.isFunction() || parentPath.isTryStatement() || parentPath.isCatchClause()) {
path.replaceWith(_core.types.blockStatement([replacement]));
} else if (path.isStaticBlock()) {
path.node.body = [replacement];
} else {
path.replaceWith(replacement);
}
}
},
SwitchStatement(path, state) {
let ctx = null;
let needsAwait = false;
const {
cases
} = path.node;
for (const c of cases) {
for (const stmt of c.consequent) {
if (isUsingDeclaration(stmt)) {
var _ctx2;
if (!state.availableHelper("usingCtx")) {
path.traverse({
VariableDeclaration(path) {
const {
node
} = path;
if (!isUsingDeclaration(node)) return;
throw path.buildCodeFrameError("`using` declarations inside `switch` statements are not supported by your current `@babel/core` version, please update to a more recent one");
}
});
}
(_ctx2 = ctx) != null ? _ctx2 : ctx = path.scope.generateUidIdentifier("usingCtx");
const isAwaitUsing = stmt.kind === "await using";
needsAwait || (needsAwait = isAwaitUsing);
stmt.kind = "const";
for (const decl of stmt.declarations) {
decl.init = _core.types.callExpression(_core.types.memberExpression(_core.types.cloneNode(ctx), isAwaitUsing ? _core.types.identifier("a") : _core.types.identifier("u")), [decl.init]);
}
}
}
}
if (!ctx) return;
const disposeCall = _core.types.callExpression(_core.types.memberExpression(_core.types.cloneNode(ctx), _core.types.identifier("d")), []);
const replacement = _core.template.statement.ast`
try {
var ${_core.types.cloneNode(ctx)} = ${state.addHelper("usingCtx")}();
${path.node}
} catch (_) {
${_core.types.cloneNode(ctx)}.e = _;
} finally {
${needsAwait ? _core.types.awaitExpression(disposeCall) : disposeCall}
}
`;
_core.types.inherits(replacement, path.node);
path.replaceWith(replacement);
}
};
const transformUsingDeclarationsVisitorSkipFn = _core.traverse.visitors.merge([transformUsingDeclarationsVisitor, {
Function(path) {
path.skip();
}
}]);
return {
name: "proposal-explicit-resource-management",
manipulateOptions: (_, p) => p.plugins.push("explicitResourceManagement"),
visitor: _core.traverse.visitors.merge([transformUsingDeclarationsVisitor, {
Program(path) {
TOP_LEVEL_USING.clear();
if (path.node.sourceType !== "module") return;
if (!path.node.body.some(isUsingDeclaration)) return;
const innerBlockBody = [];
for (const stmt of path.get("body")) {
if (stmt.isFunctionDeclaration() || stmt.isImportDeclaration()) {
continue;
}
let node = stmt.node;
let shouldRemove = true;
if (stmt.isExportDefaultDeclaration()) {
var _varId;
let {
declaration
} = stmt.node;
let varId;
if (_core.types.isClassDeclaration(declaration)) {
varId = declaration.id;
declaration.id = null;
declaration = _core.types.toExpression(declaration);
} else if (!_core.types.isExpression(declaration)) {
continue;
}
(_varId = varId) != null ? _varId : varId = path.scope.generateUidIdentifier("_default");
innerBlockBody.push(_core.types.variableDeclaration("var", [_core.types.variableDeclarator(varId, declaration)]));
stmt.replaceWith(_core.types.exportNamedDeclaration(null, [_core.types.exportSpecifier(_core.types.cloneNode(varId), _core.types.identifier("default"))]));
continue;
}
if (stmt.isExportNamedDeclaration()) {
node = stmt.node.declaration;
if (!node || _core.types.isFunction(node)) continue;
stmt.replaceWith(_core.types.exportNamedDeclaration(null, Object.keys(_core.types.getOuterBindingIdentifiers(node, false)).map(id => _core.types.exportSpecifier(_core.types.identifier(id), _core.types.identifier(id)))));
shouldRemove = false;
} else if (stmt.isExportDeclaration()) {
continue;
}
if (_core.types.isClassDeclaration(node)) {
const {
id
} = node;
node.id = null;
innerBlockBody.push(_core.types.variableDeclaration("var", [_core.types.variableDeclarator(id, _core.types.toExpression(node))]));
} else if (_core.types.isVariableDeclaration(node)) {
if (node.kind === "using") {
TOP_LEVEL_USING.set(stmt.node, 0);
} else if (node.kind === "await using") {
TOP_LEVEL_USING.set(stmt.node, 1);
}
node.kind = "var";
innerBlockBody.push(node);
} else {
innerBlockBody.push(stmt.node);
}
if (shouldRemove) stmt.remove();
}
path.pushContainer("body", _core.types.blockStatement(innerBlockBody));
},
Function(path, state) {
if (path.node.async) {
path.traverse(transformUsingDeclarationsVisitorSkipFn, state);
}
}
}])
};
});
//# sourceMappingURL=index.js.map