import { Definition , NamedNode , Node , ReferenceNode , SourceFile } from '#ast/node-types' ; import { ModifierFlags , NodeFlags , NodeKind , TypeFlags } from '#ast/enums' ; import { Language } from '#language/language' ; import { accForEach , reverseMap } from '@crosstype/common' ; import { NodeMap , NodeSet , ReadonlyNodeSet } from '#ast/components' ; import { isDefinition , isNamedNode , isNode , isSourceFile } from '#ast/utilities/node-typeguards' ; import { cloneNode } from '#ast/utilities/clone-node' ; import { NodeMetadata , NodeOrigin } from '#ast/shared-types' ; import { nodeMetadata } from '#ast/node-metadata' ; import { CompileOptionsSet } from '../types' ; export class NodeObject implements Node { public readonly id ! : number ; public readonly parent : Node ; public readonly flags : NodeFlags ; public readonly typeFlags : TypeFlags ; public readonly kind : NodeKind ; public readonly origin : NodeOrigin ; public readonly modifiers : ModifierFlags ; public readonly compileOptions : CompileOptionsSet ; private _metadata : NodeMetadata ; public _referencesToThis : NodeSet < ReferenceNode > ; constructor ( kind , origin , compileOptions ) { this . parent = undefined ! ; this . flags = NodeFlags . None ; this . typeFlags = TypeFlags . None ; this . kind = kind ; this . origin = origin ! ; this . modifiers = ModifierFlags . None ; this . compileOptions = compileOptions || accForEach ( Language . CompilerLanguages , < Record < string , any > > { } , ( { shortName } , acc ) => acc [ shortName ] = { } ) as CompileOptionsSet ; this . _referencesToThis = new NodeSet ( ) ; this . _metadata = nodeMetadata [ kind ] ; } get kindString ( ) : string { return NodeKind [ this . kind ] ; } getLineage ( ) { if ( ! this . parent ) return undefined ; const seenParents = new NodeSet ( ) ; for ( let node = this . parent ; node ; node = node . parent ) { if ( seenParents . has ( node ) ) throw new Error ( `Error, parent chain is broken. Circular reference.` ) ; seenParents . add ( node ) ; } return seenParents . toArray ( ) ; } getChildren < T extends Node = Node > ( deep ? , predicate ? ) { const res = new NodeSet < Node > ( ) ; const addChild = ( child ) => { if ( ! predicate || predicate ( child ) ) res . add ( child ) ; if ( deep ) child . getChildren ( true , predicate ) ?. forEach ( addChild ) ; } ; this . forEachChild ( addChild ) ; return res . orUndefinedIfEmpty ( ) as NodeSet < T > | undefined ; } forEachChild < T extends Node > ( this , cb ) { const childContainerKeys = ( < NodeObject > < unknown > this ) . _metadata . childContainerProperties ?. keys ( ) as Iterable < keyof T > ; if ( ! childContainerKeys ) return ; for ( const propKey of childContainerKeys ) { const prop = this [ propKey ] as any as Node | NodeMap < NamedNode > | NodeSet < Node > ; if ( ! prop ) continue ; if ( isNode ( prop ) ) cb ( prop , propKey ) ; else prop . forEach ( ( n ) => cb ( n , propKey ) ) ; } } findParent < T > ( matcher ) ; findParent < T > ( matcher ) ; findParent ( matcher ) { const seenParents = new NodeSet ( ) ; for ( let node = this . parent ; node ; node = node . parent ) { if ( seenParents . has ( node ) ) throw new Error ( `Error, parent chain is broken. Circular reference.` ) ; seenParents . add ( node ) ; if ( matcher ( node ) ) return node ; } } getNamedParent ( name ? ) { if ( ! name ) return this . findParent ( isNamedNode ) ; return this . findParent ( n => isNamedNode ( n ) && ( ( name instanceof RegExp ) ? ( ( typeof n . name === 'string' ) ? name . test ( n . name ) : false ) : n . name === name ) ) ; } getDefinition ( ) { return this . findParent ( isDefinition ) ; } getSourceFile ( ) { return this . findParent ( isSourceFile ) ; } delete ( brokenReferenceReplacer ? ) { this . _referencesToThis . forEach ( ref => { ref . target = undefined ! ; if ( ! ref . target && brokenReferenceReplacer ) { const newNode = brokenReferenceReplacer ( ref ) ; if ( newNode !== ref ) ref . replace ( newNode ) ; } } ) ; this . removeThisFromParent ( ) ; this . getChildren ( true ) ?. forEach ( n => n . delete ( brokenReferenceReplacer ) ) ; for ( const key in this ) if ( this . hasOwnProperty ( key ) ) delete this [ key ] ; Object . setPrototypeOf ( this , null ) ; } clone < T extends Node > ( this , parent ) { return ( cloneNode ( this ) as Node ) . updateProperties ( { parent } ) as T ; } replace < T extends Node > ( newNode , brokenReferenceReplacer ? , reuseMemory ? ) { if ( reuseMemory ) { ( < Node > newNode ) . updateProperties ( { parent : this . parent } ) ; for ( const key in this ) if ( this . hasOwnProperty ( key ) ) delete this [ key ] ; Object . defineProperties ( this , Object . getOwnPropertyDescriptors ( newNode ) ) ; return this as any ; } else { this . delete ( brokenReferenceReplacer ) ; return newNode ; } } compile < T extends Language . Names > ( language , options ? ) { return '' ; } getReferencesToThisNode ( ) { return this . _referencesToThis ; } updateProperties < T extends Node > ( this , props ) { return Object . assign ( this , props ) as T ; } /**
     * Cleans up child containing properties & internal references set
     * Removes any non-nodes, sets optional properties that contain empty iterables to undefined
     */ cleanup ( ) { this . _metadata . childContainerProperties ?. forEach ( ( { key , optional } ) => { const item = ( < any > this ) [ key ] ; if ( item instanceof NodeMap || item instanceof NodeSet ) { if ( optional && ! item . size ) ( < any > this ) [ key ] = void 0 ; else if ( item . size ) item . prune ( ) ; } else if ( ! isNode ( item ) ) ( < any > this ) [ key ] = void 0 ; } ) ; this . _referencesToThis . prune ( ) ; } /**
     * Fix node
     * Runs cleanup() and ensures proper parent set on this node and all descendants
     */ fixup ( ) { ( function fixNode ( node , parent ) { node . cleanup ( ) ; node . updateProperties ( { parent } ) ; node . getChildren ( ) ?. forEach ( n => fixNode ( n , node ) ) ; } ) ( this , this . parent ) ; } param replacementNode private removeThisFromParent ( replacementNode ? ) { this . parent ?. forEachChild ( ( child , parentKey ) => { if ( child === this ) { if ( this . parent [ parentKey ] === child ) return this . parent . updateProperties ( { [ parentKey ] : replacementNode } ) ; const container = this . parent [ parentKey ] as any ; if ( container instanceof NodeMap ) { const key = ( < NamedNode > child ) . name ; if ( ! replacementNode ) container . delete ( key ) ; else container . set ( key ! , < NamedNode > replacementNode ) ; } else if ( container instanceof NodeSet ) { container . delete ( this ) ; if ( replacementNode ) container . add ( replacementNode ) ; } } } ) ; } internal updateNameOnParentContainer ( this ) { const newKey = this . name ; this . parent ?. forEachChild ( ( child , parentKey ) => { if ( child === this ) { const prop = this . parent [ parentKey ] ; if ( prop instanceof NodeMap ) { const oldKey = reverseMap ( prop ) . get ( this ) ; if ( oldKey !== newKey ) { prop . delete ( oldKey ) ; prop . set ( newKey , this ) ; } } } } ) ; if ( this . kind === NodeKind . TypeParameterDeclaration ) { const typeArguments = this . getDefinition ( ) ?. typeArguments ; if ( ! typeArguments ) return ; [ ... typeArguments . entries ( ) ] . forEach ( ( [ key , node ] ) => { const { association } = node ; if ( ( association === this ) && ( key !== newKey ) ) { typeArguments . delete ( key ) ; typeArguments . set ( < string > newKey , node ) ; } } ) ; } } }