Skip to content

Commit 62fcd81

Browse files
committed
include path in error object
1 parent 4fccf5a commit 62fcd81

File tree

2 files changed

+71
-6
lines changed

2 files changed

+71
-6
lines changed

devalue.js

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,32 @@ const object_proto_names = Object.getOwnPropertyNames(Object.prototype)
2222
.sort()
2323
.join('\0');
2424

25+
class DevalueError extends Error {
26+
/**
27+
* @param {string} message
28+
* @param {string[]} keys
29+
*/
30+
constructor(message, keys) {
31+
super(message);
32+
this.name = 'DevalueError';
33+
this.path = keys.join('');
34+
}
35+
}
36+
2537
/**
2638
* Turn a value into the JavaScript that creates an equivalent value
2739
* @param {any} value
2840
*/
2941
export function devalue(value) {
3042
const counts = new Map();
3143

44+
/** @type {string[]} */
45+
const keys = [];
46+
3247
/** @param {any} thing */
3348
function walk(thing) {
3449
if (typeof thing === 'function') {
35-
throw new Error(`Cannot stringify a function`);
50+
throw new DevalueError(`Cannot stringify a function`, keys);
3651
}
3752

3853
if (counts.has(thing)) {
@@ -55,14 +70,27 @@ export function devalue(value) {
5570
return;
5671

5772
case 'Array':
58-
thing.forEach(walk);
73+
/** @type {any[]} */ (thing).forEach((value, i) => {
74+
keys.push(`[${i}]`);
75+
walk(value);
76+
keys.pop();
77+
});
5978
break;
6079

6180
case 'Set':
62-
case 'Map':
6381
Array.from(thing).forEach(walk);
6482
break;
6583

84+
case 'Map':
85+
for (const [key, value] of thing) {
86+
keys.push(
87+
`.get(${is_primitive(key) ? stringify_primitive(key) : '...'})`
88+
);
89+
walk(value);
90+
keys.pop();
91+
}
92+
break;
93+
6694
default:
6795
const proto = Object.getPrototypeOf(thing);
6896

@@ -72,14 +100,24 @@ export function devalue(value) {
72100
Object.getOwnPropertyNames(proto).sort().join('\0') !==
73101
object_proto_names
74102
) {
75-
throw new Error(`Cannot stringify arbitrary non-POJOs`);
103+
throw new DevalueError(
104+
`Cannot stringify arbitrary non-POJOs`,
105+
keys
106+
);
76107
}
77108

78109
if (Object.getOwnPropertySymbols(thing).length > 0) {
79-
throw new Error(`Cannot stringify POJOs with symbolic keys`);
110+
throw new DevalueError(
111+
`Cannot stringify POJOs with symbolic keys`,
112+
keys
113+
);
80114
}
81115

82-
Object.keys(thing).forEach((key) => walk(thing[key]));
116+
for (const key in thing) {
117+
keys.push(`.${key}`);
118+
walk(thing[key]);
119+
keys.pop();
120+
}
83121
}
84122
}
85123
}

devalue.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,31 @@ uvu.test('does not create duplicate parameter names', () => {
155155
eval(serialized);
156156
});
157157

158+
uvu.test('populates error.keys and error.path', () => {
159+
try {
160+
devalue({
161+
foo: {
162+
array: [function invalid() {}]
163+
}
164+
});
165+
} catch (e) {
166+
assert.equal(e.name, 'DevalueError');
167+
assert.equal(e.message, 'Cannot stringify a function');
168+
assert.equal(e.path, '.foo.array[0]');
169+
}
170+
171+
try {
172+
class Whatever {}
173+
devalue({
174+
foo: {
175+
map: new Map([['key', new Whatever()]])
176+
}
177+
});
178+
} catch (e) {
179+
assert.equal(e.name, 'DevalueError');
180+
assert.equal(e.message, 'Cannot stringify arbitrary non-POJOs');
181+
assert.equal(e.path, '.foo.map.get("key")');
182+
}
183+
});
184+
158185
uvu.test.run();

0 commit comments

Comments
 (0)