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 ) {
2018-05-22 23:46:57 +02:00
return declaration . members . length && declaration . members . every ( m = > ! ! m . initializer && m . initializer . kind === ts . SyntaxKind . StringLiteral ) ;
2017-08-21 18:44:03 +02:00
}
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 {
2019-06-20 09:51:29 +02:00
const walker = new DeclarationsWalker ( typeChecker , protocolFile ) ;
2016-10-12 20:02:56 +02:00
let text = "declare namespace ts.server.protocol {\n" ;
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" ;
2019-06-14 08:46:02 +02:00
text += " // these types are empty stubs for types from services and should not be used directly\n" ;
2016-10-18 00:46:05 +02:00
for ( const type of walker . removedTypes ) {
2019-06-19 17:45:02 +02:00
text += ` export type ${ type . symbol . name } = never; \ n ` ;
2016-10-18 00:46:05 +02:00
}
2019-06-14 08:46:02 +02:00
text += "}" ;
2016-10-18 00:46:05 +02:00
}
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 ) ;
2019-06-19 16:58:49 +02:00
const s = type . aliasSymbol || type . getSymbol ( ) ;
2016-10-12 20:02:56 +02:00
if ( ! s ) {
return ;
}
2018-06-11 04:28:38 +02:00
if ( s . name === "Array" || s . name === "ReadOnlyArray" ) {
2016-10-12 20:02:56 +02:00
// 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 ( ) ;
2018-06-13 19:43:48 +02:00
if ( sourceFile === this . protocolFile || /lib(\..+)?\.d.ts/ . test ( path . basename ( sourceFile . fileName ) ) ) {
2017-10-18 00:04:09 +02:00
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
2019-06-19 16:58:49 +02:00
const text = decl . getFullText ( ) ;
2017-10-18 00:04:09 +02:00
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 ) {
2018-06-13 19:43:48 +02:00
const options = { target : ts.ScriptTarget.ES5 , declaration : true , noResolve : false , types : < string [ ] > [ ] , stripInternal : true } ;
2016-10-12 20:02:56 +02:00
/ * *
* 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() {
2018-06-13 19:43:48 +02:00
const program = ts . createProgram ( [ protocolTs , typeScriptServicesDts , path . join ( typeScriptServicesDts , "../lib.es5.d.ts" ) ] , options ) ;
2016-10-12 20:02:56 +02:00
2018-05-22 23:46:57 +02:00
let protocolDts : string | undefined ;
2018-06-11 04:28:38 +02:00
const emitResult = program . emit ( program . getSourceFile ( protocolTs ) , ( file , content ) = > {
2016-10-12 20:02:56 +02:00
if ( endsWith ( file , ".d.ts" ) ) {
protocolDts = content ;
}
} ) ;
2018-06-11 04:28:38 +02:00
2016-10-12 20:02:56 +02:00
if ( protocolDts === undefined ) {
2018-06-11 04:28:38 +02:00
const diagHost : ts.FormatDiagnosticsHost = {
2019-06-14 09:38:45 +02:00
getCanonicalFileName ( f ) { return f ; } ,
2019-06-14 11:18:27 +02:00
getCurrentDirectory() { return "." ; } ,
2019-06-14 09:38:45 +02:00
getNewLine() { return "\r\n" ; }
2019-06-14 08:46:02 +02:00
} ;
2018-06-11 04:28:38 +02:00
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 } ` ) ;
2016-10-12 20:02:56 +02:00
}
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 ] ) ;
2019-06-14 08:46:02 +02:00
} ;
2016-10-12 20:02:56 +02:00
const rootFiles = includeTypeScriptServices ? [ protocolFileName , typeScriptServicesDts ] : [ protocolFileName ] ;
return ts . createProgram ( rootFiles , options , host ) ;
}
let protocolDts = getInitialDtsFileForProtocol ( ) ;
const program = getProgramWithProtocolText ( protocolDts , /*includeTypeScriptServices*/ true ) ;
2018-05-22 23:46:57 +02:00
const protocolFile = program . getSourceFile ( "protocol.d.ts" ) ! ;
2016-10-12 20:02:56 +02:00
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 ) {
2018-04-25 22:11:35 +02:00
const flattenedDiagnostics = diagnostics . map ( d = > ` ${ ts . flattenDiagnosticMessageText ( d . messageText , "\n" ) } at ${ d . file ? d . file . fileName : "<unknown>" } 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 ) ;