Skip to content

Commit

Permalink
fs: remove permissive rmdir recursive
Browse files Browse the repository at this point in the history
  • Loading branch information
aduh95 committed Feb 17, 2021
1 parent d345ac9 commit e14cb2f
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 151 deletions.
14 changes: 10 additions & 4 deletions doc/api/deprecations.md
Original file line number Diff line number Diff line change
Expand Up @@ -2668,19 +2668,25 @@ The [`crypto.Certificate()` constructor][] is deprecated. Use
### DEP0147: `fs.rmdir(path, { recursive: true })`
<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37216
description: Permissive behavior was removed, the option is still deprecated.
- version: v15.0.0
pr-url: https://github.com/nodejs/node/pull/35562
description: Runtime deprecation.
description: Runtime deprecation for permissive behavior.
- version: v14.14.0
pr-url: https://github.com/nodejs/node/pull/35579
description: Documentation-only deprecation.
-->

Type: Runtime
Type: Documentation-only

In future versions of Node.js, `fs.rmdir(path, { recursive: true })` will throw
`fs.rmdir(path, { recursive: true })`, `fs.rmdirSync(path, { recursive:true })`
and `fs.promises.rmdir(path, { recursive:true })` throws
if `path` does not exist or is a file.
Use `fs.rm(path, { recursive: true, force: true })` instead.
Use `fs.rm(path, { recursive: true, force: true })`,
`fs.rmSync(path, { recursive: true, force: true })` or
`fs.promises.rm(path, { recursive: true, force: true })` instead.

### DEP0148: Folder mappings in `"exports"` (trailing `"/"`)
<!-- YAML
Expand Down
66 changes: 45 additions & 21 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,16 @@ Renames `oldPath` to `newPath`.
<!-- YAML
added: v10.0.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37216
description: "Using `fsPromises.rmdir(path, { recursive: true })` on a `path`
that is a file is no longer permitted and results in an
`ENOENT` error on Windows and an `ENOTDIR` error on POSIX."
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37216
description: "Using `fsPromises.rmdir(path, { recursive: true })` on a `path`
that does not exist is no longer permitted and results in a
`ENOENT` error."
- version:
- v13.3.0
- v12.16.0
Expand All @@ -1069,8 +1079,8 @@ changes:
represents the number of retries. This option is ignored if the `recursive`
option is not `true`. **Default:** `0`.
* `recursive` {boolean} If `true`, perform a recursive directory removal. In
recursive mode, errors are not reported if `path` does not exist, and
operations are retried on failure. **Default:** `false`.
recursive mode, operations are retried on failure. **Default:** `false`.
**Deprecated**.
* `retryDelay` {integer} The amount of time in milliseconds to wait between
retries. This option is ignored if the `recursive` option is not `true`.
**Default:** `100`.
Expand All @@ -1082,11 +1092,8 @@ Using `fsPromises.rmdir()` on a file (not a directory) results in the
promise being rejected with an `ENOENT` error on Windows and an `ENOTDIR`
error on POSIX.
Setting `recursive` to `true` results in behavior similar to the Unix command
`rm -rf`: an error will not be raised for paths that do not exist, and paths
that represent files will be deleted. The permissive behavior of the
`recursive` option is deprecated, `ENOTDIR` and `ENOENT` will be thrown in
the future.
To get a behavior similar to the `rm -rf` Unix command, use
[`fsPromises.rm()`][] with options `{ recursive: true, force: true }`.
### `fsPromises.rm(path[, options])`
<!-- YAML
Expand Down Expand Up @@ -3131,6 +3138,16 @@ rename('oldFile.txt', 'newFile.txt', (err) => {
<!-- YAML
added: v0.0.2
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37216
description: "Using `fs.rmdir(path, { recursive: true })` on a `path` that is
a file is no longer permitted and results in an `ENOENT` error
on Windows and an `ENOTDIR` error on POSIX."
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37216
description: "Using `fs.rmdir(path, { recursive: true })` on a `path` that
does not exist is no longer permitted and results in a `ENOENT`
error."
- version:
- v13.3.0
- v12.16.0
Expand Down Expand Up @@ -3166,8 +3183,8 @@ changes:
represents the number of retries. This option is ignored if the `recursive`
option is not `true`. **Default:** `0`.
* `recursive` {boolean} If `true`, perform a recursive directory removal. In
recursive mode, errors are not reported if `path` does not exist, and
operations are retried on failure. **Default:** `false`.
recursive mode, operations are retried on failure. **Default:** `false`.
**Deprecated**.
* `retryDelay` {integer} The amount of time in milliseconds to wait between
retries. This option is ignored if the `recursive` option is not `true`.
**Default:** `100`.
Expand All @@ -3180,11 +3197,8 @@ to the completion callback.
Using `fs.rmdir()` on a file (not a directory) results in an `ENOENT` error on
Windows and an `ENOTDIR` error on POSIX.

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

### `fs.rm(path[, options], callback)`
<!-- YAML
Expand Down Expand Up @@ -4751,6 +4765,16 @@ See the POSIX rename(2) documentation for more details.
<!-- YAML
added: v0.1.21
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37216
description: "Using `fs.rmdirSync(path, { recursive: true })` on a `path`
that is a file is no longer permitted and results in an
`ENOENT` error on Windows and an `ENOTDIR` error on POSIX."
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/37216
description: "Using `fs.rmdirSync(path, { recursive: true })` on a `path`
that does not exist is no longer permitted and results in a
`ENOENT` error."
- version:
- v13.3.0
- v12.16.0
Expand Down Expand Up @@ -4778,8 +4802,8 @@ changes:
represents the number of retries. This option is ignored if the `recursive`
option is not `true`. **Default:** `0`.
* `recursive` {boolean} If `true`, perform a recursive directory removal. In
recursive mode, errors are not reported if `path` does not exist, and
operations are retried on failure. **Default:** `false`.
recursive mode, operations are retried on failure. **Default:** `false`.
**Deprecated**.
* `retryDelay` {integer} The amount of time in milliseconds to wait between
retries. This option is ignored if the `recursive` option is not `true`.
**Default:** `100`.
Expand All @@ -4789,11 +4813,8 @@ Synchronous rmdir(2). Returns `undefined`.
Using `fs.rmdirSync()` on a file (not a directory) results in an `ENOENT` error
on Windows and an `ENOTDIR` error on POSIX.
Setting `recursive` to `true` results in behavior similar to the Unix command
`rm -rf`: an error will not be raised for paths that do not exist, and paths
that represent files will be deleted. The permissive behavior of the
`recursive` option is deprecated, `ENOTDIR` and `ENOENT` will be thrown in
the future.
To get a behavior similar to the `rm -rf` Unix command, use [`fs.rmSync()`][]
with options `{ recursive: true, force: true }`.
### `fs.rmSync(path[, options])`
<!-- YAML
Expand Down Expand Up @@ -6641,6 +6662,8 @@ the file contents.
[`fs.readdirSync()`]: #fs_fs_readdirsync_path_options
[`fs.readv()`]: #fs_fs_readv_fd_buffers_position_callback
[`fs.realpath()`]: #fs_fs_realpath_path_options_callback
[`fs.rm()`]: #fs_fs_rm_path_options_callback
[`fs.rmSync()`]: #fs_fs_rmsync_path_options
[`fs.rmdir()`]: #fs_fs_rmdir_path_options_callback
[`fs.stat()`]: #fs_fs_stat_path_options_callback
[`fs.symlink()`]: #fs_fs_symlink_target_path_type_callback
Expand All @@ -6652,6 +6675,7 @@ the file contents.
[`fs.writev()`]: #fs_fs_writev_fd_buffers_position_callback
[`fsPromises.open()`]: #fs_fspromises_open_path_flags_mode
[`fsPromises.opendir()`]: #fs_fspromises_opendir_path_options
[`fsPromises.rm()`]: #fs_fspromises_rm_path_options
[`fsPromises.utimes()`]: #fs_fspromises_utimes_path_atime_mtime
[`inotify(7)`]: https://man7.org/linux/man-pages/man7/inotify.7.html
[`kqueue(2)`]: https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2
Expand Down
20 changes: 14 additions & 6 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -886,15 +886,20 @@ function rmdir(path, options, callback) {
if (options?.recursive) {
validateRmOptions(
path,
{ ...options, force: true },
{ ...options, force: false },
true,
(err, options) => {
if (err === false) {
const req = new FSReqCallback();
req.oncomplete = callback;
return binding.rmdir(path, req);
}
if (err) {
return callback(err);
}

lazyLoadRimraf();
return rimraf(path, options, callback);
rimraf(path, options, callback);
});
} else {
validateRmdirOptions(options);
Expand All @@ -908,12 +913,15 @@ function rmdirSync(path, options) {
path = getValidatedPath(path);

if (options?.recursive) {
options = validateRmOptionsSync(path, { ...options, force: true }, true);
lazyLoadRimraf();
return rimrafSync(pathModule.toNamespacedPath(path), options);
options = validateRmOptionsSync(path, { ...options, force: false }, true);
if (options !== false) {
lazyLoadRimraf();
return rimrafSync(pathModule.toNamespacedPath(path), options);
}
} else {
validateRmdirOptions(options);
}

validateRmdirOptions(options);
const ctx = { path };
binding.rmdir(pathModule.toNamespacedPath(path), undefined, ctx);
return handleErrorFromBinding(ctx);
Expand Down
5 changes: 4 additions & 1 deletion lib/internal/fs/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,10 @@ async function rmdir(path, options) {
options = validateRmdirOptions(options);

if (options.recursive) {
return rimrafPromises(path, options);
const stats = await stat(path);
if (stats.isDirectory()) {
return rimrafPromises(path, options);
}
}

return binding.rmdir(path, kUsePromises);
Expand Down
40 changes: 11 additions & 29 deletions lib/internal/fs/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -697,48 +697,45 @@ const defaultRmdirOptions = {
recursive: false,
};

const validateRmOptions = hideStackFrames((path, options, warn, callback) => {
const validateRmOptions = hideStackFrames((path, options, expectDir, cb) => {
options = validateRmdirOptions(options, defaultRmOptions);
validateBoolean(options.force, 'options.force');

lazyLoadFs().stat(path, (err, stats) => {
if (err) {
if (options.force && err.code === 'ENOENT') {
if (warn) {
emitPermissiveRmdirWarning();
}
return callback(null, options);
return cb(null, options);
}
return callback(err, options);
return cb(err, options);
}

if (warn && !stats.isDirectory()) {
emitPermissiveRmdirWarning();
if (expectDir && !stats.isDirectory()) {
return cb(false);
}

if (stats.isDirectory() && !options.recursive) {
return callback(new ERR_FS_EISDIR({
return cb(new ERR_FS_EISDIR({
code: 'EISDIR',
message: 'is a directory',
path,
syscall: 'rm',
errno: EISDIR
}));
}
return callback(null, options);
return cb(null, options);
});
});

const validateRmOptionsSync = hideStackFrames((path, options, warn) => {
const validateRmOptionsSync = hideStackFrames((path, options, expectDir) => {
options = validateRmdirOptions(options, defaultRmOptions);
validateBoolean(options.force, 'options.force');

if (!options.force || warn || !options.recursive) {
if (!options.force || expectDir || !options.recursive) {
const isDirectory = lazyLoadFs()
.statSync(path, { throwIfNoEntry: !options.force })?.isDirectory();

if (warn && !isDirectory) {
emitPermissiveRmdirWarning();
if (expectDir && !isDirectory) {
return false;
}

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

let permissiveRmdirWarned = false;

function emitPermissiveRmdirWarning() {
if (!permissiveRmdirWarned) {
process.emitWarning(
'In future versions of Node.js, fs.rmdir(path, { recursive: true }) ' +
'will throw if path does not exist or is a file. Use fs.rm(path, ' +
'{ recursive: true, force: true }) instead',
'DeprecationWarning',
'DEP0147'
);
permissiveRmdirWarned = true;
}
}

const validateRmdirOptions = hideStackFrames(
(options, defaults = defaultRmdirOptions) => {
if (options === undefined)
Expand Down
19 changes: 0 additions & 19 deletions test/parallel/test-fs-rmdir-recursive-sync-warns-not-found.js

This file was deleted.

21 changes: 0 additions & 21 deletions test/parallel/test-fs-rmdir-recursive-sync-warns-on-file.js

This file was deleted.

36 changes: 36 additions & 0 deletions test/parallel/test-fs-rmdir-recursive-throws-not-found.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict';
const common = require('../common');
const tmpdir = require('../common/tmpdir');
const assert = require('assert');
const fs = require('fs');
const path = require('path');

tmpdir.refresh();

{
assert.throws(
() =>
fs.rmdirSync(path.join(tmpdir.path, 'noexist.txt'), { recursive: true }),
{
code: 'ENOENT',
}
);
}
{
fs.rmdir(
path.join(tmpdir.path, 'noexist.txt'),
{ recursive: true },
common.mustCall((err) => {
assert.strictEqual(err.code, 'ENOENT');
})
);
}
{
assert.rejects(
() => fs.promises.rmdir(path.join(tmpdir.path, 'noexist.txt'),
{ recursive: true }),
{
code: 'ENOENT',
}
).then(common.mustCall());
}
Loading

0 comments on commit e14cb2f

Please sign in to comment.