Skip to content

fix(fs): walk does not apply exts filter to directories#7167

Open
LeSingh1 wants to merge 1 commit into
denoland:mainfrom
LeSingh1:fs-walk-exts-skip-dirs
Open

fix(fs): walk does not apply exts filter to directories#7167
LeSingh1 wants to merge 1 commit into
denoland:mainfrom
LeSingh1:fs-walk-exts-skip-dirs

Conversation

@LeSingh1

Copy link
Copy Markdown
Contributor

Fixes #6736.

walk and walkSync ran the exts filter against the directory name in include() 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 with includeDirs:true (the default).

Repro before the patch:

import { walkSync } from "jsr:@std/fs/walk";
for (const e of walkSync("./project", { includeDirs: true, exts: ["ts"] })) {
  console.log(e.path, e.isDirectory ? "(dir)" : "(file)");
}
// only "*.ts" files - every directory is missing

After:

./project (dir)
./project/sub (dir)
./project/sub/b.ts (file)
./project/a.ts (file)

The fix passes undefined for exts in the includeDirs branch of both walk and walkSync. The match and skip regex filters still apply to directories. The !followSymlinks symlink emission branch is unchanged because a symlink's name still has an extension just like the target.

Tests:

  • Existing walk() / walkSync() accepts ext option as strings tests updated to include the root directory in the expected entries (the default includeDirs:true now actually yields it).
  • Two new tests pin the includeDirs:false case so the intent stays explicit.

All ext-related tests pass. The four pre-existing * regExps failures in fs/walk_test.ts reproduce on origin/main (verified by stashing the patch and re-running) so they're unrelated.

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 denoland#6736
@github-actions github-actions Bot added the fs label May 30, 2026
@codecov

codecov Bot commented May 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.57%. Comparing base (cdf74a8) to head (9fc18f1).
⚠️ Report is 18 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #7167   +/-   ##
=======================================
  Coverage   94.57%   94.57%           
=======================================
  Files         636      636           
  Lines       52142    52142           
  Branches     9401     9401           
=======================================
  Hits        49315    49315           
  Misses       2249     2249           
  Partials      578      578           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@bartlomieju bartlomieju left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, focused fix — I traced the recursion and confirmed it applies to nested directories too (each subdir becomes the root of a recursive walk() call), and the ext/regExp tests pass on the branch. Three points below; two are inline.

exts JSDoc is now slightly inaccurate (fs/walk.ts:71-77). The doc says "entries without the file extension specified by this option are excluded." That's no longer true for directories. Worth a one-line note like "Directory entries are not subject to this filter" so the docs match the new behavior.

Comment thread fs/walk.ts
// 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)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same class of bug still exists for symlinks-to-directories in the !followSymlinks branch below (fs/walk.ts:488 and :920): include(path, exts, match, skip) still applies exts to the symlink entry. A symlink pointing at a directory has no extension, so includeSymlinks: true + exts will silently drop it — exactly the issue you're fixing here.

The PR description's justification ("a symlink's name still has an extension just like the target") isn't accurate for symlinks-to-directories. Either handle it consistently here, or — if you'd rather defer it — soften that line in the description so it doesn't read as verified-correct.

Comment thread fs/walk_test.ts

Deno.test("walkSync() accepts ext option as strings (excluding period prefix)", () =>
assertWalkSyncPaths(testdataDir, "ext", [".", "y.rs", "x.ts"], {
exts: [".rs", ".ts"],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-existing copy-paste error (optional fix-along): this (excluding period prefix) test uses exts: [".rs", ".ts"] (with leading periods), which doesn't match its own name. Its async sibling above correctly uses ["rs", "ts"]. Since you're already editing this line, cheap to fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fs/walk do not include folders if exts option is given

2 participants