Skip to content

Commit 3e89b73

Browse files
LiviaMedeirosbengl
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 <duhamelantoine1995@gmail.com> Reviewed-By: Minwoo Jung <nodecorelab@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 72a767b commit 3e89b73

6 files changed

+388
-13
lines changed

doc/api/fs.md

+63-4
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
@@ -4372,7 +4391,7 @@ This happens when:
43724391
* the file is deleted, followed by a restore
43734392
* the file is renamed and then renamed a second time back to its original name
43744393
4375-
### `fs.write(fd, buffer[, offset[, length[, position]]], callback)`
4394+
### `fs.write(fd, buffer, offset[, length[, position]], callback)`
43764395
43774396
<!-- YAML
43784397
added: v0.0.2
@@ -4439,6 +4458,29 @@ On Linux, positional writes don't work when the file is opened in append mode.
44394458
The kernel ignores the position argument and always appends the data to
44404459
the end of the file.
44414460
4461+
### `fs.write(fd, buffer[, options], callback)`
4462+
4463+
<!-- YAML
4464+
added: REPLACEME
4465+
-->
4466+
4467+
* `fd` {integer}
4468+
* `buffer` {Buffer|TypedArray|DataView}
4469+
* `options` {Object}
4470+
* `offset` {integer} **Default:** `0`
4471+
* `length` {integer} **Default:** `buffer.byteLength - offset`
4472+
* `position` {integer} **Default:** `null`
4473+
* `callback` {Function}
4474+
* `err` {Error}
4475+
* `bytesWritten` {integer}
4476+
* `buffer` {Buffer|TypedArray|DataView}
4477+
4478+
Write `buffer` to the file specified by `fd`.
4479+
4480+
Similar to the above `fs.write` function, this version takes an
4481+
optional `options` object. If no `options` object is specified, it will
4482+
default with the above values.
4483+
44424484
### `fs.write(fd, string[, position[, encoding]], callback)`
44434485
44444486
<!-- YAML
@@ -5787,7 +5829,7 @@ for more details.
57875829
For detailed information, see the documentation of the asynchronous version of
57885830
this API: [`fs.writeFile()`][].
57895831
5790-
### `fs.writeSync(fd, buffer[, offset[, length[, position]]])`
5832+
### `fs.writeSync(fd, buffer, offset[, length[, position]])`
57915833
57925834
<!-- YAML
57935835
added: v0.1.21
@@ -5818,6 +5860,23 @@ changes:
58185860
For detailed information, see the documentation of the asynchronous version of
58195861
this API: [`fs.write(fd, buffer...)`][].
58205862
5863+
### `fs.writeSync(fd, buffer[, options])`
5864+
5865+
<!-- YAML
5866+
added: REPLACEME
5867+
-->
5868+
5869+
* `fd` {integer}
5870+
* `buffer` {Buffer|TypedArray|DataView}
5871+
* `options` {Object}
5872+
* `offset` {integer} **Default:** `0`
5873+
* `length` {integer} **Default:** `buffer.byteLength - offset`
5874+
* `position` {integer} **Default:** `null`
5875+
* Returns: {number} The number of bytes written.
5876+
5877+
For detailed information, see the documentation of the asynchronous version of
5878+
this API: [`fs.write(fd, buffer...)`][].
5879+
58215880
### `fs.writeSync(fd, string[, position[, encoding]])`
58225881
58235882
<!-- YAML

lib/fs.js

+29-8
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,7 @@ function read(fd, buffer, offsetOrOptions, length, position, callback) {
634634
({
635635
offset = 0,
636636
length = buffer.byteLength - offset,
637-
position = null
637+
position = null,
638638
} = params ?? ObjectCreate(null));
639639
}
640640

@@ -705,7 +705,7 @@ function readSync(fd, buffer, offset, length, position) {
705705
({
706706
offset = 0,
707707
length = buffer.byteLength - offset,
708-
position = null
708+
position = null,
709709
} = options);
710710
}
711711

@@ -801,7 +801,7 @@ function readvSync(fd, buffers, position) {
801801
* Writes `buffer` to the specified `fd` (file descriptor).
802802
* @param {number} fd
803803
* @param {Buffer | TypedArray | DataView | string | object} buffer
804-
* @param {number} [offset]
804+
* @param {number | object} [offsetOrOptions]
805805
* @param {number} [length]
806806
* @param {number | null} [position]
807807
* @param {(
@@ -811,16 +811,26 @@ function readvSync(fd, buffers, position) {
811811
* ) => any} callback
812812
* @returns {void}
813813
*/
814-
function write(fd, buffer, offset, length, position, callback) {
814+
function write(fd, buffer, offsetOrOptions, length, position, callback) {
815815
function wrapper(err, written) {
816816
// Retain a reference to buffer so that it can't be GC'ed too soon.
817817
callback(err, written || 0, buffer);
818818
}
819819

820820
fd = getValidatedFd(fd);
821821

822+
let offset = offsetOrOptions;
822823
if (isArrayBufferView(buffer)) {
823824
callback = maybeCallback(callback || position || length || offset);
825+
826+
if (typeof offset === 'object') {
827+
({
828+
offset = 0,
829+
length = buffer.byteLength - offset,
830+
position = null,
831+
} = offsetOrOptions ?? ObjectCreate(null));
832+
}
833+
824834
if (offset == null || typeof offset === 'function') {
825835
offset = 0;
826836
} else {
@@ -869,16 +879,27 @@ ObjectDefineProperty(write, internalUtil.customPromisifyArgs,
869879
* specified `fd` (file descriptor).
870880
* @param {number} fd
871881
* @param {Buffer | TypedArray | DataView | string} buffer
872-
* @param {number} [offset]
873-
* @param {number} [length]
874-
* @param {number | null} [position]
882+
* @param {{
883+
* offset?: number;
884+
* length?: number;
885+
* position?: number | null;
886+
* }} [offsetOrOptions]
875887
* @returns {number}
876888
*/
877-
function writeSync(fd, buffer, offset, length, position) {
889+
function writeSync(fd, buffer, offsetOrOptions, length, position) {
878890
fd = getValidatedFd(fd);
879891
const ctx = {};
880892
let result;
893+
894+
let offset = offsetOrOptions;
881895
if (isArrayBufferView(buffer)) {
896+
if (typeof offset === 'object') {
897+
({
898+
offset = 0,
899+
length = buffer.byteLength - offset,
900+
position = null
901+
} = offsetOrOptions ?? ObjectCreate(null));
902+
}
882903
if (position === undefined)
883904
position = null;
884905
if (offset == null) {

lib/internal/fs/promises.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -569,11 +569,20 @@ async function readv(handle, buffers, position) {
569569
return { bytesRead, buffers };
570570
}
571571

572-
async function write(handle, buffer, offset, length, position) {
572+
async function write(handle, buffer, offsetOrOptions, length, position) {
573573
if (buffer?.byteLength === 0)
574574
return { bytesWritten: 0, buffer };
575575

576+
let offset = offsetOrOptions;
576577
if (isArrayBufferView(buffer)) {
578+
if (typeof offset === 'object') {
579+
({
580+
offset = 0,
581+
length = buffer.byteLength - offset,
582+
position = null
583+
} = offsetOrOptions ?? ObjectCreate(null));
584+
}
585+
577586
if (offset == null) {
578587
offset = 0;
579588
} else {
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)