Skip to content

Commit e14cb2f

Browse files
committed
fs: remove permissive rmdir recursive
1 parent d345ac9 commit e14cb2f

12 files changed

+159
-151
lines changed

doc/api/deprecations.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2668,19 +2668,25 @@ The [`crypto.Certificate()` constructor][] is deprecated. Use
26682668
### DEP0147: `fs.rmdir(path, { recursive: true })`
26692669
<!-- YAML
26702670
changes:
2671+
- version: REPLACEME
2672+
pr-url: https://github.com/nodejs/node/pull/37216
2673+
description: Permissive behavior was removed, the option is still deprecated.
26712674
- version: v15.0.0
26722675
pr-url: https://github.com/nodejs/node/pull/35562
2673-
description: Runtime deprecation.
2676+
description: Runtime deprecation for permissive behavior.
26742677
- version: v14.14.0
26752678
pr-url: https://github.com/nodejs/node/pull/35579
26762679
description: Documentation-only deprecation.
26772680
-->
26782681

2679-
Type: Runtime
2682+
Type: Documentation-only
26802683

2681-
In future versions of Node.js, `fs.rmdir(path, { recursive: true })` will throw
2684+
`fs.rmdir(path, { recursive: true })`, `fs.rmdirSync(path, { recursive:true })`
2685+
and `fs.promises.rmdir(path, { recursive:true })` throws
26822686
if `path` does not exist or is a file.
2683-
Use `fs.rm(path, { recursive: true, force: true })` instead.
2687+
Use `fs.rm(path, { recursive: true, force: true })`,
2688+
`fs.rmSync(path, { recursive: true, force: true })` or
2689+
`fs.promises.rm(path, { recursive: true, force: true })` instead.
26842690

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

doc/api/fs.md

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,16 @@ Renames `oldPath` to `newPath`.
10461046
<!-- YAML
10471047
added: v10.0.0
10481048
changes:
1049+
- version: REPLACEME
1050+
pr-url: https://github.com/nodejs/node/pull/37216
1051+
description: "Using `fsPromises.rmdir(path, { recursive: true })` on a `path`
1052+
that is a file is no longer permitted and results in an
1053+
`ENOENT` error on Windows and an `ENOTDIR` error on POSIX."
1054+
- version: REPLACEME
1055+
pr-url: https://github.com/nodejs/node/pull/37216
1056+
description: "Using `fsPromises.rmdir(path, { recursive: true })` on a `path`
1057+
that does not exist is no longer permitted and results in a
1058+
`ENOENT` error."
10491059
- version:
10501060
- v13.3.0
10511061
- v12.16.0
@@ -1069,8 +1079,8 @@ changes:
10691079
represents the number of retries. This option is ignored if the `recursive`
10701080
option is not `true`. **Default:** `0`.
10711081
* `recursive` {boolean} If `true`, perform a recursive directory removal. In
1072-
recursive mode, errors are not reported if `path` does not exist, and
1073-
operations are retried on failure. **Default:** `false`.
1082+
recursive mode, operations are retried on failure. **Default:** `false`.
1083+
**Deprecated**.
10741084
* `retryDelay` {integer} The amount of time in milliseconds to wait between
10751085
retries. This option is ignored if the `recursive` option is not `true`.
10761086
**Default:** `100`.
@@ -1082,11 +1092,8 @@ Using `fsPromises.rmdir()` on a file (not a directory) results in the
10821092
promise being rejected with an `ENOENT` error on Windows and an `ENOTDIR`
10831093
error on POSIX.
10841094
1085-
Setting `recursive` to `true` results in behavior similar to the Unix command
1086-
`rm -rf`: an error will not be raised for paths that do not exist, and paths
1087-
that represent files will be deleted. The permissive behavior of the
1088-
`recursive` option is deprecated, `ENOTDIR` and `ENOENT` will be thrown in
1089-
the future.
1095+
To get a behavior similar to the `rm -rf` Unix command, use
1096+
[`fsPromises.rm()`][] with options `{ recursive: true, force: true }`.
10901097
10911098
### `fsPromises.rm(path[, options])`
10921099
<!-- YAML
@@ -3131,6 +3138,16 @@ rename('oldFile.txt', 'newFile.txt', (err) => {
31313138
<!-- YAML
31323139
added: v0.0.2
31333140
changes:
3141+
- version: REPLACEME
3142+
pr-url: https://github.com/nodejs/node/pull/37216
3143+
description: "Using `fs.rmdir(path, { recursive: true })` on a `path` that is
3144+
a file is no longer permitted and results in an `ENOENT` error
3145+
on Windows and an `ENOTDIR` error on POSIX."
3146+
- version: REPLACEME
3147+
pr-url: https://github.com/nodejs/node/pull/37216
3148+
description: "Using `fs.rmdir(path, { recursive: true })` on a `path` that
3149+
does not exist is no longer permitted and results in a `ENOENT`
3150+
error."
31343151
- version:
31353152
- v13.3.0
31363153
- v12.16.0
@@ -3166,8 +3183,8 @@ changes:
31663183
represents the number of retries. This option is ignored if the `recursive`
31673184
option is not `true`. **Default:** `0`.
31683185
* `recursive` {boolean} If `true`, perform a recursive directory removal. In
3169-
recursive mode, errors are not reported if `path` does not exist, and
3170-
operations are retried on failure. **Default:** `false`.
3186+
recursive mode, operations are retried on failure. **Default:** `false`.
3187+
**Deprecated**.
31713188
* `retryDelay` {integer} The amount of time in milliseconds to wait between
31723189
retries. This option is ignored if the `recursive` option is not `true`.
31733190
**Default:** `100`.
@@ -3180,11 +3197,8 @@ to the completion callback.
31803197
Using `fs.rmdir()` on a file (not a directory) results in an `ENOENT` error on
31813198
Windows and an `ENOTDIR` error on POSIX.
31823199

3183-
Setting `recursive` to `true` results in behavior similar to the Unix command
3184-
`rm -rf`: an error will not be raised for paths that do not exist, and paths
3185-
that represent files will be deleted. The permissive behavior of the
3186-
`recursive` option is deprecated, `ENOTDIR` and `ENOENT` will be thrown in
3187-
the future.
3200+
To get a behavior similar to the `rm -rf` Unix command, use [`fs.rm()`][]
3201+
with options `{ recursive: true, force: true }`.
31883202

31893203
### `fs.rm(path[, options], callback)`
31903204
<!-- YAML
@@ -4751,6 +4765,16 @@ See the POSIX rename(2) documentation for more details.
47514765
<!-- YAML
47524766
added: v0.1.21
47534767
changes:
4768+
- version: REPLACEME
4769+
pr-url: https://github.com/nodejs/node/pull/37216
4770+
description: "Using `fs.rmdirSync(path, { recursive: true })` on a `path`
4771+
that is a file is no longer permitted and results in an
4772+
`ENOENT` error on Windows and an `ENOTDIR` error on POSIX."
4773+
- version: REPLACEME
4774+
pr-url: https://github.com/nodejs/node/pull/37216
4775+
description: "Using `fs.rmdirSync(path, { recursive: true })` on a `path`
4776+
that does not exist is no longer permitted and results in a
4777+
`ENOENT` error."
47544778
- version:
47554779
- v13.3.0
47564780
- v12.16.0
@@ -4778,8 +4802,8 @@ changes:
47784802
represents the number of retries. This option is ignored if the `recursive`
47794803
option is not `true`. **Default:** `0`.
47804804
* `recursive` {boolean} If `true`, perform a recursive directory removal. In
4781-
recursive mode, errors are not reported if `path` does not exist, and
4782-
operations are retried on failure. **Default:** `false`.
4805+
recursive mode, operations are retried on failure. **Default:** `false`.
4806+
**Deprecated**.
47834807
* `retryDelay` {integer} The amount of time in milliseconds to wait between
47844808
retries. This option is ignored if the `recursive` option is not `true`.
47854809
**Default:** `100`.
@@ -4789,11 +4813,8 @@ Synchronous rmdir(2). Returns `undefined`.
47894813
Using `fs.rmdirSync()` on a file (not a directory) results in an `ENOENT` error
47904814
on Windows and an `ENOTDIR` error on POSIX.
47914815
4792-
Setting `recursive` to `true` results in behavior similar to the Unix command
4793-
`rm -rf`: an error will not be raised for paths that do not exist, and paths
4794-
that represent files will be deleted. The permissive behavior of the
4795-
`recursive` option is deprecated, `ENOTDIR` and `ENOENT` will be thrown in
4796-
the future.
4816+
To get a behavior similar to the `rm -rf` Unix command, use [`fs.rmSync()`][]
4817+
with options `{ recursive: true, force: true }`.
47974818
47984819
### `fs.rmSync(path[, options])`
47994820
<!-- YAML
@@ -6641,6 +6662,8 @@ the file contents.
66416662
[`fs.readdirSync()`]: #fs_fs_readdirsync_path_options
66426663
[`fs.readv()`]: #fs_fs_readv_fd_buffers_position_callback
66436664
[`fs.realpath()`]: #fs_fs_realpath_path_options_callback
6665+
[`fs.rm()`]: #fs_fs_rm_path_options_callback
6666+
[`fs.rmSync()`]: #fs_fs_rmsync_path_options
66446667
[`fs.rmdir()`]: #fs_fs_rmdir_path_options_callback
66456668
[`fs.stat()`]: #fs_fs_stat_path_options_callback
66466669
[`fs.symlink()`]: #fs_fs_symlink_target_path_type_callback
@@ -6652,6 +6675,7 @@ the file contents.
66526675
[`fs.writev()`]: #fs_fs_writev_fd_buffers_position_callback
66536676
[`fsPromises.open()`]: #fs_fspromises_open_path_flags_mode
66546677
[`fsPromises.opendir()`]: #fs_fspromises_opendir_path_options
6678+
[`fsPromises.rm()`]: #fs_fspromises_rm_path_options
66556679
[`fsPromises.utimes()`]: #fs_fspromises_utimes_path_atime_mtime
66566680
[`inotify(7)`]: https://man7.org/linux/man-pages/man7/inotify.7.html
66576681
[`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
@@ -886,15 +886,20 @@ function rmdir(path, options, callback) {
886886
if (options?.recursive) {
887887
validateRmOptions(
888888
path,
889-
{ ...options, force: true },
889+
{ ...options, force: false },
890890
true,
891891
(err, options) => {
892+
if (err === false) {
893+
const req = new FSReqCallback();
894+
req.oncomplete = callback;
895+
return binding.rmdir(path, req);
896+
}
892897
if (err) {
893898
return callback(err);
894899
}
895900

896901
lazyLoadRimraf();
897-
return rimraf(path, options, callback);
902+
rimraf(path, options, callback);
898903
});
899904
} else {
900905
validateRmdirOptions(options);
@@ -908,12 +913,15 @@ function rmdirSync(path, options) {
908913
path = getValidatedPath(path);
909914

910915
if (options?.recursive) {
911-
options = validateRmOptionsSync(path, { ...options, force: true }, true);
912-
lazyLoadRimraf();
913-
return rimrafSync(pathModule.toNamespacedPath(path), options);
916+
options = validateRmOptionsSync(path, { ...options, force: false }, true);
917+
if (options !== false) {
918+
lazyLoadRimraf();
919+
return rimrafSync(pathModule.toNamespacedPath(path), options);
920+
}
921+
} else {
922+
validateRmdirOptions(options);
914923
}
915924

916-
validateRmdirOptions(options);
917925
const ctx = { path };
918926
binding.rmdir(pathModule.toNamespacedPath(path), undefined, ctx);
919927
return handleErrorFromBinding(ctx);

lib/internal/fs/promises.js

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

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

495498
return binding.rmdir(path, kUsePromises);

lib/internal/fs/utils.js

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -697,48 +697,45 @@ const defaultRmdirOptions = {
697697
recursive: false,
698698
};
699699

700-
const validateRmOptions = hideStackFrames((path, options, warn, callback) => {
700+
const validateRmOptions = hideStackFrames((path, options, expectDir, cb) => {
701701
options = validateRmdirOptions(options, defaultRmOptions);
702702
validateBoolean(options.force, 'options.force');
703703

704704
lazyLoadFs().stat(path, (err, stats) => {
705705
if (err) {
706706
if (options.force && err.code === 'ENOENT') {
707-
if (warn) {
708-
emitPermissiveRmdirWarning();
709-
}
710-
return callback(null, options);
707+
return cb(null, options);
711708
}
712-
return callback(err, options);
709+
return cb(err, options);
713710
}
714711

715-
if (warn && !stats.isDirectory()) {
716-
emitPermissiveRmdirWarning();
712+
if (expectDir && !stats.isDirectory()) {
713+
return cb(false);
717714
}
718715

719716
if (stats.isDirectory() && !options.recursive) {
720-
return callback(new ERR_FS_EISDIR({
717+
return cb(new ERR_FS_EISDIR({
721718
code: 'EISDIR',
722719
message: 'is a directory',
723720
path,
724721
syscall: 'rm',
725722
errno: EISDIR
726723
}));
727724
}
728-
return callback(null, options);
725+
return cb(null, options);
729726
});
730727
});
731728

732-
const validateRmOptionsSync = hideStackFrames((path, options, warn) => {
729+
const validateRmOptionsSync = hideStackFrames((path, options, expectDir) => {
733730
options = validateRmdirOptions(options, defaultRmOptions);
734731
validateBoolean(options.force, 'options.force');
735732

736-
if (!options.force || warn || !options.recursive) {
733+
if (!options.force || expectDir || !options.recursive) {
737734
const isDirectory = lazyLoadFs()
738735
.statSync(path, { throwIfNoEntry: !options.force })?.isDirectory();
739736

740-
if (warn && !isDirectory) {
741-
emitPermissiveRmdirWarning();
737+
if (expectDir && !isDirectory) {
738+
return false;
742739
}
743740

744741
if (isDirectory && !options.recursive) {
@@ -755,21 +752,6 @@ const validateRmOptionsSync = hideStackFrames((path, options, warn) => {
755752
return options;
756753
});
757754

758-
let permissiveRmdirWarned = false;
759-
760-
function emitPermissiveRmdirWarning() {
761-
if (!permissiveRmdirWarned) {
762-
process.emitWarning(
763-
'In future versions of Node.js, fs.rmdir(path, { recursive: true }) ' +
764-
'will throw if path does not exist or is a file. Use fs.rm(path, ' +
765-
'{ recursive: true, force: true }) instead',
766-
'DeprecationWarning',
767-
'DEP0147'
768-
);
769-
permissiveRmdirWarned = true;
770-
}
771-
}
772-
773755
const validateRmdirOptions = hideStackFrames(
774756
(options, defaults = defaultRmdirOptions) => {
775757
if (options === undefined)

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

Lines changed: 0 additions & 19 deletions
This file was deleted.

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

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict';
2+
const common = require('../common');
3+
const tmpdir = require('../common/tmpdir');
4+
const assert = require('assert');
5+
const fs = require('fs');
6+
const path = require('path');
7+
8+
tmpdir.refresh();
9+
10+
{
11+
assert.throws(
12+
() =>
13+
fs.rmdirSync(path.join(tmpdir.path, 'noexist.txt'), { recursive: true }),
14+
{
15+
code: 'ENOENT',
16+
}
17+
);
18+
}
19+
{
20+
fs.rmdir(
21+
path.join(tmpdir.path, 'noexist.txt'),
22+
{ recursive: true },
23+
common.mustCall((err) => {
24+
assert.strictEqual(err.code, 'ENOENT');
25+
})
26+
);
27+
}
28+
{
29+
assert.rejects(
30+
() => fs.promises.rmdir(path.join(tmpdir.path, 'noexist.txt'),
31+
{ recursive: true }),
32+
{
33+
code: 'ENOENT',
34+
}
35+
).then(common.mustCall());
36+
}

0 commit comments

Comments
 (0)