Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 1b65840

Browse files
author
Ethan Arrowood
committedDec 20, 2022
fs: add recursive option to readdir and opendir
Adds a naive, linear recursive algorithm for the following methods: readdir, readdirSync, opendir, opendirSync, and the promise based equivalents. Fixes: nodejs#34992
1 parent 273d869 commit 1b65840

File tree

7 files changed

+565
-26
lines changed

7 files changed

+565
-26
lines changed
 

‎doc/api/fs.md

+17
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,8 @@ changes:
12171217
* `bufferSize` {number} Number of directory entries that are buffered
12181218
internally when reading from the directory. Higher values lead to better
12191219
performance but higher memory usage. **Default:** `32`
1220+
* `recursive` {boolean} Resolved `Dir` will be an {AsyncIterable}
1221+
containing all sub files and directories. **Default:** `false`
12201222
* Returns: {Promise} Fulfills with an {fs.Dir}.
12211223
12221224
Asynchronously open a directory for iterative scanning. See the POSIX
@@ -1259,6 +1261,7 @@ changes:
12591261
* `options` {string|Object}
12601262
* `encoding` {string} **Default:** `'utf8'`
12611263
* `withFileTypes` {boolean} **Default:** `false`
1264+
* `recursive` {boolean} **Default:** `false`
12621265
* Returns: {Promise} Fulfills with an array of the names of the files in
12631266
the directory excluding `'.'` and `'..'`.
12641267
@@ -3334,6 +3337,7 @@ changes:
33343337
* `bufferSize` {number} Number of directory entries that are buffered
33353338
internally when reading from the directory. Higher values lead to better
33363339
performance but higher memory usage. **Default:** `32`
3340+
* `recursive` {boolean} **Default:** `false`
33373341
* `callback` {Function}
33383342
* `err` {Error}
33393343
* `dir` {fs.Dir}
@@ -3481,6 +3485,7 @@ changes:
34813485
* `options` {string|Object}
34823486
* `encoding` {string} **Default:** `'utf8'`
34833487
* `withFileTypes` {boolean} **Default:** `false`
3488+
* `recursive` {boolean} **Default:** `false`
34843489
* `callback` {Function}
34853490
* `err` {Error}
34863491
* `files` {string\[]|Buffer\[]|fs.Dirent\[]}
@@ -5442,6 +5447,7 @@ changes:
54425447
* `bufferSize` {number} Number of directory entries that are buffered
54435448
internally when reading from the directory. Higher values lead to better
54445449
performance but higher memory usage. **Default:** `32`
5450+
* `recursive` {boolean} **Default:** `false`
54455451
* Returns: {fs.Dir}
54465452
54475453
Synchronously open a directory. See opendir(3).
@@ -5498,6 +5504,7 @@ changes:
54985504
* `options` {string|Object}
54995505
* `encoding` {string} **Default:** `'utf8'`
55005506
* `withFileTypes` {boolean} **Default:** `false`
5507+
* `recursive` {boolean} **Default:** `false`
55015508
* Returns: {string\[]|Buffer\[]|fs.Dirent\[]}
55025509
55035510
Reads the contents of the directory.
@@ -6332,6 +6339,16 @@ The file name that this {fs.Dirent} object refers to. The type of this
63326339
value is determined by the `options.encoding` passed to [`fs.readdir()`][] or
63336340
[`fs.readdirSync()`][].
63346341
6342+
#### `dirent.path`
6343+
6344+
<!-- YAML
6345+
added: v19.0.0
6346+
-->
6347+
6348+
* {string}
6349+
6350+
The base path that this {fs.Dirent} object refers to.
6351+
63356352
### Class: `fs.FSWatcher`
63366353
63376354
<!-- YAML

‎lib/fs.js

+37
Original file line numberDiff line numberDiff line change
@@ -1384,6 +1384,33 @@ function mkdirSync(path, options) {
13841384
}
13851385
}
13861386

1387+
function readdirSyncRecursive(origPath, options) {
1388+
function _readdirSyncRecursive(path) {
1389+
path = getValidatedPath(path);
1390+
const ctx = { path };
1391+
const result = binding.readdir(pathModule.toNamespacedPath(path),
1392+
options.encoding, !!options.withFileTypes, undefined, ctx);
1393+
handleErrorFromBinding(ctx);
1394+
return options.withFileTypes ?
1395+
getDirents(path, result).flatMap((dirent) => {
1396+
return [
1397+
dirent,
1398+
...(dirent.isDirectory() ? _readdirSyncRecursive(pathModule.join(path, dirent.name)) : []),
1399+
];
1400+
}) :
1401+
result.flatMap((ent) => {
1402+
const innerPath = pathModule.join(path, ent);
1403+
const relativePath = pathModule.relative(origPath, innerPath);
1404+
const stat = fs.lstatSync(innerPath);
1405+
return [
1406+
relativePath,
1407+
...(stat.isDirectory() ? _readdirSyncRecursive(innerPath) : []),
1408+
];
1409+
});
1410+
}
1411+
return _readdirSyncRecursive(origPath);
1412+
}
1413+
13871414
/**
13881415
* Reads the contents of a directory.
13891416
* @param {string | Buffer | URL} path
@@ -1400,6 +1427,12 @@ function mkdirSync(path, options) {
14001427
function readdir(path, options, callback) {
14011428
callback = makeCallback(typeof options === 'function' ? options : callback);
14021429
options = getOptions(options);
1430+
1431+
if (options.recursive) {
1432+
callback(null, readdirSyncRecursive(path, options));
1433+
return;
1434+
}
1435+
14031436
path = getValidatedPath(path);
14041437

14051438
const req = new FSReqCallback();
@@ -1424,11 +1457,15 @@ function readdir(path, options, callback) {
14241457
* @param {string | {
14251458
* encoding?: string;
14261459
* withFileTypes?: boolean;
1460+
* recursive?: boolean;
14271461
* }} [options]
14281462
* @returns {string | Buffer[] | Dirent[]}
14291463
*/
14301464
function readdirSync(path, options) {
14311465
options = getOptions(options);
1466+
if (options.recursive) {
1467+
return readdirSyncRecursive(path, options);
1468+
}
14321469
path = getValidatedPath(path);
14331470
const ctx = { path };
14341471
const result = binding.readdir(pathModule.toNamespacedPath(path),

‎lib/internal/fs/dir.js

+71-16
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
const {
44
ArrayPrototypePush,
5-
ArrayPrototypeSlice,
6-
ArrayPrototypeSplice,
5+
ArrayPrototypeShift,
76
FunctionPrototypeBind,
87
ObjectDefineProperty,
98
PromiseReject,
@@ -99,13 +98,21 @@ class Dir {
9998
}
10099

101100
if (this[kDirBufferedEntries].length > 0) {
102-
const { 0: name, 1: type } =
103-
ArrayPrototypeSplice(this[kDirBufferedEntries], 0, 2);
104-
if (maybeSync)
105-
process.nextTick(getDirent, this[kDirPath], name, type, callback);
106-
else
107-
getDirent(this[kDirPath], name, type, callback);
108-
return;
101+
try {
102+
const dirent = ArrayPrototypeShift(this[kDirBufferedEntries]);
103+
104+
if (this[kDirOptions].recursive && dirent.isDirectory()) {
105+
this.readSyncRecursive(dirent);
106+
}
107+
108+
if (maybeSync)
109+
process.nextTick(callback, null, dirent);
110+
else
111+
callback(null, dirent);
112+
return;
113+
} catch (error) {
114+
return callback(error);
115+
}
109116
}
110117

111118
const req = new FSReqCallback();
@@ -120,8 +127,16 @@ class Dir {
120127
return callback(err, result);
121128
}
122129

123-
this[kDirBufferedEntries] = ArrayPrototypeSlice(result, 2);
124-
getDirent(this[kDirPath], result[0], result[1], callback);
130+
try {
131+
this.processReadResult(this[kDirPath], result);
132+
const dirent = ArrayPrototypeShift(this[kDirBufferedEntries]);
133+
if (this[kDirOptions].recursive && dirent.isDirectory()) {
134+
this.readSyncRecursive(dirent);
135+
}
136+
callback(null, dirent);
137+
} catch (error) {
138+
callback(error);
139+
}
125140
};
126141

127142
this[kDirOperationQueue] = [];
@@ -132,6 +147,39 @@ class Dir {
132147
);
133148
}
134149

150+
processReadResult(path, result) {
151+
for (let i = 0; i < result.length; i += 2) {
152+
ArrayPrototypePush(
153+
this[kDirBufferedEntries],
154+
getDirent(
155+
pathModule.join(path, result[i]),
156+
result[i],
157+
result[i + 1]
158+
)
159+
);
160+
}
161+
}
162+
163+
readSyncRecursive(dirent) {
164+
const ctx = { path: dirent.path };
165+
const handle = dirBinding.opendir(
166+
pathModule.toNamespacedPath(dirent.path),
167+
this[kDirOptions].encoding,
168+
undefined,
169+
ctx
170+
);
171+
handleErrorFromBinding(ctx);
172+
const result = handle.read(
173+
this[kDirOptions].encoding,
174+
this[kDirOptions].bufferSize,
175+
undefined,
176+
ctx
177+
);
178+
this.processReadResult(dirent.path, result);
179+
handle.close(undefined, ctx);
180+
handleErrorFromBinding(ctx);
181+
}
182+
135183
readSync() {
136184
if (this[kDirClosed] === true) {
137185
throw new ERR_DIR_CLOSED();
@@ -142,9 +190,11 @@ class Dir {
142190
}
143191

144192
if (this[kDirBufferedEntries].length > 0) {
145-
const { 0: name, 1: type } =
146-
ArrayPrototypeSplice(this[kDirBufferedEntries], 0, 2);
147-
return getDirent(this[kDirPath], name, type);
193+
const dirent = ArrayPrototypeShift(this[kDirBufferedEntries]);
194+
if (this[kDirOptions].recursive && dirent.isDirectory()) {
195+
this.readSyncRecursive(dirent);
196+
}
197+
return dirent;
148198
}
149199

150200
const ctx = { path: this[kDirPath] };
@@ -160,8 +210,13 @@ class Dir {
160210
return result;
161211
}
162212

163-
this[kDirBufferedEntries] = ArrayPrototypeSlice(result, 2);
164-
return getDirent(this[kDirPath], result[0], result[1]);
213+
this.processReadResult(this[kDirPath], result);
214+
215+
const dirent = ArrayPrototypeShift(this[kDirBufferedEntries]);
216+
if (this[kDirOptions].recursive && dirent.isDirectory()) {
217+
this.readSyncRecursive(dirent);
218+
}
219+
return dirent;
165220
}
166221

167222
close(callback) {

‎lib/internal/fs/promises.js

+39
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const {
44
ArrayPrototypePush,
5+
ArrayPrototypePushApply,
56
Error,
67
MathMax,
78
MathMin,
@@ -724,8 +725,46 @@ async function mkdir(path, options) {
724725
kUsePromises);
725726
}
726727

728+
async function readdirRecursive(origPath, options) {
729+
async function _readdirRecursive(path) {
730+
path = getValidatedPath(path);
731+
732+
const readdirResult = await binding.readdir(pathModule.toNamespacedPath(path),
733+
options.encoding,
734+
!!options.withFileTypes,
735+
kUsePromises);
736+
737+
const result = [];
738+
if (options.withFileTypes) {
739+
for (const dirent of getDirents(path, readdirResult)) {
740+
ArrayPrototypePush(result, dirent);
741+
if (dirent.isDirectory()) {
742+
const _dirents = await _readdirRecursive(pathModule.join(path, dirent.name));
743+
ArrayPrototypePushApply(result, _dirents);
744+
}
745+
}
746+
} else {
747+
for (const ent of readdirResult) {
748+
const innerPath = pathModule.join(path, ent);
749+
const relativePath = pathModule.relative(origPath, innerPath);
750+
ArrayPrototypePush(result, relativePath);
751+
const stat = await lstat(innerPath);
752+
if (stat.isDirectory()) {
753+
const _readdirResult = await _readdirRecursive(innerPath);
754+
ArrayPrototypePushApply(result, _readdirResult);
755+
}
756+
}
757+
}
758+
return result;
759+
}
760+
return _readdirRecursive(origPath);
761+
}
762+
727763
async function readdir(path, options) {
728764
options = getOptions(options);
765+
if (options.recursive) {
766+
return readdirRecursive(path, options);
767+
}
729768
path = getValidatedPath(path);
730769
const result = await binding.readdir(pathModule.toNamespacedPath(path),
731770
options.encoding,

‎lib/internal/fs/utils.js

+17-10
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,9 @@ function assertEncoding(encoding) {
160160
}
161161

162162
class Dirent {
163-
constructor(name, type) {
163+
constructor(name, type, path) {
164164
this.name = name;
165+
this.path = path;
165166
this[kType] = type;
166167
}
167168

@@ -195,8 +196,8 @@ class Dirent {
195196
}
196197

197198
class DirentFromStats extends Dirent {
198-
constructor(name, stats) {
199-
super(name, null);
199+
constructor(name, stats, path) {
200+
super(name, null, path);
200201
this[kStats] = stats;
201202
}
202203
}
@@ -266,13 +267,13 @@ function getDirents(path, { 0: names, 1: types }, callback) {
266267
callback(err);
267268
return;
268269
}
269-
names[idx] = new DirentFromStats(name, stats);
270+
names[idx] = new DirentFromStats(name, stats, filepath);
270271
if (--toFinish === 0) {
271272
callback(null, names);
272273
}
273274
});
274275
} else {
275-
names[i] = new Dirent(names[i], types[i]);
276+
names[i] = new Dirent(names[i], types[i], path);
276277
}
277278
}
278279
if (toFinish === 0) {
@@ -302,16 +303,17 @@ function getDirent(path, name, type, callback) {
302303
callback(err);
303304
return;
304305
}
305-
callback(null, new DirentFromStats(name, stats));
306+
callback(null, new DirentFromStats(name, stats, filepath));
306307
});
307308
} else {
308-
callback(null, new Dirent(name, type));
309+
callback(null, new Dirent(name, type, path));
309310
}
310311
} else if (type === UV_DIRENT_UNKNOWN) {
311-
const stats = lazyLoadFs().lstatSync(join(path, name));
312-
return new DirentFromStats(name, stats);
312+
const filepath = join(path, name);
313+
const stats = lazyLoadFs().lstatSync(filepath);
314+
return new DirentFromStats(name, stats, filepath);
313315
} else {
314-
return new Dirent(name, type);
316+
return new Dirent(name, type, path);
315317
}
316318
}
317319

@@ -334,6 +336,11 @@ function getOptions(options, defaultOptions = kEmptyObject) {
334336
if (options.signal !== undefined) {
335337
validateAbortSignal(options.signal, 'options.signal');
336338
}
339+
340+
if (options.recursive && typeof options.recursive !== 'boolean') {
341+
throw new ERR_INVALID_ARG_TYPE('options.recursive', ['boolean'], options);
342+
}
343+
337344
return options;
338345
}
339346

+205
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const fs = require('fs');
6+
const fsPromises = fs.promises;
7+
const pathModule = require('path');
8+
const tmpdir = require('../common/tmpdir');
9+
10+
const testDir = tmpdir.path;
11+
12+
const fileStructure = [
13+
[ 'a', [ 'foo', 'bar' ] ],
14+
[ 'b', [ 'foo', 'bar' ] ],
15+
[ 'c', [ 'foo', 'bar' ] ],
16+
[ 'd', [ 'foo', 'bar' ] ],
17+
[ 'e', [ 'foo', 'bar' ] ],
18+
[ 'f', [ 'foo', 'bar' ] ],
19+
[ 'g', [ 'foo', 'bar' ] ],
20+
[ 'h', [ 'foo', 'bar' ] ],
21+
[ 'i', [ 'foo', 'bar' ] ],
22+
[ 'j', [ 'foo', 'bar' ] ],
23+
[ 'k', [ 'foo', 'bar' ] ],
24+
[ 'l', [ 'foo', 'bar' ] ],
25+
[ 'm', [ 'foo', 'bar' ] ],
26+
[ 'n', [ 'foo', 'bar' ] ],
27+
[ 'o', [ 'foo', 'bar' ] ],
28+
[ 'p', [ 'foo', 'bar' ] ],
29+
[ 'q', [ 'foo', 'bar' ] ],
30+
[ 'r', [ 'foo', 'bar' ] ],
31+
[ 's', [ 'foo', 'bar' ] ],
32+
[ 't', [ 'foo', 'bar' ] ],
33+
[ 'u', [ 'foo', 'bar' ] ],
34+
[ 'v', [ 'foo', 'bar' ] ],
35+
[ 'w', [ 'foo', 'bar' ] ],
36+
[ 'x', [ 'foo', 'bar' ] ],
37+
[ 'y', [ 'foo', 'bar' ] ],
38+
[ 'z', [ 'foo', 'bar' ] ],
39+
[ 'aa', [ 'foo', 'bar' ] ],
40+
[ 'bb', [ 'foo', 'bar' ] ],
41+
[ 'cc', [ 'foo', 'bar' ] ],
42+
[ 'dd', [ 'foo', 'bar' ] ],
43+
[ 'ee', [ 'foo', 'bar' ] ],
44+
[ 'ff', [ 'foo', 'bar' ] ],
45+
[ 'gg', [ 'foo', 'bar' ] ],
46+
[ 'hh', [ 'foo', 'bar' ] ],
47+
[ 'ii', [ 'foo', 'bar' ] ],
48+
[ 'jj', [ 'foo', 'bar' ] ],
49+
[ 'kk', [ 'foo', 'bar' ] ],
50+
[ 'll', [ 'foo', 'bar' ] ],
51+
[ 'mm', [ 'foo', 'bar' ] ],
52+
[ 'nn', [ 'foo', 'bar' ] ],
53+
[ 'oo', [ 'foo', 'bar' ] ],
54+
[ 'pp', [ 'foo', 'bar' ] ],
55+
[ 'qq', [ 'foo', 'bar' ] ],
56+
[ 'rr', [ 'foo', 'bar' ] ],
57+
[ 'ss', [ 'foo', 'bar' ] ],
58+
[ 'tt', [ 'foo', 'bar' ] ],
59+
[ 'uu', [ 'foo', 'bar' ] ],
60+
[ 'vv', [ 'foo', 'bar' ] ],
61+
[ 'ww', [ 'foo', 'bar' ] ],
62+
[ 'xx', [ 'foo', 'bar' ] ],
63+
[ 'yy', [ 'foo', 'bar' ] ],
64+
[ 'zz', [ 'foo', 'bar' ] ],
65+
[ 'abc', [ ['def', [ 'foo', 'bar' ] ], ['ghi', [ 'foo', 'bar' ] ] ] ],
66+
];
67+
68+
function createFiles(path, fileStructure) {
69+
for (const fileOrDir of fileStructure) {
70+
if (typeof fileOrDir === 'string') {
71+
fs.closeSync(fs.openSync(`${path}/${fileOrDir}`, 'w'));
72+
} else {
73+
const dirPath = `${path}/${fileOrDir[0]}`;
74+
fs.mkdirSync(dirPath);
75+
createFiles(dirPath, fileOrDir[1]);
76+
}
77+
}
78+
}
79+
80+
// Make sure tmp directory is clean
81+
tmpdir.refresh();
82+
83+
createFiles(testDir, fileStructure);
84+
85+
const expected = [
86+
'a', 'a/bar', 'a/foo', 'aa', 'aa/bar', 'aa/foo',
87+
'abc', 'abc/def', 'abc/def/bar', 'abc/def/foo', 'abc/ghi', 'abc/ghi/bar', 'abc/ghi/foo',
88+
'b', 'b/bar', 'b/foo', 'bb', 'bb/bar', 'bb/foo',
89+
'c', 'c/bar', 'c/foo', 'cc', 'cc/bar', 'cc/foo',
90+
'd', 'd/bar', 'd/foo', 'dd', 'dd/bar', 'dd/foo',
91+
'e', 'e/bar', 'e/foo', 'ee', 'ee/bar', 'ee/foo',
92+
'f', 'f/bar', 'f/foo', 'ff', 'ff/bar', 'ff/foo',
93+
'g', 'g/bar', 'g/foo', 'gg', 'gg/bar', 'gg/foo',
94+
'h', 'h/bar', 'h/foo', 'hh', 'hh/bar', 'hh/foo',
95+
'i', 'i/bar', 'i/foo', 'ii', 'ii/bar', 'ii/foo',
96+
'j', 'j/bar', 'j/foo', 'jj', 'jj/bar', 'jj/foo',
97+
'k', 'k/bar', 'k/foo', 'kk', 'kk/bar', 'kk/foo',
98+
'l', 'l/bar', 'l/foo', 'll', 'll/bar', 'll/foo',
99+
'm', 'm/bar', 'm/foo', 'mm', 'mm/bar', 'mm/foo',
100+
'n', 'n/bar', 'n/foo', 'nn', 'nn/bar', 'nn/foo',
101+
'o', 'o/bar', 'o/foo', 'oo', 'oo/bar', 'oo/foo',
102+
'p', 'p/bar', 'p/foo', 'pp', 'pp/bar', 'pp/foo',
103+
'q', 'q/bar', 'q/foo', 'qq', 'qq/bar', 'qq/foo',
104+
'r', 'r/bar', 'r/foo', 'rr', 'rr/bar', 'rr/foo',
105+
's', 's/bar', 's/foo', 'ss', 'ss/bar', 'ss/foo',
106+
't', 't/bar', 't/foo', 'tt', 'tt/bar', 'tt/foo',
107+
'u', 'u/bar', 'u/foo', 'uu', 'uu/bar', 'uu/foo',
108+
'v', 'v/bar', 'v/foo', 'vv', 'vv/bar', 'vv/foo',
109+
'w', 'w/bar', 'w/foo', 'ww', 'ww/bar', 'ww/foo',
110+
'x', 'x/bar', 'x/foo', 'xx', 'xx/bar', 'xx/foo',
111+
'y', 'y/bar', 'y/foo', 'yy', 'yy/bar', 'yy/foo',
112+
'z', 'z/bar', 'z/foo', 'zz', 'zz/bar', 'zz/foo',
113+
];
114+
115+
function getDirentPath(dirent) {
116+
return pathModule.relative(testDir, dirent.path);
117+
}
118+
119+
function assertDirents(dirents) {
120+
dirents.sort((a, b) => (getDirentPath(a) < getDirentPath(b) ? -1 : 1));
121+
for (const [i, dirent] of dirents.entries()) {
122+
assert(dirent instanceof fs.Dirent);
123+
assert.strictEqual(getDirentPath(dirent), expected[i]);
124+
}
125+
}
126+
127+
function processDirSync(dir) {
128+
const dirents = [];
129+
let dirent = dir.readSync();
130+
while (dirent !== null) {
131+
dirents.push(dirent);
132+
dirent = dir.readSync();
133+
}
134+
assertDirents(dirents);
135+
}
136+
137+
// opendir read results sync
138+
139+
{
140+
const dir = fs.opendirSync(testDir, { recursive: true });
141+
processDirSync(dir);
142+
dir.closeSync();
143+
}
144+
145+
{
146+
fs.opendir(testDir, { recursive: true }, common.mustSucceed((dir) => {
147+
processDirSync(dir);
148+
dir.close(common.mustSucceed());
149+
}));
150+
}
151+
152+
// opendir read result using callback
153+
154+
function processDirCb(dir, cb) {
155+
const acc = [];
156+
157+
function _process(dir, acc, cb) {
158+
dir.read((err, dirent) => {
159+
if (err) {
160+
cb(err);
161+
}
162+
163+
if (dirent !== null) {
164+
acc.push(dirent);
165+
_process(dir, acc, cb);
166+
} else {
167+
cb(null, acc);
168+
}
169+
});
170+
}
171+
172+
_process(dir, acc, cb);
173+
}
174+
175+
{
176+
const dir = fs.opendirSync(testDir, { recursive: true });
177+
processDirCb(dir, common.mustSucceed((dirents) => {
178+
assertDirents(dirents);
179+
dir.close(common.mustSucceed());
180+
}));
181+
}
182+
183+
{
184+
fs.opendir(testDir, { recursive: true }, common.mustSucceed((dir) => {
185+
processDirCb(dir, common.mustSucceed((dirents) => {
186+
assertDirents(dirents);
187+
dir.close(common.mustSucceed());
188+
}));
189+
}));
190+
}
191+
192+
// opendir read result using AsyncIterator
193+
194+
{
195+
async function test() {
196+
const dir = await fsPromises.opendir(testDir, { recursive: true });
197+
const dirents = [];
198+
for await (const dirent of dir) {
199+
dirents.push(dirent);
200+
}
201+
assertDirents(dirents);
202+
}
203+
204+
test().then(common.mustCall());
205+
}
+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const fs = require('fs');
6+
const pathModule = require('path');
7+
const tmpdir = require('../common/tmpdir');
8+
9+
const readdirDir = tmpdir.path;
10+
11+
const fileStructure = [
12+
[ 'a', [ 'foo', 'bar' ] ],
13+
[ 'b', [ 'foo', 'bar' ] ],
14+
[ 'c', [ 'foo', 'bar' ] ],
15+
[ 'd', [ 'foo', 'bar' ] ],
16+
[ 'e', [ 'foo', 'bar' ] ],
17+
[ 'f', [ 'foo', 'bar' ] ],
18+
[ 'g', [ 'foo', 'bar' ] ],
19+
[ 'h', [ 'foo', 'bar' ] ],
20+
[ 'i', [ 'foo', 'bar' ] ],
21+
[ 'j', [ 'foo', 'bar' ] ],
22+
[ 'k', [ 'foo', 'bar' ] ],
23+
[ 'l', [ 'foo', 'bar' ] ],
24+
[ 'm', [ 'foo', 'bar' ] ],
25+
[ 'n', [ 'foo', 'bar' ] ],
26+
[ 'o', [ 'foo', 'bar' ] ],
27+
[ 'p', [ 'foo', 'bar' ] ],
28+
[ 'q', [ 'foo', 'bar' ] ],
29+
[ 'r', [ 'foo', 'bar' ] ],
30+
[ 's', [ 'foo', 'bar' ] ],
31+
[ 't', [ 'foo', 'bar' ] ],
32+
[ 'u', [ 'foo', 'bar' ] ],
33+
[ 'v', [ 'foo', 'bar' ] ],
34+
[ 'w', [ 'foo', 'bar' ] ],
35+
[ 'x', [ 'foo', 'bar' ] ],
36+
[ 'y', [ 'foo', 'bar' ] ],
37+
[ 'z', [ 'foo', 'bar' ] ],
38+
[ 'aa', [ 'foo', 'bar' ] ],
39+
[ 'bb', [ 'foo', 'bar' ] ],
40+
[ 'cc', [ 'foo', 'bar' ] ],
41+
[ 'dd', [ 'foo', 'bar' ] ],
42+
[ 'ee', [ 'foo', 'bar' ] ],
43+
[ 'ff', [ 'foo', 'bar' ] ],
44+
[ 'gg', [ 'foo', 'bar' ] ],
45+
[ 'hh', [ 'foo', 'bar' ] ],
46+
[ 'ii', [ 'foo', 'bar' ] ],
47+
[ 'jj', [ 'foo', 'bar' ] ],
48+
[ 'kk', [ 'foo', 'bar' ] ],
49+
[ 'll', [ 'foo', 'bar' ] ],
50+
[ 'mm', [ 'foo', 'bar' ] ],
51+
[ 'nn', [ 'foo', 'bar' ] ],
52+
[ 'oo', [ 'foo', 'bar' ] ],
53+
[ 'pp', [ 'foo', 'bar' ] ],
54+
[ 'qq', [ 'foo', 'bar' ] ],
55+
[ 'rr', [ 'foo', 'bar' ] ],
56+
[ 'ss', [ 'foo', 'bar' ] ],
57+
[ 'tt', [ 'foo', 'bar' ] ],
58+
[ 'uu', [ 'foo', 'bar' ] ],
59+
[ 'vv', [ 'foo', 'bar' ] ],
60+
[ 'ww', [ 'foo', 'bar' ] ],
61+
[ 'xx', [ 'foo', 'bar' ] ],
62+
[ 'yy', [ 'foo', 'bar' ] ],
63+
[ 'zz', [ 'foo', 'bar' ] ],
64+
[ 'abc', [ ['def', [ 'foo', 'bar' ] ], ['ghi', [ 'foo', 'bar' ] ] ] ],
65+
];
66+
67+
function createFiles(path, fileStructure) {
68+
for (const fileOrDir of fileStructure) {
69+
if (typeof fileOrDir === 'string') {
70+
fs.closeSync(fs.openSync(`${path}/${fileOrDir}`, 'w'));
71+
} else {
72+
const dirPath = `${path}/${fileOrDir[0]}`;
73+
fs.mkdirSync(dirPath);
74+
createFiles(dirPath, fileOrDir[1]);
75+
}
76+
}
77+
}
78+
79+
// Make sure tmp directory is clean
80+
tmpdir.refresh();
81+
82+
createFiles(readdirDir, fileStructure);
83+
84+
const expected = [
85+
'a', 'a/bar', 'a/foo', 'aa', 'aa/bar', 'aa/foo',
86+
'abc', 'abc/def', 'abc/def/bar', 'abc/def/foo', 'abc/ghi', 'abc/ghi/bar', 'abc/ghi/foo',
87+
'b', 'b/bar', 'b/foo', 'bb', 'bb/bar', 'bb/foo',
88+
'c', 'c/bar', 'c/foo', 'cc', 'cc/bar', 'cc/foo',
89+
'd', 'd/bar', 'd/foo', 'dd', 'dd/bar', 'dd/foo',
90+
'e', 'e/bar', 'e/foo', 'ee', 'ee/bar', 'ee/foo',
91+
'f', 'f/bar', 'f/foo', 'ff', 'ff/bar', 'ff/foo',
92+
'g', 'g/bar', 'g/foo', 'gg', 'gg/bar', 'gg/foo',
93+
'h', 'h/bar', 'h/foo', 'hh', 'hh/bar', 'hh/foo',
94+
'i', 'i/bar', 'i/foo', 'ii', 'ii/bar', 'ii/foo',
95+
'j', 'j/bar', 'j/foo', 'jj', 'jj/bar', 'jj/foo',
96+
'k', 'k/bar', 'k/foo', 'kk', 'kk/bar', 'kk/foo',
97+
'l', 'l/bar', 'l/foo', 'll', 'll/bar', 'll/foo',
98+
'm', 'm/bar', 'm/foo', 'mm', 'mm/bar', 'mm/foo',
99+
'n', 'n/bar', 'n/foo', 'nn', 'nn/bar', 'nn/foo',
100+
'o', 'o/bar', 'o/foo', 'oo', 'oo/bar', 'oo/foo',
101+
'p', 'p/bar', 'p/foo', 'pp', 'pp/bar', 'pp/foo',
102+
'q', 'q/bar', 'q/foo', 'qq', 'qq/bar', 'qq/foo',
103+
'r', 'r/bar', 'r/foo', 'rr', 'rr/bar', 'rr/foo',
104+
's', 's/bar', 's/foo', 'ss', 'ss/bar', 'ss/foo',
105+
't', 't/bar', 't/foo', 'tt', 'tt/bar', 'tt/foo',
106+
'u', 'u/bar', 'u/foo', 'uu', 'uu/bar', 'uu/foo',
107+
'v', 'v/bar', 'v/foo', 'vv', 'vv/bar', 'vv/foo',
108+
'w', 'w/bar', 'w/foo', 'ww', 'ww/bar', 'ww/foo',
109+
'x', 'x/bar', 'x/foo', 'xx', 'xx/bar', 'xx/foo',
110+
'y', 'y/bar', 'y/foo', 'yy', 'yy/bar', 'yy/foo',
111+
'z', 'z/bar', 'z/foo', 'zz', 'zz/bar', 'zz/foo',
112+
];
113+
114+
function getDirentPath(dirent) {
115+
return pathModule.relative(readdirDir, pathModule.join(dirent.path, dirent.name));
116+
}
117+
118+
function assertDirents(dirents) {
119+
dirents.sort((a, b) => (getDirentPath(a) < getDirentPath(b) ? -1 : 1));
120+
for (const [i, dirent] of dirents.entries()) {
121+
assert(dirent instanceof fs.Dirent);
122+
assert.strictEqual(getDirentPath(dirent), expected[i]);
123+
}
124+
}
125+
126+
// readdirSync
127+
128+
// readdirSync { recursive }
129+
{
130+
const result = fs.readdirSync(readdirDir, { recursive: true });
131+
assert.deepStrictEqual(result.sort(), expected);
132+
}
133+
134+
// readdirSync { recursive, withFileTypes }
135+
{
136+
const result = fs.readdirSync(readdirDir, { recursive: true, withFileTypes: true });
137+
assertDirents(result);
138+
}
139+
140+
// readdir
141+
142+
// readdir { recursive } callback
143+
{
144+
fs.readdir(readdirDir, { recursive: true },
145+
common.mustSucceed((result) => {
146+
assert.deepStrictEqual(result.sort(), expected);
147+
}));
148+
}
149+
150+
// Readdir { recursive, withFileTypes } callback
151+
{
152+
fs.readdir(readdirDir, { recursive: true, withFileTypes: true },
153+
common.mustSucceed((result) => {
154+
assertDirents(result);
155+
}));
156+
}
157+
158+
// fs.promises.readdir
159+
160+
// fs.promises.readdir { recursive }
161+
{
162+
async function test() {
163+
const result = await fs.promises.readdir(readdirDir, { recursive: true });
164+
assert.deepStrictEqual(result.sort(), expected);
165+
}
166+
167+
test().then(common.mustCall());
168+
}
169+
170+
// fs.promises.readdir { recursive, withFileTypes }
171+
{
172+
async function test() {
173+
const result = await fs.promises.readdir(readdirDir, { recursive: true, withFileTypes: true });
174+
console.log(result);
175+
assertDirents(result);
176+
}
177+
178+
test().then(common.mustCall());
179+
}

0 commit comments

Comments
 (0)
Please sign in to comment.