Skip to content
1 change: 1 addition & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ const libEntries: [string, string][] = [
["es2025.iterator", "lib.es2025.iterator.d.ts"],
["es2025.promise", "lib.es2025.promise.d.ts"],
["es2025.regexp", "lib.es2025.regexp.d.ts"],
["es2025.json", "lib.es2025.json.d.ts"],
// Fallback for backward compatibility
["esnext.asynciterable", "lib.es2018.asynciterable.d.ts"],
["esnext.symbol", "lib.es2019.symbol.d.ts"],
Expand Down
1 change: 1 addition & 0 deletions src/lib/es2025.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
/// <reference lib="es2025.iterator" />
/// <reference lib="es2025.promise" />
/// <reference lib="es2025.regexp" />
/// <reference lib="es2025.json" />
38 changes: 38 additions & 0 deletions src/lib/es2025.json.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Represents a "raw JSON" object created by `JSON.rawJSON()`.
*
* Raw JSON objects are frozen, null-prototype objects that carry pre-serialized
* JSON text. When encountered by `JSON.stringify()`, the `rawJSON` property
* value is emitted verbatim instead of the usual serialization.
*
* @see {@link https://tc39.es/proposal-json-parse-with-source/ TC39 proposal-json-parse-with-source}
*/
interface RawJSON {
readonly rawJSON: string;
}

interface JSON {
/**
* Converts a JavaScript Object Notation (JSON) string into an object.
* @param text A valid JSON string.
* @param reviver A function that transforms the results. This function is called for each member of the object.
* If a member contains nested objects, the nested objects are transformed before the parent object is.
* For each value, the reviver also receives a `context` object. When the property is unmodified and its value is
* primitive, `context` has a `source` property containing the original JSON text of that value.
*/
parse(text: string, reviver: (this: any, key: string, value: any, context: { source?: string }) => any): any;

/**
* Creates a "raw JSON" object containing a piece of JSON text.
* When serialized with `JSON.stringify()`, the raw text is emitted verbatim.
* @param text A valid JSON string representing a primitive value (string, number, boolean, or null).
* @throws {SyntaxError} If `text` is not valid JSON or represents an object or array.
*/
rawJSON(text: string): RawJSON;

/**
* Returns whether the provided value is a raw JSON object created by `JSON.rawJSON()`.
* @param value The value to test.
*/
isRawJSON(value: unknown): value is RawJSON;
}
1 change: 1 addition & 0 deletions src/lib/libs.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"es2025.iterator",
"es2025.promise",
"es2025.regexp",
"es2025.json",
"esnext.array",
"esnext.collection",
"esnext.date",
Expand Down
69 changes: 69 additions & 0 deletions tests/baselines/reference/jsonParseWithSource.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//// [tests/cases/compiler/jsonParseWithSource.ts] ////

//// [jsonParseWithSource.ts]
// JSON.rawJSON
const raw = JSON.rawJSON("123");
const rawStr: string = raw.rawJSON;
JSON.stringify({ value: raw });
JSON.stringify({ n: JSON.rawJSON("12345678901234567890") });

// JSON.isRawJSON
const maybeRaw: unknown = {};
if (JSON.isRawJSON(maybeRaw)) {
const text: string = maybeRaw.rawJSON;
}

// JSON.parse with reviver context
// `source` is optional: only present for unmodified primitive values
JSON.parse('{"key":123}', (key, value, context) => {
const src: string | undefined = context.source;
return value;
});

// Test coverage for reviver context on array elements
JSON.parse('[1, 2, 3]', (key, value, context) => {
const src: string | undefined = context.source;
return value;
});

// Test coverage for reviver context on nested objects
JSON.parse('{"a": {"b": 1}}', (key, value, context) => {
const src: string | undefined = context.source;
return value;
});

// Existing JSON.parse overloads still work
JSON.parse("{}");
JSON.parse('{"a":1}', (key, value) => value);

//// [jsonParseWithSource.js]
"use strict";
// JSON.rawJSON
const raw = JSON.rawJSON("123");
const rawStr = raw.rawJSON;
JSON.stringify({ value: raw });
JSON.stringify({ n: JSON.rawJSON("12345678901234567890") });
// JSON.isRawJSON
const maybeRaw = {};
if (JSON.isRawJSON(maybeRaw)) {
const text = maybeRaw.rawJSON;
}
// JSON.parse with reviver context
// `source` is optional: only present for unmodified primitive values
JSON.parse('{"key":123}', (key, value, context) => {
const src = context.source;
return value;
});
// Test coverage for reviver context on array elements
JSON.parse('[1, 2, 3]', (key, value, context) => {
const src = context.source;
return value;
});
// Test coverage for reviver context on nested objects
JSON.parse('{"a": {"b": 1}}', (key, value, context) => {
const src = context.source;
return value;
});
// Existing JSON.parse overloads still work
JSON.parse("{}");
JSON.parse('{"a":1}', (key, value) => value);
124 changes: 124 additions & 0 deletions tests/baselines/reference/jsonParseWithSource.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//// [tests/cases/compiler/jsonParseWithSource.ts] ////

=== jsonParseWithSource.ts ===
// JSON.rawJSON
const raw = JSON.rawJSON("123");
>raw : Symbol(raw, Decl(jsonParseWithSource.ts, 1, 5))
>JSON.rawJSON : Symbol(JSON.rawJSON, Decl(lib.es2025.json.d.ts, --, --))
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>rawJSON : Symbol(JSON.rawJSON, Decl(lib.es2025.json.d.ts, --, --))

const rawStr: string = raw.rawJSON;
>rawStr : Symbol(rawStr, Decl(jsonParseWithSource.ts, 2, 5))
>raw.rawJSON : Symbol(RawJSON.rawJSON, Decl(lib.es2025.json.d.ts, --, --))
>raw : Symbol(raw, Decl(jsonParseWithSource.ts, 1, 5))
>rawJSON : Symbol(RawJSON.rawJSON, Decl(lib.es2025.json.d.ts, --, --))

JSON.stringify({ value: raw });
>JSON.stringify : Symbol(JSON.stringify, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>stringify : Symbol(JSON.stringify, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>value : Symbol(value, Decl(jsonParseWithSource.ts, 3, 16))
>raw : Symbol(raw, Decl(jsonParseWithSource.ts, 1, 5))

JSON.stringify({ n: JSON.rawJSON("12345678901234567890") });
>JSON.stringify : Symbol(JSON.stringify, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>stringify : Symbol(JSON.stringify, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>n : Symbol(n, Decl(jsonParseWithSource.ts, 4, 16))
>JSON.rawJSON : Symbol(JSON.rawJSON, Decl(lib.es2025.json.d.ts, --, --))
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>rawJSON : Symbol(JSON.rawJSON, Decl(lib.es2025.json.d.ts, --, --))

// JSON.isRawJSON
const maybeRaw: unknown = {};
>maybeRaw : Symbol(maybeRaw, Decl(jsonParseWithSource.ts, 7, 5))

if (JSON.isRawJSON(maybeRaw)) {
>JSON.isRawJSON : Symbol(JSON.isRawJSON, Decl(lib.es2025.json.d.ts, --, --))
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>isRawJSON : Symbol(JSON.isRawJSON, Decl(lib.es2025.json.d.ts, --, --))
>maybeRaw : Symbol(maybeRaw, Decl(jsonParseWithSource.ts, 7, 5))

const text: string = maybeRaw.rawJSON;
>text : Symbol(text, Decl(jsonParseWithSource.ts, 9, 9))
>maybeRaw.rawJSON : Symbol(RawJSON.rawJSON, Decl(lib.es2025.json.d.ts, --, --))
>maybeRaw : Symbol(maybeRaw, Decl(jsonParseWithSource.ts, 7, 5))
>rawJSON : Symbol(RawJSON.rawJSON, Decl(lib.es2025.json.d.ts, --, --))
}

// JSON.parse with reviver context
// `source` is optional: only present for unmodified primitive values
JSON.parse('{"key":123}', (key, value, context) => {
>JSON.parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>key : Symbol(key, Decl(jsonParseWithSource.ts, 14, 27))
>value : Symbol(value, Decl(jsonParseWithSource.ts, 14, 31))
>context : Symbol(context, Decl(jsonParseWithSource.ts, 14, 38))

const src: string | undefined = context.source;
>src : Symbol(src, Decl(jsonParseWithSource.ts, 15, 9))
>context.source : Symbol(source, Decl(lib.es2025.json.d.ts, --, --))
>context : Symbol(context, Decl(jsonParseWithSource.ts, 14, 38))
>source : Symbol(source, Decl(lib.es2025.json.d.ts, --, --))

return value;
>value : Symbol(value, Decl(jsonParseWithSource.ts, 14, 31))

});

// Test coverage for reviver context on array elements
JSON.parse('[1, 2, 3]', (key, value, context) => {
>JSON.parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>key : Symbol(key, Decl(jsonParseWithSource.ts, 20, 25))
>value : Symbol(value, Decl(jsonParseWithSource.ts, 20, 29))
>context : Symbol(context, Decl(jsonParseWithSource.ts, 20, 36))

const src: string | undefined = context.source;
>src : Symbol(src, Decl(jsonParseWithSource.ts, 21, 9))
>context.source : Symbol(source, Decl(lib.es2025.json.d.ts, --, --))
>context : Symbol(context, Decl(jsonParseWithSource.ts, 20, 36))
>source : Symbol(source, Decl(lib.es2025.json.d.ts, --, --))

return value;
>value : Symbol(value, Decl(jsonParseWithSource.ts, 20, 29))

});

// Test coverage for reviver context on nested objects
JSON.parse('{"a": {"b": 1}}', (key, value, context) => {
>JSON.parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>key : Symbol(key, Decl(jsonParseWithSource.ts, 26, 31))
>value : Symbol(value, Decl(jsonParseWithSource.ts, 26, 35))
>context : Symbol(context, Decl(jsonParseWithSource.ts, 26, 42))

const src: string | undefined = context.source;
>src : Symbol(src, Decl(jsonParseWithSource.ts, 27, 9))
>context.source : Symbol(source, Decl(lib.es2025.json.d.ts, --, --))
>context : Symbol(context, Decl(jsonParseWithSource.ts, 26, 42))
>source : Symbol(source, Decl(lib.es2025.json.d.ts, --, --))

return value;
>value : Symbol(value, Decl(jsonParseWithSource.ts, 26, 35))

});

// Existing JSON.parse overloads still work
JSON.parse("{}");
>JSON.parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))

JSON.parse('{"a":1}', (key, value) => value);
>JSON.parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>JSON : Symbol(JSON, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>parse : Symbol(JSON.parse, Decl(lib.es5.d.ts, --, --), Decl(lib.es2025.json.d.ts, --, --))
>key : Symbol(key, Decl(jsonParseWithSource.ts, 33, 23))
>value : Symbol(value, Decl(jsonParseWithSource.ts, 33, 27))
>value : Symbol(value, Decl(jsonParseWithSource.ts, 33, 27))

Loading