2024-01-01 21:11:32 +00:00
|
|
|
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
2022-05-07 11:06:04 +00:00
|
|
|
|
|
2024-06-25 06:13:08 +00:00
|
|
|
|
import { parse } from "./parse.ts";
|
2022-05-07 11:06:04 +00:00
|
|
|
|
import {
|
2023-01-13 07:13:35 +00:00
|
|
|
|
assert,
|
2022-05-07 11:06:04 +00:00
|
|
|
|
assertEquals,
|
|
|
|
|
assertStrictEquals,
|
|
|
|
|
assertThrows,
|
2024-04-29 02:57:30 +00:00
|
|
|
|
} from "@std/assert";
|
2022-05-07 11:06:04 +00:00
|
|
|
|
|
2024-06-20 09:13:50 +00:00
|
|
|
|
import "./testdata/JSONTestSuite/test.ts";
|
|
|
|
|
import "./testdata/node-jsonc-parser/test.ts";
|
|
|
|
|
import "./testdata/test262/test.ts";
|
|
|
|
|
|
2022-05-07 11:06:04 +00:00
|
|
|
|
// The test code for the jsonc module can also be found in the testcode directory.
|
|
|
|
|
|
2024-06-25 06:13:08 +00:00
|
|
|
|
function assertValidParse(text: string, expected: unknown) {
|
|
|
|
|
assertEquals(parse(text), expected);
|
2022-05-07 11:06:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function assertInvalidParse(
|
|
|
|
|
text: string,
|
|
|
|
|
// deno-lint-ignore no-explicit-any
|
2022-06-15 22:05:49 +00:00
|
|
|
|
ErrorClass: new (...args: any[]) => Error,
|
2022-05-07 11:06:04 +00:00
|
|
|
|
msgIncludes?: string,
|
|
|
|
|
) {
|
|
|
|
|
assertThrows(
|
2024-06-25 06:13:08 +00:00
|
|
|
|
() => parse(text),
|
2022-05-07 11:06:04 +00:00
|
|
|
|
ErrorClass,
|
|
|
|
|
msgIncludes,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Deno.test({
|
2024-02-27 06:38:41 +00:00
|
|
|
|
name: "parse() handles single line comment",
|
2022-05-07 11:06:04 +00:00
|
|
|
|
fn() {
|
|
|
|
|
assertValidParse(`"aaa"//comment`, "aaa");
|
|
|
|
|
assertValidParse(`["aaa"//comment\n,"aaa"]`, ["aaa", "aaa"]);
|
|
|
|
|
assertValidParse(`["aaa"//comment\r,"aaa"]`, ["aaa", "aaa"]);
|
|
|
|
|
assertValidParse(`["aaa"//comment\n\r,"aaa"]`, ["aaa", "aaa"]);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Deno.test({
|
2024-02-27 06:38:41 +00:00
|
|
|
|
name: "parse() handles multi line comments",
|
2022-05-07 11:06:04 +00:00
|
|
|
|
fn() {
|
|
|
|
|
assertValidParse(`"aaa"/*comment*/`, "aaa");
|
|
|
|
|
assertValidParse(`100/*comment*/`, 100);
|
|
|
|
|
assertValidParse(`"aaa/*comment*/"`, "aaa/*comment*/");
|
|
|
|
|
assertValidParse(`"aaa"/*comment\ncomment*/`, "aaa");
|
|
|
|
|
assertInvalidParse(`"aaa"/*`, SyntaxError);
|
|
|
|
|
assertInvalidParse(`"aaa"/*/`, SyntaxError);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Deno.test({
|
2024-02-27 06:38:41 +00:00
|
|
|
|
name: "parse() handles special characters",
|
2022-05-07 11:06:04 +00:00
|
|
|
|
fn() {
|
|
|
|
|
assertValidParse(`"👪"`, "👪");
|
|
|
|
|
assertValidParse(`"🦕"`, "🦕");
|
|
|
|
|
assertValidParse(
|
|
|
|
|
`"\u543e\u8f29\u306f\u732b\u3067\u3042\u308b\u3002"`,
|
|
|
|
|
"\u543e\u8f29\u306f\u732b\u3067\u3042\u308b\u3002",
|
|
|
|
|
);
|
|
|
|
|
assertValidParse(
|
|
|
|
|
`"\\" \\\\ \\/ \\b \\f \\n \\r \\t"`,
|
|
|
|
|
'" \\ \/ \b \f \n \r \t',
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Deno.test({
|
2024-02-27 06:38:41 +00:00
|
|
|
|
name: "parse() handles #numberEndToken correctly",
|
2022-05-07 11:06:04 +00:00
|
|
|
|
fn() {
|
|
|
|
|
// Correctly parses the letters after the numbers (` \t\r\n[]{}:,/`)
|
|
|
|
|
assertValidParse(`{"a":0}`, { a: 0 });
|
|
|
|
|
assertValidParse(`[0]`, [0]);
|
|
|
|
|
assertValidParse(`[0,]`, [0]);
|
|
|
|
|
assertValidParse(`0//`, 0);
|
|
|
|
|
assertValidParse(`0\r`, 0);
|
|
|
|
|
assertValidParse(`0\n`, 0);
|
|
|
|
|
assertValidParse(`0\t`, 0);
|
|
|
|
|
assertValidParse(`0 `, 0);
|
|
|
|
|
assertInvalidParse(`{"a":0{}`, SyntaxError);
|
|
|
|
|
assertInvalidParse(`{"a":0[}`, SyntaxError);
|
|
|
|
|
assertInvalidParse(`{"a":0:}`, SyntaxError);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Deno.test({
|
2024-02-27 06:38:41 +00:00
|
|
|
|
name: "parse() throws error message",
|
2022-05-07 11:06:04 +00:00
|
|
|
|
fn() {
|
|
|
|
|
assertInvalidParse(
|
|
|
|
|
`:::::`,
|
|
|
|
|
SyntaxError,
|
|
|
|
|
"Unexpected token : in JSONC at position 0",
|
|
|
|
|
);
|
|
|
|
|
assertInvalidParse(
|
|
|
|
|
`[`,
|
|
|
|
|
SyntaxError,
|
|
|
|
|
"Unexpected end of JSONC input",
|
|
|
|
|
);
|
|
|
|
|
assertInvalidParse(
|
|
|
|
|
`[]100`,
|
|
|
|
|
SyntaxError,
|
|
|
|
|
"Unexpected token 100 in JSONC at position 2",
|
|
|
|
|
);
|
|
|
|
|
assertInvalidParse(
|
|
|
|
|
`[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]`,
|
|
|
|
|
SyntaxError,
|
|
|
|
|
"Unexpected token aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa... in JSONC at position 1",
|
|
|
|
|
);
|
2024-06-20 09:13:50 +00:00
|
|
|
|
assertInvalidParse(
|
|
|
|
|
`}`,
|
|
|
|
|
SyntaxError,
|
|
|
|
|
"Unexpected token } in JSONC at position 0",
|
|
|
|
|
);
|
|
|
|
|
assertInvalidParse(
|
|
|
|
|
`]`,
|
|
|
|
|
SyntaxError,
|
|
|
|
|
"Unexpected token ] in JSONC at position 0",
|
|
|
|
|
);
|
|
|
|
|
assertInvalidParse(
|
|
|
|
|
`,`,
|
|
|
|
|
SyntaxError,
|
|
|
|
|
"Unexpected token , in JSONC at position 0",
|
|
|
|
|
);
|
2022-05-07 11:06:04 +00:00
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Deno.test({
|
2024-02-27 06:38:41 +00:00
|
|
|
|
name: "parse() handles __proto__",
|
2022-05-07 11:06:04 +00:00
|
|
|
|
fn() {
|
|
|
|
|
// The result of JSON.parse and the result of JSONC.parse should match
|
|
|
|
|
const json = JSON.parse('{"__proto__": 100}');
|
2023-03-13 05:58:26 +00:00
|
|
|
|
const jsonc = parse('{"__proto__": 100}');
|
2022-05-07 11:06:04 +00:00
|
|
|
|
assertEquals(jsonc, json);
|
2022-05-20 12:08:49 +00:00
|
|
|
|
assertEquals((jsonc as Record<string, number>).__proto__, 100);
|
2022-05-07 11:06:04 +00:00
|
|
|
|
assertEquals((jsonc as Record<string, string>).__proto__, json.__proto__);
|
|
|
|
|
assertStrictEquals(Object.getPrototypeOf(jsonc), Object.prototype);
|
|
|
|
|
assertStrictEquals(
|
|
|
|
|
Object.getPrototypeOf(jsonc),
|
|
|
|
|
Object.getPrototypeOf(json),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Deno.test({
|
2024-02-27 06:38:41 +00:00
|
|
|
|
name: "parse() handles duplicate object key",
|
2022-05-07 11:06:04 +00:00
|
|
|
|
fn() {
|
|
|
|
|
// The result of JSON.parse and the result of JSONC.parse should match
|
|
|
|
|
const json = JSON.parse('{"aaa": 0, "aaa": 1}');
|
2023-03-13 05:58:26 +00:00
|
|
|
|
const jsonc = parse('{"aaa": 0, "aaa": 1}');
|
2022-05-07 11:06:04 +00:00
|
|
|
|
assertEquals(jsonc, { aaa: 1 });
|
|
|
|
|
assertEquals(jsonc, json);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Deno.test({
|
2024-02-27 06:38:41 +00:00
|
|
|
|
name: "parse() handles non-string input type",
|
2022-05-07 11:06:04 +00:00
|
|
|
|
fn() {
|
|
|
|
|
assertInvalidParse(
|
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
|
undefined as any,
|
|
|
|
|
SyntaxError,
|
|
|
|
|
"Unexpected token undefined in JSONC at position 0",
|
|
|
|
|
);
|
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
|
assertValidParse(0 as any, 0);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Deno.test({
|
2024-02-27 06:38:41 +00:00
|
|
|
|
name: "parse() handles consecutive backslash",
|
2022-05-07 11:06:04 +00:00
|
|
|
|
fn() {
|
|
|
|
|
assertValidParse('"foo\\\\"', "foo\\");
|
|
|
|
|
|
|
|
|
|
assertValidParse(' ["foo\\"", "bar"]', ['foo"', "bar"]);
|
|
|
|
|
assertInvalidParse('["foo\\\\"", "bar"]', SyntaxError);
|
|
|
|
|
assertValidParse(' ["foo\\\\\\"", "bar"]', ['foo\\"', "bar"]);
|
|
|
|
|
assertInvalidParse('["foo\\\\\\\\"", "bar"]', SyntaxError);
|
|
|
|
|
|
|
|
|
|
assertInvalidParse('["foo\\", "bar"]', SyntaxError);
|
|
|
|
|
assertValidParse(' ["foo\\\\", "bar"]', ["foo\\", "bar"]);
|
|
|
|
|
assertInvalidParse('["foo\\\\\\", "bar"]', SyntaxError);
|
|
|
|
|
assertValidParse(' ["foo\\\\\\\\", "bar"]', ["foo\\\\", "bar"]);
|
|
|
|
|
},
|
|
|
|
|
});
|
2023-01-13 07:13:35 +00:00
|
|
|
|
|
|
|
|
|
Deno.test({
|
2024-02-27 06:38:41 +00:00
|
|
|
|
name: "parse() uses Object.defineProperty when setting object property",
|
2023-01-13 07:13:35 +00:00
|
|
|
|
async fn() {
|
|
|
|
|
// Tests if the value is set using `Object.defineProperty(target, key, {value})`
|
|
|
|
|
// instead of `target[key] = value` when parsing the object.
|
|
|
|
|
// This makes a difference in behavior when __proto__ is set in Node.js and browsers.
|
|
|
|
|
// Using `Object.defineProperty` avoids prototype pollution in Node.js and browsers.
|
|
|
|
|
// reference: https://github.com/advisories/GHSA-9c47-m6qq-7p4h (CVE-2022-46175)
|
|
|
|
|
|
|
|
|
|
const testCode = `
|
|
|
|
|
Object.defineProperty(Object.prototype, "__proto__", {
|
|
|
|
|
set() {
|
|
|
|
|
throw new Error("Don't try to set the value directly to the key __proto__.")
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-03-13 05:58:26 +00:00
|
|
|
|
import { parse } from "${import.meta.resolve("./parse.ts")}";
|
2023-01-13 07:13:35 +00:00
|
|
|
|
parse('{"__proto__": {"isAdmin": true}}');
|
|
|
|
|
`;
|
|
|
|
|
const command = new Deno.Command(Deno.execPath(), {
|
|
|
|
|
stdout: "inherit",
|
|
|
|
|
stderr: "inherit",
|
2023-12-24 14:24:39 +00:00
|
|
|
|
args: ["eval", "--no-lock", testCode],
|
2023-01-13 07:13:35 +00:00
|
|
|
|
});
|
|
|
|
|
const { success } = await command.output();
|
|
|
|
|
assert(success);
|
|
|
|
|
},
|
|
|
|
|
});
|
2024-06-20 09:13:50 +00:00
|
|
|
|
|
|
|
|
|
Deno.test({
|
|
|
|
|
name: "new parse() throws error",
|
|
|
|
|
fn() {
|
|
|
|
|
assertThrows(
|
|
|
|
|
// deno-lint-ignore no-explicit-any
|
|
|
|
|
() => new (parse as any)(""),
|
|
|
|
|
TypeError,
|
|
|
|
|
"parse is not a constructor",
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
});
|
2024-06-25 06:13:08 +00:00
|
|
|
|
|
|
|
|
|
Deno.test("parse() handles lone continuation byte in key and tailing comma", () => {
|
|
|
|
|
assertEquals(parse('{"<22>":"0",}'), { "<22>": "0" });
|
|
|
|
|
});
|