Skip to content

Commit 8722e75

Browse files
committed
Throw syntax errors when parsing invalid input
Signed-off-by: Andrew Haines <[email protected]>
1 parent 9537f49 commit 8722e75

File tree

2 files changed

+72
-6
lines changed

2 files changed

+72
-6
lines changed

src/parse.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,23 @@ import {
1414
export function parse(serialized) {
1515
const parsed = JSON.parse(serialized);
1616

17-
if (typeof parsed === 'number') return hydrate(parsed);
17+
if (typeof parsed === 'number') {
18+
const constant = hydrateConstant(parsed);
19+
if (constant === NOT_A_CONSTANT) throw new SyntaxError(`Unexpected number ${parsed}`);
20+
return constant;
21+
}
22+
23+
if (!Array.isArray(parsed)) throw new SyntaxError(`Expected array, got ${parsed === null ? 'null' : typeof parsed}`);
1824

1925
const values = /** @type {any[]} */ (parsed);
26+
if (values.length === 0) throw new SyntaxError(`Unexpected empty array`);
27+
2028
const hydrated = Array(values.length);
2129

2230
/** @param {number} index */
2331
function hydrate(index) {
24-
if (index === UNDEFINED) return undefined;
25-
if (index === NAN) return NaN;
26-
if (index === POSITIVE_INFINITY) return Infinity;
27-
if (index === NEGATIVE_INFINITY) return -Infinity;
28-
if (index === NEGATIVE_ZERO) return -0;
32+
const constant = hydrateConstant(index);
33+
if (constant !== NOT_A_CONSTANT) return constant;
2934

3035
if (index in hydrated) return hydrated[index];
3136

@@ -105,3 +110,15 @@ export function parse(serialized) {
105110

106111
return hydrate(0);
107112
}
113+
114+
const NOT_A_CONSTANT = Symbol('not a constant');
115+
116+
/** @param {number} constant */
117+
function hydrateConstant(constant) {
118+
if (constant === UNDEFINED) return undefined;
119+
if (constant === NAN) return NaN;
120+
if (constant === POSITIVE_INFINITY) return Infinity;
121+
if (constant === NEGATIVE_INFINITY) return -Infinity;
122+
if (constant === NEGATIVE_ZERO) return -0;
123+
return NOT_A_CONSTANT;
124+
}

test/test.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,55 @@ for (const [name, tests] of Object.entries(fixtures)) {
390390
test.run();
391391
}
392392

393+
const syntaxErrorFixtures = [
394+
{
395+
name: 'empty string',
396+
json: ''
397+
},
398+
{
399+
name: 'invalid JSON',
400+
json: ']['
401+
},
402+
{
403+
name: 'hole',
404+
json: '-2'
405+
},
406+
{
407+
name: 'string',
408+
json: '"hello"',
409+
},
410+
{
411+
name: 'number',
412+
json: '42'
413+
},
414+
{
415+
name: 'boolean',
416+
json: 'true'
417+
},
418+
{
419+
name: 'null',
420+
json: 'null'
421+
},
422+
{
423+
name: 'object',
424+
json: '{}'
425+
},
426+
{
427+
name: 'empty array',
428+
json: '[]'
429+
}
430+
];
431+
432+
const syntaxErrorTest = uvu.suite("parse: syntax errors");
433+
434+
for (const fixture of syntaxErrorFixtures) {
435+
syntaxErrorTest(fixture.name, () => {
436+
assert.throws(() => parse(fixture.json), (error) => error instanceof SyntaxError);
437+
});
438+
}
439+
440+
syntaxErrorTest.run();
441+
393442
for (const fn of [uneval, stringify]) {
394443
uvu.test(`${fn.name} throws for non-POJOs`, () => {
395444
class Foo {}

0 commit comments

Comments
 (0)