char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
1738b3c
main
1import { 2ArrayIsArray , 3ArrayPrototypeJoin , 4ArrayPrototypePush , 5DatePrototype , 6DatePrototypeGetTime , 7DatePrototypeToISOString , 8ErrorGetStack , 9ErrorPrototype , 10JSONStringify , 11MapIteratorPrototypeNext , 12MapPrototype , 13MapPrototypeEntries , 14MathMin , 15NumberIsFinite , 16ObjectGetOwnPropertyDescriptor , 17ObjectGetPrototypeOf , 18ReflectApply , 19ReflectOwnKeys , 20RegExpPrototype , 21RegExpPrototypeExec , 22RegExpPrototypeGetFlags , 23RegExpPrototypeGetSource , 24Set , 25SetIteratorPrototypeNext , 26SetPrototype , 27SetPrototypeAdd , 28SetPrototypeDelete , 29SetPrototypeHas , 30SetPrototypeValues , 31String , 32StringPrototypeReplaceAll , 33SymbolFor , 34SymbolToStringTag , 35TypedArrayPrototypeGetLength , 36TypedArrayPrototypeGetTag , 37TypeError , 38} from "ext:core/primordials" ; 39import { promiseState } from "ext:core/promise_hooks" ; 40const identifier = / ^[A-Za-z_$][\w$]*$ / , newline = / \n / ; 41const customInspect = SymbolFor ( "runtime.avril.inspect" ); 42// Values whose custom inspector is running; inspecting one again from inside 43// its own inspector falls back to the default format instead of recursing. 44const customizing = new Set < object >(); 45 46type Context = { colors :boolean }; 47 48function keyName ( key :string | symbol ) :string { 49if ( typeof key === "symbol" ) return `[ ${ String ( key )} ]` ; 50return RegExpPrototypeExec ( identifier , key ) ?key :JSONStringify ( key ); 51} 52 53function prototypes < T > ( 54value :object | null , 55visit :( prototype :object ) => T | undefined , 56) :T | undefined { 57let prototype = value ; 58for ( let index = 0 ; prototype !== null && index < 20 ; index ++ ) { 59const result = visit ( prototype ); 60if ( result !== undefined ) return result ; 61prototype = ObjectGetPrototypeOf ( prototype ); 62} 63} 64 65function intrinsicPrototype ( value :object ) :object | null { 66return prototypes ( 67ObjectGetPrototypeOf ( value ), 68( prototype ) => 69prototype === ErrorPrototype || prototype === DatePrototype || 70prototype === RegExpPrototype || 71prototype === MapPrototype || prototype === SetPrototype 72 ?prototype 73 :undefined , 74) ?? null ; 75} 76 77/** 78* Data properties only, so naming an object never runs its getters. The 79* descriptor's own `value` decides, since a data descriptor inherits any 80* `get` or `set` added to Object.prototype. 81*/ 82function data ( value :object , key :PropertyKey ) :unknown { 83return prototypes ( value , ( prototype ) => { 84const descriptor = ObjectGetOwnPropertyDescriptor ( prototype , key ); 85return descriptor && ObjectGetOwnPropertyDescriptor ( descriptor , "value" ); 86})?. value ; 87} 88 89/** 90* Names objects that are not plain, as `Class`, `Class [Tag]`, or `[Tag]` 91* without a constructor; empty for plain and null-prototype objects. 92*/ 93function label ( value :object ) :string { 94const constructor = data ( value , "constructor" ); 95let name :unknown = typeof constructor === "function" 96 ?ObjectGetOwnPropertyDescriptor ( constructor , "name" )?. value 97 :undefined ; 98if ( typeof name !== "string" || ! name ) name = undefined ; 99let tag = data ( value , SymbolToStringTag ); 100if ( typeof tag !== "string" || ! tag || tag === name ) tag = undefined ; 101if ( name === undefined ) return tag === undefined ?"" :`[ ${ tag } ]` ; 102if ( name === "Object" && tag === undefined ) return "" ; 103return tag === undefined ?name as string :` ${ name } [ ${ tag } ]` ; 104} 105 106function customInspector ( value :object ) :Function | undefined { 107const method :unknown = prototypes ( 108value , 109( prototype ) => ObjectGetOwnPropertyDescriptor ( prototype , customInspect ), 110)?. value ; 111return typeof method === "function" ?method :undefined ; 112} 113 114function style ( 115context :Context , 116open :number , 117close :number , 118text :string , 119) :string { 120return context . colors ?`\x1b[ ${ open } m ${ text } \x1b[ ${ close } m` :text ; 121} 122 123const special = ( context :Context , text :string ) :string => 124style ( context , 36 , 39 , text ); 125 126function inspect ( value :unknown , options :unknown = undefined ) :string { 127if ( 128options !== undefined && options !== null && 129typeof options !== "object" && typeof options !== "function" 130) { 131throw new TypeError ( "Inspect options must be an object" ); 132} 133const depth = ( options as { depth ?:unknown } | null | undefined )?. depth ; 134return inspectValue ( 135value , 136NumberIsFinite ( depth ) 137 ?MathMin ( depth as number , 10 ) 138 :depth === null || depth === Infinity 139 ?10 140 :3 , 141{ colors :!! ( options as { colors ?:unknown } | null | undefined )?. colors }, 142new Set (), 143); 144} 145 146function inspectValue ( 147input :unknown , 148depth :number , 149context :Context , 150seen :Set < object >, 151) :string { 152switch ( typeof input ) { 153case "string" : 154return style ( context , 32 , 39 , JSONStringify ( input )); 155case "number" : 156case "boolean" : 157return style ( context , 33 , 39 , String ( input )); 158case "bigint" : 159return style ( context , 33 , 39 , ` ${ String ( input )} n` ); 160case "symbol" : 161return style ( context , 32 , 39 , String ( input )); 162case "undefined" : 163return style ( context , 90 , 39 , "undefined" ); 164case "object" : 165if ( input === null ) return style ( context , 1 , 22 , "null" ); 166} 167const value = input as object ; 168if ( SetPrototypeHas ( seen , value )) return special ( context , "[Circular]" ); 169SetPrototypeAdd ( seen , value ); 170try { 171const custom = SetPrototypeHas ( customizing , value ) 172 ?undefined 173 :customInspector ( value ); 174if ( custom ) { 175SetPrototypeAdd ( customizing , value ); 176try { 177const result = ReflectApply ( custom , value , [ inspect , { 178 depth, 179colors :context . colors , 180}]); 181return typeof result === "string" 182 ?result 183 :inspectValue ( result , depth , context , seen ); 184} finally { 185SetPrototypeDelete ( customizing , value ); 186} 187} 188if ( typeof value === "function" ) { 189const name = ObjectGetOwnPropertyDescriptor ( value , "name" )?. value ; 190return special ( 191context , 192typeof name === "string" && name ?`[Function: ${ name } ]` :"[Function]" , 193); 194} 195const prototype = intrinsicPrototype ( value ); 196if ( prototype === ErrorPrototype ) { 197const stack = ErrorGetStack ( value ); 198return typeof stack === "string" ?stack :"[Error]" ; 199} 200if ( prototype === DatePrototype ) { 201return style ( 202context , 20335 , 20439 , 205NumberIsFinite ( DatePrototypeGetTime ( value )) 206 ?DatePrototypeToISOString ( value ) 207 :"Invalid Date" , 208); 209} 210if ( prototype === RegExpPrototype ) { 211return style ( 212context , 21331 , 21439 , 215`/ ${ RegExpPrototypeGetSource ( value )} / ${ RegExpPrototypeGetFlags ( value )} ` , 216); 217} 218if ( prototype === MapPrototype || prototype === SetPrototype ) { 219const isMap = prototype === MapPrototype ; 220if ( depth < 0 ) return special ( context , isMap ?"[Map]" :"[Set]" ); 221const iterator = isMap 222 ?MapPrototypeEntries ( value as Map < unknown , unknown >) 223 :SetPrototypeValues ( value as Set < unknown >); 224const next = isMap ?MapIteratorPrototypeNext :SetIteratorPrototypeNext ; 225const entries :string [] = []; 226for ( let index = 0 ; index < 50 ; index ++ ) { 227const step = next ( iterator ); 228if ( step . done ) break ; 229ArrayPrototypePush ( 230entries , 231isMap 232 ?` ${ 233inspectValue ( 234( step . value as [ unknown , unknown ])[ 0 ], 235depth - 1 , 236context , 237seen , 238) 239} => ${ 240inspectValue ( 241( step . value as [ unknown , unknown ])[ 1 ], 242depth - 1 , 243context , 244seen , 245) 246} ` 247 :inspectValue ( step . value , depth - 1 , context , seen ), 248); 249} 250if ( ! next ( iterator ). done ) ArrayPrototypePush ( entries , "..." ); 251return ` ${ isMap ? "Map" : "Set" } ${ collection ( "{" , entries , "}" )} ` ; 252} 253const state = promiseState ( value ); 254if ( state !== undefined ) { 255const name = label ( value ) || "Promise" ; 256if ( depth < 0 ) return special ( context , `[ ${ name } ]` ); 257const content = state [ 0 ] === 0 258 ?special ( context , "<pending>" ) 259 :` ${ state [ 0 ] === 2 ? ` ${ special ( context , "<rejected>" )} ` : "" } ${ 260inspectValue ( state [ 1 ], depth - 1 , context , seen ) 261} ` ; 262// Multiline content, such as an error's stack, gets its own lines. 263return RegExpPrototypeExec ( newline , content ) 264 ?` ${ name } {\n ${ StringPrototypeReplaceAll ( content , "\n" , "\n " )} \n}` 265 :` ${ name } { ${ content } }` ; 266} 267const isArray = ArrayIsArray ( value ); 268const typedArray = TypedArrayPrototypeGetTag ( value ); 269const name = isArray || typedArray ?"" :label ( value ); 270if ( depth < 0 ) { 271return special ( 272context , 273isArray ?"[Array]" :name [ 0 ] === "[" ?name :`[ ${ name || "Object" } ]` , 274); 275} 276const entries :string [] = []; 277if ( isArray || typedArray ) { 278const length = isArray 279 ?ObjectGetOwnPropertyDescriptor ( value , "length" ) ! . value as number 280 :TypedArrayPrototypeGetLength ( value ); 281for ( let index = 0 ; index < MathMin ( length , 50 ); index ++ ) { 282ArrayPrototypePush ( 283entries , 284property ( value , String ( index ), depth - 1 , context , seen ), 285); 286} 287if ( length > 50 ) { 288ArrayPrototypePush ( entries , `... ${ length - 50 } more items` ); 289} 290return ` ${ typedArray ? ` ${ typedArray } ( ${ length } ) ` : "" } ${ 291collection ( "[" , entries , "]" ) 292} ` ; 293} 294const keys = ReflectOwnKeys ( value ); 295for ( let index = 0 ; index < keys . length ; index ++ ) { 296const key = keys [ index ]; 297const descriptor = ObjectGetOwnPropertyDescriptor ( value , key ); 298if ( ! descriptor ?. enumerable ) continue ; 299if ( entries . length === 50 ) { 300ArrayPrototypePush ( entries , "..." ); 301break ; 302} 303ArrayPrototypePush ( 304entries , 305` ${ keyName ( key )} : ${ property ( value , key , depth - 1 , context , seen )} ` , 306); 307} 308return ` ${ name ? ` ${ name } ` : "" } ${ collection ( "{" , entries , "}" )} ` ; 309} catch { 310return special ( context , "[Uninspectable]" ); 311} finally { 312SetPrototypeDelete ( seen , value ); 313} 314} 315 316function collection ( open :string , entries :string [], close :string ) :string { 317return ` ${ open } ${ 318entries . length ? ` ${ ArrayPrototypeJoin ( entries , ", " )} ` : "" 319} ${ close } ` ; 320} 321 322function property ( 323object :object , 324key :PropertyKey , 325depth :number , 326context :Context = { colors :false }, 327seen :Set < object > = new Set (), 328) :string { 329const descriptor = ObjectGetOwnPropertyDescriptor ( object , key ); 330if ( ! descriptor ) return style ( context , 90 , 39 , "<empty>" ); 331if ( ! ObjectGetOwnPropertyDescriptor ( descriptor , "value" )) { 332return special ( 333context , 334descriptor . get 335 ?( descriptor . set ?"[Getter/Setter]" :"[Getter]" ) 336 :"[Setter]" , 337); 338} 339return inspectValue ( descriptor . value , depth , context , seen ); 340} 341 342export { inspect , intrinsicPrototype , property };