Skip to content

fix(ext/node): throw ERR_INVALID_ARG_VALUE when rmdir gets options.recursive#33565

Merged
bartlomieju merged 2 commits intodenoland:mainfrom
nathanwhitbot:fix/node-compat-iter41
Apr 27, 2026
Merged

fix(ext/node): throw ERR_INVALID_ARG_VALUE when rmdir gets options.recursive#33565
bartlomieju merged 2 commits intodenoland:mainfrom
nathanwhitbot:fix/node-compat-iter41

Conversation

@nathanwhitbot
Copy link
Copy Markdown
Contributor

Summary

Node deprecated and then removed the recursive option on fs.rmdir / fs.rmdirSync; passing it now throws ERR_INVALID_ARG_VALUE with the message "is no longer supported" (see lib/fs.js). Deno was still emitting a deprecation warning and silently routing through the recursive remove path, which hid the divergence — rm/rmSync are the supported replacement.

This patch:

  • Throws ERR_INVALID_ARG_VALUE from fs.rmdir, fs.rmdirSync, and (via promisify) fs.promises.rmdir when options.recursive is provided.
  • Drops the now-unused rmdirRecursive helper plus the emitRecursiveRmdirWarning, RmOptions, and ERR_FS_RMDIR_ENOTDIR imports it depended on.

Enables parallel/test-fs-rmdir-recursive-error.js in the Node compat suite.

Test plan

  • cargo test --test node_compat -- test-fs-rmdir-recursive-error (was failing on main, passes here)
  • cargo build --bin deno clean
  • tools/format.js and tools/lint.js --js clean
  • Sibling enabled rmdir/rm tests still pass: test-fs-rm, test-fs-rmSync-special-char, test-fs-rmdir-throws-not-found, test-fs-rmdir-throws-on-file, test-fs-rmdir-type-check.

…cursive

Match Node's `lib/fs.js` rmdir/rmdirSync — the `recursive` option was
deprecated in Node and removed; passing it now throws
`ERR_INVALID_ARG_VALUE` with "is no longer supported". We previously
emitted a deprecation warning and silently routed through the recursive
remove path, hiding the divergence.

Also drop the now-unused `rmdirRecursive` helper and stale imports
(`emitRecursiveRmdirWarning`, `RmOptions`, `ERR_FS_RMDIR_ENOTDIR`).

Enables `parallel/test-fs-rmdir-recursive-error.js`.
Copy link
Copy Markdown
Contributor Author

@nathanwhitbot nathanwhitbot left a comment

Choose a reason for hiding this comment

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

Faithful port of Node's lib/fs.js L1115–1161:

  • rmdir (async): same order as Node — typeof options === 'function'
    options?.recursive !== undefined throw → validateFunction (Node uses
    makeCallback) → getValidatedPath…validateRmdirOptions → op. ✓
  • rmdirSync: also matches Node's order — path validation first, then the
    recursive-not-supported throw, then validateRmdirOptions. ✓
  • The options.recursive === false path still throws (because the check is
    !== undefined), matching Node. ✓
  • Cleanups (rmdirRecursive helper + the emitRecursiveRmdirWarning,
    RmOptions, ERR_FS_RMDIR_ENOTDIR imports) are correct — those were the
    only callers and they're gone now.

fs.promises.rmdir going through promisify(rmdir) works for this
specifically because the throw happens inside the Promise executor that
util.promisify builds, so it surfaces as a rejected Promise — same shape
as Node's hand-rolled async function rmdir. Worth noting for future
divergences (fs.promises.rmdir is async upstream, not promisified), but
no behavior gap today.

LGTM.

Copy link
Copy Markdown
Contributor

@fibibot fibibot left a comment

Choose a reason for hiding this comment

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

The substantive change is right — Node deprecated and removed the recursive option on fs.rmdir / fs.rmdirSync, and the verbatim message + code (ERR_INVALID_ARG_VALUE, 'is no longer supported') match lib/fs.js upstream. The validation order (recursive-throw before path/callback validation in async, after path validation in sync) also matches Node. The rmdirRecursive helper and the now-unused imports are correctly removed.

But CI is failing on two concrete issues that block merge:

  1. tests/unit_node/_fs/_fs_rmdir_test.ts has two tests that exercise the now-removed recursive path. "ASYNC: removing non-empty folder" and "SYNC: removing non-empty folder" both call rmdir(dir, { recursive: true }, ...) / rmdirSync(dir, { recursive: true }) and assert success. Those need to be migrated to rm/rmSync (which is the supported replacement) or deleted, since the behavior they test is the one this PR is removing. Inline detail.

  2. tools/lint_plugins/no_deno_api_in_polyfills.ts EXPECTED_VIOLATIONS entry for ext/node/polyfills/fs.ts needs to drop from 53 to 51 — removing rmdirRecursive and the recursive branch in rmdirSync eliminates two Deno.remove(...) calls. The lint plugin checks actual === expected and is now off by -2.

Both are mechanical fixes but the PR will stay red until they land. Sibling tests the body lists pass because they don't use recursive: true; the _fs_rmdir_test.ts cases are the only ones that hit the removed path.

Comment thread ext/node/polyfills/fs.ts
Comment thread ext/node/polyfills/fs.ts
- Migrate the two non-empty-folder removal tests off rmdir({recursive})
  (which now throws ERR_INVALID_ARG_VALUE) to rm/rmSync({recursive}),
  per fibibot's review on denoland#33565.
- Update tools/lint_plugins/no_deno_api_in_polyfills.ts EXPECTED count
  for fs.ts from 53 to 51 — removing rmdirRecursive + the recursive
  branch in rmdirSync drops two Deno.remove(...) calls.
@bartlomieju bartlomieju merged commit d6760e9 into denoland:main Apr 27, 2026
112 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants