diff --git a/packages/babel-helpers/src/helpers-generated.ts b/packages/babel-helpers/src/helpers-generated.ts index 03d1a481a41a..f870fc5841a5 100644 --- a/packages/babel-helpers/src/helpers-generated.ts +++ b/packages/babel-helpers/src/helpers-generated.ts @@ -59,7 +59,7 @@ export default Object.freeze({ ), dispose: helper( "7.0.0-beta.0", - "function dispose_SuppressedError(suppressed,error){return dispose_SuppressedError=function(suppressed,error){this.suppressed=suppressed,this.error=error,this.stack=(new Error).stack},dispose_SuppressedError.prototype=Object.create(Error.prototype,{constructor:{value:dispose_SuppressedError,writable:!0,configurable:!0}}),new dispose_SuppressedError(suppressed,error)}export default function _dispose(stack,error,hasError,SuppressedError){for(;stack.length>0;){const r=stack.pop();try{r.d.call(r.v)}catch(e){error=hasError?new(SuppressedError||dispose_SuppressedError)(e,error):e,hasError=!0}}if(hasError)throw error}", + "function dispose_SuppressedError(suppressed,error){return dispose_SuppressedError=function(suppressed,error){this.suppressed=suppressed,this.error=error,this.stack=(new Error).stack},dispose_SuppressedError.prototype=Object.create(Error.prototype,{constructor:{value:dispose_SuppressedError,writable:!0,configurable:!0}}),new dispose_SuppressedError(suppressed,error)}export default function _dispose(stack,error,hasError,SuppressedError){function next(){if(0!==stack.length){var r=stack.pop();if(r.a)return r.d.call(r.v).then(next,err);try{r.d.call(r.v)}catch(e){return err(e)}return next()}if(hasError)throw error}function err(e){return error=hasError?new(SuppressedError||dispose_SuppressedError)(e,error):e,hasError=!0,next()}return next()}", ), iterableToArrayLimit: helper( "7.0.0-beta.0", @@ -87,7 +87,7 @@ export default Object.freeze({ ), using: helper( "7.0.0-beta.0", - 'export default function _using(stack,value){if(null!=value){var dispose=value[Symbol.dispose||Symbol.for("Symbol.dispose")];if("function"!=typeof dispose)throw new TypeError("Property [Symbol.dispose] is not a function.");stack.push({v:value,d:dispose})}return value}', + 'export default function _using(stack,value,isAwait){if(null!=value){var dispose=value[Symbol.dispose||Symbol.for("Symbol.dispose")];if("function"!=typeof dispose)throw new TypeError("Property [Symbol.dispose] is not a function.");stack.push({v:value,d:dispose,a:isAwait})}return value}', ), wrapRegExp: helper( "7.19.0", diff --git a/packages/babel-helpers/src/helpers/dispose.js b/packages/babel-helpers/src/helpers/dispose.js index bfe9485769ab..caff586257fe 100644 --- a/packages/babel-helpers/src/helpers/dispose.js +++ b/packages/babel-helpers/src/helpers/dispose.js @@ -16,16 +16,33 @@ function dispose_SuppressedError(suppressed, error) { } export default function _dispose(stack, error, hasError, SuppressedError) { - while (stack.length > 0) { - const r = stack.pop(); - try { - r.d.call(r.v); - } catch (e) { - error = hasError - ? new (SuppressedError || dispose_SuppressedError)(e, error) - : e; - hasError = true; + function next() { + if (stack.length === 0) { + if (hasError) throw error; + return; } + + var r = stack.pop(); + if (r.a) { + return r.d.call(r.v).then(next, err); + } else { + try { + r.d.call(r.v); + } catch (e) { + return err(e); + } + return next(); + } + } + + function err(e) { + error = hasError + ? new (SuppressedError || dispose_SuppressedError)(e, error) + : e; + hasError = true; + + return next(); } - if (hasError) throw error; + + return next(); } diff --git a/packages/babel-helpers/src/helpers/using.js b/packages/babel-helpers/src/helpers/using.js index 7521f9259e3c..776b7629868b 100644 --- a/packages/babel-helpers/src/helpers/using.js +++ b/packages/babel-helpers/src/helpers/using.js @@ -1,13 +1,13 @@ /* @minVersion 7.0.0-beta.0 */ -export default function _using(stack, value) { +export default function _using(stack, value, isAwait) { if (value !== null && value !== void 0) { // core-js-pure uses Symbol.for("Symbol.dispose"). var dispose = value[Symbol.dispose || Symbol.for("Symbol.dispose")]; if (typeof dispose !== "function") { throw new TypeError(`Property [Symbol.dispose] is not a function.`); } - stack.push({ v: value, d: dispose }); + stack.push({ v: value, d: dispose, a: isAwait }); } return value; } diff --git a/packages/babel-plugin-proposal-explicit-resource-management/src/index.ts b/packages/babel-plugin-proposal-explicit-resource-management/src/index.ts index 563ae7df21e3..4ec8da964284 100644 --- a/packages/babel-plugin-proposal-explicit-resource-management/src/index.ts +++ b/packages/babel-plugin-proposal-explicit-resource-management/src/index.ts @@ -3,6 +3,11 @@ import syntacExplicitResourceManagement from "@babel/plugin-syntax-explicit-reso import { types as t, template } from "@babel/core"; import type { NodePath } from "@babel/traverse"; +function isUsingDeclaration(node: t.Node): node is t.VariableDeclaration { + if (!t.isVariableDeclaration(node)) return false; + return node.kind === "using" || node.kind === "await using"; +} + export default declare(api => { // TOOD: assert version 7.22.0 api.assertVersion(7); @@ -13,7 +18,7 @@ export default declare(api => { visitor: { VariableDeclaration(path) { - if (path.node.kind === "using") { + if (path.node.kind === "using" || path.node.kind === "await using") { throw path.buildCodeFrameError( `"using" declaration at the top-level of modules is not supported yet.`, ); @@ -21,7 +26,7 @@ export default declare(api => { }, ForOfStatement(path: NodePath) { const { left } = path.node; - if (!t.isVariableDeclaration(left, { kind: "using" })) return; + if (!isUsingDeclaration(left)) return; const { id } = left.declarations[0]; const tmpId = path.scope.generateUidIdentifierBasedOnNode(id); @@ -37,16 +42,19 @@ export default declare(api => { }, BlockStatement(path, state) { let stackId: t.Identifier | null = null; + let needsAwait = false; + for (const node of path.node.body) { - if (!t.isVariableDeclaration(node, { kind: "using" })) continue; - node.kind = "const"; + if (!isUsingDeclaration(node)) continue; stackId ??= path.scope.generateUidIdentifier("stack"); + const isAwaitUsing = node.kind === "await using"; + needsAwait ||= isAwaitUsing; + node.kind = "const"; node.declarations.forEach(decl => { - decl.init = t.callExpression(state.addHelper("using"), [ - t.cloneNode(stackId), - decl.init, - ]); + const args = [t.cloneNode(stackId), decl.init]; + if (isAwaitUsing) args.push(t.booleanLiteral(true)); + decl.init = t.callExpression(state.addHelper("using"), args); }); } if (!stackId) return; @@ -54,6 +62,22 @@ export default declare(api => { const errorId = path.scope.generateUidIdentifier("error"); const hasErrorId = path.scope.generateUidIdentifier("hasError"); + let disposeCall: t.Expression = t.callExpression( + state.addHelper("dispose"), + [ + t.cloneNode(stackId), + t.cloneNode(errorId), + t.cloneNode(hasErrorId), + // Pass SuppressedError so that it can be used with "pure" + // polyfills that do not compile the contents of runtime + // helpers. + template.expression.ast` + typeof SuppressedError !== undefined && SuppressedError + `, + ], + ); + if (needsAwait) disposeCall = t.awaitExpression(disposeCall); + const replacement = template.statement.ast` try { var ${stackId} = []; @@ -62,15 +86,7 @@ export default declare(api => { var ${errorId} = _; var ${hasErrorId} = true; } finally { - ${state.addHelper("dispose")}( - ${t.cloneNode(stackId)}, - ${t.cloneNode(errorId)}, - ${t.cloneNode(hasErrorId)}, - // Pass SuppressedError so that it can be used with "pure" - // polyfills that do not compile the contents of runtime - // helpers. - typeof SuppressedError !== undefined && SuppressedError - ); + ${disposeCall} } `; diff --git a/packages/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-await/mixed/input.js b/packages/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-await/mixed/input.js new file mode 100644 index 000000000000..2e6f9a0b8d68 --- /dev/null +++ b/packages/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-await/mixed/input.js @@ -0,0 +1,5 @@ +{ + using a = 1; + await using b = 2; + using c = 3; +} \ No newline at end of file diff --git a/packages/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-await/mixed/output.mjs b/packages/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-await/mixed/output.mjs new file mode 100644 index 000000000000..212e57123aa9 --- /dev/null +++ b/packages/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-await/mixed/output.mjs @@ -0,0 +1,11 @@ +try { + var _stack = []; + const a = babelHelpers.using(_stack, 1); + const b = babelHelpers.using(_stack, 2, true); + const c = babelHelpers.using(_stack, 3); +} catch (_) { + var _error = _; + var _hasError = true; +} finally { + await babelHelpers.dispose(_stack, _error, _hasError, typeof SuppressedError !== undefined && SuppressedError); +} diff --git a/packages/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-await/only-using-await/input.js b/packages/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-await/only-using-await/input.js new file mode 100644 index 000000000000..578ef4f231eb --- /dev/null +++ b/packages/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-await/only-using-await/input.js @@ -0,0 +1,6 @@ +{ + await using x = obj; + stmt; + await using y = obj, z = obj; + doSomethingWith(x, y); +} diff --git a/packages/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-await/only-using-await/output.mjs b/packages/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-await/only-using-await/output.mjs new file mode 100644 index 000000000000..1349e31e395e --- /dev/null +++ b/packages/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-await/only-using-await/output.mjs @@ -0,0 +1,13 @@ +try { + var _stack = []; + const x = babelHelpers.using(_stack, obj, true); + stmt; + const y = babelHelpers.using(_stack, obj, true), + z = babelHelpers.using(_stack, obj, true); + doSomethingWith(x, y); +} catch (_) { + var _error = _; + var _hasError = true; +} finally { + await babelHelpers.dispose(_stack, _error, _hasError, typeof SuppressedError !== undefined && SuppressedError); +} diff --git a/packages/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-await/options.json b/packages/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-await/options.json new file mode 100644 index 000000000000..53d449e808f5 --- /dev/null +++ b/packages/babel-plugin-proposal-explicit-resource-management/test/fixtures/transform-await/options.json @@ -0,0 +1,4 @@ +{ + "sourceType": "module", + "plugins": ["proposal-explicit-resource-management"] +}