Skip to content

Commit ad97314

Browse files
bengltargos
authored andcommittedSep 3, 2018
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: #15699 PR-URL: #22020 Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Roman Reiss <[email protected]> Reviewed-By: John-David Dalton <[email protected]>
1 parent 725a2b1 commit ad97314

File tree

8 files changed

+524
-52
lines changed

8 files changed

+524
-52
lines changed
 

‎doc/api/fs.md

+99-2
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,92 @@ synchronous use libuv's threadpool, which can have surprising and negative
283283
performance implications for some applications. See the
284284
[`UV_THREADPOOL_SIZE`][] documentation for more information.
285285

286+
## Class: fs.Dirent
287+
<!-- YAML
288+
added: REPLACEME
289+
-->
290+
291+
When [`fs.readdir()`][] or [`fs.readdirSync()`][] is called with the
292+
`withFileTypes` option set to `true`, the resulting array is filled with
293+
`fs.Dirent` objects, rather than strings or `Buffers`.
294+
295+
### dirent.isBlockDevice()
296+
<!-- YAML
297+
added: REPLACEME
298+
-->
299+
300+
* Returns: {boolean}
301+
302+
Returns `true` if the `fs.Dirent` object describes a block device.
303+
304+
### dirent.isCharacterDevice()
305+
<!-- YAML
306+
added: REPLACEME
307+
-->
308+
309+
* Returns: {boolean}
310+
311+
Returns `true` if the `fs.Dirent` object describes a character device.
312+
313+
### dirent.isDirectory()
314+
<!-- YAML
315+
added: REPLACEME
316+
-->
317+
318+
* Returns: {boolean}
319+
320+
Returns `true` if the `fs.Dirent` object describes a file system
321+
directory.
322+
323+
### dirent.isFIFO()
324+
<!-- YAML
325+
added: REPLACEME
326+
-->
327+
328+
* Returns: {boolean}
329+
330+
Returns `true` if the `fs.Dirent` object describes a first-in-first-out
331+
(FIFO) pipe.
332+
333+
### dirent.isFile()
334+
<!-- YAML
335+
added: REPLACEME
336+
-->
337+
338+
* Returns: {boolean}
339+
340+
Returns `true` if the `fs.Dirent` object describes a regular file.
341+
342+
### dirent.isSocket()
343+
<!-- YAML
344+
added: REPLACEME
345+
-->
346+
347+
* Returns: {boolean}
348+
349+
Returns `true` if the `fs.Dirent` object describes a socket.
350+
351+
### dirent.isSymbolicLink()
352+
<!-- YAML
353+
added: REPLACEME
354+
-->
355+
356+
* Returns: {boolean}
357+
358+
Returns `true` if the `fs.Dirent` object describes a symbolic link.
359+
360+
361+
### dirent.name
362+
<!-- YAML
363+
added: REPLACEME
364+
-->
365+
366+
* {string|Buffer}
367+
368+
The file name that this `fs.Dirent` object refers to. The type of this
369+
value is determined by the `options.encoding` passed to [`fs.readdir()`][] or
370+
[`fs.readdirSync()`][].
371+
286372
## Class: fs.FSWatcher
287373
<!-- YAML
288374
added: v0.5.8
@@ -2299,9 +2385,10 @@ changes:
22992385
* `path` {string|Buffer|URL}
23002386
* `options` {string|Object}
23012387
* `encoding` {string} **Default:** `'utf8'`
2388+
* `withFileTypes` {boolean} **Default:** `false`
23022389
* `callback` {Function}
23032390
* `err` {Error}
2304-
* `files` {string[]|Buffer[]}
2391+
* `files` {string[]|Buffer[]|fs.Dirent[]}
23052392

23062393
Asynchronous readdir(3). Reads the contents of a directory.
23072394
The callback gets two arguments `(err, files)` where `files` is an array of
@@ -2312,6 +2399,9 @@ object with an `encoding` property specifying the character encoding to use for
23122399
the filenames passed to the callback. If the `encoding` is set to `'buffer'`,
23132400
the filenames returned will be passed as `Buffer` objects.
23142401

2402+
If `options.withFileTypes` is set to `true`, the `files` array will contain
2403+
[`fs.Dirent`][] objects.
2404+
23152405
## fs.readdirSync(path[, options])
23162406
<!-- YAML
23172407
added: v0.1.21
@@ -2325,7 +2415,8 @@ changes:
23252415
* `path` {string|Buffer|URL}
23262416
* `options` {string|Object}
23272417
* `encoding` {string} **Default:** `'utf8'`
2328-
* Returns: {string[]} An array of filenames excluding `'.'` and `'..'`.
2418+
* `withFileTypes` {boolean} **Default:** `false`
2419+
* Returns: {string[]|Buffer[]|fs.Dirent[]}
23292420

23302421
Synchronous readdir(3).
23312422

@@ -2334,6 +2425,9 @@ object with an `encoding` property specifying the character encoding to use for
23342425
the filenames returned. If the `encoding` is set to `'buffer'`,
23352426
the filenames returned will be passed as `Buffer` objects.
23362427

2428+
If `options.withFileTypes` is set to `true`, the result will contain
2429+
[`fs.Dirent`][] objects.
2430+
23372431
## fs.readFile(path[, options], callback)
23382432
<!-- YAML
23392433
added: v0.1.29
@@ -4611,6 +4705,7 @@ the file contents.
46114705
[`WriteStream`]: #fs_class_fs_writestream
46124706
[`EventEmitter`]: events.html
46134707
[`event ports`]: http://illumos.org/man/port_create
4708+
[`fs.Dirent`]: #fs_class_fs_dirent
46144709
[`fs.FSWatcher`]: #fs_class_fs_fswatcher
46154710
[`fs.Stats`]: #fs_class_fs_stats
46164711
[`fs.access()`]: #fs_fs_access_path_mode_callback
@@ -4626,6 +4721,8 @@ the file contents.
46264721
[`fs.mkdtemp()`]: #fs_fs_mkdtemp_prefix_options_callback
46274722
[`fs.open()`]: #fs_fs_open_path_flags_mode_callback
46284723
[`fs.read()`]: #fs_fs_read_fd_buffer_offset_length_position_callback
4724+
[`fs.readdir()`]: #fs_fs_readdir_path_options_callback
4725+
[`fs.readdirSync()`]: #fs_fs_readdirsync_path_options
46294726
[`fs.readFile()`]: #fs_fs_readfile_path_options_callback
46304727
[`fs.readFileSync()`]: #fs_fs_readfilesync_path_options
46314728
[`fs.realpath()`]: #fs_fs_realpath_path_options_callback

‎lib/fs.js

+19-4
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ const { getPathFromURL } = require('internal/url');
5858
const internalUtil = require('internal/util');
5959
const {
6060
copyObject,
61+
Dirent,
62+
getDirents,
6163
getOptions,
6264
nullCheck,
6365
preprocessSymlinkDestination,
@@ -736,8 +738,19 @@ function readdir(path, options, callback) {
736738
validatePath(path);
737739

738740
const req = new FSReqWrap();
739-
req.oncomplete = callback;
740-
binding.readdir(pathModule.toNamespacedPath(path), options.encoding, req);
741+
if (!options.withFileTypes) {
742+
req.oncomplete = callback;
743+
} else {
744+
req.oncomplete = (err, result) => {
745+
if (err) {
746+
callback(err);
747+
return;
748+
}
749+
getDirents(path, result, callback);
750+
};
751+
}
752+
binding.readdir(pathModule.toNamespacedPath(path), options.encoding,
753+
!!options.withFileTypes, req);
741754
}
742755

743756
function readdirSync(path, options) {
@@ -746,9 +759,10 @@ function readdirSync(path, options) {
746759
validatePath(path);
747760
const ctx = { path };
748761
const result = binding.readdir(pathModule.toNamespacedPath(path),
749-
options.encoding, undefined, ctx);
762+
options.encoding, !!options.withFileTypes,
763+
undefined, ctx);
750764
handleErrorFromBinding(ctx);
751-
return result;
765+
return options.withFileTypes ? getDirents(path, result) : result;
752766
}
753767

754768
function fstat(fd, options, callback) {
@@ -1773,6 +1787,7 @@ module.exports = fs = {
17731787
writeFileSync,
17741788
write,
17751789
writeSync,
1790+
Dirent,
17761791
Stats,
17771792

17781793
// Stream constructors

‎lib/internal/fs/promises.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const { getPathFromURL } = require('internal/url');
1818
const { isUint8Array } = require('internal/util/types');
1919
const {
2020
copyObject,
21+
getDirents,
2122
getOptions,
2223
getStatsFromBinding,
2324
nullCheck,
@@ -36,10 +37,13 @@ const {
3637
validateUint32
3738
} = require('internal/validators');
3839
const pathModule = require('path');
40+
const { promisify } = require('internal/util');
3941

4042
const kHandle = Symbol('handle');
4143
const { kUsePromises } = binding;
4244

45+
const getDirectoryEntriesPromise = promisify(getDirents);
46+
4347
class FileHandle {
4448
constructor(filehandle) {
4549
this[kHandle] = filehandle;
@@ -294,8 +298,12 @@ async function readdir(path, options) {
294298
options = getOptions(options, {});
295299
path = getPathFromURL(path);
296300
validatePath(path);
297-
return binding.readdir(pathModule.toNamespacedPath(path),
298-
options.encoding, kUsePromises);
301+
const result = await binding.readdir(pathModule.toNamespacedPath(path),
302+
options.encoding, !!options.withTypes,
303+
kUsePromises);
304+
return options.withFileTypes ?
305+
getDirectoryEntriesPromise(path, result) :
306+
result;
299307
}
300308

301309
async function readlink(path, options) {

‎lib/internal/fs/utils.js

+116-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ const {
1212
const { isUint8Array } = require('internal/util/types');
1313
const pathModule = require('path');
1414
const util = require('util');
15+
const kType = Symbol('type');
16+
const kStats = Symbol('stats');
1517

1618
const {
1719
O_APPEND,
@@ -31,24 +33,135 @@ const {
3133
S_IFREG,
3234
S_IFSOCK,
3335
UV_FS_SYMLINK_DIR,
34-
UV_FS_SYMLINK_JUNCTION
36+
UV_FS_SYMLINK_JUNCTION,
37+
UV_DIRENT_UNKNOWN,
38+
UV_DIRENT_FILE,
39+
UV_DIRENT_DIR,
40+
UV_DIRENT_LINK,
41+
UV_DIRENT_FIFO,
42+
UV_DIRENT_SOCKET,
43+
UV_DIRENT_CHAR,
44+
UV_DIRENT_BLOCK
3545
} = process.binding('constants').fs;
3646

3747
const isWindows = process.platform === 'win32';
3848

49+
let fs;
50+
function lazyLoadFs() {
51+
if (!fs) {
52+
fs = require('fs');
53+
}
54+
return fs;
55+
}
56+
3957
function assertEncoding(encoding) {
4058
if (encoding && !Buffer.isEncoding(encoding)) {
4159
throw new ERR_INVALID_OPT_VALUE_ENCODING(encoding);
4260
}
4361
}
4462

63+
class Dirent {
64+
constructor(name, type) {
65+
this.name = name;
66+
this[kType] = type;
67+
}
68+
69+
isDirectory() {
70+
return this[kType] === UV_DIRENT_DIR;
71+
}
72+
73+
isFile() {
74+
return this[kType] === UV_DIRENT_FILE;
75+
}
76+
77+
isBlockDevice() {
78+
return this[kType] === UV_DIRENT_BLOCK;
79+
}
80+
81+
isCharacterDevice() {
82+
return this[kType] === UV_DIRENT_CHAR;
83+
}
84+
85+
isSymbolicLink() {
86+
return this[kType] === UV_DIRENT_LINK;
87+
}
88+
89+
isFIFO() {
90+
return this[kType] === UV_DIRENT_FIFO;
91+
}
92+
93+
isSocket() {
94+
return this[kType] === UV_DIRENT_SOCKET;
95+
}
96+
}
97+
98+
class DirentFromStats extends Dirent {
99+
constructor(name, stats) {
100+
super(name, null);
101+
this[kStats] = stats;
102+
}
103+
}
104+
105+
for (const name of Reflect.ownKeys(Dirent.prototype)) {
106+
if (name === 'constructor') {
107+
continue;
108+
}
109+
DirentFromStats.prototype[name] = function() {
110+
return this[kStats][name]();
111+
};
112+
}
113+
45114
function copyObject(source) {
46115
var target = {};
47116
for (var key in source)
48117
target[key] = source[key];
49118
return target;
50119
}
51120

121+
function getDirents(path, [names, types], callback) {
122+
var i;
123+
if (typeof callback == 'function') {
124+
const len = names.length;
125+
let toFinish = 0;
126+
for (i = 0; i < len; i++) {
127+
const type = types[i];
128+
if (type === UV_DIRENT_UNKNOWN) {
129+
const name = names[i];
130+
const idx = i;
131+
toFinish++;
132+
lazyLoadFs().stat(pathModule.resolve(path, name), (err, stats) => {
133+
if (err) {
134+
callback(err);
135+
return;
136+
}
137+
names[idx] = new DirentFromStats(name, stats);
138+
if (--toFinish === 0) {
139+
callback(null, names);
140+
}
141+
});
142+
} else {
143+
names[i] = new Dirent(names[i], types[i]);
144+
}
145+
}
146+
if (toFinish === 0) {
147+
callback(null, names);
148+
}
149+
} else {
150+
const len = names.length;
151+
for (i = 0; i < len; i++) {
152+
const type = types[i];
153+
if (type === UV_DIRENT_UNKNOWN) {
154+
const name = names[i];
155+
const stats = lazyLoadFs().statSync(pathModule.resolve(path, name));
156+
names[i] = new DirentFromStats(name, stats);
157+
} else {
158+
names[i] = new Dirent(names[i], types[i]);
159+
}
160+
}
161+
return names;
162+
}
163+
}
164+
52165
function getOptions(options, defaultOptions) {
53166
if (options === null || options === undefined ||
54167
typeof options === 'function') {
@@ -341,6 +454,8 @@ function validatePath(path, propName = 'path') {
341454
module.exports = {
342455
assertEncoding,
343456
copyObject,
457+
Dirent,
458+
getDirents,
344459
getOptions,
345460
nullCheck,
346461
preprocessSymlinkDestination,

‎src/node_constants.cc

+10
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,16 @@ void DefineSystemConstants(Local<Object> target) {
10221022
NODE_DEFINE_CONSTANT(target, O_WRONLY);
10231023
NODE_DEFINE_CONSTANT(target, O_RDWR);
10241024

1025+
// file types from readdir
1026+
NODE_DEFINE_CONSTANT(target, UV_DIRENT_UNKNOWN);
1027+
NODE_DEFINE_CONSTANT(target, UV_DIRENT_FILE);
1028+
NODE_DEFINE_CONSTANT(target, UV_DIRENT_DIR);
1029+
NODE_DEFINE_CONSTANT(target, UV_DIRENT_LINK);
1030+
NODE_DEFINE_CONSTANT(target, UV_DIRENT_FIFO);
1031+
NODE_DEFINE_CONSTANT(target, UV_DIRENT_SOCKET);
1032+
NODE_DEFINE_CONSTANT(target, UV_DIRENT_CHAR);
1033+
NODE_DEFINE_CONSTANT(target, UV_DIRENT_BLOCK);
1034+
10251035
NODE_DEFINE_CONSTANT(target, S_IFMT);
10261036
NODE_DEFINE_CONSTANT(target, S_IFREG);
10271037
NODE_DEFINE_CONSTANT(target, S_IFDIR);

‎src/node_file.cc

+174-43
Original file line numberDiff line numberDiff line change
@@ -551,51 +551,139 @@ void AfterScanDir(uv_fs_t* req) {
551551
FSReqBase* req_wrap = FSReqBase::from_req(req);
552552
FSReqAfterScope after(req_wrap, req);
553553

554-
if (after.Proceed()) {
555-
Environment* env = req_wrap->env();
556-
Local<Value> error;
557-
int r;
558-
Local<Array> names = Array::New(env->isolate(), 0);
559-
Local<Function> fn = env->push_values_to_array_function();
560-
Local<Value> name_argv[NODE_PUSH_VAL_TO_ARRAY_MAX];
561-
size_t name_idx = 0;
554+
if (!after.Proceed()) {
555+
return;
556+
}
557+
Environment* env = req_wrap->env();
558+
Local<Value> error;
559+
int r;
560+
Local<Array> names = Array::New(env->isolate(), 0);
561+
Local<Function> fn = env->push_values_to_array_function();
562+
Local<Value> name_argv[NODE_PUSH_VAL_TO_ARRAY_MAX];
563+
size_t name_idx = 0;
564+
565+
for (int i = 0; ; i++) {
566+
uv_dirent_t ent;
567+
568+
r = uv_fs_scandir_next(req, &ent);
569+
if (r == UV_EOF)
570+
break;
571+
if (r != 0) {
572+
return req_wrap->Reject(
573+
UVException(r, nullptr, req_wrap->syscall(),
574+
static_cast<const char*>(req->path)));
575+
}
562576

563-
for (int i = 0; ; i++) {
564-
uv_dirent_t ent;
577+
MaybeLocal<Value> filename =
578+
StringBytes::Encode(env->isolate(),
579+
ent.name,
580+
req_wrap->encoding(),
581+
&error);
582+
if (filename.IsEmpty())
583+
return req_wrap->Reject(error);
565584

566-
r = uv_fs_scandir_next(req, &ent);
567-
if (r == UV_EOF)
568-
break;
569-
if (r != 0) {
570-
return req_wrap->Reject(
571-
UVException(r, nullptr, req_wrap->syscall(),
572-
static_cast<const char*>(req->path)));
585+
name_argv[name_idx++] = filename.ToLocalChecked();
586+
587+
if (name_idx >= arraysize(name_argv)) {
588+
MaybeLocal<Value> ret = fn->Call(env->context(), names, name_idx,
589+
name_argv);
590+
if (ret.IsEmpty()) {
591+
return;
573592
}
593+
name_idx = 0;
594+
}
595+
}
574596

575-
MaybeLocal<Value> filename =
576-
StringBytes::Encode(env->isolate(),
577-
ent.name,
578-
req_wrap->encoding(),
579-
&error);
580-
if (filename.IsEmpty())
581-
return req_wrap->Reject(error);
597+
if (name_idx > 0) {
598+
fn->Call(env->context(), names, name_idx, name_argv)
599+
.ToLocalChecked();
600+
}
582601

583-
name_argv[name_idx++] = filename.ToLocalChecked();
602+
req_wrap->Resolve(names);
603+
}
584604

585-
if (name_idx >= arraysize(name_argv)) {
586-
fn->Call(env->context(), names, name_idx, name_argv)
587-
.ToLocalChecked();
588-
name_idx = 0;
605+
void AfterScanDirWithTypes(uv_fs_t* req) {
606+
FSReqBase* req_wrap = FSReqBase::from_req(req);
607+
FSReqAfterScope after(req_wrap, req);
608+
609+
if (!after.Proceed()) {
610+
return;
611+
}
612+
613+
Environment* env = req_wrap->env();
614+
Local<Value> error;
615+
int r;
616+
Local<Array> names = Array::New(env->isolate(), 0);
617+
Local<Function> fn = env->push_values_to_array_function();
618+
Local<Value> name_argv[NODE_PUSH_VAL_TO_ARRAY_MAX];
619+
size_t name_idx = 0;
620+
Local<Value> types = Array::New(env->isolate(), 0);
621+
Local<Value> type_argv[NODE_PUSH_VAL_TO_ARRAY_MAX];
622+
size_t type_idx = 0;
623+
624+
for (int i = 0; ; i++) {
625+
uv_dirent_t ent;
626+
627+
r = uv_fs_scandir_next(req, &ent);
628+
if (r == UV_EOF)
629+
break;
630+
if (r != 0) {
631+
return req_wrap->Reject(
632+
UVException(r, nullptr, req_wrap->syscall(),
633+
static_cast<const char*>(req->path)));
634+
}
635+
636+
MaybeLocal<Value> filename =
637+
StringBytes::Encode(env->isolate(),
638+
ent.name,
639+
req_wrap->encoding(),
640+
&error);
641+
if (filename.IsEmpty())
642+
return req_wrap->Reject(error);
643+
644+
name_argv[name_idx++] = filename.ToLocalChecked();
645+
646+
if (name_idx >= arraysize(name_argv)) {
647+
MaybeLocal<Value> ret = fn->Call(env->context(), names, name_idx,
648+
name_argv);
649+
if (ret.IsEmpty()) {
650+
return;
589651
}
652+
name_idx = 0;
590653
}
591654

592-
if (name_idx > 0) {
593-
fn->Call(env->context(), names, name_idx, name_argv)
594-
.ToLocalChecked();
655+
type_argv[type_idx++] = Integer::New(env->isolate(), ent.type);
656+
657+
if (type_idx >= arraysize(type_argv)) {
658+
MaybeLocal<Value> ret = fn->Call(env->context(), types, type_idx,
659+
type_argv);
660+
if (ret.IsEmpty()) {
661+
return;
662+
}
663+
type_idx = 0;
664+
}
665+
}
666+
667+
if (name_idx > 0) {
668+
MaybeLocal<Value> ret = fn->Call(env->context(), names, name_idx,
669+
name_argv);
670+
if (ret.IsEmpty()) {
671+
return;
595672
}
673+
}
596674

597-
req_wrap->Resolve(names);
675+
if (type_idx > 0) {
676+
MaybeLocal<Value> ret = fn->Call(env->context(), types, type_idx,
677+
type_argv);
678+
if (ret.IsEmpty()) {
679+
return;
680+
}
598681
}
682+
683+
Local<Array> result = Array::New(env->isolate(), 2);
684+
result->Set(0, names);
685+
result->Set(1, types);
686+
req_wrap->Resolve(result);
599687
}
600688

601689

@@ -1228,15 +1316,22 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
12281316

12291317
const enum encoding encoding = ParseEncoding(env->isolate(), args[1], UTF8);
12301318

1231-
FSReqBase* req_wrap_async = GetReqWrap(env, args[2]);
1232-
if (req_wrap_async != nullptr) { // readdir(path, encoding, req)
1233-
AsyncCall(env, req_wrap_async, args, "scandir", encoding, AfterScanDir,
1234-
uv_fs_scandir, *path, 0 /*flags*/);
1235-
} else { // readdir(path, encoding, undefined, ctx)
1236-
CHECK_EQ(argc, 4);
1319+
bool with_types = args[2]->BooleanValue();
1320+
1321+
FSReqBase* req_wrap_async = GetReqWrap(env, args[3]);
1322+
if (req_wrap_async != nullptr) { // readdir(path, encoding, withTypes, req)
1323+
if (with_types) {
1324+
AsyncCall(env, req_wrap_async, args, "scandir", encoding,
1325+
AfterScanDirWithTypes, uv_fs_scandir, *path, 0 /*flags*/);
1326+
} else {
1327+
AsyncCall(env, req_wrap_async, args, "scandir", encoding,
1328+
AfterScanDir, uv_fs_scandir, *path, 0 /*flags*/);
1329+
}
1330+
} else { // readdir(path, encoding, withTypes, undefined, ctx)
1331+
CHECK_EQ(argc, 5);
12371332
FSReqWrapSync req_wrap_sync;
12381333
FS_SYNC_TRACE_BEGIN(readdir);
1239-
int err = SyncCall(env, args[3], &req_wrap_sync, "scandir",
1334+
int err = SyncCall(env, args[4], &req_wrap_sync, "scandir",
12401335
uv_fs_scandir, *path, 0 /*flags*/);
12411336
FS_SYNC_TRACE_END(readdir);
12421337
if (err < 0) {
@@ -1250,14 +1345,22 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
12501345
Local<Value> name_v[NODE_PUSH_VAL_TO_ARRAY_MAX];
12511346
size_t name_idx = 0;
12521347

1348+
Local<Value> types;
1349+
Local<Value> type_v[NODE_PUSH_VAL_TO_ARRAY_MAX];
1350+
size_t type_idx;
1351+
if (with_types) {
1352+
types = Array::New(env->isolate(), 0);
1353+
type_idx = 0;
1354+
}
1355+
12531356
for (int i = 0; ; i++) {
12541357
uv_dirent_t ent;
12551358

12561359
r = uv_fs_scandir_next(&(req_wrap_sync.req), &ent);
12571360
if (r == UV_EOF)
12581361
break;
12591362
if (r != 0) {
1260-
Local<Object> ctx = args[3].As<Object>();
1363+
Local<Object> ctx = args[4].As<Object>();
12611364
ctx->Set(env->context(), env->errno_string(),
12621365
Integer::New(env->isolate(), r)).FromJust();
12631366
ctx->Set(env->context(), env->syscall_string(),
@@ -1270,8 +1373,9 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
12701373
ent.name,
12711374
encoding,
12721375
&error);
1376+
12731377
if (filename.IsEmpty()) {
1274-
Local<Object> ctx = args[3].As<Object>();
1378+
Local<Object> ctx = args[4].As<Object>();
12751379
ctx->Set(env->context(), env->error_string(), error).FromJust();
12761380
return;
12771381
}
@@ -1286,6 +1390,19 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
12861390
}
12871391
name_idx = 0;
12881392
}
1393+
1394+
if (with_types) {
1395+
type_v[type_idx++] = Integer::New(env->isolate(), ent.type);
1396+
1397+
if (type_idx >= arraysize(type_v)) {
1398+
MaybeLocal<Value> ret = fn->Call(env->context(), types, type_idx,
1399+
type_v);
1400+
if (ret.IsEmpty()) {
1401+
return;
1402+
}
1403+
type_idx = 0;
1404+
}
1405+
}
12891406
}
12901407

12911408
if (name_idx > 0) {
@@ -1295,7 +1412,21 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
12951412
}
12961413
}
12971414

1298-
args.GetReturnValue().Set(names);
1415+
if (with_types && type_idx > 0) {
1416+
MaybeLocal<Value> ret = fn->Call(env->context(), types, type_idx, type_v);
1417+
if (ret.IsEmpty()) {
1418+
return;
1419+
}
1420+
}
1421+
1422+
if (with_types) {
1423+
Local<Array> result = Array::New(env->isolate(), 2);
1424+
result->Set(0, names);
1425+
result->Set(1, types);
1426+
args.GetReturnValue().Set(result);
1427+
} else {
1428+
args.GetReturnValue().Set(names);
1429+
}
12991430
}
13001431
}
13011432

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const fs = require('fs');
6+
7+
const tmpdir = require('../common/tmpdir');
8+
9+
const binding = process.binding('fs');
10+
11+
const readdirDir = tmpdir.path;
12+
const files = ['empty', 'files', 'for', 'just', 'testing'];
13+
const constants = process.binding('constants').fs;
14+
const types = {
15+
isDirectory: constants.UV_DIRENT_DIR,
16+
isFile: constants.UV_DIRENT_FILE,
17+
isBlockDevice: constants.UV_DIRENT_BLOCK,
18+
isCharacterDevice: constants.UV_DIRENT_CHAR,
19+
isSymbolicLink: constants.UV_DIRENT_LINK,
20+
isFIFO: constants.UV_DIRENT_FIFO,
21+
isSocket: constants.UV_DIRENT_SOCKET
22+
};
23+
const typeMethods = Object.keys(types);
24+
25+
// Make sure tmp directory is clean
26+
tmpdir.refresh();
27+
28+
// Create the necessary files
29+
files.forEach(function(currentFile) {
30+
fs.closeSync(fs.openSync(`${readdirDir}/${currentFile}`, 'w'));
31+
});
32+
33+
34+
function assertDirents(dirents) {
35+
assert.strictEqual(files.length, dirents.length);
36+
for (const [i, dirent] of dirents.entries()) {
37+
assert(dirent instanceof fs.Dirent);
38+
assert.strictEqual(dirent.name, files[i]);
39+
assert.strictEqual(dirent.isFile(), true);
40+
assert.strictEqual(dirent.isDirectory(), false);
41+
assert.strictEqual(dirent.isSocket(), false);
42+
assert.strictEqual(dirent.isBlockDevice(), false);
43+
assert.strictEqual(dirent.isCharacterDevice(), false);
44+
assert.strictEqual(dirent.isFIFO(), false);
45+
assert.strictEqual(dirent.isSymbolicLink(), false);
46+
}
47+
}
48+
49+
// Check the readdir Sync version
50+
assertDirents(fs.readdirSync(readdirDir, { withFileTypes: true }));
51+
52+
// Check the readdir async version
53+
fs.readdir(readdirDir, {
54+
withFileTypes: true
55+
}, common.mustCall((err, dirents) => {
56+
assert.ifError(err);
57+
assertDirents(dirents);
58+
}));
59+
60+
// Check for correct types when the binding returns unknowns
61+
const UNKNOWN = constants.UV_DIRENT_UNKNOWN;
62+
const oldReaddir = binding.readdir;
63+
binding.readdir = common.mustCall((path, encoding, types, req, ctx) => {
64+
if (req) {
65+
const oldCb = req.oncomplete;
66+
req.oncomplete = (err, results) => {
67+
if (err) {
68+
oldCb(err);
69+
return;
70+
}
71+
results[1] = results[1].map(() => UNKNOWN);
72+
oldCb(null, results);
73+
};
74+
oldReaddir(path, encoding, types, req);
75+
} else {
76+
const results = oldReaddir(path, encoding, types, req, ctx);
77+
results[1] = results[1].map(() => UNKNOWN);
78+
return results;
79+
}
80+
}, 2);
81+
assertDirents(fs.readdirSync(readdirDir, { withFileTypes: true }));
82+
fs.readdir(readdirDir, {
83+
withFileTypes: true
84+
}, common.mustCall((err, dirents) => {
85+
assert.ifError(err);
86+
assertDirents(dirents);
87+
}));
88+
89+
// Dirent types
90+
for (const method of typeMethods) {
91+
const dirent = new fs.Dirent('foo', types[method]);
92+
for (const testMethod of typeMethods) {
93+
assert.strictEqual(dirent[testMethod](), testMethod === method);
94+
}
95+
}

‎tools/doc/type-parser.js

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const customTypesMap = {
5858
'EventEmitter': 'events.html#events_class_eventemitter',
5959

6060
'FileHandle': 'fs.html#fs_class_filehandle',
61+
'fs.Dirent': 'fs.html#fs_class_fs_dirent',
6162
'fs.FSWatcher': 'fs.html#fs_class_fs_fswatcher',
6263
'fs.ReadStream': 'fs.html#fs_class_fs_readstream',
6364
'fs.Stats': 'fs.html#fs_class_fs_stats',

0 commit comments

Comments
 (0)
Please sign in to comment.