summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/fs.md101
-rw-r--r--lib/fs.js23
-rw-r--r--lib/internal/fs/promises.js12
-rw-r--r--lib/internal/fs/utils.js117
-rw-r--r--src/node_constants.cc10
-rw-r--r--src/node_file.cc217
-rw-r--r--test/parallel/test-fs-readdir-types.js95
-rw-r--r--tools/doc/type-parser.js1
8 files changed, 524 insertions, 52 deletions
diff --git a/doc/api/fs.md b/doc/api/fs.md
index 1f71a537e3..01023eb56e 100644
--- a/doc/api/fs.md
+++ b/doc/api/fs.md
@@ -283,6 +283,92 @@ synchronous use libuv's threadpool, which can have surprising and negative
performance implications for some applications. See the
[`UV_THREADPOOL_SIZE`][] documentation for more information.
+## Class: fs.Dirent
+<!-- YAML
+added: REPLACEME
+-->
+
+When [`fs.readdir()`][] or [`fs.readdirSync()`][] is called with the
+`withFileTypes` option set to `true`, the resulting array is filled with
+`fs.Dirent` objects, rather than strings or `Buffers`.
+
+### dirent.isBlockDevice()
+<!-- YAML
+added: REPLACEME
+-->
+
+* Returns: {boolean}
+
+Returns `true` if the `fs.Dirent` object describes a block device.
+
+### dirent.isCharacterDevice()
+<!-- YAML
+added: REPLACEME
+-->
+
+* Returns: {boolean}
+
+Returns `true` if the `fs.Dirent` object describes a character device.
+
+### dirent.isDirectory()
+<!-- YAML
+added: REPLACEME
+-->
+
+* Returns: {boolean}
+
+Returns `true` if the `fs.Dirent` object describes a file system
+directory.
+
+### dirent.isFIFO()
+<!-- YAML
+added: REPLACEME
+-->
+
+* Returns: {boolean}
+
+Returns `true` if the `fs.Dirent` object describes a first-in-first-out
+(FIFO) pipe.
+
+### dirent.isFile()
+<!-- YAML
+added: REPLACEME
+-->
+
+* Returns: {boolean}
+
+Returns `true` if the `fs.Dirent` object describes a regular file.
+
+### dirent.isSocket()
+<!-- YAML
+added: REPLACEME
+-->
+
+* Returns: {boolean}
+
+Returns `true` if the `fs.Dirent` object describes a socket.
+
+### dirent.isSymbolicLink()
+<!-- YAML
+added: REPLACEME
+-->
+
+* Returns: {boolean}
+
+Returns `true` if the `fs.Dirent` object describes a symbolic link.
+
+
+### dirent.name
+<!-- YAML
+added: REPLACEME
+-->
+
+* {string|Buffer}
+
+The file name that this `fs.Dirent` object refers to. The type of this
+value is determined by the `options.encoding` passed to [`fs.readdir()`][] or
+[`fs.readdirSync()`][].
+
## Class: fs.FSWatcher
<!-- YAML
added: v0.5.8
@@ -2319,9 +2405,10 @@ changes:
* `path` {string|Buffer|URL}
* `options` {string|Object}
* `encoding` {string} **Default:** `'utf8'`
+ * `withFileTypes` {boolean} **Default:** `false`
* `callback` {Function}
* `err` {Error}
- * `files` {string[]|Buffer[]}
+ * `files` {string[]|Buffer[]|fs.Dirent[]}
Asynchronous readdir(3). Reads the contents of a directory.
The callback gets two arguments `(err, files)` where `files` is an array of
@@ -2332,6 +2419,9 @@ object with an `encoding` property specifying the character encoding to use for
the filenames passed to the callback. If the `encoding` is set to `'buffer'`,
the filenames returned will be passed as `Buffer` objects.
+If `options.withFileTypes` is set to `true`, the `files` array will contain
+[`fs.Dirent`][] objects.
+
## fs.readdirSync(path[, options])
<!-- YAML
added: v0.1.21
@@ -2345,7 +2435,8 @@ changes:
* `path` {string|Buffer|URL}
* `options` {string|Object}
* `encoding` {string} **Default:** `'utf8'`
-* Returns: {string[]} An array of filenames excluding `'.'` and `'..'`.
+ * `withFileTypes` {boolean} **Default:** `false`
+* Returns: {string[]|Buffer[]|fs.Dirent[]}
Synchronous readdir(3).
@@ -2354,6 +2445,9 @@ object with an `encoding` property specifying the character encoding to use for
the filenames returned. If the `encoding` is set to `'buffer'`,
the filenames returned will be passed as `Buffer` objects.
+If `options.withFileTypes` is set to `true`, the result will contain
+[`fs.Dirent`][] objects.
+
## fs.readFile(path[, options], callback)
<!-- YAML
added: v0.1.29
@@ -4637,6 +4731,7 @@ the file contents.
[`WriteStream`]: #fs_class_fs_writestream
[`EventEmitter`]: events.html
[`event ports`]: http://illumos.org/man/port_create
+[`fs.Dirent`]: #fs_class_fs_dirent
[`fs.FSWatcher`]: #fs_class_fs_fswatcher
[`fs.Stats`]: #fs_class_fs_stats
[`fs.access()`]: #fs_fs_access_path_mode_callback
@@ -4652,6 +4747,8 @@ the file contents.
[`fs.mkdtemp()`]: #fs_fs_mkdtemp_prefix_options_callback
[`fs.open()`]: #fs_fs_open_path_flags_mode_callback
[`fs.read()`]: #fs_fs_read_fd_buffer_offset_length_position_callback
+[`fs.readdir()`]: #fs_fs_readdir_path_options_callback
+[`fs.readdirSync()`]: #fs_fs_readdirsync_path_options
[`fs.readFile()`]: #fs_fs_readfile_path_options_callback
[`fs.readFileSync()`]: #fs_fs_readfilesync_path_options
[`fs.realpath()`]: #fs_fs_realpath_path_options_callback
diff --git a/lib/fs.js b/lib/fs.js
index bb8c17bbbb..a0e5f64476 100644
--- a/lib/fs.js
+++ b/lib/fs.js
@@ -58,6 +58,8 @@ const { getPathFromURL } = require('internal/url');
const internalUtil = require('internal/util');
const {
copyObject,
+ Dirent,
+ getDirents,
getOptions,
nullCheck,
preprocessSymlinkDestination,
@@ -773,8 +775,19 @@ function readdir(path, options, callback) {
validatePath(path);
const req = new FSReqCallback();
- req.oncomplete = callback;
- binding.readdir(pathModule.toNamespacedPath(path), options.encoding, req);
+ if (!options.withFileTypes) {
+ req.oncomplete = callback;
+ } else {
+ req.oncomplete = (err, result) => {
+ if (err) {
+ callback(err);
+ return;
+ }
+ getDirents(path, result, callback);
+ };
+ }
+ binding.readdir(pathModule.toNamespacedPath(path), options.encoding,
+ !!options.withFileTypes, req);
}
function readdirSync(path, options) {
@@ -783,9 +796,10 @@ function readdirSync(path, options) {
validatePath(path);
const ctx = { path };
const result = binding.readdir(pathModule.toNamespacedPath(path),
- options.encoding, undefined, ctx);
+ options.encoding, !!options.withFileTypes,
+ undefined, ctx);
handleErrorFromBinding(ctx);
- return result;
+ return options.withFileTypes ? getDirents(path, result) : result;
}
function fstat(fd, options, callback) {
@@ -1819,6 +1833,7 @@ module.exports = fs = {
writeFileSync,
write,
writeSync,
+ Dirent,
Stats,
get ReadStream() {
diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js
index e8cb2317fa..e0ddee4c7f 100644
--- a/lib/internal/fs/promises.js
+++ b/lib/internal/fs/promises.js
@@ -19,6 +19,7 @@ const { getPathFromURL } = require('internal/url');
const { isUint8Array } = require('internal/util/types');
const {
copyObject,
+ getDirents,
getOptions,
getStatsFromBinding,
nullCheck,
@@ -37,10 +38,13 @@ const {
validateUint32
} = require('internal/validators');
const pathModule = require('path');
+const { promisify } = require('internal/util');
const kHandle = Symbol('handle');
const { kUsePromises } = binding;
+const getDirectoryEntriesPromise = promisify(getDirents);
+
class FileHandle {
constructor(filehandle) {
this[kHandle] = filehandle;
@@ -312,8 +316,12 @@ async function readdir(path, options) {
options = getOptions(options, {});
path = getPathFromURL(path);
validatePath(path);
- return binding.readdir(pathModule.toNamespacedPath(path),
- options.encoding, kUsePromises);
+ const result = await binding.readdir(pathModule.toNamespacedPath(path),
+ options.encoding, !!options.withTypes,
+ kUsePromises);
+ return options.withFileTypes ?
+ getDirectoryEntriesPromise(path, result) :
+ result;
}
async function readlink(path, options) {
diff --git a/lib/internal/fs/utils.js b/lib/internal/fs/utils.js
index a2f1015c62..f065ba41e8 100644
--- a/lib/internal/fs/utils.js
+++ b/lib/internal/fs/utils.js
@@ -12,6 +12,8 @@ const {
const { isUint8Array } = require('internal/util/types');
const pathModule = require('path');
const util = require('util');
+const kType = Symbol('type');
+const kStats = Symbol('stats');
const {
O_APPEND,
@@ -31,17 +33,84 @@ const {
S_IFREG,
S_IFSOCK,
UV_FS_SYMLINK_DIR,
- UV_FS_SYMLINK_JUNCTION
+ UV_FS_SYMLINK_JUNCTION,
+ UV_DIRENT_UNKNOWN,
+ UV_DIRENT_FILE,
+ UV_DIRENT_DIR,
+ UV_DIRENT_LINK,
+ UV_DIRENT_FIFO,
+ UV_DIRENT_SOCKET,
+ UV_DIRENT_CHAR,
+ UV_DIRENT_BLOCK
} = process.binding('constants').fs;
const isWindows = process.platform === 'win32';
+let fs;
+function lazyLoadFs() {
+ if (!fs) {
+ fs = require('fs');
+ }
+ return fs;
+}
+
function assertEncoding(encoding) {
if (encoding && !Buffer.isEncoding(encoding)) {
throw new ERR_INVALID_OPT_VALUE_ENCODING(encoding);
}
}
+class Dirent {
+ constructor(name, type) {
+ this.name = name;
+ this[kType] = type;
+ }
+
+ isDirectory() {
+ return this[kType] === UV_DIRENT_DIR;
+ }
+
+ isFile() {
+ return this[kType] === UV_DIRENT_FILE;
+ }
+
+ isBlockDevice() {
+ return this[kType] === UV_DIRENT_BLOCK;
+ }
+
+ isCharacterDevice() {
+ return this[kType] === UV_DIRENT_CHAR;
+ }
+
+ isSymbolicLink() {
+ return this[kType] === UV_DIRENT_LINK;
+ }
+
+ isFIFO() {
+ return this[kType] === UV_DIRENT_FIFO;
+ }
+
+ isSocket() {
+ return this[kType] === UV_DIRENT_SOCKET;
+ }
+}
+
+class DirentFromStats extends Dirent {
+ constructor(name, stats) {
+ super(name, null);
+ this[kStats] = stats;
+ }
+}
+
+for (const name of Reflect.ownKeys(Dirent.prototype)) {
+ if (name === 'constructor') {
+ continue;
+ }
+ DirentFromStats.prototype[name] = function() {
+ return this[kStats][name]();
+ };
+}
+
function copyObject(source) {
var target = {};
for (var key in source)
@@ -49,6 +118,50 @@ function copyObject(source) {
return target;
}
+function getDirents(path, [names, types], callback) {
+ var i;
+ if (typeof callback == 'function') {
+ const len = names.length;
+ let toFinish = 0;
+ for (i = 0; i < len; i++) {
+ const type = types[i];
+ if (type === UV_DIRENT_UNKNOWN) {
+ const name = names[i];
+ const idx = i;
+ toFinish++;
+ lazyLoadFs().stat(pathModule.resolve(path, name), (err, stats) => {
+ if (err) {
+ callback(err);
+ return;
+ }
+ names[idx] = new DirentFromStats(name, stats);
+ if (--toFinish === 0) {
+ callback(null, names);
+ }
+ });
+ } else {
+ names[i] = new Dirent(names[i], types[i]);
+ }
+ }
+ if (toFinish === 0) {
+ callback(null, names);
+ }
+ } else {
+ const len = names.length;
+ for (i = 0; i < len; i++) {
+ const type = types[i];
+ if (type === UV_DIRENT_UNKNOWN) {
+ const name = names[i];
+ const stats = lazyLoadFs().statSync(pathModule.resolve(path, name));
+ names[i] = new DirentFromStats(name, stats);
+ } else {
+ names[i] = new Dirent(names[i], types[i]);
+ }
+ }
+ return names;
+ }
+}
+
function getOptions(options, defaultOptions) {
if (options === null || options === undefined ||
typeof options === 'function') {
@@ -342,6 +455,8 @@ function validatePath(path, propName = 'path') {
module.exports = {
assertEncoding,
copyObject,
+ Dirent,
+ getDirents,
getOptions,
nullCheck,
preprocessSymlinkDestination,
diff --git a/src/node_constants.cc b/src/node_constants.cc
index 61aa42a8ef..28d5a9ca4e 100644
--- a/src/node_constants.cc
+++ b/src/node_constants.cc
@@ -1022,6 +1022,16 @@ void DefineSystemConstants(Local<Object> target) {
NODE_DEFINE_CONSTANT(target, O_WRONLY);
NODE_DEFINE_CONSTANT(target, O_RDWR);
+ // file types from readdir
+ NODE_DEFINE_CONSTANT(target, UV_DIRENT_UNKNOWN);
+ NODE_DEFINE_CONSTANT(target, UV_DIRENT_FILE);
+ NODE_DEFINE_CONSTANT(target, UV_DIRENT_DIR);
+ NODE_DEFINE_CONSTANT(target, UV_DIRENT_LINK);
+ NODE_DEFINE_CONSTANT(target, UV_DIRENT_FIFO);
+ NODE_DEFINE_CONSTANT(target, UV_DIRENT_SOCKET);
+ NODE_DEFINE_CONSTANT(target, UV_DIRENT_CHAR);
+ NODE_DEFINE_CONSTANT(target, UV_DIRENT_BLOCK);
+
NODE_DEFINE_CONSTANT(target, S_IFMT);
NODE_DEFINE_CONSTANT(target, S_IFREG);
NODE_DEFINE_CONSTANT(target, S_IFDIR);
diff --git a/src/node_file.cc b/src/node_file.cc
index 714dec157b..fb976a31a5 100644
--- a/src/node_file.cc
+++ b/src/node_file.cc
@@ -561,51 +561,139 @@ void AfterScanDir(uv_fs_t* req) {
FSReqBase* req_wrap = FSReqBase::from_req(req);
FSReqAfterScope after(req_wrap, req);
- if (after.Proceed()) {
- Environment* env = req_wrap->env();
- Local<Value> error;
- int r;
- Local<Array> names = Array::New(env->isolate(), 0);
- Local<Function> fn = env->push_values_to_array_function();
- Local<Value> name_argv[NODE_PUSH_VAL_TO_ARRAY_MAX];
- size_t name_idx = 0;
+ if (!after.Proceed()) {
+ return;
+ }
+ Environment* env = req_wrap->env();
+ Local<Value> error;
+ int r;
+ Local<Array> names = Array::New(env->isolate(), 0);
+ Local<Function> fn = env->push_values_to_array_function();
+ Local<Value> name_argv[NODE_PUSH_VAL_TO_ARRAY_MAX];
+ size_t name_idx = 0;
- for (int i = 0; ; i++) {
- uv_dirent_t ent;
+ for (int i = 0; ; i++) {
+ uv_dirent_t ent;
- r = uv_fs_scandir_next(req, &ent);
- if (r == UV_EOF)
- break;
- if (r != 0) {
- return req_wrap->Reject(
- UVException(r, nullptr, req_wrap->syscall(),
- static_cast<const char*>(req->path)));
+ r = uv_fs_scandir_next(req, &ent);
+ if (r == UV_EOF)
+ break;
+ if (r != 0) {
+ return req_wrap->Reject(
+ UVException(r, nullptr, req_wrap->syscall(),
+ static_cast<const char*>(req->path)));
+ }
+
+ MaybeLocal<Value> filename =
+ StringBytes::Encode(env->isolate(),
+ ent.name,
+ req_wrap->encoding(),
+ &error);
+ if (filename.IsEmpty())
+ return req_wrap->Reject(error);
+
+ name_argv[name_idx++] = filename.ToLocalChecked();
+
+ if (name_idx >= arraysize(name_argv)) {
+ MaybeLocal<Value> ret = fn->Call(env->context(), names, name_idx,
+ name_argv);
+ if (ret.IsEmpty()) {
+ return;
}
+ name_idx = 0;
+ }
+ }
- MaybeLocal<Value> filename =
- StringBytes::Encode(env->isolate(),
- ent.name,
- req_wrap->encoding(),
- &error);
- if (filename.IsEmpty())
- return req_wrap->Reject(error);
+ if (name_idx > 0) {
+ fn->Call(env->context(), names, name_idx, name_argv)
+ .ToLocalChecked();
+ }
- name_argv[name_idx++] = filename.ToLocalChecked();
+ req_wrap->Resolve(names);
+}
- if (name_idx >= arraysize(name_argv)) {
- fn->Call(env->context(), names, name_idx, name_argv)
- .ToLocalChecked();
- name_idx = 0;
+void AfterScanDirWithTypes(uv_fs_t* req) {
+ FSReqBase* req_wrap = FSReqBase::from_req(req);
+ FSReqAfterScope after(req_wrap, req);
+
+ if (!after.Proceed()) {
+ return;
+ }
+
+ Environment* env = req_wrap->env();
+ Local<Value> error;
+ int r;
+ Local<Array> names = Array::New(env->isolate(), 0);
+ Local<Function> fn = env->push_values_to_array_function();
+ Local<Value> name_argv[NODE_PUSH_VAL_TO_ARRAY_MAX];
+ size_t name_idx = 0;
+ Local<Value> types = Array::New(env->isolate(), 0);
+ Local<Value> type_argv[NODE_PUSH_VAL_TO_ARRAY_MAX];
+ size_t type_idx = 0;
+
+ for (int i = 0; ; i++) {
+ uv_dirent_t ent;
+
+ r = uv_fs_scandir_next(req, &ent);
+ if (r == UV_EOF)
+ break;
+ if (r != 0) {
+ return req_wrap->Reject(
+ UVException(r, nullptr, req_wrap->syscall(),
+ static_cast<const char*>(req->path)));
+ }
+
+ MaybeLocal<Value> filename =
+ StringBytes::Encode(env->isolate(),
+ ent.name,
+ req_wrap->encoding(),
+ &error);
+ if (filename.IsEmpty())
+ return req_wrap->Reject(error);
+
+ name_argv[name_idx++] = filename.ToLocalChecked();
+
+ if (name_idx >= arraysize(name_argv)) {
+ MaybeLocal<Value> ret = fn->Call(env->context(), names, name_idx,
+ name_argv);
+ if (ret.IsEmpty()) {
+ return;
}
+ name_idx = 0;
}
- if (name_idx > 0) {
- fn->Call(env->context(), names, name_idx, name_argv)
- .ToLocalChecked();
+ type_argv[type_idx++] = Integer::New(env->isolate(), ent.type);
+
+ if (type_idx >= arraysize(type_argv)) {
+ MaybeLocal<Value> ret = fn->Call(env->context(), types, type_idx,
+ type_argv);
+ if (ret.IsEmpty()) {
+ return;
+ }
+ type_idx = 0;
}
+ }
- req_wrap->Resolve(names);
+ if (name_idx > 0) {
+ MaybeLocal<Value> ret = fn->Call(env->context(), names, name_idx,
+ name_argv);
+ if (ret.IsEmpty()) {
+ return;
+ }
+ }
+
+ if (type_idx > 0) {
+ MaybeLocal<Value> ret = fn->Call(env->context(), types, type_idx,
+ type_argv);
+ if (ret.IsEmpty()) {
+ return;
+ }
}
+
+ Local<Array> result = Array::New(env->isolate(), 2);
+ result->Set(0, names);
+ result->Set(1, types);
+ req_wrap->Resolve(result);
}
@@ -1372,15 +1460,22 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8);
- FSReqBase* req_wrap_async = GetReqWrap(env, args[2]);
- if (req_wrap_async != nullptr) { // readdir(path, encoding, req)
- AsyncCall(env, req_wrap_async, args, "scandir", encoding, AfterScanDir,
- uv_fs_scandir, *path, 0 /*flags*/);
- } else { // readdir(path, encoding, undefined, ctx)
- CHECK_EQ(argc, 4);
+ bool with_types = args[2]->BooleanValue();
+
+ FSReqBase* req_wrap_async = GetReqWrap(env, args[3]);
+ if (req_wrap_async != nullptr) { // readdir(path, encoding, withTypes, req)
+ if (with_types) {
+ AsyncCall(env, req_wrap_async, args, "scandir", encoding,
+ AfterScanDirWithTypes, uv_fs_scandir, *path, 0 /*flags*/);
+ } else {
+ AsyncCall(env, req_wrap_async, args, "scandir", encoding,
+ AfterScanDir, uv_fs_scandir, *path, 0 /*flags*/);
+ }
+ } else { // readdir(path, encoding, withTypes, undefined, ctx)
+ CHECK_EQ(argc, 5);
FSReqWrapSync req_wrap_sync;
FS_SYNC_TRACE_BEGIN(readdir);
- int err = SyncCall(env, args[3], &req_wrap_sync, "scandir",
+ int err = SyncCall(env, args[4], &req_wrap_sync, "scandir",
uv_fs_scandir, *path, 0 /*flags*/);
FS_SYNC_TRACE_END(readdir);
if (err < 0) {
@@ -1394,6 +1489,14 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
Local<Value> name_v[NODE_PUSH_VAL_TO_ARRAY_MAX];
size_t name_idx = 0;
+ Local<Value> types;
+ Local<Value> type_v[NODE_PUSH_VAL_TO_ARRAY_MAX];
+ size_t type_idx;
+ if (with_types) {
+ types = Array::New(env->isolate(), 0);
+ type_idx = 0;
+ }
+
for (int i = 0; ; i++) {
uv_dirent_t ent;
@@ -1401,7 +1504,7 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
if (r == UV_EOF)
break;
if (r != 0) {
- Local<Object> ctx = args[3].As<Object>();
+ Local<Object> ctx = args[4].As<Object>();
ctx->Set(env->context(), env->errno_string(),
Integer::New(env->isolate(), r)).FromJust();
ctx->Set(env->context(), env->syscall_string(),
@@ -1414,8 +1517,9 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
ent.name,
encoding,
&error);
+
if (filename.IsEmpty()) {
- Local<Object> ctx = args[3].As<Object>();
+ Local<Object> ctx = args[4].As<Object>();
ctx->Set(env->context(), env->error_string(), error).FromJust();
return;
}
@@ -1430,6 +1534,19 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
}
name_idx = 0;
}
+
+ if (with_types) {
+ type_v[type_idx++] = Integer::New(env->isolate(), ent.type);
+
+ if (type_idx >= arraysize(type_v)) {
+ MaybeLocal<Value> ret = fn->Call(env->context(), types, type_idx,
+ type_v);
+ if (ret.IsEmpty()) {
+ return;
+ }
+ type_idx = 0;
+ }
+ }
}
if (name_idx > 0) {
@@ -1439,7 +1556,21 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
}
}
- args.GetReturnValue().Set(names);
+ if (with_types && type_idx > 0) {
+ MaybeLocal<Value> ret = fn->Call(env->context(), types, type_idx, type_v);
+ if (ret.IsEmpty()) {
+ return;
+ }
+ }
+
+ if (with_types) {
+ Local<Array> result = Array::New(env->isolate(), 2);
+ result->Set(0, names);
+ result->Set(1, types);
+ args.GetReturnValue().Set(result);
+ } else {
+ args.GetReturnValue().Set(names);
+ }
}
}
diff --git a/test/parallel/test-fs-readdir-types.js b/test/parallel/test-fs-readdir-types.js
new file mode 100644
index 0000000000..75452895cc
--- /dev/null
+++ b/test/parallel/test-fs-readdir-types.js
@@ -0,0 +1,95 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const fs = require('fs');
+
+const tmpdir = require('../common/tmpdir');
+
+const binding = process.binding('fs');
+
+const readdirDir = tmpdir.path;
+const files = ['empty', 'files', 'for', 'just', 'testing'];
+const constants = process.binding('constants').fs;
+const types = {
+ isDirectory: constants.UV_DIRENT_DIR,
+ isFile: constants.UV_DIRENT_FILE,
+ isBlockDevice: constants.UV_DIRENT_BLOCK,
+ isCharacterDevice: constants.UV_DIRENT_CHAR,
+ isSymbolicLink: constants.UV_DIRENT_LINK,
+ isFIFO: constants.UV_DIRENT_FIFO,
+ isSocket: constants.UV_DIRENT_SOCKET
+};
+const typeMethods = Object.keys(types);
+
+// Make sure tmp directory is clean
+tmpdir.refresh();
+
+// Create the necessary files
+files.forEach(function(currentFile) {
+ fs.closeSync(fs.openSync(`${readdirDir}/${currentFile}`, 'w'));
+});
+
+
+function assertDirents(dirents) {
+ assert.strictEqual(files.length, dirents.length);
+ for (const [i, dirent] of dirents.entries()) {
+ assert(dirent instanceof fs.Dirent);
+ assert.strictEqual(dirent.name, files[i]);
+ assert.strictEqual(dirent.isFile(), true);
+ assert.strictEqual(dirent.isDirectory(), false);
+ assert.strictEqual(dirent.isSocket(), false);
+ assert.strictEqual(dirent.isBlockDevice(), false);
+ assert.strictEqual(dirent.isCharacterDevice(), false);
+ assert.strictEqual(dirent.isFIFO(), false);
+ assert.strictEqual(dirent.isSymbolicLink(), false);
+ }
+}
+
+// Check the readdir Sync version
+assertDirents(fs.readdirSync(readdirDir, { withFileTypes: true }));
+
+// Check the readdir async version
+fs.readdir(readdirDir, {
+ withFileTypes: true
+}, common.mustCall((err, dirents) => {
+ assert.ifError(err);
+ assertDirents(dirents);
+}));
+
+// Check for correct types when the binding returns unknowns
+const UNKNOWN = constants.UV_DIRENT_UNKNOWN;
+const oldReaddir = binding.readdir;
+binding.readdir = common.mustCall((path, encoding, types, req, ctx) => {
+ if (req) {
+ const oldCb = req.oncomplete;
+ req.oncomplete = (err, results) => {
+ if (err) {
+ oldCb(err);
+ return;
+ }
+ results[1] = results[1].map(() => UNKNOWN);
+ oldCb(null, results);
+ };
+ oldReaddir(path, encoding, types, req);
+ } else {
+ const results = oldReaddir(path, encoding, types, req, ctx);
+ results[1] = results[1].map(() => UNKNOWN);
+ return results;
+ }
+}, 2);
+assertDirents(fs.readdirSync(readdirDir, { withFileTypes: true }));
+fs.readdir(readdirDir, {
+ withFileTypes: true
+}, common.mustCall((err, dirents) => {
+ assert.ifError(err);
+ assertDirents(dirents);
+}));
+
+// Dirent types
+for (const method of typeMethods) {
+ const dirent = new fs.Dirent('foo', types[method]);
+ for (const testMethod of typeMethods) {
+ assert.strictEqual(dirent[testMethod](), testMethod === method);
+ }
+}
diff --git a/tools/doc/type-parser.js b/tools/doc/type-parser.js
index 42cc27f19c..2a63f97c79 100644
--- a/tools/doc/type-parser.js
+++ b/tools/doc/type-parser.js
@@ -58,6 +58,7 @@ const customTypesMap = {
'EventEmitter': 'events.html#events_class_eventemitter',
'FileHandle': 'fs.html#fs_class_filehandle',
+ 'fs.Dirent': 'fs.html#fs_class_fs_dirent',
'fs.FSWatcher': 'fs.html#fs_class_fs_fswatcher',
'fs.ReadStream': 'fs.html#fs_class_fs_readstream',
'fs.Stats': 'fs.html#fs_class_fs_stats',