From 607654fb07876dced006e44290095f252b919dfe Mon Sep 17 00:00:00 2001 From: fibibot Date: Thu, 14 May 2026 03:52:43 +0000 Subject: [PATCH 1/2] fix(fs): expose iterator helpers on `walk`/`walkSync` and `expandGlob`/`expandGlobSync` The four generator functions in `@std/fs` were annotated with the `IterableIterator` / `AsyncIterableIterator` return types, which widen the actual `Generator` / `AsyncGenerator` runtime type and hide the ES2025 iterator helpers (`.map`, `.filter`, `.take`, etc.) at the type level. Switch the annotations to `Generator` / `AsyncGenerator` so callers can chain helpers without widening, e.g. `walkSync(".").map((v) => v.name)`. Closes denoland/std#7099 --- fs/expand_glob.ts | 4 ++-- fs/expand_glob_test.ts | 24 ++++++++++++++++++++++++ fs/walk.ts | 4 ++-- fs/walk_test.ts | 20 ++++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/fs/expand_glob.ts b/fs/expand_glob.ts index 59c3b5a78f0e..e001fa5bd75b 100644 --- a/fs/expand_glob.ts +++ b/fs/expand_glob.ts @@ -271,7 +271,7 @@ function comparePath(a: WalkEntry, b: WalkEntry): number { export async function* expandGlob( glob: string | URL, options?: ExpandGlobOptions, -): AsyncIterableIterator { +): AsyncGenerator { let { root, exclude = [], @@ -428,7 +428,7 @@ export async function* expandGlob( export function* expandGlobSync( glob: string | URL, options?: ExpandGlobOptions, -): IterableIterator { +): Generator { let { root, exclude = [], diff --git a/fs/expand_glob_test.ts b/fs/expand_glob_test.ts index f9386c58b8d4..193a416bbbb6 100644 --- a/fs/expand_glob_test.ts +++ b/fs/expand_glob_test.ts @@ -464,3 +464,27 @@ Deno.test("expandGlobSync() finds directory with escaped brackets", function () [join("a[b]c", "foo")], ); }); + +Deno.test("expandGlobSync() returns a generator that exposes iterator helpers", () => { + // Regression test for https://github.com/denoland/std/issues/7099 + // `expandGlobSync` is a generator function, so the returned value exposes + // the ES2025 iterator helpers (`.map`, `.filter`, etc.) at the type level. + const names = expandGlobSync("*", EG_OPTIONS) + .map((entry) => entry.name) + .toArray(); + assert(names.length > 0); + for (const name of names) { + assertEquals(typeof name, "string"); + } +}); + +Deno.test("expandGlob() returns an async generator", async () => { + // Regression test for https://github.com/denoland/std/issues/7099 + // `expandGlob` is annotated as `AsyncGenerator` so it can be + // combined with future async iterator helpers without the type widening to + // the helper-free `AsyncIterableIterator`. + const iter = expandGlob("*", EG_OPTIONS); + const next = await iter.next(); + assertEquals(typeof next.value?.name, "string"); + await iter.return(undefined); +}); diff --git a/fs/walk.ts b/fs/walk.ts index 600e301cfa3f..be5b639d06a2 100644 --- a/fs/walk.ts +++ b/fs/walk.ts @@ -449,7 +449,7 @@ export type { WalkEntry }; export async function* walk( root: string | URL, options?: WalkOptions, -): AsyncIterableIterator { +): AsyncGenerator { let { maxDepth = Infinity, includeFiles = true, @@ -878,7 +878,7 @@ export async function* walk( export function* walkSync( root: string | URL, options?: WalkOptions, -): IterableIterator { +): Generator { let { maxDepth = Infinity, includeFiles = true, diff --git a/fs/walk_test.ts b/fs/walk_test.ts index ae507dd3ad25..367d4d25d05b 100644 --- a/fs/walk_test.ts +++ b/fs/walk_test.ts @@ -338,6 +338,26 @@ Deno.test({ }, }); +Deno.test("walkSync() returns a generator that exposes iterator helpers", () => { + // Regression test for https://github.com/denoland/std/issues/7099 + // `walkSync` is a generator function, so the returned value exposes the + // ES2025 iterator helpers (`.map`, `.filter`, etc.) at the type level. + const iter = walkSync(testdataDir); + const names = iter.map((entry) => entry.name).toArray(); + assertEquals(typeof names[0], "string"); +}); + +Deno.test("walk() returns an async generator", async () => { + // Regression test for https://github.com/denoland/std/issues/7099 + // `walk` is annotated as `AsyncGenerator` so it can be combined + // with future async iterator helpers without the type widening to the + // helper-free `AsyncIterableIterator`. + const iter = walk(testdataDir); + const next = await iter.next(); + assertEquals(typeof next.value?.name, "string"); + await iter.return(undefined); +}); + Deno.test("walk() rejects with `Deno.errors.NotFound` when root is removed during execution", async () => { const tempDirPath = await Deno.makeTempDir({ prefix: "deno_std_walk_", From 384a72270a721a426a9d7a4f464533e338e288cf Mon Sep 17 00:00:00 2001 From: fibibot Date: Thu, 14 May 2026 03:58:14 +0000 Subject: [PATCH 2/2] fix(fs): keep Deno v1.x TypeScript compat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit added `Generator` annotations and tests that exercise `.map(...).toArray()`. Both regressed on Deno v1.x (TypeScript 5.5, which predates iterator helpers in `lib.es2025.iterator.d.ts`): - `expandGlob` / `expandGlobSync` ended with `yield* currentMatches`, where `currentMatches` is `WalkEntry[]`. Array's iterator declares `TNext` as `undefined`, but the outer `Generator`'s `TNext` defaults to `unknown`, so TS 5.5 reports TS2766. Newer TS widens the array iterator to `unknown` and the call type-checks. Replace the delegation with an explicit `for ... yield` loop so it compiles on both. - The new tests called `.map(...).toArray()` on the returned iterator, which TS 5.5 rejects (no iterator helpers in lib). Switch to a type-level assertion: `const iter: Generator = walkSync(...)`. This still enforces the regression — `Generator` requires the `.return()`/`.throw()` methods that `IterableIterator` leaves optional, so the assignment fails if the return type widens back — but compiles on every TS version where `Generator` is defined. --- fs/expand_glob.ts | 8 ++++++-- fs/expand_glob_test.ts | 34 +++++++++++++++------------------- fs/walk_test.ts | 31 +++++++++++++++---------------- 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/fs/expand_glob.ts b/fs/expand_glob.ts index e001fa5bd75b..7f9800f07a33 100644 --- a/fs/expand_glob.ts +++ b/fs/expand_glob.ts @@ -375,7 +375,9 @@ export async function* expandGlob( (entry: WalkEntry): boolean => !entry.isDirectory, ); } - yield* currentMatches; + for (const match of currentMatches) { + yield match; + } } /** @@ -530,7 +532,9 @@ export function* expandGlobSync( (entry: WalkEntry): boolean => !entry.isDirectory, ); } - yield* currentMatches; + for (const match of currentMatches) { + yield match; + } } const globEscapeChar = diff --git a/fs/expand_glob_test.ts b/fs/expand_glob_test.ts index 193a416bbbb6..d6d7b3258b3b 100644 --- a/fs/expand_glob_test.ts +++ b/fs/expand_glob_test.ts @@ -11,6 +11,7 @@ import { expandGlob, type ExpandGlobOptions, expandGlobSync, + type WalkEntry, } from "./expand_glob.ts"; async function expandGlobArray( @@ -465,26 +466,21 @@ Deno.test("expandGlobSync() finds directory with escaped brackets", function () ); }); -Deno.test("expandGlobSync() returns a generator that exposes iterator helpers", () => { - // Regression test for https://github.com/denoland/std/issues/7099 - // `expandGlobSync` is a generator function, so the returned value exposes - // the ES2025 iterator helpers (`.map`, `.filter`, etc.) at the type level. - const names = expandGlobSync("*", EG_OPTIONS) - .map((entry) => entry.name) - .toArray(); - assert(names.length > 0); - for (const name of names) { - assertEquals(typeof name, "string"); - } +Deno.test("expandGlobSync() preserves the Generator return type", () => { + // Regression test for https://github.com/denoland/std/issues/7099: + // the return type must be `Generator` so the ES2025 iterator + // helpers (`.map`, `.filter`, etc.) are exposed at the type level wherever + // the host TypeScript lib defines them. `Generator` requires the + // `.return()` / `.throw()` methods that `IterableIterator` leaves + // optional, so this assignment fails if the return type is widened back + // to `IterableIterator`. + const iter: Generator = expandGlobSync("*", EG_OPTIONS); + iter.return(undefined); }); -Deno.test("expandGlob() returns an async generator", async () => { - // Regression test for https://github.com/denoland/std/issues/7099 - // `expandGlob` is annotated as `AsyncGenerator` so it can be - // combined with future async iterator helpers without the type widening to - // the helper-free `AsyncIterableIterator`. - const iter = expandGlob("*", EG_OPTIONS); - const next = await iter.next(); - assertEquals(typeof next.value?.name, "string"); +Deno.test("expandGlob() preserves the AsyncGenerator return type", async () => { + // Regression test for https://github.com/denoland/std/issues/7099 — see the + // sync counterpart for rationale. + const iter: AsyncGenerator = expandGlob("*", EG_OPTIONS); await iter.return(undefined); }); diff --git a/fs/walk_test.ts b/fs/walk_test.ts index 367d4d25d05b..26d7990159e3 100644 --- a/fs/walk_test.ts +++ b/fs/walk_test.ts @@ -1,5 +1,5 @@ // Copyright 2018-2026 the Deno authors. MIT license. -import { walk, type WalkOptions, walkSync } from "./walk.ts"; +import { walk, type WalkEntry, type WalkOptions, walkSync } from "./walk.ts"; import { assertArrayIncludes, assertEquals, @@ -338,23 +338,22 @@ Deno.test({ }, }); -Deno.test("walkSync() returns a generator that exposes iterator helpers", () => { - // Regression test for https://github.com/denoland/std/issues/7099 - // `walkSync` is a generator function, so the returned value exposes the - // ES2025 iterator helpers (`.map`, `.filter`, etc.) at the type level. - const iter = walkSync(testdataDir); - const names = iter.map((entry) => entry.name).toArray(); - assertEquals(typeof names[0], "string"); +Deno.test("walkSync() preserves the Generator return type", () => { + // Regression test for https://github.com/denoland/std/issues/7099: + // the return type must be `Generator` so the ES2025 iterator + // helpers (`.map`, `.filter`, etc.) are exposed at the type level wherever + // the host TypeScript lib defines them. `Generator` requires the + // `.return()` / `.throw()` methods that `IterableIterator` leaves + // optional, so this assignment fails if the return type is widened back + // to `IterableIterator`. + const iter: Generator = walkSync(testdataDir); + iter.return(undefined); }); -Deno.test("walk() returns an async generator", async () => { - // Regression test for https://github.com/denoland/std/issues/7099 - // `walk` is annotated as `AsyncGenerator` so it can be combined - // with future async iterator helpers without the type widening to the - // helper-free `AsyncIterableIterator`. - const iter = walk(testdataDir); - const next = await iter.next(); - assertEquals(typeof next.value?.name, "string"); +Deno.test("walk() preserves the AsyncGenerator return type", async () => { + // Regression test for https://github.com/denoland/std/issues/7099 — see the + // sync counterpart for rationale. + const iter: AsyncGenerator = walk(testdataDir); await iter.return(undefined); });