Repository: denoland/deno
Pull request: fix(ext/node): use primordials in `ext/node/polyfills/internal/fs/utils.mjs`

Towards #24236. Replaces JS builtins with equivalent primordials. As a result of primordial updates, this PR also modifies the function, `arrayBufferViewToUint8Array`, to call either `DataView` or `TypedArray` primordial functions when passing `DataView` and `TypedArray` buffer instances respectively to convert to `Uint8Array`s.

<!--
Before submitting a PR, please read https://docs.deno.com/runtime/manual/references/contributing

1. Give the PR a descriptive title.

  Examples of good title:
    - fix(std/http): Fix race condition in server
    - docs(console): Update docstrings
    - feat(doc): Handle nested reexports

  Examples of bad title:
    - fix #7123
    - update docs
    - fix bugs

2. Ensure there is a related issue and it is referenced in the PR text.
3. Ensure there are tests that cover the changes.
4. Ensure `cargo test` passes.
5. Ensure `./tools/format.js` passes without changing files.
6. Ensure `./tools/lint.js` passes.
7. Open as a draft PR if your work is still in progress. The CI won't run
   all steps, but you can add '[ci]' to a commit message to force it to.
8. If you would like to run the benchmarks on the CI, add the 'ci-bench' label.
-->


Changed files:
filename: ext/node/polyfills/internal/fs/utils.mjs
patch: @@ -1,12 +1,42 @@
 // Copyright 2018-2025 the Deno authors. MIT license.
 
-// TODO(petamoriken): enable prefer-primordials for node polyfills
-// deno-lint-ignore-file prefer-primordials
-
 "use strict";
 
 import { primordials } from "ext:core/mod.js";
-const { DatePrototypeGetTime } = primordials;
+const {
+  ArrayIsArray,
+  BigInt,
+  DataViewPrototype,
+  DataViewPrototypeGetBuffer,
+  DataViewPrototypeGetByteOffset,
+  DataViewPrototypeGetByteLength,
+  Date,
+  DateNow,
+  DatePrototypeGetTime,
+  ErrorCaptureStackTrace,
+  FunctionPrototypeCall,
+  MathMin,
+  Number,
+  NumberIsFinite,
+  NumberIsInteger,
+  ObjectIs,
+  ObjectPrototypeIsPrototypeOf,
+  ObjectSetPrototypeOf,
+  ReflectApply,
+  ReflectOwnKeys,
+  SafeArrayIterator,
+  SafeRegExp,
+  StringPrototypeEndsWith,
+  StringPrototypeIncludes,
+  StringPrototypeReplace,
+  Symbol,
+  TypedArrayPrototypeGetBuffer,
+  TypedArrayPrototypeGetByteLength,
+  TypedArrayPrototypeGetByteOffset,
+  TypedArrayPrototypeIncludes,
+  Uint8Array,
+  Uint8ArrayPrototype,
+} = primordials;
 import { Buffer } from "node:buffer";
 import {
   ERR_FS_EISDIR,
@@ -87,14 +117,14 @@ const {
 // The access modes can be any of F_OK, R_OK, W_OK or X_OK. Some might not be
 // available on specific systems. They can be used in combination as well
 // (F_OK | R_OK | W_OK | X_OK).
-const kMinimumAccessMode = Math.min(F_OK, W_OK, R_OK, X_OK);
+const kMinimumAccessMode = MathMin(F_OK, W_OK, R_OK, X_OK);
 const kMaximumAccessMode = F_OK | W_OK | R_OK | X_OK;
 
 const kDefaultCopyMode = 0;
 // The copy modes can be any of COPYFILE_EXCL, COPYFILE_FICLONE or
 // COPYFILE_FICLONE_FORCE. They can be used in combination as well
 // (COPYFILE_EXCL | COPYFILE_FICLONE | COPYFILE_FICLONE_FORCE).
-const kMinimumCopyMode = Math.min(
+const kMinimumCopyMode = MathMin(
   kDefaultCopyMode,
   COPYFILE_EXCL,
   COPYFILE_FICLONE,
@@ -170,7 +200,7 @@ class DirentFromStats extends Dirent {
   }
 }
 
-for (const name of Reflect.ownKeys(Dirent.prototype)) {
+for (const name of new SafeArrayIterator(ReflectOwnKeys(Dirent.prototype))) {
   if (name === "constructor") {
     continue;
   }
@@ -198,15 +228,21 @@ function join(path, name) {
   }
 
   if (typeof path === "string" && isUint8Array(name)) {
+    // deno-lint-ignore prefer-primordials `join` is a `node:path` function
     const pathBuffer = Buffer.from(pathModule.join(path, pathModule.sep));
+    // Ignore lint. `concat` is a 'node:buffer' static method on `Buffer`
+    // deno-lint-ignore prefer-primordials
     return Buffer.concat([pathBuffer, name]);
   }
 
   if (typeof path === "string" && typeof name === "string") {
+    // deno-lint-ignore prefer-primordials `join` is a `node:path` function
     return pathModule.join(path, name);
   }
 
   if (isUint8Array(path) && isUint8Array(name)) {
+    // Ignore lint. `concat` is a 'node:buffer' static method on `Buffer`
+    // deno-lint-ignore prefer-primordials
     return Buffer.concat([path, bufferSep, name]);
   }
 
@@ -322,14 +358,14 @@ export function getOptions(options, defaultOptions) {
 export function handleErrorFromBinding(ctx) {
   if (ctx.errno !== undefined) { // libuv error numbers
     const err = uvException(ctx);
-    Error.captureStackTrace(err, handleErrorFromBinding);
+    ErrorCaptureStackTrace(err, handleErrorFromBinding);
     throw err;
   }
   if (ctx.error !== undefined) { // Errors created in C++ land.
     // TODO(joyeecheung): currently, ctx.error are encoding errors
     // usually caused by memory problems. We need to figure out proper error
     // code(s) for this.
-    Error.captureStackTrace(ctx.error, handleErrorFromBinding);
+    ErrorCaptureStackTrace(ctx.error, handleErrorFromBinding);
     throw ctx.error;
   }
 }
@@ -344,8 +380,8 @@ export const nullCheck = hideStackFrames(
     // We can only perform meaningful checks on strings and Uint8Arrays.
     if (
       (!pathIsString && !pathIsUint8Array) ||
-      (pathIsString && !path.includes("\u0000")) ||
-      (pathIsUint8Array && !path.includes(0))
+      (pathIsString && !StringPrototypeIncludes(path, "\u0000")) ||
+      (pathIsUint8Array && !TypedArrayPrototypeIncludes(path, 0))
     ) {
       return;
     }
@@ -379,7 +415,7 @@ export function preprocessSymlinkDestination(path, type, linkPath) {
     return pathModule.toNamespacedPath(path);
   }
   // Windows symlinks don't tolerate forward slashes.
-  return path.replace(/\//g, "\\");
+  return StringPrototypeReplace(path, new SafeRegExp(/\//g), "\\");
 }
 
 // Constructor for file stats.
@@ -473,7 +509,7 @@ export function BigIntStats(
   ctimeNs,
   birthtimeNs,
 ) {
-  Reflect.apply(StatsBase, this, [
+  ReflectApply(StatsBase, this, [
     dev,
     mode,
     nlink,
@@ -500,8 +536,8 @@ export function BigIntStats(
   this.birthtime = dateFromMs(this.birthtimeMs);
 }
 
-Object.setPrototypeOf(BigIntStats.prototype, StatsBase.prototype);
-Object.setPrototypeOf(BigIntStats, StatsBase);
+ObjectSetPrototypeOf(BigIntStats.prototype, StatsBase.prototype);
+ObjectSetPrototypeOf(BigIntStats, StatsBase);
 
 BigIntStats.prototype._checkModeProperty = function (property) {
   if (
@@ -529,7 +565,8 @@ export function Stats(
   ctimeMs,
   birthtimeMs,
 ) {
-  StatsBase.call(
+  FunctionPrototypeCall(
+    StatsBase,
     this,
     dev,
     mode,
@@ -552,8 +589,8 @@ export function Stats(
   this.birthtime = dateFromMs(birthtimeMs);
 }
 
-Object.setPrototypeOf(Stats.prototype, StatsBase.prototype);
-Object.setPrototypeOf(Stats, StatsBase);
+ObjectSetPrototypeOf(Stats.prototype, StatsBase.prototype);
+ObjectSetPrototypeOf(Stats, StatsBase);
 
 // HACK: Workaround for https://github.com/standard-things/esm/issues/821.
 // TODO(ronag): Remove this as soon as `esm` publishes a fixed version.
@@ -692,9 +729,9 @@ export function toUnixTimestamp(time, name = "time") {
   if (typeof time === "string" && +time == time) {
     return +time;
   }
-  if (Number.isFinite(time)) {
+  if (NumberIsFinite(time)) {
     if (time < 0) {
-      return Date.now() / 1000;
+      return DateNow() / 1000;
     }
     return time;
   }
@@ -762,7 +799,7 @@ export const getValidatedPath = hideStackFrames(
 );
 
 export const getValidatedFd = hideStackFrames((fd, propName = "fd") => {
-  if (Object.is(fd, -0)) {
+  if (ObjectIs(fd, -0)) {
     return 0;
   }
 
@@ -773,7 +810,7 @@ export const getValidatedFd = hideStackFrames((fd, propName = "fd") => {
 
 export const validateBufferArray = hideStackFrames(
   (buffers, propName = "buffers") => {
-    if (!Array.isArray(buffers)) {
+    if (!ArrayIsArray(buffers)) {
       throw new ERR_INVALID_ARG_TYPE(propName, "ArrayBufferView[]", buffers);
     }
 
@@ -792,7 +829,7 @@ let nonPortableTemplateWarn = true;
 export function warnOnNonPortableTemplate(template) {
   // Template strings passed to the mkdtemp() family of functions should not
   // end with 'X' because they are handled inconsistently across platforms.
-  if (nonPortableTemplateWarn && template.endsWith("X")) {
+  if (nonPortableTemplateWarn && StringPrototypeEndsWith(template, "X")) {
     process.emitWarning(
       "mkdtemp() templates ending with X are not portable. " +
         "For details see: https://nodejs.org/api/fs.html",
@@ -945,7 +982,7 @@ export const getValidMode = hideStackFrames((mode, type) => {
   if (mode == null) {
     return def;
   }
-  if (Number.isInteger(mode) && mode >= min && mode <= max) {
+  if (NumberIsInteger(mode) && mode >= min && mode <= max) {
     return mode;
   }
   if (typeof mode !== "number") {
@@ -989,12 +1026,20 @@ export const validatePosition = hideStackFrames((position) => {
 /** @type {(buffer: ArrayBufferView) => Uint8Array} */
 export const arrayBufferViewToUint8Array = hideStackFrames(
   (buffer) => {
-    if (!(buffer instanceof Uint8Array)) {
-      return new Uint8Array(
-        buffer.buffer,
-        buffer.byteOffset,
-        buffer.byteLength,
-      );
+    if (!ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, buffer)) {
+      if (ObjectPrototypeIsPrototypeOf(DataViewPrototype, buffer)) {
+        return new Uint8Array(
+          DataViewPrototypeGetBuffer(buffer),
+          DataViewPrototypeGetByteOffset(buffer),
+          DataViewPrototypeGetByteLength(buffer),
+        );
+      } else {
+        return new Uint8Array(
+          TypedArrayPrototypeGetBuffer(buffer),
+          TypedArrayPrototypeGetByteOffset(buffer),
+          TypedArrayPrototypeGetByteLength(buffer),
+        );
+      }
     }
     return buffer;
   },