diff --git a/src/stringify.js b/src/stringify.js index df291fd..0904fc7 100644 --- a/src/stringify.js +++ b/src/stringify.js @@ -44,11 +44,21 @@ export function stringify(value, reducers) { /** @param {any} thing */ function flatten(thing) { - if (typeof thing === 'function') { - throw new DevalueError(`Cannot stringify a function`, keys); + if (indexes.has(thing)) return indexes.get(thing); + + for (const { key, fn } of custom) { + const value = fn(thing); + if (value) { + const index = p++; + indexes.set(thing, index); + stringified[index] = `["${key}",${flatten(value)}]`; + return index; + } } - if (indexes.has(thing)) return indexes.get(thing); + if (typeof thing === 'function') { + throw new DevalueError(`Cannot stringify a function`, keys); + } if (thing === undefined) return UNDEFINED; if (Number.isNaN(thing)) return NAN; @@ -59,14 +69,6 @@ export function stringify(value, reducers) { const index = p++; indexes.set(thing, index); - for (const { key, fn } of custom) { - const value = fn(thing); - if (value) { - stringified[index] = `["${key}",${flatten(value)}]`; - return index; - } - } - let str = ''; if (is_primitive(thing)) { @@ -157,16 +159,16 @@ export function stringify(value, reducers) { str = '["' + type + '","' + base64 + '"]'; break; } - + case "ArrayBuffer": { /** @type {ArrayBuffer} */ const arraybuffer = thing; const base64 = encode64(arraybuffer); - + str = `["ArrayBuffer","${base64}"]`; break; } - + default: if (!is_plain_object(thing)) { throw new DevalueError( diff --git a/test/test.js b/test/test.js index ebd4147..279ba1e 100644 --- a/test/test.js +++ b/test/test.js @@ -442,12 +442,36 @@ const fixtures = { assert.equal(obj1.value.answer, 42); } } - ])(new Custom({ answer: 42 })) + ])(new Custom({ answer: 42 })), + + customFunction: ((func) => [ + { + name: 'Custom function type', + value: func, + json: '[["function",1],"func"]', + reducers: { + function: (x) => x === func && 'func' + }, + revivers: { + function: (x) => { + assert.is(x, 'func'); + return func; + } + }, + validate: (f) => { + assert.is(f, func); + } + } + ])(() => 42) }; for (const [name, tests] of Object.entries(fixtures)) { const test = uvu.suite(`uneval: ${name}`); for (const t of tests) { + if (!t.js) { + continue; + } + test(t.name, () => { const actual = uneval(t.value, t.replacer); const expected = t.js;