Skip to content

Commit 5a19fd4

Browse files
committed
fixup! fs: remove permissive rmdir recursive
1 parent 96f79ed commit 5a19fd4

7 files changed

Lines changed: 92 additions & 33 deletions

File tree

doc/api/deprecations.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2664,7 +2664,7 @@ The [`crypto.Certificate()` constructor][] is deprecated. Use
26642664
<!-- YAML
26652665
changes:
26662666
- version: REPLACEME
2667-
pr-url: https://github.com/nodejs/node/pull/
2667+
pr-url: https://github.com/nodejs/node/pull/37216
26682668
description: End-of-Life.
26692669
- version: v15.0.0
26702670
pr-url: https://github.com/nodejs/node/pull/35562
@@ -2676,9 +2676,12 @@ changes:
26762676

26772677
Type: End-of-Life
26782678

2679-
`fs.rmdir(path, { recursive: true })` will throw
2679+
`fs.rmdir(path, { recursive: true })`, `fs.rmdirSync(path, { recursive:true })`
2680+
and `fs.promises.rmdir(path, { recursive:true })` will throw
26802681
if `path` does not exist or is a file.
2681-
Use `fs.rm(path, { recursive: true, force: true })` instead.
2682+
Use `fs.rm(path, { recursive: true, force: true })`,
2683+
`fs.rmSync(path, { recursive: true, force: true })` or
2684+
`fs.promises.rm(path, { recursive: true, force: true })` instead.
26822685

26832686
### DEP0148: Folder mappings in `"exports"` (trailing `"/"`)
26842687
<!-- YAML

doc/api/fs.md

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3584,15 +3584,15 @@ Synchronous rename(2). Returns `undefined`.
35843584
added: v0.0.2
35853585
changes:
35863586
- version: REPLACEME
3587-
pr-url: https://github.com/nodejs/node/pull/
3588-
description: Using `fs.rmdir(path, { recursive: true })` on a `path` that is
3587+
pr-url: https://github.com/nodejs/node/pull/37216
3588+
description: "Using `fs.rmdir(path, { recursive: true })` on a `path` that is
35893589
a file is no longer permitted and results in an `ENOENT` error
3590-
on Windows and an `ENOTDIR` error on POSIX.
3590+
on Windows and an `ENOTDIR` error on POSIX."
35913591
- version: REPLACEME
3592-
pr-url: https://github.com/nodejs/node/pull/
3593-
description: Using `fs.rmdir(path, { recursive: true })` on a `path` that
3592+
pr-url: https://github.com/nodejs/node/pull/37216
3593+
description: "Using `fs.rmdir(path, { recursive: true })` on a `path` that
35943594
does not exist is no longer permitted and results in a `ENOENT`
3595-
error.
3595+
error."
35963596
- version:
35973597
- v13.3.0
35983598
- v12.16.0
@@ -3649,15 +3649,15 @@ with options `{ recursive: true, force: true }`.
36493649
added: v0.1.21
36503650
changes:
36513651
- version: REPLACEME
3652-
pr-url: https://github.com/nodejs/node/pull/
3653-
description: Using `fs.rmdirSync(path, { recursive: true })` on a `path`
3652+
pr-url: https://github.com/nodejs/node/pull/37216
3653+
description: "Using `fs.rmdirSync(path, { recursive: true })` on a `path`
36543654
that is a file is no longer permitted and results in an
3655-
`ENOENT` error on Windows and an `ENOTDIR` error on POSIX.
3655+
`ENOENT` error on Windows and an `ENOTDIR` error on POSIX."
36563656
- version: REPLACEME
3657-
pr-url: https://github.com/nodejs/node/pull/
3658-
description: Using `fs.rmdirSync(path, { recursive: true })` on a `path`
3657+
pr-url: https://github.com/nodejs/node/pull/37216
3658+
description: "Using `fs.rmdirSync(path, { recursive: true })` on a `path`
36593659
that does not exist is no longer permitted and results in a
3660-
`ENOENT` error.
3660+
`ENOENT` error."
36613661
- version:
36623662
- v13.3.0
36633663
- v12.16.0
@@ -5677,6 +5677,16 @@ upon success.
56775677
<!-- YAML
56785678
added: v10.0.0
56795679
changes:
5680+
- version: REPLACEME
5681+
pr-url: https://github.com/nodejs/node/pull/37216
5682+
description: "Using `fsPromises.rmdir(path, { recursive: true })` on a `path`
5683+
that is a file is no longer permitted and results in an
5684+
`ENOENT` error on Windows and an `ENOTDIR` error on POSIX."
5685+
- version: REPLACEME
5686+
pr-url: https://github.com/nodejs/node/pull/37216
5687+
description: "Using `fsPromises.rmdir(path, { recursive: true })` on a `path`
5688+
that does not exist is no longer permitted and results in a
5689+
`ENOENT` error."
56805690
- version:
56815691
- v13.3.0
56825692
- v12.16.0
@@ -5700,8 +5710,7 @@ changes:
57005710
represents the number of retries. This option is ignored if the `recursive`
57015711
option is not `true`. **Default:** `0`.
57025712
* `recursive` {boolean} If `true`, perform a recursive directory removal. In
5703-
recursive mode, errors are not reported if `path` does not exist, and
5704-
operations are retried on failure. **Default:** `false`.
5713+
recursive mode, operations are retried on failure. **Default:** `false`.
57055714
* `retryDelay` {integer} The amount of time in milliseconds to wait between
57065715
retries. This option is ignored if the `recursive` option is not `true`.
57075716
**Default:** `100`.
@@ -5714,11 +5723,8 @@ Using `fsPromises.rmdir()` on a file (not a directory) results in the
57145723
`Promise` being rejected with an `ENOENT` error on Windows and an `ENOTDIR`
57155724
error on POSIX.
57165725

5717-
Setting `recursive` to `true` results in behavior similar to the Unix command
5718-
`rm -rf`: an error will not be raised for paths that do not exist, and paths
5719-
that represent files will be deleted. The permissive behavior of the
5720-
`recursive` option is deprecated, `ENOTDIR` and `ENOENT` will be thrown in
5721-
the future.
5726+
To get a behavior similar to the `rm -rf` Unix command, use
5727+
[`fsPromises.rm()`][] with options `{ recursive: true, force: true }`.
57225728

57235729
### `fsPromises.rm(path[, options])`
57245730
<!-- YAML
@@ -6312,6 +6318,8 @@ the file contents.
63126318
[`fs.readdirSync()`]: #fs_fs_readdirsync_path_options
63136319
[`fs.readv()`]: #fs_fs_readv_fd_buffers_position_callback
63146320
[`fs.realpath()`]: #fs_fs_realpath_path_options_callback
6321+
[`fs.rm()`]: #fs_fs_rm_path_options_callback
6322+
[`fs.rmSync()`]: #fs_fs_rmsync_path_options
63156323
[`fs.rmdir()`]: #fs_fs_rmdir_path_options_callback
63166324
[`fs.stat()`]: #fs_fs_stat_path_options_callback
63176325
[`fs.symlink()`]: #fs_fs_symlink_target_path_type_callback
@@ -6323,6 +6331,7 @@ the file contents.
63236331
[`fs.writev()`]: #fs_fs_writev_fd_buffers_position_callback
63246332
[`fsPromises.open()`]: #fs_fspromises_open_path_flags_mode
63256333
[`fsPromises.opendir()`]: #fs_fspromises_opendir_path_options
6334+
[`fsPromises.rm()`]: #fs_fspromises_rm_path_options
63266335
[`fsPromises.utimes()`]: #fs_fspromises_utimes_path_atime_mtime
63276336
[`inotify(7)`]: https://man7.org/linux/man-pages/man7/inotify.7.html
63286337
[`kqueue(2)`]: https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2

lib/fs.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -881,15 +881,20 @@ function rmdir(path, options, callback) {
881881
if (options?.recursive) {
882882
validateRmOptions(
883883
path,
884-
{ ...options, force: true },
884+
{ ...options, force: false },
885885
true,
886886
(err, options) => {
887+
if (err === false) {
888+
const req = new FSReqCallback();
889+
req.oncomplete = callback;
890+
return binding.rmdir(path, req);
891+
}
887892
if (err) {
888893
return callback(err);
889894
}
890895

891896
lazyLoadRimraf();
892-
return rimraf(path, options, callback);
897+
rimraf(path, options, callback);
893898
});
894899
} else {
895900
validateRmdirOptions(options);
@@ -903,12 +908,15 @@ function rmdirSync(path, options) {
903908
path = getValidatedPath(path);
904909

905910
if (options?.recursive) {
906-
options = validateRmOptionsSync(path, { ...options, force: true }, true);
907-
lazyLoadRimraf();
908-
return rimrafSync(pathModule.toNamespacedPath(path), options);
911+
options = validateRmOptionsSync(path, { ...options, force: false }, true);
912+
if (options !== false) {
913+
lazyLoadRimraf();
914+
return rimrafSync(pathModule.toNamespacedPath(path), options);
915+
}
916+
} else {
917+
validateRmdirOptions(options);
909918
}
910919

911-
validateRmdirOptions(options);
912920
const ctx = { path };
913921
binding.rmdir(pathModule.toNamespacedPath(path), undefined, ctx);
914922
return handleErrorFromBinding(ctx);

lib/internal/fs/promises.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,10 @@ async function rmdir(path, options) {
488488
options = validateRmdirOptions(options);
489489

490490
if (options.recursive) {
491-
return rimrafPromises(path, options);
491+
const stats = await stat(path);
492+
if (stats.isDirectory()) {
493+
return rimrafPromises(path, options);
494+
}
492495
}
493496

494497
return binding.rmdir(path, kUsePromises);

lib/internal/fs/utils.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ const validateRmOptions = hideStackFrames((path, options, expectDir, cb) => {
699699
}
700700

701701
if (expectDir && !stats.isDirectory()) {
702-
return cb(new Error('Not a directory'));
702+
return cb(false);
703703
}
704704

705705
if (stats.isDirectory() && !options.recursive) {
@@ -724,7 +724,7 @@ const validateRmOptionsSync = hideStackFrames((path, options, expectDir) => {
724724
.statSync(path, { throwIfNoEntry: !options.force })?.isDirectory();
725725

726726
if (expectDir && !isDirectory) {
727-
throw new Error('Not a directory');
727+
return false;
728728
}
729729

730730
if (isDirectory && !options.recursive) {

test/parallel/test-fs-rmdir-recursive-warns-not-found.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,32 @@ const path = require('path');
77

88
tmpdir.refresh();
99

10+
{
11+
assert.throws(
12+
() =>
13+
fs.rmdirSync(path.join(tmpdir.path, 'noexist.txt'), { recursive: true }),
14+
{
15+
code: 'ENOENT',
16+
}
17+
);
18+
}
1019
{
1120
fs.rmdir(
1221
path.join(tmpdir.path, 'noexist.txt'),
1322
{ recursive: true },
1423
common.mustCall((err) => {
15-
assert.match(err.message, /Not a directory/);
24+
assert.throws(() => { throw err; }, {
25+
code: 'ENOENT',
26+
});
1627
})
1728
);
1829
}
30+
{
31+
assert.rejects(
32+
() => fs.promises.rmdir(path.join(tmpdir.path, 'noexist.txt'),
33+
{ recursive: true }),
34+
{
35+
code: 'ENOENT',
36+
}
37+
).then(common.mustCall());
38+
}

test/parallel/test-fs-rmdir-recursive-warns-on-file.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,26 @@ const path = require('path');
77

88
tmpdir.refresh();
99

10+
{
11+
const filePath = path.join(tmpdir.path, 'rmdir-recursive.txt');
12+
fs.writeFileSync(filePath, '');
13+
assert.throws(() => fs.rmdirSync(filePath, { recursive: true }), {
14+
code: 'ENOTDIR',
15+
});
16+
}
1017
{
1118
const filePath = path.join(tmpdir.path, 'rmdir-recursive.txt');
1219
fs.writeFileSync(filePath, '');
1320
fs.rmdir(filePath, { recursive: true }, common.mustCall((err) => {
14-
assert.match(err.message, /Not a directory/);
21+
assert.throws(() => { throw err; }, {
22+
code: 'ENOTDIR',
23+
});
1524
}));
1625
}
26+
{
27+
const filePath = path.join(tmpdir.path, 'rmdir-recursive.txt');
28+
fs.writeFileSync(filePath, '');
29+
assert.rejects(() => fs.promises.rmdir(filePath, { recursive: true }), {
30+
code: 'ENOTDIR',
31+
}).then(common.mustCall());
32+
}

0 commit comments

Comments
 (0)