diff --git a/cli/tsc/dts/node/globals.d.cts b/cli/tsc/dts/node/globals.d.cts index 8f31a4c6b0aab7..be0f387d741478 100644 --- a/cli/tsc/dts/node/globals.d.cts +++ b/cli/tsc/dts/node/globals.d.cts @@ -2,19 +2,21 @@ export {}; // Make this a module // #region Fetch and friends // Conditional type aliases, used at the end of this file. -// Will either be empty if lib.dom (or lib.webworker) is included, or the undici version otherwise. -type _Request = typeof globalThis extends { onmessage: any } ? {} : import("./undici/index.d.ts").Request; -type _Response = typeof globalThis extends { onmessage: any } ? {} : import("./undici/index.d.ts").Response; -type _FormData = typeof globalThis extends { onmessage: any } ? {} : import("./undici/index.d.ts").FormData; -type _Headers = typeof globalThis extends { onmessage: any } ? {} : import("./undici/index.d.ts").Headers; -type _MessageEvent = typeof globalThis extends { onmessage: any } ? {} : import("./undici/index.d.ts").MessageEvent; -type _RequestInit = typeof globalThis extends { onmessage: any } ? {} +// Deno's web globals don't expose `onmessage` on `globalThis` outside of +// worker contexts, so detect Deno's web fetch surface via `Blob` instead. +type _HasWebFetchGlobals = typeof globalThis extends { Blob: any } ? true : false; +type _Request = _HasWebFetchGlobals extends true ? {} : import("./undici/index.d.ts").Request; +type _Response = _HasWebFetchGlobals extends true ? {} : import("./undici/index.d.ts").Response; +type _FormData = _HasWebFetchGlobals extends true ? {} : import("./undici/index.d.ts").FormData; +type _Headers = _HasWebFetchGlobals extends true ? {} : import("./undici/index.d.ts").Headers; +type _MessageEvent = _HasWebFetchGlobals extends true ? {} : import("./undici/index.d.ts").MessageEvent; +type _RequestInit = _HasWebFetchGlobals extends true ? {} : import("./undici/index.d.ts").RequestInit; -type _ResponseInit = typeof globalThis extends { onmessage: any } ? {} +type _ResponseInit = _HasWebFetchGlobals extends true ? {} : import("./undici/index.d.ts").ResponseInit; -type _WebSocket = typeof globalThis extends { onmessage: any } ? {} : import("./undici/index.d.ts").WebSocket; -type _EventSource = typeof globalThis extends { onmessage: any } ? {} : import("./undici/index.d.ts").EventSource; -type _CloseEvent = typeof globalThis extends { onmessage: any } ? {} : import("./undici/index.d.ts").CloseEvent; +type _WebSocket = _HasWebFetchGlobals extends true ? {} : import("./undici/index.d.ts").WebSocket; +type _EventSource = _HasWebFetchGlobals extends true ? {} : import("./undici/index.d.ts").EventSource; +type _CloseEvent = _HasWebFetchGlobals extends true ? {} : import("./undici/index.d.ts").CloseEvent; // #endregion Fetch and friends // Conditional type definitions for webstorage interface, which conflicts with lib.dom otherwise. diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index 62ce133339976f..849eddc032a34a 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -17545,6 +17545,30 @@ fn lsp_tsconfig_node_modules_dts_diagnostics() { client.shutdown(); } +// Regression test for #33012: the @types/node `globals.d.cts` heuristic for +// detecting the web fetch surface used `onmessage`, which isn't on Deno's +// `globalThis` outside of workers, so LSP would fall back to undici types +// for `RequestInit` and reject otherwise-valid `new Request(url, init)`. +#[test(timeout = 300)] +fn lsp_request_init_global_matches_check() { + let context = TestContextBuilder::new().use_temp_cwd().build(); + let temp_dir = context.temp_dir(); + temp_dir.write("deno.json", json!({}).to_string()); + let file = temp_dir.source_file( + "main.ts", + concat!( + "const init: Parameters[1] = {};\n", + "const _request = new Request(\"https://deno.com\", init);\n", + "console.log(_request);\n", + ), + ); + let mut client = context.new_lsp_command().build(); + client.initialize_default(); + let diagnostics = client.did_open_file(&file); + assert_eq!(diagnostics.all(), vec![]); + client.shutdown(); +} + #[test(timeout = 300)] fn lsp_tsconfig_root_dirs() { let context = TestContextBuilder::new() diff --git a/tools/update_types_node.ts b/tools/update_types_node.ts index f988614f86089b..d761105ad754f7 100755 --- a/tools/update_types_node.ts +++ b/tools/update_types_node.ts @@ -14,6 +14,7 @@ import { Node, Project, ScriptTarget, + SourceFile, SyntaxKind, VariableDeclaration, } from "jsr:@ts-morph/ts-morph@27.0.2"; @@ -260,11 +261,53 @@ function modifySourceFiles() { ), ); } + + // Detect Deno's web fetch surface via `Blob` instead of `onmessage`. + // `onmessage` only exists on `globalThis` in worker contexts, so the + // upstream heuristic falls back to undici's types in regular Deno code + // and breaks LSP type checks against `RequestInit` etc. (denoland/deno#33012). + if (sourceFile.getBaseName() === "globals.d.cts") { + rewriteFetchGlobalsDiscriminator(sourceFile); + } } project.saveSync(); } +function rewriteFetchGlobalsDiscriminator(sourceFile: SourceFile) { + const startMarker = "// #region Fetch and friends\n"; + const endMarker = "// #endregion Fetch and friends\n"; + const text = sourceFile.getFullText(); + const start = text.indexOf(startMarker); + const end = text.indexOf(endMarker); + if (start === -1 || end === -1) { + return; + } + const blockStart = start + startMarker.length; + const blockBody = text.slice(blockStart, end); + if (!blockBody.includes("{ onmessage: any }")) { + return; + } + const rewritten = blockBody + .replace( + "// Conditional type aliases, used at the end of this file.\n" + + "// Will either be empty if lib.dom (or lib.webworker) is included, " + + "or the undici version otherwise.\n", + "// Conditional type aliases, used at the end of this file.\n" + + "// Deno's web globals don't expose `onmessage` on `globalThis` " + + "outside of\n" + + "// worker contexts, so detect Deno's web fetch surface via `Blob` " + + "instead.\n" + + "type _HasWebFetchGlobals = typeof globalThis extends { Blob: any } " + + "? true : false;\n", + ) + .replaceAll( + "typeof globalThis extends { onmessage: any }", + "_HasWebFetchGlobals extends true", + ); + sourceFile.replaceText([blockStart, end], rewritten); +} + function handleInterface(decl: InterfaceDeclaration) { switch (decl.getName()) { case "ImportMeta": {