2016-10-12 20:02:56 +02:00
/// <reference types="node"/>
import * as ts from "../lib/typescript" ;
import * as path from "path" ;
function endsWith ( s : string , suffix : string ) {
return s . lastIndexOf ( suffix , s . length - suffix . length ) !== - 1 ;
}
2017-08-21 18:44:03 +02:00
function isStringEnum ( declaration : ts.EnumDeclaration ) {
return declaration . members . length && declaration . members . every ( m = > m . initializer && m . initializer . kind === ts . SyntaxKind . StringLiteral ) ;
}
2016-10-12 20:02:56 +02:00
class DeclarationsWalker {
private visitedTypes : ts.Type [ ] = [ ] ;
private text = "" ;
2016-10-18 00:46:05 +02:00
private removedTypes : ts.Type [ ] = [ ] ;
2017-08-21 18:44:03 +02:00
2016-10-12 20:02:56 +02:00
private constructor ( private typeChecker : ts.TypeChecker , private protocolFile : ts.SourceFile ) {
}
static getExtraDeclarations ( typeChecker : ts.TypeChecker , protocolFile : ts.SourceFile ) : string {
let text = "declare namespace ts.server.protocol {\n" ;
var walker = new DeclarationsWalker ( typeChecker , protocolFile ) ;
walker . visitTypeNodes ( protocolFile ) ;
2017-08-21 18:44:03 +02:00
text = walker . text
2016-10-12 20:02:56 +02:00
? ` declare namespace ts.server.protocol { \ n ${ walker . text } } `
: "" ;
2016-10-18 00:46:05 +02:00
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 ;
2016-10-12 20:02:56 +02:00
}
private processType ( type : ts . Type ) : void {
if ( this . visitedTypes . indexOf ( type ) >= 0 ) {
return ;
}
this . visitedTypes . push ( type ) ;
let s = type . aliasSymbol || type . getSymbol ( ) ;
if ( ! s ) {
return ;
}
if ( s . name === "Array" ) {
// we should process type argument instead
return this . processType ( ( < any > type ) . typeArguments [ 0 ] ) ;
}
else {
2017-10-18 00:04:09 +02:00
const declarations = s . getDeclarations ( ) ;
if ( declarations ) {
for ( const decl of declarations ) {
const sourceFile = decl . getSourceFile ( ) ;
if ( sourceFile === this . protocolFile || path . basename ( sourceFile . fileName ) === "lib.d.ts" ) {
return ;
}
if ( decl . kind === ts . SyntaxKind . EnumDeclaration && ! isStringEnum ( decl as ts . EnumDeclaration ) ) {
this . removedTypes . push ( type ) ;
return ;
}
else {
// splice declaration in final d.ts file
let text = decl . getFullText ( ) ;
this . text += ` ${ text } \ n ` ;
// recursively pull all dependencies into result dts file
2016-10-12 20:02:56 +02:00
2017-10-18 00:04:09 +02:00
this . visitTypeNodes ( decl ) ;
}
2016-10-18 00:46:05 +02:00
}
2016-10-12 20:02:56 +02:00
}
}
}
private visitTypeNodes ( node : ts.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 ( ( ( < ts.VariableDeclaration | ts.MethodDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration | ts.PropertySignature | ts.MethodSignature | ts.IndexSignatureDeclaration > node . parent ) . type ) === node ) {
2016-10-18 00:46:05 +02:00
this . processTypeOfNode ( node ) ;
2016-10-12 20:02:56 +02:00
}
break ;
2016-10-18 00:46:05 +02:00
case ts . SyntaxKind . InterfaceDeclaration :
const heritageClauses = ( < ts.InterfaceDeclaration > node . parent ) . 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 ) ;
}
2017-08-21 18:44:03 +02:00
}
2016-10-18 00:46:05 +02:00
break ;
2016-10-12 20:02:56 +02:00
}
}
ts . forEachChild ( node , n = > this . visitTypeNodes ( n ) ) ;
2016-10-18 00:46:05 +02:00
}
private processTypeOfNode ( node : ts.Node ) : void {
if ( node . kind === ts . SyntaxKind . UnionType ) {
for ( const t of ( < ts.UnionTypeNode > node ) . types ) {
this . processTypeOfNode ( t ) ;
}
}
else {
const type = this . typeChecker . getTypeAtLocation ( node ) ;
if ( type && ! ( type . flags & ( ts . TypeFlags . TypeParameter ) ) ) {
this . processType ( type ) ;
}
}
2017-08-21 18:44:03 +02:00
}
2016-10-12 20:02:56 +02:00
}
2017-06-07 02:43:30 +02:00
function writeProtocolFile ( outputFile : string , protocolTs : string , typeScriptServicesDts : string ) {
2016-10-12 20:02:56 +02:00
const options = { target : ts.ScriptTarget.ES5 , declaration : true , noResolve : true , types : < string [ ] > [ ] , stripInternal : true } ;
/ * *
* 1 st pass - generate a program from protocol . ts and typescriptservices . d . ts and emit core version of protocol . d . ts with all internal members stripped
* @return text of protocol . d . t . s
* /
function getInitialDtsFileForProtocol() {
const program = ts . createProgram ( [ protocolTs , typeScriptServicesDts ] , options ) ;
let protocolDts : string ;
program . emit ( program . getSourceFile ( protocolTs ) , ( file , content ) = > {
if ( endsWith ( file , ".d.ts" ) ) {
protocolDts = content ;
}
} ) ;
if ( protocolDts === undefined ) {
throw new Error ( ` Declaration file for protocol.ts is not generated ` )
}
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 : string , includeTypeScriptServices : boolean ) {
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 , /*includeTypeScriptServices*/ true ) ;
const protocolFile = program . getSourceFile ( "protocol.d.ts" ) ;
const extraDeclarations = DeclarationsWalker . getExtraDeclarations ( program . getTypeChecker ( ) , protocolFile ) ;
if ( extraDeclarations ) {
protocolDts += extraDeclarations ;
}
2016-10-18 00:46:05 +02:00
protocolDts += "\nimport protocol = ts.server.protocol;" ;
protocolDts += "\nexport = protocol;" ;
protocolDts += "\nexport as namespace protocol;" ;
2017-06-06 23:58:18 +02:00
2016-10-12 20:02:56 +02:00
// do sanity check and try to compile generated text as standalone program
const sanityCheckProgram = getProgramWithProtocolText ( protocolDts , /*includeTypeScriptServices*/ false ) ;
2016-10-18 00:46:05 +02:00
const diagnostics = [ . . . sanityCheckProgram . getSyntacticDiagnostics ( ) , . . . sanityCheckProgram . getSemanticDiagnostics ( ) , . . . sanityCheckProgram . getGlobalDiagnostics ( ) ] ;
2017-06-06 23:58:18 +02:00
ts . sys . writeFile ( outputFile , protocolDts ) ;
2016-10-12 20:02:56 +02:00
if ( diagnostics . length ) {
2017-02-14 22:35:16 +01:00
const flattenedDiagnostics = diagnostics . map ( d = > ` ${ ts . flattenDiagnosticMessageText ( d . messageText , "\n" ) } at ${ d . file . fileName } line ${ d . start } ` ) . join ( "\n" ) ;
2016-10-12 20:02:56 +02:00
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 ] ;
2017-06-07 02:43:30 +02:00
writeProtocolFile ( outputFile , protocolTs , typeScriptServicesDts ) ;