Skip to content

Commit 2bae8c4

Browse files
committed
fs: extends readdir option 'recursive' to allow a function to limit traversal
1 parent 41a3a1d commit 2bae8c4

File tree

4 files changed

+452
-72
lines changed

4 files changed

+452
-72
lines changed

doc/api/fs.md

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,7 +1304,11 @@ changes:
13041304
* `options` {string|Object}
13051305
* `encoding` {string} **Default:** `'utf8'`
13061306
* `withFileTypes` {boolean} **Default:** `false`
1307-
* `recursive` {boolean} **Default:** `false`
1307+
* `recursive` {boolean|Function} **Default:** `false`
1308+
* `directory` {string} A given directory file system artifact name.
1309+
* `depth` {integer} The number of steps traversed from the supplied base
1310+
path.
1311+
* Returns: {boolean}
13081312
* Returns: {Promise} Fulfills with an array of the names of the files in
13091313
the directory excluding `'.'` and `'..'`.
13101314
@@ -1318,6 +1322,12 @@ will be passed as {Buffer} objects.
13181322
If `options.withFileTypes` is set to `true`, the resolved array will contain
13191323
{fs.Dirent} objects.
13201324
1325+
When passing a function to `options.recursive` the first argument populates
1326+
with the name of a directory, or {fs.Dirent} object if `options.withFileTypes`
1327+
is true, and directory depth relative to the base path as the second argument.
1328+
This function expects to return a boolean which determines the descendant
1329+
directories to traverse.
1330+
13211331
```mjs
13221332
import { readdir } from 'node:fs/promises';
13231333
@@ -3614,7 +3624,11 @@ changes:
36143624
* `options` {string|Object}
36153625
* `encoding` {string} **Default:** `'utf8'`
36163626
* `withFileTypes` {boolean} **Default:** `false`
3617-
* `recursive` {boolean} **Default:** `false`
3627+
* `recursive` {boolean|Function} **Default:** `false`
3628+
* `directory` {string} A given directory file system artifact name.
3629+
* `depth` {integer} The number of steps traversed from the supplied base
3630+
path.
3631+
* Returns: {boolean}
36183632
* `callback` {Function}
36193633
* `err` {Error}
36203634
* `files` {string\[]|Buffer\[]|fs.Dirent\[]}
@@ -3633,6 +3647,12 @@ the filenames returned will be passed as {Buffer} objects.
36333647
If `options.withFileTypes` is set to `true`, the `files` array will contain
36343648
{fs.Dirent} objects.
36353649
3650+
When passing a function to `options.recursive` the first argument populates
3651+
with the name of a directory, or {fs.Dirent} object if `options.withFileTypes`
3652+
is true, and directory depth relative to the base path as the second argument.
3653+
This function expects to return a boolean which determines the descendant
3654+
directories to traverse.
3655+
36363656
### `fs.readFile(path[, options], callback)`
36373657
36383658
<!-- YAML
@@ -5680,7 +5700,11 @@ changes:
56805700
* `options` {string|Object}
56815701
* `encoding` {string} **Default:** `'utf8'`
56825702
* `withFileTypes` {boolean} **Default:** `false`
5683-
* `recursive` {boolean} **Default:** `false`
5703+
* `recursive` {boolean|Function} **Default:** `false`
5704+
* `directory` {string} A given directory file system artifact name.
5705+
* `depth` {integer} The number of steps traversed from the supplied base
5706+
path.
5707+
* Returns: {boolean}
56845708
* Returns: {string\[]|Buffer\[]|fs.Dirent\[]}
56855709
56865710
Reads the contents of the directory.
@@ -5695,6 +5719,12 @@ the filenames returned will be passed as {Buffer} objects.
56955719
If `options.withFileTypes` is set to `true`, the result will contain
56965720
{fs.Dirent} objects.
56975721
5722+
When passing a function to `options.recursive` the first argument populates
5723+
with the name of a directory, or {fs.Dirent} object if `options.withFileTypes`
5724+
is true, and directory depth relative to the base path as the second argument.
5725+
This function expects to return a boolean which determines the descendant
5726+
directories to traverse.
5727+
56985728
### `fs.readFileSync(path[, options])`
56995729
57005730
<!-- YAML

lib/fs.js

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,11 +1429,21 @@ function readdirSyncRecursive(basePath, options) {
14291429
const encoding = options.encoding;
14301430

14311431
const readdirResults = [];
1432-
const pathsQueue = [basePath];
1432+
const pathsQueue = [[basePath, 1]];
14331433

14341434
const ctx = { path: basePath };
1435-
function read(path) {
1435+
function read({ 0: path, 1: depth }) {
14361436
ctx.path = path;
1437+
1438+
function filterCheck(item, dirEnt) {
1439+
if (typeof options.recursive !== 'function' ||
1440+
(dirEnt === true && typeof item.name !== 'string') || (dirEnt === false && typeof item !== 'string')
1441+
) {
1442+
return true;
1443+
}
1444+
return options.recursive(item, depth);
1445+
}
1446+
14371447
const readdirResult = binding.readdir(
14381448
pathModule.toNamespacedPath(path),
14391449
encoding,
@@ -1452,8 +1462,8 @@ function readdirSyncRecursive(basePath, options) {
14521462
for (let i = 0; i < length; i++) {
14531463
const dirent = getDirent(path, readdirResult[0][i], readdirResult[1][i]);
14541464
ArrayPrototypePush(readdirResults, dirent);
1455-
if (dirent.isDirectory()) {
1456-
ArrayPrototypePush(pathsQueue, pathModule.join(dirent.path, dirent.name));
1465+
if (dirent.isDirectory() && filterCheck(dirent, true)) {
1466+
ArrayPrototypePush(pathsQueue, [pathModule.join(dirent.path, dirent.name), depth + 1]);
14571467
}
14581468
}
14591469
} else {
@@ -1463,8 +1473,8 @@ function readdirSyncRecursive(basePath, options) {
14631473
const stat = binding.internalModuleStat(resultPath);
14641474
ArrayPrototypePush(readdirResults, relativeResultPath);
14651475
// 1 indicates directory
1466-
if (stat === 1) {
1467-
ArrayPrototypePush(pathsQueue, resultPath);
1476+
if (stat === 1 && filterCheck(readdirResult[i], false)) {
1477+
ArrayPrototypePush(pathsQueue, [resultPath, depth + 1]);
14681478
}
14691479
}
14701480
}
@@ -1483,6 +1493,7 @@ function readdirSyncRecursive(basePath, options) {
14831493
* @param {string | {
14841494
* encoding?: string;
14851495
* withFileTypes?: boolean;
1496+
* recursive?: boolean | (name: string | dirent: Dirent, depth: number) => boolean;
14861497
* }} [options]
14871498
* @param {(
14881499
* err?: Error,
@@ -1494,9 +1505,6 @@ function readdir(path, options, callback) {
14941505
callback = makeCallback(typeof options === 'function' ? options : callback);
14951506
options = getOptions(options);
14961507
path = getValidatedPath(path);
1497-
if (options.recursive != null) {
1498-
validateBoolean(options.recursive, 'options.recursive');
1499-
}
15001508

15011509
if (options.recursive) {
15021510
callback(null, readdirSyncRecursive(path, options));
@@ -1525,16 +1533,13 @@ function readdir(path, options, callback) {
15251533
* @param {string | {
15261534
* encoding?: string;
15271535
* withFileTypes?: boolean;
1528-
* recursive?: boolean;
1536+
* recursive?: boolean | (name: string | dirent: Dirent, depth: number) => boolean;
15291537
* }} [options]
15301538
* @returns {string | Buffer[] | Dirent[]}
15311539
*/
15321540
function readdirSync(path, options) {
15331541
options = getOptions(options);
15341542
path = getValidatedPath(path);
1535-
if (options.recursive != null) {
1536-
validateBoolean(options.recursive, 'options.recursive');
1537-
}
15381543

15391544
if (options.recursive) {
15401545
return readdirSyncRecursive(path, options);

lib/internal/fs/promises.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -790,17 +790,17 @@ async function readdirRecursive(originalPath, options) {
790790
!!options.withFileTypes,
791791
kUsePromises,
792792
),
793+
1,
793794
],
794795
];
795796

796-
797797
if (options.withFileTypes) {
798798
while (queue.length > 0) {
799799
// If we want to implement BFS make this a `shift` call instead of `pop`
800-
const { 0: path, 1: readdir } = ArrayPrototypePop(queue);
800+
const { 0: path, 1: readdir, 2: depth } = ArrayPrototypePop(queue);
801801
for (const dirent of getDirents(path, readdir)) {
802802
ArrayPrototypePush(result, dirent);
803-
if (dirent.isDirectory()) {
803+
if (dirent.isDirectory() && (typeof options.recursive !== 'function' || options.recursive(dirent, depth))) {
804804
const direntPath = pathModule.join(path, dirent.name);
805805
ArrayPrototypePush(queue, [
806806
direntPath,
@@ -810,21 +810,22 @@ async function readdirRecursive(originalPath, options) {
810810
true,
811811
kUsePromises,
812812
),
813+
depth + 1,
813814
]);
814815
}
815816
}
816817
}
817818
} else {
818819
while (queue.length > 0) {
819-
const { 0: path, 1: readdir } = ArrayPrototypePop(queue);
820+
const { 0: path, 1: readdir, 2: depth } = ArrayPrototypePop(queue);
820821
for (const ent of readdir) {
821822
const direntPath = pathModule.join(path, ent);
822823
const stat = binding.internalModuleStat(direntPath);
823824
ArrayPrototypePush(
824825
result,
825826
pathModule.relative(originalPath, direntPath),
826827
);
827-
if (stat === 1) {
828+
if (stat === 1 && (typeof options.recursive !== 'function' || options.recursive(ent, depth))) {
828829
ArrayPrototypePush(queue, [
829830
direntPath,
830831
await binding.readdir(
@@ -833,6 +834,7 @@ async function readdirRecursive(originalPath, options) {
833834
false,
834835
kUsePromises,
835836
),
837+
depth + 1,
836838
]);
837839
}
838840
}

0 commit comments

Comments
 (0)