BREAKING(jsonc): remove allowTrailingComma option (#5119)

This commit is contained in:
Asher Gomez 2024-06-25 16:13:08 +10:00 committed by GitHub
parent 9411d0957e
commit 255b7994ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 18 additions and 62 deletions

View File

@ -14,13 +14,7 @@
* import { assertEquals } from "@std/assert/assert-equals"; * import { assertEquals } from "@std/assert/assert-equals";
* *
* assertEquals(parse('{"foo": "bar", } // comment'), { foo: "bar" }); * assertEquals(parse('{"foo": "bar", } // comment'), { foo: "bar" });
*
* assertEquals(parse('{"foo": "bar", } /* comment *\/'), { foo: "bar" }); * assertEquals(parse('{"foo": "bar", } /* comment *\/'), { foo: "bar" });
*
* assertEquals(
* parse('{"foo": "bar" } // comment', { allowTrailingComma: false }),
* { foo: "bar" }
* );
* ``` * ```
* *
* @module * @module

View File

@ -4,15 +4,6 @@
import type { JsonValue } from "@std/json/types"; import type { JsonValue } from "@std/json/types";
export type { JsonValue } from "@std/json/types"; export type { JsonValue } from "@std/json/types";
/** Options for {@linkcode parse}. */
export interface ParseOptions {
/** Allow trailing commas at the end of arrays and objects.
*
* @default {true}
*/
allowTrailingComma?: boolean;
}
/** /**
* Converts a JSON with Comments (JSONC) string into an object. * Converts a JSON with Comments (JSONC) string into an object.
* *
@ -24,7 +15,6 @@ export interface ParseOptions {
* assertEquals(parse('{"foo": "bar"}'), { foo: "bar" }); * assertEquals(parse('{"foo": "bar"}'), { foo: "bar" });
* assertEquals(parse('{"foo": "bar", }'), { foo: "bar" }); * assertEquals(parse('{"foo": "bar", }'), { foo: "bar" });
* assertEquals(parse('{"foo": "bar", } /* comment *\/'), { foo: "bar" }); * assertEquals(parse('{"foo": "bar", } /* comment *\/'), { foo: "bar" });
* assertEquals(parse('{"foo": "bar" } // comment', { allowTrailingComma: false }), { foo: "bar" });
* ``` * ```
* *
* @throws {SyntaxError} If the JSONC string is invalid. * @throws {SyntaxError} If the JSONC string is invalid.
@ -32,15 +22,11 @@ export interface ParseOptions {
* @param options Options for parsing. * @param options Options for parsing.
* @returns The parsed JsonValue from the JSONC string. * @returns The parsed JsonValue from the JSONC string.
*/ */
export function parse( export function parse(text: string): JsonValue {
text: string,
options?: ParseOptions,
): JsonValue {
const { allowTrailingComma = true } = { ...options };
if (new.target) { if (new.target) {
throw new TypeError("parse is not a constructor"); throw new TypeError("parse is not a constructor");
} }
return new JSONCParser(text, { allowTrailingComma }).parse(); return new JSONCParser(text).parse();
} }
type TokenType = type TokenType =
@ -77,12 +63,10 @@ class JSONCParser {
#text: string; #text: string;
#length: number; #length: number;
#tokenized: Generator<Token, void>; #tokenized: Generator<Token, void>;
#options: ParseOptions; constructor(text: string) {
constructor(text: string, options: ParseOptions) {
this.#text = `${text}`; this.#text = `${text}`;
this.#length = this.#text.length; this.#length = this.#text.length;
this.#tokenized = this.#tokenize(); this.#tokenized = this.#tokenize();
this.#options = options;
} }
parse(): JsonValue { parse(): JsonValue {
const token = this.#getNext(); const token = this.#getNext();
@ -238,12 +222,9 @@ class JSONCParser {
// │ │ │ │ │ │ ┌─────token3 // │ │ │ │ │ │ ┌─────token3
// │ │ │ │ │ │ │ ┌─token4 // │ │ │ │ │ │ │ ┌─token4
// { "key" : value , "key" : value } // { "key" : value , "key" : value }
for (let isFirst = true;; isFirst = false) { while (true) {
const token1 = this.#getNext(); const token1 = this.#getNext();
if ( if (token1.type === "EndObject") {
(isFirst || this.#options.allowTrailingComma) &&
token1.type === "EndObject"
) {
return target; return target;
} }
if (token1.type !== "String") { if (token1.type !== "String") {
@ -290,12 +271,9 @@ class JSONCParser {
// │ │ ┌─────token1 // │ │ ┌─────token1
// │ │ │ ┌─token2 // │ │ │ ┌─token2
// [ value , value ] // [ value , value ]
for (let isFirst = true;; isFirst = false) { while (true) {
const token1 = this.#getNext(); const token1 = this.#getNext();
if ( if (token1.type === "EndArray") {
(isFirst || this.#options.allowTrailingComma) &&
token1.type === "EndArray"
) {
return target; return target;
} }
target.push(this.#parseJsonValue(token1)); target.push(this.#parseJsonValue(token1));

View File

@ -1,6 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { parse, type ParseOptions } from "./parse.ts"; import { parse } from "./parse.ts";
import { import {
assert, assert,
assertEquals, assertEquals,
@ -14,12 +14,8 @@ import "./testdata/test262/test.ts";
// The test code for the jsonc module can also be found in the testcode directory. // The test code for the jsonc module can also be found in the testcode directory.
function assertValidParse( function assertValidParse(text: string, expected: unknown) {
text: string, assertEquals(parse(text), expected);
expected: unknown,
options?: ParseOptions,
) {
assertEquals(parse(text, options), expected);
} }
function assertInvalidParse( function assertInvalidParse(
@ -27,10 +23,9 @@ function assertInvalidParse(
// deno-lint-ignore no-explicit-any // deno-lint-ignore no-explicit-any
ErrorClass: new (...args: any[]) => Error, ErrorClass: new (...args: any[]) => Error,
msgIncludes?: string, msgIncludes?: string,
options?: ParseOptions,
) { ) {
assertThrows( assertThrows(
() => parse(text, options), () => parse(text),
ErrorClass, ErrorClass,
msgIncludes, msgIncludes,
); );
@ -231,3 +226,7 @@ Deno.test({
); );
}, },
}); });
Deno.test("parse() handles lone continuation byte in key and tailing comma", () => {
assertEquals(parse('{"<22>":"0",}'), { "<22>": "0" });
});

View File

@ -34,7 +34,7 @@ for await (
JSON.parse(text); JSON.parse(text);
}); });
const [hasJsoncError, jsoncError, jsoncResult] = getError(() => { const [hasJsoncError, jsoncError, jsoncResult] = getError(() => {
JSONC.parse(text, { allowTrailingComma: false }); JSONC.parse(text);
}); });
// If an error occurs in JSON.parse() but no error occurs in JSONC.parse(), or vice versa, an error is thrown. // If an error occurs in JSON.parse() but no error occurs in JSONC.parse(), or vice versa, an error is thrown.

View File

@ -11,19 +11,17 @@ import { assertEquals, assertThrows } from "../../../assert/mod.ts";
function assertValidParse( function assertValidParse(
text: string, text: string,
expected: unknown, expected: unknown,
options?: JSONC.ParseOptions,
) { ) {
assertEquals(JSONC.parse(text, options), expected); assertEquals(JSONC.parse(text), expected);
} }
function assertInvalidParse( function assertInvalidParse(
text: string, text: string,
// deno-lint-ignore no-explicit-any // deno-lint-ignore no-explicit-any
ErrorClass: (new (...args: any[]) => Error), ErrorClass: (new (...args: any[]) => Error),
msgIncludes?: string, msgIncludes?: string,
options?: JSONC.ParseOptions,
) { ) {
assertThrows( assertThrows(
() => JSONC.parse(text, options), () => JSONC.parse(text),
ErrorClass, ErrorClass,
msgIncludes, msgIncludes,
); );
@ -83,9 +81,6 @@ Deno.test("[jsonc] parse node-jsonc-parser:arrays", () => {
Deno.test("[jsonc] parse node-jsonc-parser:objects with errors", () => { Deno.test("[jsonc] parse node-jsonc-parser:objects with errors", () => {
assertInvalidParse("{,}", SyntaxError); assertInvalidParse("{,}", SyntaxError);
assertInvalidParse('{ "foo": true, }', SyntaxError, undefined, {
allowTrailingComma: false,
});
assertInvalidParse('{ "bar": 8 "xoo": "foo" }', SyntaxError); assertInvalidParse('{ "bar": 8 "xoo": "foo" }', SyntaxError);
assertInvalidParse('{ ,"bar": 8 }', SyntaxError); assertInvalidParse('{ ,"bar": 8 }', SyntaxError);
assertInvalidParse('{ ,"bar": 8, "foo" }', SyntaxError); assertInvalidParse('{ ,"bar": 8, "foo" }', SyntaxError);
@ -119,13 +114,4 @@ Deno.test("[jsonc] parse node-jsonc-parser:trailing comma", () => {
); );
assertValidParse("[ 1, 2, ]", [1, 2]); assertValidParse("[ 1, 2, ]", [1, 2]);
assertValidParse("[ 1, 2 ]", [1, 2]); assertValidParse("[ 1, 2 ]", [1, 2]);
assertInvalidParse('{ "hello": [], }', SyntaxError, undefined, options);
assertInvalidParse(
'{ "hello": [], "world": {}, }',
SyntaxError,
undefined,
options,
);
assertInvalidParse("[ 1, 2, ]", SyntaxError, undefined, options);
}); });