| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 5b7d02e commit 262f0ec
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,16 +8,18 @@ const bench = common.createBenchmark(main, { | |||
| 8 | 8 | n: [10], | |
| 9 | 9 | dir: [ 'lib', 'test/parallel'], | |
| 10 | 10 | withFileTypes: ['true', 'false'], | |
| 11 | + recursive: ['true', 'false'], | ||
| 11 | 12 | }); | |
| 12 | 13 | ||
| 13 | - function main({ n, dir, withFileTypes }) { | ||
| 14 | + function main({ n, dir, withFileTypes, recursive }) { | ||
| 14 | 15 | withFileTypes = withFileTypes === 'true'; | |
| 16 | + recursive = recursive === 'true'; | ||
| 15 | 17 | const fullPath = path.resolve(__dirname, '../../', dir); | |
| 16 | 18 | bench.start(); | |
| 17 | 19 | (function r(cntr) { | |
| 18 | 20 | if (cntr-- <= 0) | |
| 19 | 21 | return bench.end(n); | |
| 20 | - fs.readdir(fullPath, { withFileTypes }, () => { | ||
| 22 | + fs.readdir(fullPath, { withFileTypes, recursive }, () => { | ||
| 21 | 23 | r(cntr); | |
| 22 | 24 | }); | |
| 23 | 25 | }(n)); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,15 +8,17 @@ const bench = common.createBenchmark(main, { | |||
| 8 | 8 | n: [10], | |
| 9 | 9 | dir: [ 'lib', 'test/parallel'], | |
| 10 | 10 | withFileTypes: ['true', 'false'], | |
| 11 | + recursive: ['true', 'false'], | ||
| 11 | 12 | }); | |
| 12 | 13 | ||
| 13 | 14 | ||
| 14 | - function main({ n, dir, withFileTypes }) { | ||
| 15 | + function main({ n, dir, withFileTypes, recursive }) { | ||
| 15 | 16 | withFileTypes = withFileTypes === 'true'; | |
| 17 | + recursive = recursive === 'true'; | ||
| 16 | 18 | const fullPath = path.resolve(__dirname, '../../', dir); | |
| 17 | 19 | bench.start(); | |
| 18 | 20 | for (let i = 0; i < n; i++) { | |
| 19 | - fs.readdirSync(fullPath, { withFileTypes }); | ||
| 21 | + fs.readdirSync(fullPath, { withFileTypes, recursive }); | ||
| 20 | 22 | } | |
| 21 | 23 | bench.end(n); | |
| 22 | 24 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -57,6 +57,9 @@ const { | |||
| 57 | 57 | F_OK, | |
| 58 | 58 | O_WRONLY, | |
| 59 | 59 | O_SYMLINK, | |
| 60 | + UV_DIRENT_DIR, | ||
| 61 | + UV_DIRENT_LINK, | ||
| 62 | + UV_DIRENT_UNKNOWN, | ||
| 60 | 63 | } = constants; | |
| 61 | 64 | ||
| 62 | 65 | const pathModule = require('path'); | |
@@ -1739,6 +1742,43 @@ function mkdirSync(path, options) { | |||
| 1739 | 1742 | } | |
| 1740 | 1743 | } | |
| 1741 | 1744 | ||
| 1745 | + /** | ||
| 1746 | + * Appends one directory's entries to `context.results` and the subdirectories | ||
| 1747 | + * still to visit to `context.dirs` (with the prefix their entries get in | ||
| 1748 | + * string results in `context.prefixes`). `result` is a `binding.readdir()` | ||
| 1749 | + * result with file types, so only symbolic links and entries of unknown type | ||
| 1750 | + * need a stat() to find out whether they lead to a directory. | ||
| 1751 | + * @param {string} dir | ||
| 1752 | + * @param {string} prefix | ||
| 1753 | + * @param {[string[], number[]]} result | ||
| 1754 | + * @param {{ withFileTypes: boolean, results: (string | Dirent)[], dirs: string[], prefixes: string[] }} context | ||
| 1755 | + */ | ||
| 1756 | + function collectRecursiveReaddirResult(dir, prefix, { 0: names, 1: types }, context) { | ||
| 1757 | + const { length } = names; | ||
| 1758 | + for (let i = 0; i < length; i++) { | ||
| 1759 | + const name = names[i]; | ||
| 1760 | + const relative = prefix === '' ? name : `${prefix}${pathModule.sep}${name}`; | ||
| 1761 | + let isDirectory; | ||
| 1762 | + if (context.withFileTypes) { | ||
| 1763 | + const dirent = getDirent(dir, name, types[i]); | ||
| 1764 | + ArrayPrototypePush(context.results, dirent); | ||
| 1765 | + // Follow symbolic links to directories, see https://github.com/nodejs/node/issues/52663 | ||
| 1766 | + isDirectory = dirent.isDirectory() || | ||
| 1767 | + (dirent.isSymbolicLink() && binding.internalModuleStat(pathModule.join(dir, name)) === 1); | ||
| 1768 | + } else { | ||
| 1769 | + ArrayPrototypePush(context.results, relative); | ||
| 1770 | + const type = types[i]; | ||
| 1771 | + isDirectory = type === UV_DIRENT_DIR || | ||
| 1772 | + ((type === UV_DIRENT_LINK || type === UV_DIRENT_UNKNOWN) && | ||
| 1773 | + binding.internalModuleStat(pathModule.join(dir, name)) === 1); | ||
| 1774 | + } | ||
| 1775 | + if (isDirectory) { | ||
| 1776 | + ArrayPrototypePush(context.dirs, pathModule.join(dir, name)); | ||
| 1777 | + ArrayPrototypePush(context.prefixes, relative); | ||
| 1778 | + } | ||
| 1779 | + } | ||
| 1780 | + } | ||
| 1781 | + | ||
| 1742 | 1782 | /* | |
| 1743 | 1783 | * An recursive algorithm for reading the entire contents of the `basePath` directory. | |
| 1744 | 1784 | * This function does not validate `basePath` as a directory. It is passed directly to | |
@@ -1754,15 +1794,20 @@ function mkdirSync(path, options) { | |||
| 1754 | 1794 | function readdirRecursive(basePath, options, callback) { | |
| 1755 | 1795 | const context = { | |
| 1756 | 1796 | withFileTypes: Boolean(options.withFileTypes), | |
| 1757 | - encoding: options.encoding, | ||
| 1758 | - basePath, | ||
| 1759 | - readdirResults: [], | ||
| 1760 | - pathsQueue: [basePath], | ||
| 1797 | + results: [], | ||
| 1798 | + dirs: [basePath], | ||
| 1799 | + prefixes: [''], | ||
| 1761 | 1800 | }; | |
| 1762 | 1801 | ||
| 1763 | 1802 | let i = 0; | |
| 1764 | 1803 | ||
| 1765 | - function read(path) { | ||
| 1804 | + /** | ||
| 1805 | + * Reads one directory from `context.dirs` and then moves on to the next | ||
| 1806 | + * one, or calls back once none are left. | ||
| 1807 | + * @param {string} path | ||
| 1808 | + * @param {string} prefix path of this directory relative to `basePath` | ||
| 1809 | + */ | ||
| 1810 | + function read(path, prefix) { | ||
| 1766 | 1811 | const req = new FSReqCallback(); | |
| 1767 | 1812 | req.oncomplete = (err, result) => { | |
| 1768 | 1813 | if (err) { | |
@@ -1771,68 +1816,28 @@ function readdirRecursive(basePath, options, callback) { | |||
| 1771 | 1816 | } | |
| 1772 | 1817 | ||
| 1773 | 1818 | if (result === undefined) { | |
| 1774 | - callback(null, context.readdirResults); | ||
| 1819 | + callback(null, context.results); | ||
| 1775 | 1820 | return; | |
| 1776 | 1821 | } | |
| 1777 | 1822 | ||
| 1778 | - processReaddirResult({ | ||
| 1779 | - result, | ||
| 1780 | - currentPath: path, | ||
| 1781 | - context, | ||
| 1782 | - }); | ||
| 1823 | + try { | ||
| 1824 | + collectRecursiveReaddirResult(path, prefix, result, context); | ||
| 1825 | + } catch (err) { | ||
| 1826 | + callback(err); | ||
| 1827 | + return; | ||
| 1828 | + } | ||
| 1783 | 1829 | ||
| 1784 | - if (i < context.pathsQueue.length) { | ||
| 1785 | - read(context.pathsQueue[i++]); | ||
| 1830 | + if (i < context.dirs.length) { | ||
| 1831 | + read(context.dirs[i], context.prefixes[i++]); | ||
| 1786 | 1832 | } else { | |
| 1787 | - callback(null, context.readdirResults); | ||
| 1833 | + callback(null, context.results); | ||
| 1788 | 1834 | } | |
| 1789 | 1835 | }; | |
| 1790 | 1836 | ||
| 1791 | - binding.readdir( | ||
| 1792 | - path, | ||
| 1793 | - context.encoding, | ||
| 1794 | - context.withFileTypes, | ||
| 1795 | - req, | ||
| 1796 | - ); | ||
| 1797 | - } | ||
| 1798 | - | ||
| 1799 | - read(context.pathsQueue[i++]); | ||
| 1800 | - } | ||
| 1801 | - | ||
| 1802 | - // Calling `readdir` with `withFileTypes=true`, the result is an array of arrays. | ||
| 1803 | - // The first array is the names, and the second array is the types. | ||
| 1804 | - // They are guaranteed to be the same length; hence, setting `length` to the length | ||
| 1805 | - // of the first array within the result. | ||
| 1806 | - const processReaddirResult = (args) => (args.context.withFileTypes ? handleDirents(args) : handleFilePaths(args)); | ||
| 1807 | - | ||
| 1808 | - function handleDirents({ result, currentPath, context }) { | ||
| 1809 | - const { 0: names, 1: types } = result; | ||
| 1810 | - const { length } = names; | ||
| 1811 | - | ||
| 1812 | - for (let i = 0; i < length; i++) { | ||
| 1813 | - // Avoid excluding symlinks, as they are not directories. | ||
| 1814 | - // Refs: https://github.com/nodejs/node/issues/52663 | ||
| 1815 | - const fullPath = pathModule.join(currentPath, names[i]); | ||
| 1816 | - const dirent = getDirent(currentPath, names[i], types[i]); | ||
| 1817 | - ArrayPrototypePush(context.readdirResults, dirent); | ||
| 1818 | - | ||
| 1819 | - if (dirent.isDirectory() || binding.internalModuleStat(fullPath) === 1) { | ||
| 1820 | - ArrayPrototypePush(context.pathsQueue, fullPath); | ||
| 1821 | - } | ||
| 1837 | + binding.readdir(path, options.encoding, true, req); | ||
| 1822 | 1838 | } | |
| 1823 | - } | ||
| 1824 | - | ||
| 1825 | - function handleFilePaths({ result, currentPath, context }) { | ||
| 1826 | - for (let i = 0; i < result.length; i++) { | ||
| 1827 | - const resultPath = pathModule.join(currentPath, result[i]); | ||
| 1828 | - const relativeResultPath = pathModule.relative(context.basePath, resultPath); | ||
| 1829 | - const stat = binding.internalModuleStat(resultPath); | ||
| 1830 | - ArrayPrototypePush(context.readdirResults, relativeResultPath); | ||
| 1831 | 1839 | ||
| 1832 | - if (stat === 1) { | ||
| 1833 | - ArrayPrototypePush(context.pathsQueue, resultPath); | ||
| 1834 | - } | ||
| 1835 | - } | ||
| 1840 | + read(context.dirs[i], context.prefixes[i++]); | ||
| 1836 | 1841 | } | |
| 1837 | 1842 | ||
| 1838 | 1843 | /** | |
@@ -1846,35 +1851,20 @@ function handleFilePaths({ result, currentPath, context }) { | |||
| 1846 | 1851 | function readdirSyncRecursive(basePath, options) { | |
| 1847 | 1852 | const context = { | |
| 1848 | 1853 | withFileTypes: Boolean(options.withFileTypes), | |
| 1849 | - encoding: options.encoding, | ||
| 1850 | - basePath, | ||
| 1851 | - readdirResults: [], | ||
| 1852 | - pathsQueue: [basePath], | ||
| 1854 | + results: [], | ||
| 1855 | + dirs: [basePath], | ||
| 1856 | + prefixes: [''], | ||
| 1853 | 1857 | }; | |
| 1854 | 1858 | ||
| 1855 | - function read(path) { | ||
| 1856 | - const readdirResult = binding.readdir( | ||
| 1857 | - path, | ||
| 1858 | - context.encoding, | ||
| 1859 | - context.withFileTypes, | ||
| 1860 | - ); | ||
| 1861 | - | ||
| 1862 | - if (readdirResult === undefined) { | ||
| 1863 | - return; | ||
| 1859 | + for (let i = 0; i < context.dirs.length; i++) { | ||
| 1860 | + const dir = context.dirs[i]; | ||
| 1861 | + const result = binding.readdir(dir, options.encoding, true); | ||
| 1862 | + if (result !== undefined) { | ||
| 1863 | + collectRecursiveReaddirResult(dir, context.prefixes[i], result, context); | ||
| 1864 | 1864 | } | |
| 1865 | - | ||
| 1866 | - processReaddirResult({ | ||
| 1867 | - result: readdirResult, | ||
| 1868 | - currentPath: path, | ||
| 1869 | - context, | ||
| 1870 | - }); | ||
| 1871 | - } | ||
| 1872 | - | ||
| 1873 | - for (let i = 0; i < context.pathsQueue.length; i++) { | ||
| 1874 | - read(context.pathsQueue[i]); | ||
| 1875 | 1865 | } | |
| 1876 | 1866 | ||
| 1877 | - return context.readdirResults; | ||
| 1867 | + return context.results; | ||
| 1878 | 1868 | } | |
| 1879 | 1869 | ||
| 1880 | 1870 | /** | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -32,6 +32,9 @@ const { | |||
| 32 | 32 | O_WRONLY, | |
| 33 | 33 | S_IFMT, | |
| 34 | 34 | S_IFREG, | |
| 35 | + UV_DIRENT_DIR, | ||
| 36 | + UV_DIRENT_LINK, | ||
| 37 | + UV_DIRENT_UNKNOWN, | ||
| 35 | 38 | } = constants; | |
| 36 | 39 | ||
| 37 | 40 | const binding = internalBinding('fs'); | |
@@ -63,6 +66,7 @@ const { | |||
| 63 | 66 | kWriteFileMaxChunkSize, | |
| 64 | 67 | }, | |
| 65 | 68 | copyObject, | |
| 69 | + getDirent, | ||
| 66 | 70 | getDirents, | |
| 67 | 71 | getOptions, | |
| 68 | 72 | getStatFsFromBinding, | |
@@ -1638,73 +1642,37 @@ async function mkdir(path, options) { | |||
| 1638 | 1642 | } | |
| 1639 | 1643 | ||
| 1640 | 1644 | async function readdirRecursive(originalPath, options) { | |
| 1645 | + const withFileTypes = !!options.withFileTypes; | ||
| 1646 | + const readdirWithTypes = (path) => PromisePrototypeThen( | ||
| 1647 | + binding.readdir(path, options.encoding, true, kUsePromises), | ||
| 1648 | + undefined, | ||
| 1649 | + handleErrorFromBinding, | ||
| 1650 | + ); | ||
| 1641 | 1651 | const result = []; | |
| 1642 | - const queue = [ | ||
| 1643 | - [ | ||
| 1644 | - originalPath, | ||
| 1645 | - await PromisePrototypeThen( | ||
| 1646 | - binding.readdir( | ||
| 1647 | - originalPath, | ||
| 1648 | - options.encoding, | ||
| 1649 | - !!options.withFileTypes, | ||
| 1650 | - kUsePromises, | ||
| 1651 | - ), | ||
| 1652 | - undefined, | ||
| 1653 | - handleErrorFromBinding, | ||
| 1654 | - ), | ||
| 1655 | - ], | ||
| 1656 | - ]; | ||
| 1657 | - | ||
| 1658 | - | ||
| 1659 | - if (options.withFileTypes) { | ||
| 1660 | - while (queue.length > 0) { | ||
| 1661 | - // If we want to implement BFS make this a `shift` call instead of `pop` | ||
| 1662 | - const { 0: path, 1: readdir } = ArrayPrototypePop(queue); | ||
| 1663 | - for (const dirent of getDirents(path, readdir)) { | ||
| 1652 | + const queue = [[originalPath, '', await readdirWithTypes(originalPath)]]; | ||
| 1653 | + | ||
| 1654 | + while (queue.length > 0) { | ||
| 1655 | + // If we want to implement BFS make this a `shift` call instead of `pop` | ||
| 1656 | + const { 0: path, 1: prefix, 2: { 0: names, 1: types } } = ArrayPrototypePop(queue); | ||
| 1657 | + for (let i = 0; i < names.length; i++) { | ||
| 1658 | + const name = names[i]; | ||
| 1659 | + const relative = prefix === '' ? name : `${prefix}${pathModule.sep}${name}`; | ||
| 1660 | + let isDirectory; | ||
| 1661 | + if (withFileTypes) { | ||
| 1662 | + const dirent = getDirent(path, name, types[i]); | ||
| 1664 | 1663 | ArrayPrototypePush(result, dirent); | |
| 1665 | - if (dirent.isDirectory()) { | ||
| 1666 | - const direntPath = pathModule.join(path, dirent.name); | ||
| 1667 | - ArrayPrototypePush(queue, [ | ||
| 1668 | - direntPath, | ||
| 1669 | - await PromisePrototypeThen( | ||
| 1670 | - binding.readdir( | ||
| 1671 | - direntPath, | ||
| 1672 | - options.encoding, | ||
| 1673 | - true, | ||
| 1674 | - kUsePromises, | ||
| 1675 | - ), | ||
| 1676 | - undefined, | ||
| 1677 | - handleErrorFromBinding, | ||
| 1678 | - ), | ||
| 1679 | - ]); | ||
| 1680 | - } | ||
| 1664 | + isDirectory = dirent.isDirectory(); | ||
| 1665 | + } else { | ||
| 1666 | + ArrayPrototypePush(result, relative); | ||
| 1667 | + // Entries that are, or may be, symbolic links to directories are followed. | ||
| 1668 | + const type = types[i]; | ||
| 1669 | + isDirectory = type === UV_DIRENT_DIR || | ||
| 1670 | + ((type === UV_DIRENT_LINK || type === UV_DIRENT_UNKNOWN) && | ||
| 1671 | + binding.internalModuleStat(pathModule.join(path, name)) === 1); | ||
| 1681 | 1672 | } | |
| 1682 | - } | ||
| 1683 | - } else { | ||
| 1684 | - while (queue.length > 0) { | ||
| 1685 | - const { 0: path, 1: readdir } = ArrayPrototypePop(queue); | ||
| 1686 | - for (const ent of readdir) { | ||
| 1687 | - const direntPath = pathModule.join(path, ent); | ||
| 1688 | - const stat = binding.internalModuleStat(direntPath); | ||
| 1689 | - ArrayPrototypePush( | ||
| 1690 | - result, | ||
| 1691 | - pathModule.relative(originalPath, direntPath), | ||
| 1692 | - ); | ||
| 1693 | - if (stat === 1) { | ||
| 1694 | - ArrayPrototypePush(queue, [ | ||
| 1695 | - direntPath, | ||
| 1696 | - await PromisePrototypeThen( | ||
| 1697 | - binding.readdir( | ||
| 1698 | - direntPath, | ||
| 1699 | - options.encoding, | ||
| 1700 | - false, | ||
| 1701 | - kUsePromises, | ||
| 1702 | - ), | ||
| 1703 | - undefined, | ||
| 1704 | - handleErrorFromBinding, | ||
| 1705 | - ), | ||
| 1706 | - ]); | ||
| 1707 | - } | ||
| 1673 | + if (isDirectory) { | ||
| 1674 | + const direntPath = pathModule.join(path, name); | ||
| 1675 | + ArrayPrototypePush(queue, [direntPath, relative, await readdirWithTypes(direntPath)]); | ||
| 1708 | 1676 | } | |
| 1709 | 1677 | } | |
| 1710 | 1678 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,4 +12,4 @@ const { readdir } = require('node:fs'); | |||
| 12 | 12 | const { join } = require('node:path'); | |
| 13 | 13 | ||
| 14 | 14 | const testDirPath = join(__dirname, '..', '..'); | |
| 15 | - readdir(Buffer.from(testDirPath), { recursive: true }, common.mustCall()); | ||
| 15 | + readdir(Buffer.from(testDirPath), { recursive: true }, common.mustSucceed()); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments