Skip to content

Commit 4648309

Browse files
LiviaMedeirostargos
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 a61b7fa commit 4648309

6 files changed

+388
-13
lines changed

doc/api/fs.md

+63-4
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@ added: v10.0.0
561561
Change the file system timestamps of the object referenced by the {FileHandle}
562562
then resolves the promise with no arguments upon success.
563563
564-
#### `filehandle.write(buffer[, offset[, length[, position]]])`
564+
#### `filehandle.write(buffer, offset[, length[, position]])`
565565
566566
<!-- YAML
567567
added: v10.0.0
@@ -574,7 +574,7 @@ changes:
574574
575575
* `buffer` {Buffer|TypedArray|DataView}
576576
* `offset` {integer} The start position from within `buffer` where the data
577-
to write begins. **Default:** `0`
577+
to write begins.
578578
* `length` {integer} The number of bytes from `buffer` to write. **Default:**
579579
`buffer.byteLength - offset`
580580
* `position` {integer|null} The offset from the beginning of the file where the
@@ -599,6 +599,25 @@ On Linux, positional writes do not work when the file is opened in append mode.
599599
The kernel ignores the position argument and always appends the data to
600600
the end of the file.
601601
602+
#### `filehandle.write(buffer[, options])`
603+
604+
<!-- YAML
605+
added: REPLACEME
606+
-->
607+
608+
* `buffer` {Buffer|TypedArray|DataView}
609+
* `options` {Object}
610+
* `offset` {integer} **Default:** `0`
611+
* `length` {integer} **Default:** `buffer.byteLength - offset`
612+
* `position` {integer} **Default:** `null`
613+
* Returns: {Promise}
614+
615+
Write `buffer` to the file.
616+
617+
Similar to the above `filehandle.write` function, this version takes an
618+
optional `options` object. If no `options` object is specified, it will
619+
default with the above values.
620+
602621
#### `filehandle.write(string[, position[, encoding]])`
603622
604623
<!-- YAML
@@ -4144,7 +4163,7 @@ This happens when:
41444163
* the file is deleted, followed by a restore
41454164
* the file is renamed and then renamed a second time back to its original name
41464165
4147-
### `fs.write(fd, buffer[, offset[, length[, position]]], callback)`
4166+
### `fs.write(fd, buffer, offset[, length[, position]], callback)`
41484167
41494168
<!-- YAML
41504169
added: v0.0.2
@@ -4206,6 +4225,29 @@ On Linux, positional writes don't work when the file is opened in append mode.
42064225
The kernel ignores the position argument and always appends the data to
42074226
the end of the file.
42084227
4228+
### `fs.write(fd, buffer[, options], callback)`
4229+
4230+
<!-- YAML
4231+
added: REPLACEME
4232+
-->
4233+
4234+
* `fd` {integer}
4235+
* `buffer` {Buffer|TypedArray|DataView}
4236+
* `options` {Object}
4237+
* `offset` {integer} **Default:** `0`
4238+
* `length` {integer} **Default:** `buffer.byteLength - offset`
4239+
* `position` {integer} **Default:** `null`
4240+
* `callback` {Function}
4241+
* `err` {Error}
4242+
* `bytesWritten` {integer}
4243+
* `buffer` {Buffer|TypedArray|DataView}
4244+
4245+
Write `buffer` to the file specified by `fd`.
4246+
4247+
Similar to the above `fs.write` function, this version takes an
4248+
optional `options` object. If no `options` object is specified, it will
4249+
default with the above values.
4250+
42094251
### `fs.write(fd, string[, position[, encoding]], callback)`
42104252
42114253
<!-- YAML
@@ -5531,7 +5573,7 @@ for more details.
55315573
For detailed information, see the documentation of the asynchronous version of
55325574
this API: [`fs.writeFile()`][].
55335575
5534-
### `fs.writeSync(fd, buffer[, offset[, length[, position]]])`
5576+
### `fs.writeSync(fd, buffer, offset[, length[, position]])`
55355577
55365578
<!-- YAML
55375579
added: v0.1.21
@@ -5562,6 +5604,23 @@ changes:
55625604
For detailed information, see the documentation of the asynchronous version of
55635605
this API: [`fs.write(fd, buffer...)`][].
55645606
5607+
### `fs.writeSync(fd, buffer[, options])`
5608+
5609+
<!-- YAML
5610+
added: REPLACEME
5611+
-->
5612+
5613+
* `fd` {integer}
5614+
* `buffer` {Buffer|TypedArray|DataView}
5615+
* `options` {Object}
5616+
* `offset` {integer} **Default:** `0`
5617+
* `length` {integer} **Default:** `buffer.byteLength - offset`
5618+
* `position` {integer} **Default:** `null`
5619+
* Returns: {number} The number of bytes written.
5620+
5621+
For detailed information, see the documentation of the asynchronous version of
5622+
this API: [`fs.write(fd, buffer...)`][].
5623+
55655624
### `fs.writeSync(fd, string[, position[, encoding]])`
55665625
55675626
<!-- YAML

lib/fs.js

+29-8
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

+10-1
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 {
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)