import { ClassDeclaration , Node , Scope , TypeChecker } from "ts-morph" ; import { decomposeType , decomposeTypes , GenericsInfo , typeToString } from "./typeDecomposition" ; /**
 * Total result of a class decomposition which may be reconstructed into an equivalent class
 * declaration to bypass issues with normal class type comparisions
 */ export interface ClassData { readonly typeParameters : string [ ] ; readonly properties : string [ ] ; readonly replacedTypes : Set < string > ; readonly requiredGenerics : GenericsInfo ; } function mergeIntoSet < T > ( into , from ) { from . forEach ( ( v ) => into . add ( v ) ) ; } param typeChecker param node returns export function decomposeClassDeclaration ( typeChecker , node ) { const replacedTypes = new Set < string > ( ) ; const replacedProperties = [ ] ; const requiredGenerics = new GenericsInfo ( ) ; const typeParameters = [ ] ; node . getTypeParameters ( ) . forEach ( ( tp ) => { typeParameters . push ( typeToString ( typeChecker , tp . getType ( ) ) ) ; } ) ; const extendsExpr = node . getExtends ( ) ; if ( extendsExpr !== undefined ) { const result = decomposeType ( typeChecker , extendsExpr . getType ( ) ) ; mergeIntoSet ( replacedTypes , result . replacedTypes ) ; requiredGenerics . merge ( result . requiredGenerics ) ; const typeName = typeToString ( typeChecker , extendsExpr . getType ( ) ) . replace ( /[^\w]/g , "_" ) ; replacedProperties . push ( `__extends__${ typeName }: ${ result . typeAsString };` ) ; } node . getImplements ( ) . forEach ( ( ex ) => { const result = decomposeType ( typeChecker , ex . getType ( ) ) ; mergeIntoSet ( replacedTypes , result . replacedTypes ) ; requiredGenerics . merge ( result . requiredGenerics ) ; const typeName = typeToString ( typeChecker , ex . getType ( ) ) . replace ( /[^\w]/g , "_" ) ; replacedProperties . push ( `__implements__${ typeName }: ${ result . typeAsString };` ) ; } ) ; for ( const member of node . getMembers ( ) ) { const modifierList = member . getModifiers ( ) . map ( ( val ) => val . getText ( ) ) ; if ( modifierList . indexOf ( Scope . Private ) != - 1 ) { continue ; } let propNamePrefix = "" ; const modifiers = modifierList . filter ( ( modifier ) => { switch ( modifier ) { case Scope . Protected : case "static" : { propNamePrefix += `__${ modifier }__` ; return false ; } case "async" : return false ; default : return true ; } } ) . join ( " " ) ; if ( Node . isMethodDeclaration ( member ) ) { let typeArgsString = "" ; if ( member . getTypeParameters ( ) . length > 0 ) { const typeArgsResult = decomposeTypes ( typeChecker , member . getTypeParameters ( ) . map ( ( tp ) => tp . getType ( ) ) , ", " ) ; mergeIntoSet ( replacedTypes , typeArgsResult . replacedTypes ) ; requiredGenerics . merge ( typeArgsResult . requiredGenerics ) ; typeArgsString = `<${ typeArgsResult . typeAsString }>` ; } let paramsString = "" ; paramsString = member . getParameters ( ) . map ( ( p ) => { const subResult = decomposeType ( typeChecker , p . getType ( ) ) ; mergeIntoSet ( replacedTypes , subResult . replacedTypes ) ; requiredGenerics . merge ( subResult . requiredGenerics ) ; return `${ p . getName ( ) }: ${ subResult . typeAsString }` ; } ) . join ( ", " ) ; const returnResult = decomposeType ( typeChecker , member . getReturnType ( ) ) ; mergeIntoSet ( replacedTypes , returnResult . replacedTypes ) ; requiredGenerics . merge ( returnResult . requiredGenerics ) ; const qToken = member . hasQuestionToken ( ) ? "?" : "" ; const method = `${ modifiers } ${ propNamePrefix }${ member . getName ( ) }${ qToken }${ typeArgsString }(${ paramsString }): ${ returnResult . typeAsString };` ; replacedProperties . push ( method ) ; } else if ( Node . isConstructorDeclaration ( member ) ) { let paramsString = "" ; paramsString = member . getParameters ( ) . map ( ( p ) => { const subResult = decomposeType ( typeChecker , p . getType ( ) ) ; mergeIntoSet ( replacedTypes , subResult . replacedTypes ) ; requiredGenerics . merge ( subResult . requiredGenerics ) ; const paramModifiers = p . getModifiers ( ) . map ( ( val ) => val . getText ( ) ) ; if ( paramModifiers . length > 0 && paramModifiers . indexOf ( Scope . Private ) === - 1 ) { let prefix = "__ctorProp__" ; const protectedIndex = paramModifiers . indexOf ( Scope . Protected ) ; if ( protectedIndex !== - 1 ) { paramModifiers . splice ( protectedIndex , 1 ) ; prefix += "__protected__" ; } const qToken = p . hasQuestionToken ( ) ? "?" : "" ; const ctorProperty = `${ paramModifiers . join ( " " ) } ${ prefix }${ p . getName ( ) }${ qToken }: ${ subResult . typeAsString };` ; replacedProperties . push ( ctorProperty ) ; } return `${ p . getName ( ) }: ${ subResult . typeAsString }` ; } ) . join ( ", " ) ; const method = `${ modifiers } __ctorDecl__(${ paramsString }): void;` ; replacedProperties . push ( method ) ; } else if ( Node . isPropertyDeclaration ( member ) ) { const result = decomposeType ( typeChecker , member . getType ( ) ) ; mergeIntoSet ( replacedTypes , result . replacedTypes ) ; requiredGenerics . merge ( result . requiredGenerics ) ; const qToken = member . hasQuestionToken ( ) ? "?" : "" ; const property = `${ modifiers } ${ propNamePrefix }${ member . getName ( ) }${ qToken }: ${ result . typeAsString };` ; replacedProperties . push ( property ) ; } else if ( Node . isGetAccessorDeclaration ( member ) ) { const result = decomposeType ( typeChecker , member . getReturnType ( ) ) ; mergeIntoSet ( replacedTypes , result . replacedTypes ) ; requiredGenerics . merge ( result . requiredGenerics ) ; const getter = `${ modifiers } get ${ propNamePrefix }${ member . getName ( ) }(): ${ result . typeAsString }` ; replacedProperties . push ( getter ) ; } else if ( Node . isSetAccessorDeclaration ( member ) ) { const param = member . getParameters ( ) [ 0 ] ; const paramResult = decomposeType ( typeChecker , param . getType ( ) ) ; mergeIntoSet ( replacedTypes , paramResult . replacedTypes ) ; requiredGenerics . merge ( paramResult . requiredGenerics ) ; const setter = `${ modifiers } set ${ propNamePrefix }${ member . getName ( ) }(${ param . getName ( ) }: ${ paramResult . typeAsString });` ; replacedProperties . push ( setter ) ; } } return { typeParameters , properties : replacedProperties , replacedTypes , requiredGenerics , } ; }