Skip to content

Commit c3ae514

Browse files
LiviaMedeirosjuanarbol
authored andcommitted
fs: make params in writing methods optional
This change allows passing objects as "named parameters": - `fs.write(fd, buffer[, options], callback)` - `fs.writeSync(fd, buffer[, options])` - `filehandle.write(buffer[, options])` Fixes: #41666 PR-URL: #42601 Reviewed-By: Antoine du Hamel <[email protected]> Reviewed-By: Minwoo Jung <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent c4833ff commit c3ae514

File tree

6 files changed

+388
-13
lines changed

6 files changed

+388
-13
lines changed

doc/api/fs.md

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ added: v10.0.0
608608
Change the file system timestamps of the object referenced by the {FileHandle}
609609
then resolves the promise with no arguments upon success.
610610
611-
#### `filehandle.write(buffer[, offset[, length[, position]]])`
611+
#### `filehandle.write(buffer, offset[, length[, position]])`
612612
613613
<!-- YAML
614614
added: v10.0.0
@@ -621,7 +621,7 @@ changes:
621621
622622
* `buffer` {Buffer|TypedArray|DataView}
623623
* `offset` {integer} The start position from within `buffer` where the data
624-
to write begins. **Default:** `0`
624+
to write begins.
625625
* `length` {integer} The number of bytes from `buffer` to write. **Default:**
626626
`buffer.byteLength - offset`
627627
* `position` {integer|null} The offset from the beginning of the file where the
@@ -646,6 +646,25 @@ On Linux, positional writes do not work when the file is opened in append mode.
646646
The kernel ignores the position argument and always appends the data to
647647
the end of the file.
648648
649+
#### `filehandle.write(buffer[, options])`
650+
651+
<!-- YAML
652+
added: REPLACEME
653+
-->
654+
655+
* `buffer` {Buffer|TypedArray|DataView}
656+
* `options` {Object}
657+
* `offset` {integer} **Default:** `0`
658+
* `length` {integer} **Default:** `buffer.byteLength - offset`
659+
* `position` {integer} **Default:** `null`
660+
* Returns: {Promise}
661+
662+
Write `buffer` to the file.
663+
664+
Similar to the above `filehandle.write` function, this version takes an
665+
optional `options` object. If no `options` object is specified, it will
666+
default with the above values.
667+
649668
#### `filehandle.write(string[, position[, encoding]])`
650669
651670
<!-- YAML
@@ -4161,7 +4180,7 @@ This happens when:
41614180
* the file is deleted, followed by a restore
41624181
* the file is renamed and then renamed a second time back to its original name
41634182
4164-
### `fs.write(fd, buffer[, offset[, length[, position]]], callback)`
4183+
### `fs.write(fd, buffer, offset[, length[, position]], callback)`
41654184
41664185
<!-- YAML
41674186
added: v0.0.2
@@ -4223,6 +4242,29 @@ On Linux, positional writes don't work when the file is opened in append mode.
42234242
The kernel ignores the position argument and always appends the data to
42244243
the end of the file.
42254244
4245+
### `fs.write(fd, buffer[, options], callback)`
4246+
4247+
<!-- YAML
4248+
added: REPLACEME
4249+
-->
4250+
4251+
* `fd` {integer}
4252+
* `buffer` {Buffer|TypedArray|DataView}
4253+
* `options` {Object}
4254+
* `offset` {integer} **Default:** `0`
4255+
* `length` {integer} **Default:** `buffer.byteLength - offset`
4256+
* `position` {integer} **Default:** `null`
4257+
* `callback` {Function}
4258+
* `err` {Error}
4259+
* `bytesWritten` {integer}
4260+
* `buffer` {Buffer|TypedArray|DataView}
4261+
4262+
Write `buffer` to the file specified by `fd`.
4263+
4264+
Similar to the above `fs.write` function, this version takes an
4265+
optional `options` object. If no `options` object is specified, it will
4266+
default with the above values.
4267+
42264268
### `fs.write(fd, string[, position[, encoding]], callback)`
42274269
42284270
<!-- YAML
@@ -5555,7 +5597,7 @@ for more details.
55555597
For detailed information, see the documentation of the asynchronous version of
55565598
this API: [`fs.writeFile()`][].
55575599
5558-
### `fs.writeSync(fd, buffer[, offset[, length[, position]]])`
5600+
### `fs.writeSync(fd, buffer, offset[, length[, position]])`
55595601
55605602
<!-- YAML
55615603
added: v0.1.21
@@ -5586,6 +5628,23 @@ changes:
55865628
For detailed information, see the documentation of the asynchronous version of
55875629
this API: [`fs.write(fd, buffer...)`][].
55885630
5631+
### `fs.writeSync(fd, buffer[, options])`
5632+
5633+
<!-- YAML
5634+
added: REPLACEME
5635+
-->
5636+
5637+
* `fd` {integer}
5638+
* `buffer` {Buffer|TypedArray|DataView}
5639+
* `options` {Object}
5640+
* `offset` {integer} **Default:** `0`
5641+
* `length` {integer} **Default:** `buffer.byteLength - offset`
5642+
* `position` {integer} **Default:** `null`
5643+
* Returns: {number} The number of bytes written.
5644+
5645+
For detailed information, see the documentation of the asynchronous version of
5646+
this API: [`fs.write(fd, buffer...)`][].
5647+
55895648
### `fs.writeSync(fd, string[, position[, encoding]])`
55905649
55915650
<!-- YAML

lib/fs.js

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,7 @@ function read(fd, buffer, offsetOrOptions, length, position, callback) {
630630
({
631631
offset = 0,
632632
length = buffer.byteLength - offset,
633-
position = null
633+
position = null,
634634
} = params ?? ObjectCreate(null));
635635
}
636636

@@ -701,7 +701,7 @@ function readSync(fd, buffer, offset, length, position) {
701701
({
702702
offset = 0,
703703
length = buffer.byteLength - offset,
704-
position = null
704+
position = null,
705705
} = options);
706706
}
707707

@@ -797,7 +797,7 @@ function readvSync(fd, buffers, position) {
797797
* Writes `buffer` to the specified `fd` (file descriptor).
798798
* @param {number} fd
799799
* @param {Buffer | TypedArray | DataView | string | object} buffer
800-
* @param {number} [offset]
800+
* @param {number | object} [offsetOrOptions]
801801
* @param {number} [length]
802802
* @param {number | null} [position]
803803
* @param {(
@@ -807,16 +807,26 @@ function readvSync(fd, buffers, position) {
807807
* ) => any} callback
808808
* @returns {void}
809809
*/
810-
function write(fd, buffer, offset, length, position, callback) {
810+
function write(fd, buffer, offsetOrOptions, length, position, callback) {
811811
function wrapper(err, written) {
812812
// Retain a reference to buffer so that it can't be GC'ed too soon.
813813
callback(err, written || 0, buffer);
814814
}
815815

816816
fd = getValidatedFd(fd);
817817

818+
let offset = offsetOrOptions;
818819
if (isArrayBufferView(buffer)) {
819820
callback = maybeCallback(callback || position || length || offset);
821+
822+
if (typeof offset === 'object') {
823+
({
824+
offset = 0,
825+
length = buffer.byteLength - offset,
826+
position = null,
827+
} = offsetOrOptions ?? ObjectCreate(null));
828+
}
829+
820830
if (offset == null || typeof offset === 'function') {
821831
offset = 0;
822832
} else {
@@ -862,16 +872,27 @@ ObjectDefineProperty(write, internalUtil.customPromisifyArgs,
862872
* specified `fd` (file descriptor).
863873
* @param {number} fd
864874
* @param {Buffer | TypedArray | DataView | string} buffer
865-
* @param {number} [offset]
866-
* @param {number} [length]
867-
* @param {number | null} [position]
875+
* @param {{
876+
* offset?: number;
877+
* length?: number;
878+
* position?: number | null;
879+
* }} [offsetOrOptions]
868880
* @returns {number}
869881
*/
870-
function writeSync(fd, buffer, offset, length, position) {
882+
function writeSync(fd, buffer, offsetOrOptions, length, position) {
871883
fd = getValidatedFd(fd);
872884
const ctx = {};
873885
let result;
886+
887+
let offset = offsetOrOptions;
874888
if (isArrayBufferView(buffer)) {
889+
if (typeof offset === 'object') {
890+
({
891+
offset = 0,
892+
length = buffer.byteLength - offset,
893+
position = null
894+
} = offsetOrOptions ?? ObjectCreate(null));
895+
}
875896
if (position === undefined)
876897
position = null;
877898
if (offset == null) {

lib/internal/fs/promises.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,11 +517,20 @@ async function readv(handle, buffers, position) {
517517
return { bytesRead, buffers };
518518
}
519519

520-
async function write(handle, buffer, offset, length, position) {
520+
async function write(handle, buffer, offsetOrOptions, length, position) {
521521
if (buffer?.byteLength === 0)
522522
return { bytesWritten: 0, buffer };
523523

524+
let offset = offsetOrOptions;
524525
if (isArrayBufferView(buffer)) {
526+
if (typeof offset === 'object') {
527+
({
528+
offset = 0,
529+
length = buffer.byteLength - offset,
530+
position = null
531+
} = offsetOrOptions ?? ObjectCreate(null));
532+
}
533+
525534
if (offset == null) {
526535
offset = 0;
527536
} else {
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
5+
// This test ensures that filehandle.write accepts "named parameters" object
6+
// and doesn't interpret objects as strings
7+
8+
const assert = require('assert');
9+
const fsPromises = require('fs').promises;
10+
const path = require('path');
11+
const tmpdir = require('../common/tmpdir');
12+
13+
tmpdir.refresh();
14+
15+
const dest = path.resolve(tmpdir.path, 'tmp.txt');
16+
const buffer = Buffer.from('zyx');
17+
18+
async function testInvalid(dest, expectedCode, ...params) {
19+
let fh;
20+
try {
21+
fh = await fsPromises.open(dest, 'w+');
22+
await assert.rejects(
23+
fh.write(...params),
24+
{ code: expectedCode });
25+
} finally {
26+
await fh?.close();
27+
}
28+
}
29+
30+
async function testValid(dest, buffer, options) {
31+
let fh;
32+
try {
33+
fh = await fsPromises.open(dest, 'w+');
34+
const writeResult = await fh.write(buffer, options);
35+
const writeBufCopy = Uint8Array.prototype.slice.call(writeResult.buffer);
36+
37+
const readResult = await fh.read(buffer, options);
38+
const readBufCopy = Uint8Array.prototype.slice.call(readResult.buffer);
39+
40+
assert.ok(writeResult.bytesWritten >= readResult.bytesRead);
41+
if (options.length !== undefined && options.length !== null) {
42+
assert.strictEqual(writeResult.bytesWritten, options.length);
43+
}
44+
if (options.offset === undefined || options.offset === 0) {
45+
assert.deepStrictEqual(writeBufCopy, readBufCopy);
46+
}
47+
assert.deepStrictEqual(writeResult.buffer, readResult.buffer);
48+
} finally {
49+
await fh?.close();
50+
}
51+
}
52+
53+
(async () => {
54+
// Test if first argument is not wrongly interpreted as ArrayBufferView|string
55+
for (const badBuffer of [
56+
undefined, null, true, 42, 42n, Symbol('42'), NaN, [], () => {},
57+
Promise.resolve(new Uint8Array(1)),
58+
{},
59+
{ buffer: 'amNotParam' },
60+
{ string: 'amNotParam' },
61+
{ buffer: new Uint8Array(1).buffer },
62+
new Date(),
63+
new String('notPrimitive'),
64+
{ toString() { return 'amObject'; } },
65+
{ [Symbol.toPrimitive]: (hint) => 'amObject' },
66+
]) {
67+
await testInvalid(dest, 'ERR_INVALID_ARG_TYPE', badBuffer, {});
68+
}
69+
70+
// First argument (buffer or string) is mandatory
71+
await testInvalid(dest, 'ERR_INVALID_ARG_TYPE');
72+
73+
// Various invalid options
74+
await testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { length: 5 });
75+
await testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { offset: 5 });
76+
await testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { length: 1, offset: 3 });
77+
await testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { length: -1 });
78+
await testInvalid(dest, 'ERR_OUT_OF_RANGE', buffer, { offset: -1 });
79+
await testInvalid(dest, 'ERR_INVALID_ARG_TYPE', buffer, { offset: false });
80+
await testInvalid(dest, 'ERR_INVALID_ARG_TYPE', buffer, { offset: true });
81+
82+
// Test compatibility with filehandle.read counterpart
83+
for (const options of [
84+
{},
85+
{ length: 1 },
86+
{ position: 5 },
87+
{ length: 1, position: 5 },
88+
{ length: 1, position: -1, offset: 2 },
89+
{ length: null },
90+
{ position: null },
91+
{ offset: 1 },
92+
]) {
93+
await testValid(dest, buffer, options);
94+
}
95+
})().then(common.mustCall());

0 commit comments

Comments
 (0)