From 55b8b49525d4f9f817989de3fb6bbe663889a6f6 Mon Sep 17 00:00:00 2001 From: Nic Date: Tue, 4 Aug 2026 19:04:24 -0400 Subject: [PATCH] fix: emit valid JS for Node `Buffer` in `uneval` --- .changeset/olive-pugs-repeat.md | 5 +++++ src/uneval.js | 18 ++++++++++++------ test/index.test.js | 9 +++++++++ 3 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 .changeset/olive-pugs-repeat.md diff --git a/.changeset/olive-pugs-repeat.md b/.changeset/olive-pugs-repeat.md new file mode 100644 index 0000000..d747f9c --- /dev/null +++ b/.changeset/olive-pugs-repeat.md @@ -0,0 +1,5 @@ +--- +'devalue': patch +--- + +fix: emit valid JS for Node `Buffer` in `uneval` diff --git a/src/uneval.js b/src/uneval.js index 1e8ab97..7091c50 100644 --- a/src/uneval.js +++ b/src/uneval.js @@ -290,7 +290,7 @@ export function uneval(value, replacer) { let str = `new ${type}`; if (!names.has(thing.buffer)) { - str += `([${stringify_typed_array_elements(new thing.constructor(thing.buffer))}])`; + str += `([${stringify_typed_array_elements(type, thing.buffer)}])`; } else { str += `(${stringify(thing.buffer)})`; } @@ -451,7 +451,7 @@ export function uneval(value, replacer) { let str = `new ${type}`; if (!names.has(thing.buffer)) { - str += `([${stringify_typed_array_elements(new thing.constructor(thing.buffer))}])`; + str += `([${stringify_typed_array_elements(type, thing.buffer)}])`; } else { str += `(${stringify(thing.buffer)})`; } @@ -522,13 +522,19 @@ export function uneval(value, replacer) { } /** - * Serialize the elements of a typed array as a comma-separated list. + * Serialize the elements of `buffer`, read as `type`, as a comma-separated list. + * The view is created from `type` rather than from the serialized value's own + * constructor, which may be a subclass like Node's `Buffer` whose `toString` + * decodes the bytes instead of listing them. * `BigInt64Array`/`BigUint64Array` elements are bigints and must be written * with an `n` suffix, otherwise the emitted `new BigInt64Array([...])` throws. - * @param {import('./types.js').TypedArray} array + * @param {string} type + * @param {ArrayBufferLike} buffer */ -function stringify_typed_array_elements(array) { - if (array instanceof BigInt64Array || array instanceof BigUint64Array) { +function stringify_typed_array_elements(type, buffer) { + const array = new (/** @type {any} */ (globalThis)[type])(buffer); + + if (type === 'BigInt64Array' || type === 'BigUint64Array') { return Array.from(array, (element) => `${element}n`).join(','); } diff --git a/test/index.test.js b/test/index.test.js index 51083db..8eb9075 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -299,6 +299,15 @@ const fixtures = { js: 'new Uint8Array([1,2,3])', json: '[["Uint8Array",1],["ArrayBuffer","AQID"]]' }, + { + // `Buffer.alloc` does not allocate from Node's shared pool, so the buffer + // backing this view is exactly four bytes and the expectations are stable + name: 'Node Buffer', + value: Buffer.alloc(4, 65), + js: 'new Uint8Array([65,65,65,65])', + json: '[["Uint8Array",1],["ArrayBuffer","QUFBQQ=="]]', + validate: (value) => assert.equal(value, new Uint8Array([65, 65, 65, 65])) + }, { name: 'BigInt64Array', value: new BigInt64Array([1n, -2n, 3n]),