import * as ts from "../lib/typescript" ; import * as path from "path" ; function endsWith ( s , suffix ) { return s . lastIndexOf ( suffix , s . length - suffix . length ) !== - 1 ; } function isStringEnum ( declaration ) { return declaration . members . length && declaration . members . every ( m => ! ! m . initializer && m . initializer . kind === ts . SyntaxKind . StringLiteral ) ; } class DeclarationsWalker { private visitedTypes : ts . Type [ ] = [ ] ; private text = "" ; private removedTypes : ts . Type [ ] = [ ] ; private constructor ( private typeChecker , private protocolFile ) { } static getExtraDeclarations ( typeChecker , protocolFile ) { const walker = new DeclarationsWalker ( typeChecker , protocolFile ) ; walker . visitTypeNodes ( protocolFile ) ; let text = walker . text ? `declare namespace ts.server.protocol {\n${ walker . text }}` : "" ; if ( walker . removedTypes ) { text += "\ndeclare namespace ts {\n" ; text += "    // these types are empty stubs for types from services and should not be used directly\n" ; for ( const type of walker . removedTypes ) { text += `    export type ${ type . symbol . name } = never;\n` ; } text += "}" ; } return text ; } private processType ( type ) { if ( this . visitedTypes . indexOf ( type ) >= 0 ) { return ; } this . visitedTypes . push ( type ) ; const s = type . aliasSymbol || type . getSymbol ( ) ; if ( ! s ) { return ; } if ( s . name === "Array" || s . name === "ReadOnlyArray" ) { return this . processType ( ( type as any ) . typeArguments [ 0 ] ) ; } else { const declarations = s . getDeclarations ( ) ; if ( declarations ) { for ( const decl of declarations ) { const sourceFile = decl . getSourceFile ( ) ; if ( sourceFile === this . protocolFile || /lib(\..+)?\.d.ts/ . test ( path . basename ( sourceFile . fileName ) ) ) { return ; } if ( decl . kind === ts . SyntaxKind . EnumDeclaration && ! isStringEnum ( decl as ts . EnumDeclaration ) ) { this . removedTypes . push ( type ) ; return ; } else { const text = decl . getFullText ( ) ; this . text += `${ text }\n` ; this . visitTypeNodes ( decl ) ; } } } } } private visitTypeNodes ( node ) { if ( node . parent ) { switch ( node . parent . kind ) { case ts . SyntaxKind . VariableDeclaration : case ts . SyntaxKind . MethodDeclaration : case ts . SyntaxKind . MethodSignature : case ts . SyntaxKind . PropertyDeclaration : case ts . SyntaxKind . PropertySignature : case ts . SyntaxKind . Parameter : case ts . SyntaxKind . IndexSignature : if ( ( ( node . parent as ts . VariableDeclaration | ts . MethodDeclaration | ts . PropertyDeclaration | ts . ParameterDeclaration | ts . PropertySignature | ts . MethodSignature | ts . IndexSignatureDeclaration ) . type ) === node ) { this . processTypeOfNode ( node ) ; } break ; case ts . SyntaxKind . InterfaceDeclaration : const heritageClauses = ( node . parent as ts . InterfaceDeclaration ) . heritageClauses ; if ( heritageClauses ) { if ( heritageClauses [ 0 ] . token !== ts . SyntaxKind . ExtendsKeyword ) { throw new Error ( `Unexpected kind of heritage clause: ${ ts . SyntaxKind [ heritageClauses [ 0 ] . kind ] }` ) ; } for ( const type of heritageClauses [ 0 ] . types ) { this . processTypeOfNode ( type ) ; } } break ; } } ts . forEachChild ( node , n => this . visitTypeNodes ( n ) ) ; } private processTypeOfNode ( node ) { if ( node . kind === ts . SyntaxKind . UnionType ) { for ( const t of ( node as ts . UnionTypeNode ) . types ) { this . processTypeOfNode ( t ) ; } } else { const type = this . typeChecker . getTypeAtLocation ( node ) ; if ( type && ! ( type . flags & ( ts . TypeFlags . TypeParameter ) ) ) { this . processType ( type ) ; } } } } function writeProtocolFile ( outputFile , protocolTs , typeScriptServicesDts ) { const options = { target : ts . ScriptTarget . ES5 , declaration : true , noResolve : false , types : [ ] as string [ ] , stripInternal : true } ; return function getInitialDtsFileForProtocol ( ) { const program = ts . createProgram ( [ protocolTs , typeScriptServicesDts , path . join ( typeScriptServicesDts , "../lib.es5.d.ts" ) ] , options ) ; let protocolDts ; const emitResult = program . emit ( program . getSourceFile ( protocolTs ) , ( file , content ) => { if ( endsWith ( file , ".d.ts" ) ) { protocolDts = content ; } } ) ; if ( protocolDts === undefined ) { const diagHost = { getCanonicalFileName ( f ) { return f ; } , getCurrentDirectory ( ) { return "." ; } , getNewLine ( ) { return "\r\n" ; } } ; const diags = emitResult . diagnostics . map ( d => ts . formatDiagnostic ( d , diagHost ) ) . join ( "\r\n" ) ; throw new Error ( `Declaration file for protocol.ts is not generated:\r\n${ diags }` ) ; } return protocolDts ; } const protocolFileName = "protocol.d.ts" ; /**
     * Second pass - generate a program from protocol.d.ts and typescriptservices.d.ts, then augment core protocol.d.ts with extra types from typescriptservices.d.ts
     */ function getProgramWithProtocolText ( protocolDts , includeTypeScriptServices ) { const host = ts . createCompilerHost ( options ) ; const originalGetSourceFile = host . getSourceFile ; host . getSourceFile = ( fileName ) => { if ( fileName === protocolFileName ) { return ts . createSourceFile ( fileName , protocolDts , options . target ) ; } return originalGetSourceFile . apply ( host , [ fileName ] ) ; } ; const rootFiles = includeTypeScriptServices ? [ protocolFileName , typeScriptServicesDts ] : [ protocolFileName ] ; return ts . createProgram ( rootFiles , options , host ) ; } let protocolDts = getInitialDtsFileForProtocol ( ) ; const program = getProgramWithProtocolText ( protocolDts , true ) ; const protocolFile = program . getSourceFile ( "protocol.d.ts" ) ! ; const extraDeclarations = DeclarationsWalker . getExtraDeclarations ( program . getTypeChecker ( ) , protocolFile ) ; if ( extraDeclarations ) { protocolDts += extraDeclarations ; } protocolDts += "\nimport protocol = ts.server.protocol;" ; protocolDts += "\nexport = protocol;" ; protocolDts += "\nexport as namespace protocol;" ; const sanityCheckProgram = getProgramWithProtocolText ( protocolDts , false ) ; const diagnostics = [ ... sanityCheckProgram . getSyntacticDiagnostics ( ) , ... sanityCheckProgram . getSemanticDiagnostics ( ) , ... sanityCheckProgram . getGlobalDiagnostics ( ) ] ; ts . sys . writeFile ( outputFile , protocolDts ) ; if ( diagnostics . length ) { const flattenedDiagnostics = diagnostics . map ( d => `${ ts . flattenDiagnosticMessageText ( d . messageText , "\n" ) } at ${ d . file ? d . file . fileName : "<unknown>" } line ${ d . start }` ) . join ( "\n" ) ; throw new Error ( `Unexpected errors during sanity check: ${ flattenedDiagnostics }` ) ; } } if ( process . argv . length < 5 ) { console . log ( `Expected 3 arguments: path to 'protocol.ts', path to 'typescriptservices.d.ts' and path to output file` ) ; process . exit ( 1 ) ; } const protocolTs = process . argv [ 2 ] ; const typeScriptServicesDts = process . argv [ 3 ] ; const outputFile = process . argv [ 4 ] ; writeProtocolFile ( outputFile , protocolTs , typeScriptServicesDts ) ;