import * as sast from "ts-morph" ; import { SourceFile , SyntaxKind , TypeGuards , ts } from "ts-morph" ; import * as emitter from "./CSharpEmitter" ; import { ContextInterface } from "./Context" ; import { addWhitespace , getWhitespace , emitStatic , addLeadingComment , addTrailingComment , addSemicolon , addComma , endNode , generateExportForClass , generateExportForProperty , generateExportForMethod , pushContext , swapContext , popContext , identifyInterfaces , isDeclarationOfInterface , generateExportForInterfaceDeclaration , loadInterfaceProperties , loadInterfaceMethods , loadInterfaceIndexers , hasAccessModifiers , isMap , emitDefaultNameSpace , emitUsings , } from './GeneratorHelpers' ; export function TsToCSharpGenerator ( node , context ) { const source = [ ] ; emitUsings ( source , context ) ; emitDefaultNameSpace ( source , context , true ) ; identifyInterfaces ( node , context ) ; visitStatements ( source , node , context ) ; addWhitespace ( source , node , context ) ; endNode ( node , context ) ; emitDefaultNameSpace ( source , context , false ) ; return source . join ( '' ) ; } function visit ( node , context ) { if ( ( visitor as any ) [ node . getKind ( ) ] ) { return ( visitor as any ) [ node . getKind ( ) ] ( node , context ) ; } throw new Error ( `Unknown node kind ${ SyntaxKind [ node . getKind ( ) ] }` ) ; } function visitStatements ( source , node , context ) { node . getStatements ( ) . forEach ( statement => source . push ( visitStatement ( statement , context ) ) ) ; } function visitStatement ( node , context ) { switch ( node . getKind ( ) ) { case SyntaxKind . VariableStatement : return visitVariableStatement ( node as sast . VariableStatement , context ) ; case SyntaxKind . TypeAliasDeclaration : return visitTypeAliasDeclaration ( node as sast . TypeAliasDeclaration , context ) ; case SyntaxKind . InterfaceDeclaration : return visitInterfaceDeclaration ( node as sast . InterfaceDeclaration , context ) ; default : throw new Error ( `Unknown statement kind '${ SyntaxKind [ node . getKind ( ) ] }'` ) ; } } function visitModifiers ( source , node , context ) { node . getModifiers ( ) . forEach ( modifier => { source . push ( emitter . emitModifierable ( modifier , context ) ) ; } ) ; } function visitTypeParameters ( source , node , context ) { node . getTypeParameters ( ) . forEach ( typeParameter => { source . push ( emitter . emitTypeParameter ( typeParameter , context ) ) ; } ) ; } function visitMembers ( source , node , context ) { const members = node . getMembers ( ) ; for ( let x = 0 ; x < members . length ; x ++ ) { let member = members [ x ] ; source . push ( visit ( member , context ) ) ; addTrailingComment ( source , context . offset , node , context ) ; } } function visitIndexSignature ( node , context ) { const source = [ ] ; addLeadingComment ( source , node , context ) ; addWhitespace ( source , node , context ) ; if ( hasAccessModifiers ( node ) ) { visitModifiers ( source , node , context ) ; } else { if ( context . emitImplementation ) { source . push ( context . genOptions . interfaceAccessModifier , " " ) ; } } if ( node . compilerNode . modifiers ) { node . compilerNode . modifiers . forEach ( modifier => { source . push ( emitter . emitModifierable ( sast . createWrappedNode ( modifier ) , context ) ) ; } ) ; } context . offset = node . getKeyNameNode ( ) . getStart ( ) ; pushContext ( context ) ; source . push ( visitTypeNode ( node . getReturnTypeNode ( ) , context ) ) ; source . push ( " " ) ; swapContext ( context ) ; emitStatic ( source , 'this[' , node , context ) ; source . push ( visitTypeNode ( node . getKeyTypeNode ( ) , context ) ) ; source . push ( " " ) ; source . push ( emitter . emitParameterName ( node . getKeyNameNode ( ) as sast . Identifier , context ) ) ; emitStatic ( source , ']' , node , context ) ; if ( node . isReadonly ( ) ) { if ( context . emitImplementation ) { source . push ( " => throw new NotImplementedException();" ) ; } else source . push ( " { get; }" ) ; } else { if ( context . emitImplementation ) { source . push ( " { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }" ) ; } else source . push ( " { get; set; }" ) ; } popContext ( context ) ; endNode ( node , context ) ; addTrailingComment ( source , node , context ) ; return source . join ( '' ) ; } function visitHeritageClause ( node , context ) { const source = [ ] ; addLeadingComment ( source , node , context ) ; const clauseTypes = node . getTypeNodes ( ) ; const n = clauseTypes . length ; if ( n > 0 ) { for ( let t = 0 ; t < n ; t ++ ) { source . push ( emitter . emit ( clauseTypes [ t ] , context ) ) ; if ( ( t < n - 1 ) ) { source . push ( ", " ) ; } } ; } return source . join ( '' ) ; } function visitHeritageClauses ( source , node , context ) { const ancestors = node . getHeritageClauses ( ) ; const n = ancestors . length ; if ( ancestors . length > 0 ) { source . push ( " : " ) ; for ( let x = 0 ; x < n ; x ++ ) { const leaf = ancestors [ x ] ; source . push ( visit ( leaf , context ) ) ; if ( ( x < n - 1 ) ) { source . push ( ", " ) ; } } } } function visitInterfaceDeclaration ( node , context ) { const source = [ ] ; if ( isMap ( node ) ) { endNode ( node , context ) ; addTrailingComment ( source , node , context ) ; return source . join ( '' ) ; } addLeadingComment ( source , node , context ) ; addWhitespace ( source , node , context ) ; if ( node . getModifiers ( ) . length == 0 ) { if ( context . genOptions . interfaceAccessModifier && context . genOptions . interfaceAccessModifier . length > 0 ) { source . push ( context . genOptions . interfaceAccessModifier , " " ) ; } } else { visitModifiers ( source , node , context ) ; } source . push ( emitter . emit ( node . getFirstChildByKind ( SyntaxKind . InterfaceKeyword ) , context ) ) ; addWhitespace ( source , node , context ) ; source . push ( emitter . emitInterfaceName ( node . getNameNode ( ) , context ) ) ; emitter . emitTypeParameters ( source , node , context ) ; visitHeritageClauses ( source , node , context ) ; emitter . emitTypeConstraints ( source , node , context ) ; addTrailingComment ( source , context . offset , node , context ) ; source . push ( emitter . emit ( node . getFirstChildByKind ( SyntaxKind . FirstPunctuation ) , context ) ) ; addTrailingComment ( source , context . offset , node , context ) ; visitMembers ( source , node , context ) ; addLeadingComment ( source , context . offset , node , context ) ; source . push ( emitter . emit ( node . getLastToken ( ) , context ) ) ; endNode ( node , context ) ; addTrailingComment ( source , node , context ) ; return source . join ( '' ) ; } function visitTypeNode ( node , context ) { return emitter . emitTypeNode ( node , context ) ; } function isPropertyAnEventHandler ( node , context ) { if ( node . getName ( ) . length > 2 && node . getName ( ) . substr ( 0 , 2 ) . toLowerCase ( ) === "on" ) { const typeNode = node . getTypeNode ( ) ; if ( TypeGuards . isFunctionTypeNode ( typeNode ) && typeNode . getReturnTypeNode ( ) . getKind ( ) === SyntaxKind . AnyKeyword ) { const parameters = typeNode . getParameters ( ) ; if ( parameters && parameters . length === 2 ) { if ( parameters [ 0 ] . getName ( ) === "this" ) { const parm2Type = parameters [ 1 ] . getTypeNode ( ) ; if ( TypeGuards . isTypeReferenceNode ( parm2Type ) ) { return true ; } } } } } return false ; } function visitPropertySignature ( node , context ) { const source = [ ] ; if ( node . getNameNode ( ) . getKind ( ) === SyntaxKind . StringLiteral ) return "" ; addLeadingComment ( source , node , context ) ; if ( isPropertyAnEventHandler ( node , context ) ) { source . push ( emitter . emitDOMEventHandler ( node , context ) ) ; } else { source . push ( generateExportForProperty ( node , context ) ) ; if ( hasAccessModifiers ( node ) ) { visitModifiers ( source , node , context ) ; } else { if ( context . emitImplementation ) { source . push ( context . genOptions . interfaceAccessModifier , " " ) ; } } context . offset = node . getNameNode ( ) . getStart ( ) ; pushContext ( context ) ; source . push ( visitTypeNode ( node . getTypeNode ( ) , context ) ) ; source . push ( " " ) ; swapContext ( context ) ; source . push ( emitter . emitPropertyName ( node . getNameNode ( ) , context ) ) ; source . push ( emitter . emitPropertyBody ( node , context ) ) ; popContext ( context ) ; } endNode ( node , context ) ; addTrailingComment ( source , node , context ) ; return source . join ( '' ) ; } function visitParameter ( source , node , context ) { context . offset = node . getStart ( ) ; pushContext ( context ) ; if ( node . isRestParameter ( ) ) { source . push ( emitter . emitRestParameter ( node . getTypeNode ( ) , context ) ) ; } else { source . push ( visitTypeNode ( node . getTypeNode ( ) , context ) ) ; } source . push ( " " ) ; swapContext ( context ) ; source . push ( emitter . emitParameterName ( node . getNameNode ( ) , context ) ) ; popContext ( context ) ; } function visitParameters ( source , node , context ) { const parmList = node . getParameters ( ) ; let n = node . getParameters ( ) . length ; if ( n > 0 ) { for ( let p = 0 ; p < n ; p ++ ) { visitParameter ( source , parmList [ p ] , context ) ; if ( p < n - 1 ) { source . push ( ", " ) ; } } } } function visitMethodSignature ( node , context ) { const source = [ ] ; const types = node . getTypeParameters ( ) ; if ( types && types . length > 0 ) { const keyof = types [ 0 ] ; if ( TypeGuards . isTypeParameterDeclaration ( keyof ) ) { endNode ( node , context ) ; return source . join ( '' ) ; } } addLeadingComment ( source , node , context ) ; source . push ( generateExportForMethod ( node , context ) ) ; if ( context . emitImplementation ) { source . push ( context . genOptions . interfaceAccessModifier , " " ) ; } context . offset = node . getNameNode ( ) . getStart ( ) ; pushContext ( context ) ; source . push ( visitTypeNode ( node . getReturnTypeNode ( ) , context ) ) ; source . push ( " " ) ; swapContext ( context ) ; visitTypeParameters ( source , node , context ) ; source . push ( emitter . emitMethodName ( node . getNameNode ( ) , context ) ) ; emitStatic ( source , '(' , node , context ) ; visitParameters ( source , node , context ) ; emitStatic ( source , ')' , node , context ) ; popContext ( context ) ; source . push ( emitter . emitMethodBody ( node , context ) ) ; endNode ( node , context ) ; addTrailingComment ( source , context . offset , node , context ) ; return source . join ( '' ) ; } function visitVariableStatement ( node , context ) { const source = [ ] ; addLeadingComment ( source , node , context ) ; visitVariableDeclarationList ( source , node . getDeclarationList ( ) , context ) ; endNode ( node , context ) ; addTrailingComment ( source , node , context ) ; return source . join ( '' ) ; } function visitVariableDeclarationList ( source , node , context ) { switch ( node . getDeclarationKindKeyword ( ) . getKind ( ) ) { case SyntaxKind . VarKeyword : visitVariableDeclarations ( source , node , context ) ; break ; default : context . diagnostics . pushErrorAtLoc ( "Declaration type " + node . getDeclarationKindKeyword ( ) . getKindName ( ) + " is not yet supported" , node ) ; } } function visitVariableDeclarations ( source , node , context ) { const declarations = node . getDeclarations ( ) ; for ( let x = 0 ; x < declarations . length ; x ++ ) { let declaration = declarations [ x ] ; source . push ( visit ( declaration , context ) ) ; addTrailingComment ( source , context . offset , node , context ) ; } } function visitVariableDeclaration ( node , context ) { const source = [ ] ; addLeadingComment ( source , node , context ) ; if ( isDeclarationOfInterface ( node ) ) { source . push ( visitDeclarationOfInterface ( node , context ) ) ; } else { context . diagnostics . pushWarningAtLoc ( "Variable Declarations other than Interfaces are not supported" , node ) ; } endNode ( node , context ) ; addTrailingComment ( source , node , context ) ; return source . join ( '' ) ; } function visitDeclarationOfInterface ( node , context ) { const source = [ ] ; addLeadingComment ( source , node , context ) ; source . push ( generateExportForInterfaceDeclaration ( node , context ) ) ; emitter . emitClassDefinitionOfInterfaceDeclaration ( source , node , context , context . genOptions . isCaseChange && context . genOptions . isCaseChangeClasses ) ; source . push ( visit ( node . getTypeNode ( ) , context ) ) ; return source . join ( '' ) ; } function visitTypeLiteral ( node , context ) { const source = [ ] ; addLeadingComment ( source , node , context ) ; addWhitespace ( source , node , context ) ; source . push ( emitter . emit ( node . getFirstChildByKind ( SyntaxKind . FirstPunctuation ) , context ) ) ; addTrailingComment ( source , node . getFirstChildByKind ( SyntaxKind . FirstPunctuation ) , context ) ; addWhitespace ( source , node , context ) ; pushContext ( context ) ; emitter . emitClassConstructor ( source , node , context ) ; swapContext ( context ) ; visitConstructors ( source , node , context ) ; addTrailingComment ( source , context . offset , node , context ) ; context . emitImplementation = true ; const propertiesBag = new Map < string , sast . PropertySignature > ( ) ; const propertySignatures = node . getDescendantsOfKind ( SyntaxKind . PropertySignature ) ; let prototypeDefinition = null ; for ( let x = 0 ; x < propertySignatures . length ; x ++ ) { let property = propertySignatures [ x ] ; if ( property . getName ( ) === "prototype" ) { const propertyTypeNode = property . getTypeNode ( ) ; if ( TypeGuards . isTypeReferenceNode ( propertyTypeNode ) ) { const interfaceName = propertyTypeNode . getText ( ) ; prototypeDefinition = propertyTypeNode . getSourceFile ( ) . getInterface ( interfaceName ) ; } } else { propertiesBag . set ( property . getName ( ) , property ) ; } } if ( prototypeDefinition ) { loadInterfaceProperties ( propertiesBag , prototypeDefinition ) ; } for ( const property of propertiesBag . values ( ) ) { pushContext ( context ) ; context . offset = property . getPos ( ) ; source . push ( visit ( property , context ) ) ; addTrailingComment ( source , context . offset , node , context ) ; popContext ( context ) ; } const methods = node . getDescendantsOfKind ( SyntaxKind . MethodSignature ) ; const methodsBag = new Map < string , sast . MethodSignature > ( ) ; for ( let x = 0 ; x < methods . length ; x ++ ) { let method = methods [ x ] ; methodsBag . set ( method . getName ( ) , method ) ; } if ( prototypeDefinition ) { loadInterfaceMethods ( methodsBag , prototypeDefinition ) ; } for ( const method of methodsBag . values ( ) ) { pushContext ( context ) ; context . offset = method . getPos ( ) ; source . push ( visit ( method , context ) ) ; addTrailingComment ( source , context . offset , node , context ) ; popContext ( context ) ; } const indexers = node . getDescendantsOfKind ( SyntaxKind . IndexSignature ) ; const indexersBag = new Map < string , sast . IndexSignatureDeclaration > ( ) ; for ( let x = 0 ; x < indexers . length ; x ++ ) { let indexer = indexers [ x ] ; indexersBag . set ( "this[]" , indexer ) ; } if ( prototypeDefinition ) { loadInterfaceIndexers ( indexersBag , prototypeDefinition ) ; } for ( const indexer of indexersBag . values ( ) ) { pushContext ( context ) ; context . offset = indexer . getPos ( ) ; source . push ( visit ( indexer , context ) ) ; addTrailingComment ( source , context . offset , node , context ) ; popContext ( context ) ; } context . emitImplementation = false ; source . push ( emitter . emit ( node . getFirstChildByKind ( SyntaxKind . CloseBraceToken ) , context ) ) ; endNode ( node , context ) ; return source . join ( '' ) ; } function visitConstructors ( source , node , context ) { const constructors = node . getConstructSignatures ( ) ; for ( let c = 0 ; c < constructors . length ; c ++ ) { source . push ( visit ( constructors [ c ] , context ) ) ; } } function visitConstructSignature ( node , context ) { const source = [ ] ; context . offset = node . getPos ( ) ; addLeadingComment ( source , node . getPos ( ) , node , context ) ; addWhitespace ( source , node , context ) ; const parameters = node . getTypeParameters ( ) ; pushContext ( context ) ; source . push ( "public" ) ; const savePrefixInterface = context . genOptions . isPrefixInterface ; const saveCaseChangeInterfaces = context . genOptions . isCaseChangeInterfaces ; context . genOptions . isPrefixInterface = false ; context . genOptions . isCaseChangeInterfaces = false ; addWhitespace ( source , node . getReturnTypeNode ( ) , context ) ; source . push ( visitTypeNode ( node . getReturnTypeNode ( ) , context ) ) ; context . genOptions . isCaseChangeInterfaces = saveCaseChangeInterfaces ; context . genOptions . isPrefixInterface = savePrefixInterface ; source . push ( " " ) ; swapContext ( context ) ; visitTypeParameters ( source , node , context ) ; emitStatic ( source , '(' , node , context ) ; visitParameters ( source , node , context ) ; emitStatic ( source , ')' , node , context ) ; popContext ( context ) ; source . push ( " { }" ) ; endNode ( node , context ) ; addTrailingComment ( source , context . offset , node , context ) ; return source . join ( '' ) ; } function visitTypeAliasDeclaration ( node , context ) { const source = [ ] ; context . diagnostics . pushWarningAtLoc ( "Nodes of type TypeAliasDeclaration are not supported at this time " , node ) ; return source . join ( '' ) ; } function visitCallSignature ( node , context ) { const source = [ ] ; context . offset = node . getPos ( ) ; addLeadingComment ( source , node . getPos ( ) , node , context ) ; addWhitespace ( source , node , context ) ; addSemicolon ( source , node , context ) ; endNode ( node , context ) ; addTrailingComment ( source , context . offset , node , context ) ; return source . join ( '' ) ; } const visitor = { [ SyntaxKind . SourceFile ] : TsToCSharpGenerator , [ SyntaxKind . PropertySignature ] : visitPropertySignature , [ SyntaxKind . MethodSignature ] : visitMethodSignature , [ SyntaxKind . HeritageClause ] : visitHeritageClause , [ SyntaxKind . IndexSignature ] : visitIndexSignature , [ SyntaxKind . VariableDeclaration ] : visitVariableDeclaration , [ SyntaxKind . ConstructSignature ] : visitConstructSignature , [ SyntaxKind . TypeLiteral ] : visitTypeLiteral , [ SyntaxKind . CallSignature ] : visitCallSignature , } ;