Repository: denoland/deno
Pull request: fix: update setTimeout and setInterval callback type to support strin…

  fix(types): allow string or function as callback for setTimeout/setInterval  

This PR updates the type definitions for `setTimeout` and `setInterval` in `lib.deno.shared_globals.d.ts` to accept both a string and a function as the callback, matching the DOM standard and Deno's runtime behavior.

  Previously, the types only allowed a function, but Deno's implementation (and the web standard) also allow a string, which is evaluated as code. This change ensures that TypeScript users can use both forms without type errors, aligning Deno's types with web compatibility.

  Fixes #30166

  - Updated the callback type for `setTimeout` and `setInterval` to `string | ((...args: any[]) => void)`.
  - No runtime changes; this is a type fix only.
  - The change is covered by existing tests, as the runtime already supports both forms.

  Checklist:
  - [x] Related issue is referenced
  - [x] Types now match runtime and web standard
  - [x] `cargo test` passes
  - [x] `./tools/format.js` passes
  - [x] `./tools/lint.js` passes

Changed files:
filename: cli/tsc/dts/lib.deno.shared_globals.d.ts
patch: @@ -361,11 +361,8 @@ declare namespace WebAssembly {
  * @category Platform
  */
 declare function setTimeout(
-  /** callback function to execute when timer expires */
-  cb: (...args: any[]) => void,
-  /** delay in ms */
+  cb: string | ((...args: any[]) => void),
   delay?: number,
-  /** arguments passed to callback function */
   ...args: any[]
 ): number;
 
@@ -379,11 +376,8 @@ declare function setTimeout(
  * @category Platform
  */
 declare function setInterval(
-  /** callback function to execute when timer expires */
-  cb: (...args: any[]) => void,
-  /** delay in ms */
+  cb: string | ((...args: any[]) => void),
   delay?: number,
-  /** arguments passed to callback function */
   ...args: any[]
 ): number;
 

filename: tests/unit/timers_test.ts
patch: @@ -778,3 +778,38 @@ Deno.test({
     AbortSignal.timeout(2000);
   },
 });
+
+Deno.test(async function setTimeoutWithStringCallback() {
+  const global = globalThis as unknown as {
+    timeoutStringTest: number;
+    timeoutStringPromise: ReturnType<typeof Promise.withResolvers<void>>;
+  };
+  global.timeoutStringTest = 0;
+  global.timeoutStringPromise = Promise.withResolvers<void>();
+  setTimeout(
+    "globalThis.timeoutStringTest = 42; globalThis.timeoutStringPromise.resolve();",
+    10,
+  );
+  await global.timeoutStringPromise.promise;
+  assertEquals(global.timeoutStringTest, 42);
+  Reflect.deleteProperty(global, "timeoutStringTest");
+  Reflect.deleteProperty(global, "timeoutStringPromise");
+});
+
+Deno.test(async function setIntervalWithStringCallback() {
+  const global = globalThis as unknown as {
+    intervalStringTest: number;
+    intervalStringPromise: ReturnType<typeof Promise.withResolvers<void>>;
+  };
+  global.intervalStringTest = 0;
+  global.intervalStringPromise = Promise.withResolvers<void>();
+  const id = setInterval(
+    "globalThis.intervalStringTest += 10; if (globalThis.intervalStringTest >= 20) globalThis.intervalStringPromise.resolve();",
+    10,
+  );
+  global.intervalStringPromise.promise.then(() => clearInterval(id));
+  await global.intervalStringPromise.promise;
+  assert(global.intervalStringTest >= 20);
+  Reflect.deleteProperty(global, "intervalStringTest");
+  Reflect.deleteProperty(global, "intervalStringPromise");
+});