lib: fix `fs.readdir` recursive async · nodejs/node@d2007ae
@@ -1372,6 +1372,102 @@ function mkdirSync(path, options) {
13721372}
13731373}
137413741375+/*
1376+ * An recursive algorithm for reading the entire contents of the `basePath` directory.
1377+ * This function does not validate `basePath` as a directory. It is passed directly to
1378+ * `binding.readdir`.
1379+ * @param {string} basePath
1380+ * @param {{ encoding: string, withFileTypes: boolean }} options
1381+ * @param {(
1382+ * err?: Error,
1383+ * files?: string[] | Buffer[] | Dirent[]
1384+ * ) => any} callback
1385+ * @returns {void}
1386+*/
1387+function readdirRecursive(basePath, options, callback) {
1388+const context = {
1389+withFileTypes: Boolean(options.withFileTypes),
1390+encoding: options.encoding,
1391+ basePath,
1392+readdirResults: [],
1393+pathsQueue: [basePath],
1394+};
1395+1396+let i = 0;
1397+1398+function read(path) {
1399+const req = new FSReqCallback();
1400+req.oncomplete = (err, result) => {
1401+if (err) {
1402+callback(err);
1403+return;
1404+}
1405+1406+if (result === undefined) {
1407+callback(null, context.readdirResults);
1408+return;
1409+}
1410+1411+processReaddirResult({
1412+ result,
1413+currentPath: path,
1414+ context,
1415+});
1416+1417+if (i < context.pathsQueue.length) {
1418+read(context.pathsQueue[i++]);
1419+} else {
1420+callback(null, context.readdirResults);
1421+}
1422+};
1423+1424+binding.readdir(
1425+path,
1426+context.encoding,
1427+context.withFileTypes,
1428+req,
1429+);
1430+}
1431+1432+read(context.pathsQueue[i++]);
1433+}
1434+1435+// Calling `readdir` with `withFileTypes=true`, the result is an array of arrays.
1436+// The first array is the names, and the second array is the types.
1437+// They are guaranteed to be the same length; hence, setting `length` to the length
1438+// of the first array within the result.
1439+const processReaddirResult = (args) => (args.context.withFileTypes ? handleDirents(args) : handleFilePaths(args));
1440+1441+function handleDirents({ result, currentPath, context }) {
1442+const { 0: names, 1: types } = result;
1443+const { length } = names;
1444+1445+for (let i = 0; i < length; i++) {
1446+// Avoid excluding symlinks, as they are not directories.
1447+// Refs: https://github.com/nodejs/node/issues/52663
1448+const fullPath = pathModule.join(currentPath, names[i]);
1449+const dirent = getDirent(currentPath, names[i], types[i]);
1450+ArrayPrototypePush(context.readdirResults, dirent);
1451+1452+if (dirent.isDirectory() || binding.internalModuleStat(binding, fullPath) === 1) {
1453+ArrayPrototypePush(context.pathsQueue, fullPath);
1454+}
1455+}
1456+}
1457+1458+function handleFilePaths({ result, currentPath, context }) {
1459+for (let i = 0; i < result.length; i++) {
1460+const resultPath = pathModule.join(currentPath, result[i]);
1461+const relativeResultPath = pathModule.relative(context.basePath, resultPath);
1462+const stat = binding.internalModuleStat(binding, resultPath);
1463+ArrayPrototypePush(context.readdirResults, relativeResultPath);
1464+1465+if (stat === 1) {
1466+ArrayPrototypePush(context.pathsQueue, resultPath);
1467+}
1468+}
1469+}
1470+13751471/**
13761472 * An iterative algorithm for reading the entire contents of the `basePath` directory.
13771473 * This function does not validate `basePath` as a directory. It is passed directly to
@@ -1381,58 +1477,37 @@ function mkdirSync(path, options) {
13811477 * @returns {string[] | Dirent[]}
13821478 */
13831479function readdirSyncRecursive(basePath, options) {
1384-const withFileTypes = Boolean(options.withFileTypes);
1385-const encoding = options.encoding;
1386-1387-const readdirResults = [];
1388-const pathsQueue = [basePath];
1480+const context = {
1481+withFileTypes: Boolean(options.withFileTypes),
1482+encoding: options.encoding,
1483+ basePath,
1484+readdirResults: [],
1485+pathsQueue: [basePath],
1486+};
1389148713901488function read(path) {
13911489const readdirResult = binding.readdir(
13921490path,
1393-encoding,
1394-withFileTypes,
1491+context.encoding,
1492+context.withFileTypes,
13951493);
1396149413971495if (readdirResult === undefined) {
13981496return;
13991497}
140014981401-if (withFileTypes) {
1402-// Calling `readdir` with `withFileTypes=true`, the result is an array of arrays.
1403-// The first array is the names, and the second array is the types.
1404-// They are guaranteed to be the same length; hence, setting `length` to the length
1405-// of the first array within the result.
1406-const length = readdirResult[0].length;
1407-for (let i = 0; i < length; i++) {
1408-// Avoid excluding symlinks, as they are not directories.
1409-// Refs: https://github.com/nodejs/node/issues/52663
1410-const stat = binding.internalModuleStat(binding, pathModule.join(path, readdirResult[0][i]));
1411-const dirent = getDirent(path, readdirResult[0][i], readdirResult[1][i]);
1412-ArrayPrototypePush(readdirResults, dirent);
1413-if (dirent.isDirectory() || stat === 1) {
1414-ArrayPrototypePush(pathsQueue, pathModule.join(dirent.parentPath, dirent.name));
1415-}
1416-}
1417-} else {
1418-for (let i = 0; i < readdirResult.length; i++) {
1419-const resultPath = pathModule.join(path, readdirResult[i]);
1420-const relativeResultPath = pathModule.relative(basePath, resultPath);
1421-const stat = binding.internalModuleStat(binding, resultPath);
1422-ArrayPrototypePush(readdirResults, relativeResultPath);
1423-// 1 indicates directory
1424-if (stat === 1) {
1425-ArrayPrototypePush(pathsQueue, resultPath);
1426-}
1427-}
1428-}
1499+processReaddirResult({
1500+result: readdirResult,
1501+currentPath: path,
1502+ context,
1503+});
14291504}
143015051431-for (let i = 0; i < pathsQueue.length; i++) {
1432-read(pathsQueue[i]);
1506+for (let i = 0; i < context.pathsQueue.length; i++) {
1507+read(context.pathsQueue[i]);
14331508}
143415091435-return readdirResults;
1510+return context.readdirResults;
14361511}
1437151214381513/**
@@ -1458,7 +1533,7 @@ function readdir(path, options, callback) {
14581533}
1459153414601535if (options.recursive) {
1461-callback(null, readdirSyncRecursive(path, options));
1536+readdirRecursive(path, options, callback);
14621537return;
14631538}
14641539