import { toString } from "./stringify" ; import { stringifyPath } from "./quote" ; import { Next , ToString } from "./types" ; export interface Options { maxDepth ? : number ; maxValues ? : number ; references ? : boolean ; skipUndefinedProperties ? : boolean ; } /**
 * Root path node.
 */ const ROOT_SENTINEL = Symbol ( "root" ) ; /**
 * Stringify any JavaScript value.
 */ export function stringify ( value , replacer ? , indent ? , options = { } ) { const space = typeof indent === "string" ? indent : " " . repeat ( indent || 0 ) ; const path = [ ] ; const stack = new Set ( ) ; const tracking = new Map < any , PropertyKey [ ] > ( ) ; const unpack = new Map < PropertyKey [ ] , PropertyKey [ ] > ( ) ; let valueCount = 0 ; const { maxDepth = 100 , references = false , skipUndefinedProperties = false , maxValues = 100000 , } = options ; const valueToString = replacerToString ( replacer ) ; const onNext = ( value , key ) => { if ( ++ valueCount > maxValues ) return ; if ( skipUndefinedProperties && value === undefined ) return ; if ( path . length > maxDepth ) return ; if ( key === undefined ) return valueToString ( value , space , onNext , key ) ; path . push ( key ) ; const result = builder ( value , key === ROOT_SENTINEL ? undefined : key ) ; path . pop ( ) ; return result ; } ; const builder = references ? ( value , key ) => { if ( value !== null && ( typeof value === "object" || typeof value === "function" || typeof value === "symbol" ) ) { if ( tracking . has ( value ) ) { unpack . set ( path . slice ( 1 ) , tracking . get ( value ) ! ) ; return valueToString ( undefined , space , onNext , key ) ; } tracking . set ( value , path . slice ( 1 ) ) ; } return valueToString ( value , space , onNext , key ) ; } : ( value , key ) => { if ( stack . has ( value ) ) return ; stack . add ( value ) ; const result = valueToString ( value , space , onNext , key ) ; stack . delete ( value ) ; return result ; } ; const result = onNext ( value , ROOT_SENTINEL ) ; if ( unpack . size ) { const sp = space ? " " : "" ; const eol = space ? "\n" : "" ; let wrapper = `var x${ sp }=${ sp }${ result };${ eol }` ; for ( const [ key , value ] of unpack . entries ( ) ) { const keyPath = stringifyPath ( key , onNext ) ; const valuePath = stringifyPath ( value , onNext ) ; wrapper += `x${ keyPath }${ sp }=${ sp }x${ valuePath };${ eol }` ; } return `(function${ sp }()${ sp }{${ eol }${ wrapper }return x;${ eol }}())` ; } return result ; } /**
 * Create `toString()` function from replacer.
 */ function replacerToString ( replacer ? ) { if ( ! replacer ) return toString ; return ( value , space , next , key ) => { return replacer ( value , space , ( value ) => toString ( value , space , next , key ) , key ) ; } ; }