From 9fc18f1aa811fce5c72f84651d6cc3a7d2714083 Mon Sep 17 00:00:00 2001 From: LeSingh1 Date: Fri, 29 May 2026 17:16:16 -0700 Subject: [PATCH] fix(fs): walk does not apply exts filter to directories walk and walkSync's include() check ran the exts filter against the directory name when deciding whether to yield a directory entry. Directories generally don't have file extensions, so any `exts` option effectively dropped every directory from the output, even when includeDirs:true was set (the default). Pass undefined for exts in the includeDirs branch so the filter only applies to file entries. The match and skip regex filters still apply to directories. Symlink emission (the !followSymlinks branch) is unchanged because a symlink's name still has an extension just like the file or directory it points to. Existing 'walk() accepts ext option as strings' tests are updated to include the root directory in the expected entries (default includeDirs:true). Two new tests pin the includeDirs:false case so the intent is explicit. Fixes #6736 --- fs/walk.ts | 10 ++++++++-- fs/walk_test.ts | 25 ++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/fs/walk.ts b/fs/walk.ts index 600e301cfa3f..51dc70ceef49 100644 --- a/fs/walk.ts +++ b/fs/walk.ts @@ -469,7 +469,10 @@ export async function* walk( if (exts) { exts = exts.map((ext) => ext.startsWith(".") ? ext : `.${ext}`); } - if (includeDirs && include(root, exts, match, skip)) { + // Directories don't have file extensions, so the `exts` filter must not be + // applied when deciding whether to yield a directory entry. The `match` + // and `skip` regex filters still apply. See #6736. + if (includeDirs && include(root, undefined, match, skip)) { yield await createWalkEntry(root); } if (maxDepth < 1 || !include(root, undefined, undefined, skip)) { @@ -898,7 +901,10 @@ export function* walkSync( if (maxDepth < 0) { return; } - if (includeDirs && include(root, exts, match, skip)) { + // Directories don't have file extensions, so the `exts` filter must not + // be applied when deciding whether to yield a directory entry. The + // `match` and `skip` regex filters still apply. See #6736. + if (includeDirs && include(root, undefined, match, skip)) { yield createWalkEntrySync(root); } if (maxDepth < 1 || !include(root, undefined, undefined, skip)) { diff --git a/fs/walk_test.ts b/fs/walk_test.ts index ae507dd3ad25..68f9ef453eea 100644 --- a/fs/walk_test.ts +++ b/fs/walk_test.ts @@ -113,23 +113,42 @@ Deno.test("walkSync() accepts includeFiles option set to false", () => includeFiles: false, })); +// `includeDirs` defaults to true, so the root directory entry is also +// expected. `exts` no longer filters directories (see #6736). Deno.test("walk() accepts ext option as strings", async () => - await assertWalkPaths(testdataDir, "ext", ["y.rs", "x.ts"], { + await assertWalkPaths(testdataDir, "ext", [".", "y.rs", "x.ts"], { exts: [".rs", ".ts"], })); Deno.test("walk() accepts ext option as strings (excluding period prefix)", async () => - await assertWalkPaths(testdataDir, "ext", ["y.rs", "x.ts"], { + await assertWalkPaths(testdataDir, "ext", [".", "y.rs", "x.ts"], { exts: ["rs", "ts"], })); Deno.test("walkSync() accepts ext option as strings", () => - assertWalkSyncPaths(testdataDir, "ext", ["y.rs", "x.ts"], { + assertWalkSyncPaths(testdataDir, "ext", [".", "y.rs", "x.ts"], { exts: [".rs", ".ts"], })); Deno.test("walkSync() accepts ext option as strings (excluding period prefix)", () => + assertWalkSyncPaths(testdataDir, "ext", [".", "y.rs", "x.ts"], { + exts: [".rs", ".ts"], + })); + +// https://github.com/denoland/std/issues/6736 — explicit includeDirs:false +// still drops the directory when an `exts` filter is set. Before the fix, +// includeDirs:true also dropped it (because the dir name had no extension); +// after the fix, the dir is yielded as expected (verified by the strings +// tests above). +Deno.test("walk() with exts and includeDirs:false yields only files", async () => + await assertWalkPaths(testdataDir, "ext", ["y.rs", "x.ts"], { + includeDirs: false, + exts: [".rs", ".ts"], + })); + +Deno.test("walkSync() with exts and includeDirs:false yields only files", () => assertWalkSyncPaths(testdataDir, "ext", ["y.rs", "x.ts"], { + includeDirs: false, exts: [".rs", ".ts"], }));