Repository: denoland/deno
Pull request: fix(ext/node): add `FileHandle#writeFile`

This PR adds `writeFile` methods of `FileHandle` class https://nodejs.org/api/fs.html#filehandlewritefiledata-options

closes #25528

part of #25554

Changed files:
filename: ext/node/polyfills/_fs/_fs_writeFile.ts
patch: @@ -23,14 +23,15 @@ import {
   validateStringAfterArrayBufferView,
 } from "ext:deno_node/internal/fs/utils.mjs";
 import { promisify } from "ext:deno_node/internal/util.mjs";
+import { FileHandle } from "ext:deno_node/internal/fs/handle.ts";
 import { FsFile } from "ext:deno_fs/30_fs.js";
 
 interface Writer {
   write(p: Uint8Array): Promise<number>;
 }
 
 export function writeFile(
-  pathOrRid: string | number | URL,
+  pathOrRid: string | number | URL | FileHandle,
   data: string | Uint8Array,
   optOrCallback: Encodings | CallbackWithError | WriteFileOptions | undefined,
   callback?: CallbackWithError,
@@ -45,6 +46,7 @@ export function writeFile(
   }
 
   pathOrRid = pathOrRid instanceof URL ? pathFromURL(pathOrRid) : pathOrRid;
+  pathOrRid = pathOrRid instanceof FileHandle ? pathOrRid.fd : pathOrRid;
 
   const flag: string | undefined = isFileOptions(options)
     ? options.flag

filename: ext/node/polyfills/internal/fs/handle.ts
patch: @@ -133,12 +133,28 @@ export class FileHandle extends EventEmitter {
     }
   }
 
+  writeFile(data, options): Promise<void> {
+    return fsCall(promises.writeFile, this, data, options);
+  }
+
   close(): Promise<void> {
     // Note that Deno.close is not async
     return Promise.resolve(core.close(this.fd));
   }
 }
 
+function fsCall(fn, handle, ...args) {
+  if (handle.fd === -1) {
+    const err = new Error("file closed");
+    throw Object.assign(err, {
+      code: "EBADF",
+      syscall: fn.name,
+    });
+  }
+
+  return fn(handle, ...args);
+}
+
 export default {
   FileHandle,
 };

filename: tests/node_compat/config.jsonc
patch: @@ -335,6 +335,7 @@
       "test-fs-open-numeric-flags.js",
       "test-fs-open.js",
       "test-fs-opendir.js",
+      "test-fs-promises-writefile-with-fd.js",
       "test-fs-read-stream-autoClose.js",
       "test-fs-read-stream-concurrent-reads.js",
       "test-fs-read-stream-double-close.js",

filename: tests/node_compat/runner/TODO.md
patch: @@ -829,7 +829,6 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co
 - [parallel/test-fs-promises-watch.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-promises-watch.js)
 - [parallel/test-fs-promises-write-optional-params.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-promises-write-optional-params.js)
 - [parallel/test-fs-promises-writefile-typedarray.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-promises-writefile-typedarray.js)
-- [parallel/test-fs-promises-writefile-with-fd.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-promises-writefile-with-fd.js)
 - [parallel/test-fs-promises-writefile.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-promises-writefile.js)
 - [parallel/test-fs-promises.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-promises.js)
 - [parallel/test-fs-promisified.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-fs-promisified.js)

filename: tests/node_compat/test/parallel/test-fs-promises-writefile-with-fd.js
patch: @@ -0,0 +1,42 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+// Taken from Node 18.12.1
+// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually.
+
+'use strict';
+
+// This test makes sure that `writeFile()` always writes from the current
+// position of the file, instead of truncating the file.
+
+const common = require('../common');
+const assert = require('assert');
+const { readFileSync } = require('fs');
+const { open } = require('fs').promises;
+
+const tmpdir = require('../common/tmpdir');
+tmpdir.refresh();
+
+const fn = tmpdir.resolve('test.txt');
+
+async function writeFileTest() {
+  const handle = await open(fn, 'w');
+
+  /* Write only five bytes, so that the position moves to five. */
+  const buf = Buffer.from('Hello');
+  const { bytesWritten } = await handle.write(buf, 0, 5, null);
+  assert.strictEqual(bytesWritten, 5);
+
+  /* Write some more with writeFile(). */
+  await handle.writeFile('World');
+
+  /* New content should be written at position five, instead of zero. */
+  assert.strictEqual(readFileSync(fn).toString(), 'HelloWorld');
+
+  await handle.close();
+}
+
+
+writeFileTest()
+  .then(common.mustCall());