Repository: denoland/deno
Pull request: fix(ext/node): implement `Buffer.copyBytesFrom`

Enables the full `parallel/test-buffer-from.js` test

Changed files:
filename: ext/node/polyfills/internal/buffer.mjs
patch: @@ -49,6 +49,7 @@ const {
   TypedArrayPrototypeGetBuffer,
   TypedArrayPrototypeGetByteLength,
   TypedArrayPrototypeGetByteOffset,
+  TypedArrayPrototypeGetLength,
   TypedArrayPrototypeSet,
   TypedArrayPrototypeSlice,
   TypedArrayPrototypeSubarray,
@@ -74,9 +75,13 @@ import {
   utf16leToBytes,
 } from "ext:deno_node/internal_binding/_utils.ts";
 import { normalizeEncoding } from "ext:deno_node/internal/util.mjs";
-import { validateBuffer } from "ext:deno_node/internal/validators.mjs";
+import {
+  validateBuffer,
+  validateInteger,
+} from "ext:deno_node/internal/validators.mjs";
 import { isUint8Array } from "ext:deno_node/internal/util/types.ts";
 import {
+  ERR_INVALID_ARG_TYPE,
   ERR_INVALID_STATE,
   genericNodeError,
   NodeError,
@@ -229,6 +234,47 @@ const BufferFrom = Buffer.from = function from(
   return _from(value, encodingOrOffset, length);
 };
 
+Buffer.copyBytesFrom = function copyBytesFrom(
+  view,
+  offset,
+  length,
+) {
+  if (!isTypedArray(view)) {
+    throw new ERR_INVALID_ARG_TYPE("view", ["TypedArray"], view);
+  }
+
+  const viewLength = TypedArrayPrototypeGetLength(view);
+  if (viewLength === 0) {
+    return Buffer.alloc(0);
+  }
+
+  if (offset !== undefined || length !== undefined) {
+    if (offset !== undefined) {
+      validateInteger(offset, "offset", 0);
+      if (offset >= viewLength) return Buffer.alloc(0);
+    } else {
+      offset = 0;
+    }
+    let end;
+    if (length !== undefined) {
+      validateInteger(length, "length", 0);
+      end = offset + length;
+    } else {
+      end = viewLength;
+    }
+
+    view = TypedArrayPrototypeSlice(view, offset, end);
+  }
+
+  return fromArrayLike(
+    new Uint8Array(
+      TypedArrayPrototypeGetBuffer(view),
+      TypedArrayPrototypeGetByteOffset(view),
+      TypedArrayPrototypeGetByteLength(view),
+    ),
+  );
+};
+
 const BufferPrototype = Buffer.prototype;
 
 ObjectSetPrototypeOf(Buffer.prototype, Uint8ArrayPrototype);

filename: tests/node_compat/config.jsonc
patch: @@ -24,7 +24,6 @@
       "test-blocklist.js",
       "test-buffer-alloc.js",
       "test-buffer-arraybuffer.js",
-      "test-buffer-from.js",
       "test-buffer-includes.js",
       "test-buffer-indexof.js",
       "test-buffer-tostring-range.js",

filename: tests/node_compat/test/parallel/test-buffer-from.js
patch: @@ -2,14 +2,14 @@
 // deno-lint-ignore-file
 
 // Copyright Joyent and Node contributors. All rights reserved. MIT license.
-// Taken from Node 18.12.1
+// Taken from Node 23.9.0
 // This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually.
 
 'use strict';
 
 const common = require('../common');
-const { deepStrictEqual, throws } = require('assert');
-// const { runInNewContext } = require('vm');
+const { deepStrictEqual, strictEqual, throws } = require('assert');
+const { runInNewContext } = require('vm');
 
 const checkString = 'test';
 
@@ -36,20 +36,18 @@ class MyBadPrimitive {
 deepStrictEqual(Buffer.from(new String(checkString)), check);
 deepStrictEqual(Buffer.from(new MyString()), check);
 deepStrictEqual(Buffer.from(new MyPrimitive()), check);
-// TODO(Soremwar)
-// Enable once again when vm works correctly
-// deepStrictEqual(
-//   Buffer.from(runInNewContext('new String(checkString)', { checkString })),
-//   check
-// );
+deepStrictEqual(
+  Buffer.from(runInNewContext('new String(checkString)', { checkString })),
+  check
+);
 
 [
   {},
   new Boolean(true),
   { valueOf() { return null; } },
   { valueOf() { return undefined; } },
   { valueOf: null },
-  Object.create(null),
+  { __proto__: null },
   new Number(true),
   new MyBadPrimitive(),
   Symbol(),
@@ -71,3 +69,83 @@ deepStrictEqual(Buffer.from(new MyPrimitive()), check);
 
 Buffer.allocUnsafe(10); // Should not throw.
 Buffer.from('deadbeaf', 'hex'); // Should not throw.
+
+
+{
+  const u16 = new Uint16Array([0xffff]);
+  const b16 = Buffer.copyBytesFrom(u16);
+  u16[0] = 0;
+  strictEqual(b16.length, 2);
+  strictEqual(b16[0], 255);
+  strictEqual(b16[1], 255);
+}
+
+{
+  const u16 = new Uint16Array([0, 0xffff]);
+  const b16 = Buffer.copyBytesFrom(u16, 1, 5);
+  u16[0] = 0xffff;
+  u16[1] = 0;
+  strictEqual(b16.length, 2);
+  strictEqual(b16[0], 255);
+  strictEqual(b16[1], 255);
+}
+
+{
+  const u32 = new Uint32Array([0xffffffff]);
+  const b32 = Buffer.copyBytesFrom(u32);
+  u32[0] = 0;
+  strictEqual(b32.length, 4);
+  strictEqual(b32[0], 255);
+  strictEqual(b32[1], 255);
+  strictEqual(b32[2], 255);
+  strictEqual(b32[3], 255);
+}
+
+throws(() => {
+  Buffer.copyBytesFrom();
+}, {
+  code: 'ERR_INVALID_ARG_TYPE',
+});
+
+['', Symbol(), true, false, {}, [], () => {}, 1, 1n, null, undefined].forEach(
+  (notTypedArray) => throws(() => {
+    Buffer.copyBytesFrom('nope');
+  }, {
+    code: 'ERR_INVALID_ARG_TYPE',
+  })
+);
+
+['', Symbol(), true, false, {}, [], () => {}, 1n].forEach((notANumber) =>
+  throws(() => {
+    Buffer.copyBytesFrom(new Uint8Array(1), notANumber);
+  }, {
+    code: 'ERR_INVALID_ARG_TYPE',
+  })
+);
+
+[-1, NaN, 1.1, -Infinity].forEach((outOfRange) =>
+  throws(() => {
+    Buffer.copyBytesFrom(new Uint8Array(1), outOfRange);
+  }, {
+    code: 'ERR_OUT_OF_RANGE',
+  })
+);
+
+['', Symbol(), true, false, {}, [], () => {}, 1n].forEach((notANumber) =>
+  throws(() => {
+    Buffer.copyBytesFrom(new Uint8Array(1), 0, notANumber);
+  }, {
+    code: 'ERR_INVALID_ARG_TYPE',
+  })
+);
+
+[-1, NaN, 1.1, -Infinity].forEach((outOfRange) =>
+  throws(() => {
+    Buffer.copyBytesFrom(new Uint8Array(1), 0, outOfRange);
+  }, {
+    code: 'ERR_OUT_OF_RANGE',
+  })
+);
+
+// Invalid encoding is allowed
+Buffer.from('asd', 1);