Skip to content

Commit ad97314

Browse files
bengltargos
authored andcommitted
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,

0 commit comments

Comments
 (0)