Skip to content

Commit

Permalink
fs: add new option depth to readdir which regulates depth of recursion
Browse files Browse the repository at this point in the history
This commit contains the necessary fixes to code and documentation per
manual testing, but I still need to complete test units for test
autoamtion.

fixes: https://github.com/nodjs/node/issues/49243
  • Loading branch information
prettydiff committed Aug 20, 2023
1 parent 41a3a1d commit 7fa9316
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 35 deletions.
21 changes: 21 additions & 0 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,7 @@ changes:
* `encoding` {string} **Default:** `'utf8'`
* `withFileTypes` {boolean} **Default:** `false`
* `recursive` {boolean} **Default:** `false`
* `depth` {number} **Default:** `1`
* Returns: {Promise} Fulfills with an array of the names of the files in
the directory excluding `'.'` and `'..'`.
Expand All @@ -1318,6 +1319,12 @@ will be passed as {Buffer} objects.
If `options.withFileTypes` is set to `true`, the resolved array will contain
{fs.Dirent} objects.
The `options.depth` determines the amount of recursion to apply. A value less
than 1 applies full recursion, value of 1 applies no recursion, and greater
values determine the depth of descendant directies to traverse. If
`options.recursive` is value `true` then `options.depth` defaults to a value
of `0` and otherwise defaults to a value `1`.
```mjs
import { readdir } from 'node:fs/promises';
Expand Down Expand Up @@ -3615,6 +3622,7 @@ changes:
* `encoding` {string} **Default:** `'utf8'`
* `withFileTypes` {boolean} **Default:** `false`
* `recursive` {boolean} **Default:** `false`
* `depth` {number} **Default:** `1`
* `callback` {Function}
* `err` {Error}
* `files` {string\[]|Buffer\[]|fs.Dirent\[]}
Expand All @@ -3633,6 +3641,12 @@ the filenames returned will be passed as {Buffer} objects.
If `options.withFileTypes` is set to `true`, the `files` array will contain
{fs.Dirent} objects.
The `options.depth` determines the amount of recursion to apply. A value less
than 1 applies full recursion, value of 1 applies no recursion, and greater
values determine the depth of descendant directies to traverse. If
`options.recursive` is value `true` then `options.depth` defaults to a value
of `0` and otherwise defaults to a value `1`.
### `fs.readFile(path[, options], callback)`
<!-- YAML
Expand Down Expand Up @@ -5681,6 +5695,7 @@ changes:
* `encoding` {string} **Default:** `'utf8'`
* `withFileTypes` {boolean} **Default:** `false`
* `recursive` {boolean} **Default:** `false`
* `depth` {boolean} **Default:** `1`
* Returns: {string\[]|Buffer\[]|fs.Dirent\[]}
Reads the contents of the directory.
Expand All @@ -5695,6 +5710,12 @@ the filenames returned will be passed as {Buffer} objects.
If `options.withFileTypes` is set to `true`, the result will contain
{fs.Dirent} objects.
The `options.depth` determines the amount of recursion to apply. A value less
than 1 applies full recursion, value of 1 applies no recursion, and greater
values determine the depth of descendant directies to traverse. If
`options.recursive` is value `true` then `options.depth` defaults to a value
of `0` and otherwise defaults to a value `1`.
### `fs.readFileSync(path[, options])`
<!-- YAML
Expand Down
23 changes: 19 additions & 4 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -1427,6 +1427,7 @@ function mkdirSync(path, options) {
function readdirSyncRecursive(basePath, options) {
const withFileTypes = Boolean(options.withFileTypes);
const encoding = options.encoding;
const sep = pathModule.sep;

const readdirResults = [];
const pathsQueue = [basePath];
Expand Down Expand Up @@ -1469,9 +1470,11 @@ function readdirSyncRecursive(basePath, options) {
}
}
}

const base = basePath.replace(/\//g, sep) + sep;
for (let i = 0; i < pathsQueue.length; i++) {
read(pathsQueue[i]);
if (options.depth < 1 || pathsQueue[i] === basePath || pathsQueue[i].replace(base, "").split(sep).length < options.depth) {
read(pathsQueue[i]);
}
}

return readdirResults;
Expand All @@ -1498,7 +1501,13 @@ function readdir(path, options, callback) {
validateBoolean(options.recursive, 'options.recursive');
}

if (options.recursive) {
const depth = (options !== null && options !== undefined && typeof options.depth === 'number')
? Math.floor(options.depth)
: options.recursive
? 0
: 1;
if (depth !== 1) {
options.depth = depth;
callback(null, readdirSyncRecursive(path, options));
return;
}
Expand Down Expand Up @@ -1536,7 +1545,13 @@ function readdirSync(path, options) {
validateBoolean(options.recursive, 'options.recursive');
}

if (options.recursive) {
const depth = (options !== null && options !== undefined && typeof options.depth === 'number')
? Math.floor(options.depth)
: options.recursive
? 0
: 1;
if (depth !== 1) {
options.depth = depth;
return readdirSyncRecursive(path, options);
}

Expand Down
72 changes: 41 additions & 31 deletions lib/internal/fs/promises.js
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,8 @@ async function mkdir(path, options) {

async function readdirRecursive(originalPath, options) {
const result = [];
const sep = pathModule.sep;
const origin = originalPath.replace(/\//g, sep) + sep;
const queue = [
[
originalPath,
Expand All @@ -793,47 +795,50 @@ async function readdirRecursive(originalPath, options) {
],
];


if (options.withFileTypes) {
while (queue.length > 0) {
// If we want to implement BFS make this a `shift` call instead of `pop`
const { 0: path, 1: readdir } = ArrayPrototypePop(queue);
for (const dirent of getDirents(path, readdir)) {
ArrayPrototypePush(result, dirent);
if (dirent.isDirectory()) {
const direntPath = pathModule.join(path, dirent.name);
ArrayPrototypePush(queue, [
direntPath,
await binding.readdir(
if (options.depth < 1 || path === originalPath || path.replace(origin, "").split(sep).length < options.depth) {
for (const dirent of getDirents(path, readdir)) {
ArrayPrototypePush(result, dirent);
if (dirent.isDirectory()) {
const direntPath = pathModule.join(path, dirent.name);
ArrayPrototypePush(queue, [
direntPath,
options.encoding,
true,
kUsePromises,
),
]);
await binding.readdir(
direntPath,
options.encoding,
true,
kUsePromises,
),
]);
}
}
}
}
} else {
while (queue.length > 0) {
const { 0: path, 1: readdir } = ArrayPrototypePop(queue);
for (const ent of readdir) {
const direntPath = pathModule.join(path, ent);
const stat = binding.internalModuleStat(direntPath);
ArrayPrototypePush(
result,
pathModule.relative(originalPath, direntPath),
);
if (stat === 1) {
ArrayPrototypePush(queue, [
direntPath,
await binding.readdir(
pathModule.toNamespacedPath(direntPath),
options.encoding,
false,
kUsePromises,
),
]);
if (options.depth < 1 || path === originalPath || path.replace(origin, "").split(sep).length < options.depth) {
for (const ent of readdir) {
const direntPath = pathModule.join(path, ent);
const stat = binding.internalModuleStat(direntPath);
ArrayPrototypePush(
result,
pathModule.relative(originalPath, direntPath),
);
if (stat === 1) {
ArrayPrototypePush(queue, [
direntPath,
await binding.readdir(
pathModule.toNamespacedPath(direntPath),
options.encoding,
false,
kUsePromises,
),
]);
}
}
}
}
Expand All @@ -845,7 +850,12 @@ async function readdirRecursive(originalPath, options) {
async function readdir(path, options) {
options = getOptions(options);
path = getValidatedPath(path);
if (options.recursive) {
const depth = (options !== null && options !== undefined && typeof options.depth === 'number')
? Math.floor(options.depth)
: options.recursive
? 0
: 1;
if (depth !== 1) {
return readdirRecursive(path, options);
}
const result = await binding.readdir(
Expand Down

0 comments on commit 7fa9316

Please sign in to comment.