feat: enable explicit resource management for JavaScript (#28119)

This commit enabled [explicit resource management]
(https://github.com/tc39/proposal-explicit-resource-management)
proposal for JavaScript code.

This is done by upgrading `deno_ast` to a version that no longer
transpiles TS files with `using` keyword, and instead enables
a V8 flag that provides native support.

Closes https://github.com/denoland/deno/issues/20821

---------

Co-authored-by: Luca Casonato <hello@lcas.dev>
Co-authored-by: Divy Srivastava <dj.srivastava23@gmail.com>
This commit is contained in:
Bartek Iwańczuk 2025-04-09 00:12:10 +02:00 committed by Nathan Whitaker
parent 25defa74d5
commit 00941c15ca
No known key found for this signature in database
9 changed files with 62 additions and 33 deletions

View file

@ -503,6 +503,7 @@ fn resolve_flags_and_init(
let default_v8_flags = match flags.subcommand {
DenoSubcommand::Lsp => vec![
"--stack-size=1024".to_string(),
"--js-explicit-resource-management".to_string(),
// Using same default as VSCode:
// https://github.com/microsoft/vscode/blob/48d4ba271686e8072fc6674137415bc80d936bc7/extensions/typescript-language-features/src/configuration/configuration.ts#L213-L214
"--max-old-space-size=3072".to_string(),
@ -510,6 +511,7 @@ fn resolve_flags_and_init(
_ => {
vec![
"--stack-size=1024".to_string(),
"--js-explicit-resource-management".to_string(),
// TODO(bartlomieju): I think this can be removed as it's handled by `deno_core`
// and its settings.
// deno_ast removes TypeScript `assert` keywords, so this flag only affects JavaScript

View file

@ -225,7 +225,8 @@ function collectHttpQuotedString(input, position, extractValue) {
value += input[position];
// 5.5.3.
position++;
} else { // 5.6.
} else {
// 5.6.
// 5.6.1
if (quoteOrBackslash !== "\u0022") throw new TypeError('must be "');
// 5.6.2
@ -288,11 +289,7 @@ function convertBase64urlToBase64(base64url) {
throw new TypeError("Failed to decode base64url: invalid character");
}
return StringPrototypeReplaceAll(
StringPrototypeReplaceAll(
addPaddingToBase64url(base64url),
"-",
"+",
),
StringPrototypeReplaceAll(addPaddingToBase64url(base64url), "-", "+"),
"_",
"/",
);
@ -397,21 +394,9 @@ const PERCENT_RE = new SafeRegExp(/%(?![0-9A-Fa-f]{2})/g);
* @returns {string}
*/
function pathFromURLWin32(url) {
let p = StringPrototypeReplace(
url.pathname,
PATHNAME_WIN_RE,
"$1/",
);
p = StringPrototypeReplace(
p,
SLASH_WIN_RE,
"\\",
);
p = StringPrototypeReplace(
p,
PERCENT_RE,
"%25",
);
let p = StringPrototypeReplace(url.pathname, PATHNAME_WIN_RE, "$1/");
p = StringPrototypeReplace(p, SLASH_WIN_RE, "\\");
p = StringPrototypeReplace(p, PERCENT_RE, "%25");
let path = decodeURIComponent(p);
if (url.hostname != "") {
// Note: The `URL` implementation guarantees that the drive letter and
@ -433,11 +418,7 @@ function pathFromURLPosix(url) {
}
return decodeURIComponent(
StringPrototypeReplace(
url.pathname,
PERCENT_RE,
"%25",
),
StringPrototypeReplace(url.pathname, PERCENT_RE, "%25"),
);
}
@ -459,13 +440,11 @@ function pathFromURL(pathOrUrl) {
internals.pathFromURL = pathFromURL;
// deno-lint-ignore prefer-primordials
export const SymbolDispose = Symbol.dispose ?? Symbol("Symbol.dispose");
export const SymbolDispose = Symbol.dispose;
// deno-lint-ignore prefer-primordials
export const SymbolAsyncDispose = Symbol.asyncDispose ??
Symbol("Symbol.asyncDispose");
export const SymbolAsyncDispose = Symbol.asyncDispose;
// deno-lint-ignore prefer-primordials
export const SymbolMetadata = Symbol.metadata ??
Symbol("Symbol.metadata");
export const SymbolMetadata = Symbol.metadata ?? Symbol("Symbol.metadata");
export {
ASCII_ALPHA,

View file

@ -1,4 +1,16 @@
{
"args": "run --quiet --check explicit_resource_management/main.ts",
"output": "explicit_resource_management/main.out"
"tests": {
"js": {
"args": "run --quiet --check js/main.js",
"output": "js/main.out"
},
"ts": {
"args": "run --quiet --check ts/main.ts",
"output": "ts/main.out"
},
"api": {
"args": "run --quiet --check api/main.ts",
"output": "api/main.out"
}
}
}

View file

@ -0,0 +1,4 @@
true
DisposableStack {}
AsyncDisposableStack {}
[Function: [Symbol.dispose]]

View file

@ -0,0 +1,6 @@
import console from "node:console";
console.log(new SuppressedError("asd", new Error("foo")) instanceof Error);
console.log(new DisposableStack());
console.log(new AsyncDisposableStack());
console.log(Iterator.from([])[Symbol.dispose]);

View file

@ -0,0 +1,5 @@
A
Disposed
B
Async disposed
C

View file

@ -0,0 +1,21 @@
class Resource {
[Symbol.dispose]() {
console.log("Disposed");
}
}
class AsyncResource {
async [Symbol.asyncDispose]() {
await new Promise((resolve) => setTimeout(resolve, 10));
console.log("Async disposed");
}
}
{
using resource = new Resource();
console.log("A");
}
{
await using resource = new AsyncResource();
console.log("B");
}
console.log("C");