@@ -1372,6 +1372,102 @@ function mkdirSync(path, options) { |
1372 | 1372 | } |
1373 | 1373 | } |
1374 | 1374 |
|
| 1375 | +/* |
| 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 | + |
1375 | 1471 | /** |
1376 | 1472 | * An iterative algorithm for reading the entire contents of the `basePath` directory. |
1377 | 1473 | * This function does not validate `basePath` as a directory. It is passed directly to |
@@ -1381,58 +1477,37 @@ function mkdirSync(path, options) { |
1381 | 1477 | * @returns {string[] | Dirent[]} |
1382 | 1478 | */ |
1383 | 1479 | function 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 | + }; |
1389 | 1487 |
|
1390 | 1488 | function read(path) { |
1391 | 1489 | const readdirResult = binding.readdir( |
1392 | 1490 | path, |
1393 | | - encoding, |
1394 | | - withFileTypes, |
| 1491 | + context.encoding, |
| 1492 | + context.withFileTypes, |
1395 | 1493 | ); |
1396 | 1494 |
|
1397 | 1495 | if (readdirResult === undefined) { |
1398 | 1496 | return; |
1399 | 1497 | } |
1400 | 1498 |
|
1401 | | - 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 | + }); |
1429 | 1504 | } |
1430 | 1505 |
|
1431 | | - 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]); |
1433 | 1508 | } |
1434 | 1509 |
|
1435 | | - return readdirResults; |
| 1510 | + return context.readdirResults; |
1436 | 1511 | } |
1437 | 1512 |
|
1438 | 1513 | /** |
@@ -1458,7 +1533,7 @@ function readdir(path, options, callback) { |
1458 | 1533 | } |
1459 | 1534 |
|
1460 | 1535 | if (options.recursive) { |
1461 | | - callback(null, readdirSyncRecursive(path, options)); |
| 1536 | + readdirRecursive(path, options, callback); |
1462 | 1537 | return; |
1463 | 1538 | } |
1464 | 1539 |
|
|