Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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" />
39 changes: 39 additions & 0 deletions src/lib/es2025.json.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* 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 primitive values the reviver also receives a `context` object whose `source` property is the original JSON
* text of that value.
* @throws {SyntaxError} If `text` is not valid JSON.
*/
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
46 changes: 46 additions & 0 deletions tests/baselines/reference/jsonParseWithSource.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//// [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
JSON.parse('{"key":123}', (key, value, context) => {
const src: string = 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
JSON.parse('{"key":123}', (key, value, context) => {
const src = context.source;
return value;
});
// Existing JSON.parse overloads still work
JSON.parse("{}");
JSON.parse('{"a":1}', (key, value) => value);
83 changes: 83 additions & 0 deletions tests/baselines/reference/jsonParseWithSource.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//// [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
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, 13, 27))
>value : Symbol(value, Decl(jsonParseWithSource.ts, 13, 31))
>context : Symbol(context, Decl(jsonParseWithSource.ts, 13, 38))

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

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

});

// 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, 20, 23))
>value : Symbol(value, Decl(jsonParseWithSource.ts, 20, 27))
>value : Symbol(value, Decl(jsonParseWithSource.ts, 20, 27))

161 changes: 161 additions & 0 deletions tests/baselines/reference/jsonParseWithSource.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
//// [tests/cases/compiler/jsonParseWithSource.ts] ////

=== jsonParseWithSource.ts ===
// JSON.rawJSON
const raw = JSON.rawJSON("123");
>raw : RawJSON
> : ^^^^^^^
>JSON.rawJSON("123") : RawJSON
> : ^^^^^^^
>JSON.rawJSON : (text: string) => RawJSON
> : ^ ^^ ^^^^^
>JSON : JSON
> : ^^^^
>rawJSON : (text: string) => RawJSON
> : ^ ^^ ^^^^^
>"123" : "123"
> : ^^^^^

const rawStr: string = raw.rawJSON;
>rawStr : string
> : ^^^^^^
>raw.rawJSON : string
> : ^^^^^^
>raw : RawJSON
> : ^^^^^^^
>rawJSON : string
> : ^^^^^^

JSON.stringify({ value: raw });
>JSON.stringify({ value: raw }) : string
> : ^^^^^^
>JSON.stringify : { (value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (number | string)[] | null, space?: string | number): string; }
> : ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^
>JSON : JSON
> : ^^^^
>stringify : { (value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (number | string)[] | null, space?: string | number): string; }
> : ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^
>{ value: raw } : { value: RawJSON; }
> : ^^^^^^^^^^^^^^^^^^^
>value : RawJSON
> : ^^^^^^^
>raw : RawJSON
> : ^^^^^^^

JSON.stringify({ n: JSON.rawJSON("12345678901234567890") });
>JSON.stringify({ n: JSON.rawJSON("12345678901234567890") }) : string
> : ^^^^^^
>JSON.stringify : { (value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (number | string)[] | null, space?: string | number): string; }
> : ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^
>JSON : JSON
> : ^^^^
>stringify : { (value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string; (value: any, replacer?: (number | string)[] | null, space?: string | number): string; }
> : ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^^ ^^ ^^^ ^^^ ^^^
>{ n: JSON.rawJSON("12345678901234567890") } : { n: RawJSON; }
> : ^^^^^^^^^^^^^^^
>n : RawJSON
> : ^^^^^^^
>JSON.rawJSON("12345678901234567890") : RawJSON
> : ^^^^^^^
>JSON.rawJSON : (text: string) => RawJSON
> : ^ ^^ ^^^^^
>JSON : JSON
> : ^^^^
>rawJSON : (text: string) => RawJSON
> : ^ ^^ ^^^^^
>"12345678901234567890" : "12345678901234567890"
> : ^^^^^^^^^^^^^^^^^^^^^^

// JSON.isRawJSON
const maybeRaw: unknown = {};
>maybeRaw : unknown
> : ^^^^^^^
>{} : {}
> : ^^

if (JSON.isRawJSON(maybeRaw)) {
>JSON.isRawJSON(maybeRaw) : boolean
> : ^^^^^^^
>JSON.isRawJSON : (value: unknown) => value is RawJSON
> : ^ ^^ ^^^^^
>JSON : JSON
> : ^^^^
>isRawJSON : (value: unknown) => value is RawJSON
> : ^ ^^ ^^^^^
>maybeRaw : unknown
> : ^^^^^^^

const text: string = maybeRaw.rawJSON;
>text : string
> : ^^^^^^
>maybeRaw.rawJSON : string
> : ^^^^^^
>maybeRaw : RawJSON
> : ^^^^^^^
>rawJSON : string
> : ^^^^^^
}

// JSON.parse with reviver context
JSON.parse('{"key":123}', (key, value, context) => {
>JSON.parse('{"key":123}', (key, value, context) => { const src: string = context.source; return value;}) : any
>JSON.parse : { (text: string, reviver?: (this: any, key: string, value: any) => any): any; (text: string, reviver: (this: any, key: string, value: any, context: { source: string; }) => any): any; }
> : ^^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^ ^^^ ^^^
>JSON : JSON
> : ^^^^
>parse : { (text: string, reviver?: (this: any, key: string, value: any) => any): any; (text: string, reviver: (this: any, key: string, value: any, context: { source: string; }) => any): any; }
> : ^^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^ ^^^ ^^^
>'{"key":123}' : "{\"key\":123}"
> : ^^^^^^^^^^^^^^^
>(key, value, context) => { const src: string = context.source; return value;} : (this: any, key: string, value: any, context: { source: string; }) => any
> : ^ ^^ ^^ ^^^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^^^
>key : string
> : ^^^^^^
>value : any
>context : { source: string; }
> : ^^^^^^^^^^ ^^^

const src: string = context.source;
>src : string
> : ^^^^^^
>context.source : string
> : ^^^^^^
>context : { source: string; }
> : ^^^^^^^^^^ ^^^
>source : string
> : ^^^^^^

return value;
>value : any

});

// Existing JSON.parse overloads still work
JSON.parse("{}");
>JSON.parse("{}") : any
>JSON.parse : { (text: string, reviver?: (this: any, key: string, value: any) => any): any; (text: string, reviver: (this: any, key: string, value: any, context: { source: string; }) => any): any; }
> : ^^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^ ^^^ ^^^
>JSON : JSON
> : ^^^^
>parse : { (text: string, reviver?: (this: any, key: string, value: any) => any): any; (text: string, reviver: (this: any, key: string, value: any, context: { source: string; }) => any): any; }
> : ^^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^ ^^^ ^^^
>"{}" : "{}"
> : ^^^^

JSON.parse('{"a":1}', (key, value) => value);
>JSON.parse('{"a":1}', (key, value) => value) : any
>JSON.parse : { (text: string, reviver?: (this: any, key: string, value: any) => any): any; (text: string, reviver: (this: any, key: string, value: any, context: { source: string; }) => any): any; }
> : ^^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^ ^^^ ^^^
>JSON : JSON
> : ^^^^
>parse : { (text: string, reviver?: (this: any, key: string, value: any) => any): any; (text: string, reviver: (this: any, key: string, value: any, context: { source: string; }) => any): any; }
> : ^^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^ ^^^ ^^^
>'{"a":1}' : "{\"a\":1}"
> : ^^^^^^^^^^^
>(key, value) => value : (this: any, key: string, value: any) => any
> : ^ ^^ ^^ ^^^^^^^^^^ ^^^^^^^^^^^^^
>key : string
> : ^^^^^^
>value : any
>value : any

24 changes: 24 additions & 0 deletions tests/cases/compiler/jsonParseWithSource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// @lib: es2025
// @strictNullChecks: true

// 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
JSON.parse('{"key":123}', (key, value, context) => {
const src: string = context.source;
return value;
});

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