fs: readdir optionally returning type information

readdir and readdirSync now have a "withFileTypes" option, which, when
enabled, provides an array of DirectoryEntry objects, similar to Stats
objects, which have the filename and the type information.

Refs: https://github.com/nodejs/node/issues/15699

PR-URL: https://github.com/nodejs/node/pull/22020
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Roman Reiss <me@silverwind.io>
Reviewed-By: John-David Dalton <john.david.dalton@gmail.com>
This commit is contained in:
Bryan English 2018-07-27 19:29:32 -07:00
parent 78584b64d8
commit c7944a7a7b
8 changed files with 535 additions and 63 deletions

View File

@ -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

View File

@ -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() {

View File

@ -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) {

View File

@ -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,

View File

@ -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);

View File

@ -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;
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)) {
fn->Call(env->context(), names, name_idx, name_argv)
.ToLocalChecked();
name_idx = 0;
}
}
if (name_idx > 0) {
fn->Call(env->context(), names, name_idx, name_argv)
.ToLocalChecked();
}
req_wrap->Resolve(names);
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;
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();
}
req_wrap->Resolve(names);
}
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;
}
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;
}
}
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);
}
}
}

View File

@ -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);
}
}

View File

@ -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',