From 6475aea8bfa73c2fe066f6214ff0aac7340a23f6 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Thu, 27 Jun 2024 14:32:34 -0400 Subject: [PATCH] update `await using` behavior to match TypeScript --- CHANGELOG.md | 4 ++ internal/runtime/runtime.go | 8 +++- scripts/end-to-end-tests.js | 74 +++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44cb404d57..c0ed809719 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,10 @@ In addition, this release increases the minimum required node version for esbuild's JavaScript API from node 12 to node 18. Node 18 is the oldest version of node that is still being supported (see node's [release schedule](https://nodejs.org/en/about/previous-releases) for more information). This increase is because of an incompatibility between the JavaScript that the Go compiler generates for the `esbuild-wasm` package and versions of node before node 17.4 (specifically the `crypto.getRandomValues` function). +* Update `await using` behavior to match TypeScript + + TypeScript 5.5 subtly changes the way `await using` behaves. This release updates esbuild to match these changes in TypeScript. You can read more about these changes in [microsoft/TypeScript#58624](https://github.com/microsoft/TypeScript/pull/58624). + ## 0.21.5 * Fix `Symbol.metadata` on classes without a class decorator ([#3781](https://github.com/evanw/esbuild/issues/3781)) diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index cca5e98122..42ccf39f53 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -505,10 +505,14 @@ func Source(unsupportedJSFeatures compat.JSFeature) logger.Source { export var __using = (stack, value, async) => { if (value != null) { if (typeof value !== 'object' && typeof value !== 'function') __typeError('Object expected') - var dispose + var dispose, inner if (async) dispose = value[__knownSymbol('asyncDispose')] - if (dispose === void 0) dispose = value[__knownSymbol('dispose')] + if (dispose === void 0) { + dispose = value[__knownSymbol('dispose')] + if (async) inner = dispose + } if (typeof dispose !== 'function') __typeError('Object not disposable') + if (inner) dispose = function() { try { inner.call(this) } catch (e) { return Promise.reject(e) } } stack.push([async, dispose, value]) } else if (async) { stack.push([async]) diff --git a/scripts/end-to-end-tests.js b/scripts/end-to-end-tests.js index c3dea204db..8508d3981c 100644 --- a/scripts/end-to-end-tests.js +++ b/scripts/end-to-end-tests.js @@ -8801,6 +8801,80 @@ for (const flags of [[], '--supported:async-await=false']) { } `, }, { async: true }), + + // From https://github.com/microsoft/TypeScript/pull/58624 + test(['in.ts', '--outfile=node.js', '--supported:using=false', '--format=esm'].concat(flags), { + 'in.ts': ` + Symbol.asyncDispose ||= Symbol.for('Symbol.asyncDispose') + Symbol.dispose ||= Symbol.for('Symbol.dispose') + export const output: any[] = []; + export async function main() { + const promiseDispose = new Promise((resolve) => { + setTimeout(() => { + output.push("y dispose promise body"); + resolve(); + }, 0); + }); + { + await using x = { + async [Symbol.asyncDispose]() { + output.push("x asyncDispose body"); + }, + }; + await using y = { + [Symbol.dispose]() { + output.push("y dispose body"); + return promiseDispose; + }, + }; + } + output.push("body"); + await promiseDispose; + return output; + } + export let async = async () => { + const output = await main() + const expected = [ + "y dispose body", + "x asyncDispose body", + "body", + "y dispose promise body", + ] + if (output.join(',') !== expected.join(',')) throw 'fail: ' + output + } + `, + }, { async: true }), + test(['in.ts', '--outfile=node.js', '--supported:using=false', '--format=esm'].concat(flags), { + 'in.ts': ` + Symbol.dispose ||= Symbol.for('Symbol.dispose') + export const output: any[] = []; + export async function main() { + const interleave = Promise.resolve().then(() => { output.push("interleave"); }); + try { + await using x = { + [Symbol.dispose]() { + output.push("dispose"); + throw null; + }, + }; + } + catch { + output.push("catch"); + } + await interleave; + return output; + } + export let async = async () => { + const output = await main() + const expected = [ + "dispose", + "interleave", + "catch", + ] + if (output.join(',') !== expected.join(',')) throw 'fail: ' + output + } + `, + }, { async: true }), ) }