Skip to content
Permalink
Browse files

fs: readdir optionally returning type information

readdir and readdirSync now have a "withFileTypes" option, which, when
enabled, provides an array of DirectoryEntry objects, similar to Stats
objects, which have the filename and the type information.

Refs: #15699

PR-URL: #22020
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Roman Reiss <me@silverwind.io>
Reviewed-By: John-David Dalton <john.david.dalton@gmail.com>
  • Loading branch information...
bengl committed Jul 28, 2018
1 parent 78584b6 commit c7944a7a7bd92a446ea81d774ccad31e0e7c8a3f
Showing with 524 additions and 52 deletions.
  1. +99 −2 doc/api/fs.md
  2. +19 −4 lib/fs.js
  3. +10 −2 lib/internal/fs/promises.js
  4. +116 −1 lib/internal/fs/utils.js
  5. +10 −0 src/node_constants.cc
  6. +174 −43 src/node_file.cc
  7. +95 −0 test/parallel/test-fs-readdir-types.js
  8. +1 −0 tools/doc/type-parser.js
@@ -283,6 +283,92 @@ synchronous use libuv's threadpool, which can have surprising and negative
performance implications for some applications. See the
[`UV_THREADPOOL_SIZE`][] documentation for more information.

## Class: fs.Dirent
<!-- YAML
added: REPLACEME
-->

When [`fs.readdir()`][] or [`fs.readdirSync()`][] is called with the
`withFileTypes` option set to `true`, the resulting array is filled with
`fs.Dirent` objects, rather than strings or `Buffers`.

### dirent.isBlockDevice()
<!-- YAML
added: REPLACEME
-->

* Returns: {boolean}

Returns `true` if the `fs.Dirent` object describes a block device.

### dirent.isCharacterDevice()
<!-- YAML
added: REPLACEME
-->

* Returns: {boolean}

Returns `true` if the `fs.Dirent` object describes a character device.

### dirent.isDirectory()
<!-- YAML
added: REPLACEME
-->

* Returns: {boolean}

Returns `true` if the `fs.Dirent` object describes a file system
directory.

### dirent.isFIFO()
<!-- YAML
added: REPLACEME
-->

* Returns: {boolean}

Returns `true` if the `fs.Dirent` object describes a first-in-first-out
(FIFO) pipe.

### dirent.isFile()
<!-- YAML
added: REPLACEME
-->

* Returns: {boolean}

Returns `true` if the `fs.Dirent` object describes a regular file.

### dirent.isSocket()
<!-- YAML
added: REPLACEME
-->

* Returns: {boolean}

Returns `true` if the `fs.Dirent` object describes a socket.

### dirent.isSymbolicLink()
<!-- YAML
added: REPLACEME
-->

* Returns: {boolean}

Returns `true` if the `fs.Dirent` object describes a symbolic link.


### dirent.name
<!-- YAML
added: REPLACEME
-->

* {string|Buffer}

The file name that this `fs.Dirent` object refers to. The type of this
value is determined by the `options.encoding` passed to [`fs.readdir()`][] or
[`fs.readdirSync()`][].

## Class: fs.FSWatcher
<!-- YAML
added: v0.5.8
@@ -2319,9 +2405,10 @@ changes:
* `path` {string|Buffer|URL}
* `options` {string|Object}
* `encoding` {string} **Default:** `'utf8'`
* `withFileTypes` {boolean} **Default:** `false`
* `callback` {Function}
* `err` {Error}
* `files` {string[]|Buffer[]}
* `files` {string[]|Buffer[]|fs.Dirent[]}

Asynchronous readdir(3). Reads the contents of a directory.
The callback gets two arguments `(err, files)` where `files` is an array of
@@ -2332,6 +2419,9 @@ object with an `encoding` property specifying the character encoding to use for
the filenames passed to the callback. If the `encoding` is set to `'buffer'`,
the filenames returned will be passed as `Buffer` objects.

If `options.withFileTypes` is set to `true`, the `files` array will contain
[`fs.Dirent`][] objects.

## fs.readdirSync(path[, options])
<!-- YAML
added: v0.1.21
@@ -2345,7 +2435,8 @@ changes:
* `path` {string|Buffer|URL}
* `options` {string|Object}
* `encoding` {string} **Default:** `'utf8'`
* Returns: {string[]} An array of filenames excluding `'.'` and `'..'`.
* `withFileTypes` {boolean} **Default:** `false`
* Returns: {string[]|Buffer[]|fs.Dirent[]}

Synchronous readdir(3).

@@ -2354,6 +2445,9 @@ object with an `encoding` property specifying the character encoding to use for
the filenames returned. If the `encoding` is set to `'buffer'`,
the filenames returned will be passed as `Buffer` objects.

If `options.withFileTypes` is set to `true`, the result will contain
[`fs.Dirent`][] objects.

## fs.readFile(path[, options], callback)
<!-- YAML
added: v0.1.29
@@ -4637,6 +4731,7 @@ the file contents.
[`WriteStream`]: #fs_class_fs_writestream
[`EventEmitter`]: events.html
[`event ports`]: http://illumos.org/man/port_create
[`fs.Dirent`]: #fs_class_fs_dirent
[`fs.FSWatcher`]: #fs_class_fs_fswatcher
[`fs.Stats`]: #fs_class_fs_stats
[`fs.access()`]: #fs_fs_access_path_mode_callback
@@ -4652,6 +4747,8 @@ the file contents.
[`fs.mkdtemp()`]: #fs_fs_mkdtemp_prefix_options_callback
[`fs.open()`]: #fs_fs_open_path_flags_mode_callback
[`fs.read()`]: #fs_fs_read_fd_buffer_offset_length_position_callback
[`fs.readdir()`]: #fs_fs_readdir_path_options_callback
[`fs.readdirSync()`]: #fs_fs_readdirsync_path_options
[`fs.readFile()`]: #fs_fs_readfile_path_options_callback
[`fs.readFileSync()`]: #fs_fs_readfilesync_path_options
[`fs.realpath()`]: #fs_fs_realpath_path_options_callback
@@ -58,6 +58,8 @@ const { getPathFromURL } = require('internal/url');
const internalUtil = require('internal/util');
const {
copyObject,
Dirent,
getDirents,
getOptions,
nullCheck,
preprocessSymlinkDestination,
@@ -773,8 +775,19 @@ function readdir(path, options, callback) {
validatePath(path);

const req = new FSReqCallback();
req.oncomplete = callback;
binding.readdir(pathModule.toNamespacedPath(path), options.encoding, req);
if (!options.withFileTypes) {
req.oncomplete = callback;
} else {
req.oncomplete = (err, result) => {
if (err) {
callback(err);
return;
}
getDirents(path, result, callback);
};
}
binding.readdir(pathModule.toNamespacedPath(path), options.encoding,
!!options.withFileTypes, req);
}

function readdirSync(path, options) {
@@ -783,9 +796,10 @@ function readdirSync(path, options) {
validatePath(path);
const ctx = { path };
const result = binding.readdir(pathModule.toNamespacedPath(path),
options.encoding, undefined, ctx);
options.encoding, !!options.withFileTypes,
undefined, ctx);
handleErrorFromBinding(ctx);
return result;
return options.withFileTypes ? getDirents(path, result) : result;
}

function fstat(fd, options, callback) {
@@ -1819,6 +1833,7 @@ module.exports = fs = {
writeFileSync,
write,
writeSync,
Dirent,
Stats,

get ReadStream() {
@@ -19,6 +19,7 @@ const { getPathFromURL } = require('internal/url');
const { isUint8Array } = require('internal/util/types');
const {
copyObject,
getDirents,
getOptions,
getStatsFromBinding,
nullCheck,
@@ -37,10 +38,13 @@ const {
validateUint32
} = require('internal/validators');
const pathModule = require('path');
const { promisify } = require('internal/util');

const kHandle = Symbol('handle');
const { kUsePromises } = binding;

const getDirectoryEntriesPromise = promisify(getDirents);

class FileHandle {
constructor(filehandle) {
this[kHandle] = filehandle;
@@ -312,8 +316,12 @@ async function readdir(path, options) {
options = getOptions(options, {});
path = getPathFromURL(path);
validatePath(path);
return binding.readdir(pathModule.toNamespacedPath(path),
options.encoding, kUsePromises);
const result = await binding.readdir(pathModule.toNamespacedPath(path),
options.encoding, !!options.withTypes,
kUsePromises);
return options.withFileTypes ?
getDirectoryEntriesPromise(path, result) :
result;
}

async function readlink(path, options) {
@@ -12,6 +12,8 @@ const {
const { isUint8Array } = require('internal/util/types');
const pathModule = require('path');
const util = require('util');
const kType = Symbol('type');
const kStats = Symbol('stats');

const {
O_APPEND,
@@ -31,24 +33,135 @@ const {
S_IFREG,
S_IFSOCK,
UV_FS_SYMLINK_DIR,
UV_FS_SYMLINK_JUNCTION
UV_FS_SYMLINK_JUNCTION,
UV_DIRENT_UNKNOWN,
UV_DIRENT_FILE,
UV_DIRENT_DIR,
UV_DIRENT_LINK,
UV_DIRENT_FIFO,
UV_DIRENT_SOCKET,
UV_DIRENT_CHAR,
UV_DIRENT_BLOCK
} = process.binding('constants').fs;

const isWindows = process.platform === 'win32';

let fs;
function lazyLoadFs() {
if (!fs) {
fs = require('fs');
}
return fs;
}

function assertEncoding(encoding) {
if (encoding && !Buffer.isEncoding(encoding)) {
throw new ERR_INVALID_OPT_VALUE_ENCODING(encoding);
}
}

class Dirent {
constructor(name, type) {
this.name = name;
this[kType] = type;
}

isDirectory() {
return this[kType] === UV_DIRENT_DIR;
}

isFile() {
return this[kType] === UV_DIRENT_FILE;
}

isBlockDevice() {
return this[kType] === UV_DIRENT_BLOCK;
}

isCharacterDevice() {
return this[kType] === UV_DIRENT_CHAR;
}

isSymbolicLink() {
return this[kType] === UV_DIRENT_LINK;
}

isFIFO() {
return this[kType] === UV_DIRENT_FIFO;
}

isSocket() {
return this[kType] === UV_DIRENT_SOCKET;
}
}

class DirentFromStats extends Dirent {
constructor(name, stats) {
super(name, null);
this[kStats] = stats;
}
}

for (const name of Reflect.ownKeys(Dirent.prototype)) {
if (name === 'constructor') {
continue;
}
DirentFromStats.prototype[name] = function() {
return this[kStats][name]();
};
}

function copyObject(source) {
var target = {};
for (var key in source)
target[key] = source[key];
return target;
}

function getDirents(path, [names, types], callback) {
var i;
if (typeof callback == 'function') {
const len = names.length;
let toFinish = 0;
for (i = 0; i < len; i++) {
const type = types[i];
if (type === UV_DIRENT_UNKNOWN) {
const name = names[i];
const idx = i;
toFinish++;
lazyLoadFs().stat(pathModule.resolve(path, name), (err, stats) => {
if (err) {
callback(err);
return;
}
names[idx] = new DirentFromStats(name, stats);
if (--toFinish === 0) {
callback(null, names);
}
});
} else {
names[i] = new Dirent(names[i], types[i]);
}
}
if (toFinish === 0) {
callback(null, names);
}
} else {
const len = names.length;
for (i = 0; i < len; i++) {
const type = types[i];
if (type === UV_DIRENT_UNKNOWN) {
const name = names[i];
const stats = lazyLoadFs().statSync(pathModule.resolve(path, name));
names[i] = new DirentFromStats(name, stats);
} else {
names[i] = new Dirent(names[i], types[i]);
}
}
return names;
}
}

function getOptions(options, defaultOptions) {
if (options === null || options === undefined ||
typeof options === 'function') {
@@ -342,6 +455,8 @@ function validatePath(path, propName = 'path') {
module.exports = {
assertEncoding,
copyObject,
Dirent,
getDirents,
getOptions,
nullCheck,
preprocessSymlinkDestination,
Oops, something went wrong.

0 comments on commit c7944a7

Please sign in to comment.
You can’t perform that action at this time.