import fs = require ( 'fs' ) ; import ts = require ( 'typescript' ) ; import path = require ( 'path' ) ; var util = require ( 'gulp-util' ) ; function log ( message , ... rest ) { util . log ( util . colors . cyan ( '[monaco.d.ts]' ) , message , ... rest ) ; } const SRC = path . join ( __dirname , '../../src' ) ; const OUT_ROOT = path . join ( __dirname , '../../' ) ; const RECIPE_PATH = path . join ( __dirname , './monaco.d.ts.recipe' ) ; const DECLARATION_PATH = path . join ( __dirname , '../../src/vs/monaco.d.ts' ) ; var CURRENT_PROCESSING_RULE = '' ; function logErr ( message , ... rest ) { util . log ( util . colors . red ( '[monaco.d.ts]' ) , 'WHILE HANDLING RULE: ' , CURRENT_PROCESSING_RULE ) ; util . log ( util . colors . red ( '[monaco.d.ts]' ) , message , ... rest ) ; } function moduleIdToPath ( out , moduleId ) { if ( /\.d\.ts/ . test ( moduleId ) ) { return path . join ( SRC , moduleId ) ; } return path . join ( OUT_ROOT , out , moduleId ) + '.d.ts' ; } let SOURCE_FILE_MAP = { } ; function getSourceFile ( out , moduleId ) { if ( ! SOURCE_FILE_MAP [ moduleId ] ) { let filePath = moduleIdToPath ( out , moduleId ) ; let fileContents ; try { fileContents = fs . readFileSync ( filePath ) . toString ( ) ; } catch ( err ) { logErr ( 'CANNOT FIND FILE ' + filePath ) ; return null ; } let sourceFile = ts . createSourceFile ( filePath , fileContents , ts . ScriptTarget . ES5 ) ; SOURCE_FILE_MAP [ moduleId ] = sourceFile ; } return SOURCE_FILE_MAP [ moduleId ] ; } type TSTopLevelDeclaration = ts . InterfaceDeclaration | ts . EnumDeclaration | ts . ClassDeclaration | ts . TypeAliasDeclaration | ts . FunctionDeclaration | ts . ModuleDeclaration ; type TSTopLevelDeclare = TSTopLevelDeclaration | ts . VariableStatement ; function isDeclaration ( a ) { return ( a . kind === ts . SyntaxKind . InterfaceDeclaration || a . kind === ts . SyntaxKind . EnumDeclaration || a . kind === ts . SyntaxKind . ClassDeclaration || a . kind === ts . SyntaxKind . TypeAliasDeclaration || a . kind === ts . SyntaxKind . FunctionDeclaration || a . kind === ts . SyntaxKind . ModuleDeclaration ) ; } function visitTopLevelDeclarations ( sourceFile , visitor ) { let stop = false ; let visit = ( node ) : void => { if ( stop ) { return ; } switch ( node . kind ) { case ts . SyntaxKind . InterfaceDeclaration : case ts . SyntaxKind . EnumDeclaration : case ts . SyntaxKind . ClassDeclaration : case ts . SyntaxKind . VariableStatement : case ts . SyntaxKind . TypeAliasDeclaration : case ts . SyntaxKind . FunctionDeclaration : case ts . SyntaxKind . ModuleDeclaration : stop = visitor ( < TSTopLevelDeclare > node ) ; } if ( stop ) { return ; } ts . forEachChild ( node , visit ) ; } ; visit ( sourceFile ) ; } function getAllTopLevelDeclarations ( sourceFile ) { let all = [ ] ; visitTopLevelDeclarations ( sourceFile , ( node ) => { if ( node . kind === ts . SyntaxKind . InterfaceDeclaration || node . kind === ts . SyntaxKind . ClassDeclaration || node . kind === ts . SyntaxKind . ModuleDeclaration ) { let interfaceDeclaration = < ts . InterfaceDeclaration > node ; let triviaStart = interfaceDeclaration . pos ; let triviaEnd = interfaceDeclaration . name . pos ; let triviaText = getNodeText ( sourceFile , { pos : triviaStart , end : triviaEnd } ) ; if ( triviaText . indexOf ( '@internal' ) === - 1 ) { all . push ( node ) ; } } else { let nodeText = getNodeText ( sourceFile , node ) ; if ( nodeText . indexOf ( '@internal' ) === - 1 ) { all . push ( node ) ; } } return false ; } ) ; return all ; } function getTopLevelDeclaration ( sourceFile , typeName ) { let result = null ; visitTopLevelDeclarations ( sourceFile , ( node ) => { if ( isDeclaration ( node ) ) { if ( node . name . text === typeName ) { result = node ; return true ; } return false ; } if ( getNodeText ( sourceFile , node ) . indexOf ( typeName ) >= 0 ) { result = node ; return true ; } return false ; } ) ; return result ; } function getNodeText ( sourceFile , node ) { return sourceFile . getFullText ( ) . substring ( node . pos , node . end ) ; } function getMassagedTopLevelDeclarationText ( sourceFile , declaration ) { let result = getNodeText ( sourceFile , declaration ) ; if ( declaration . kind === ts . SyntaxKind . InterfaceDeclaration || declaration . kind === ts . SyntaxKind . ClassDeclaration ) { let interfaceDeclaration = < ts . InterfaceDeclaration | ts . ClassDeclaration > declaration ; let members = interfaceDeclaration . members ; members . forEach ( ( member ) => { try { let memberText = getNodeText ( sourceFile , member ) ; if ( memberText . indexOf ( '@internal' ) >= 0 || memberText . indexOf ( 'private' ) >= 0 ) { result = result . replace ( memberText , '' ) ; } } catch ( err ) { } } ) ; } result = result . replace ( /export default/g , 'export' ) ; result = result . replace ( /export declare/g , 'export' ) ; return result ; } function format ( text ) { let options = getDefaultOptions ( ) ; let sourceFile = ts . createSourceFile ( 'file.ts' , text , ts . ScriptTarget . Latest , true ) ; let edits = ( < any > ts ) . formatting . formatDocument ( sourceFile , getRuleProvider ( options ) , options ) ; return applyEdits ( text , edits ) ; function getRuleProvider ( options ) { let ruleProvider = new ( < any > ts ) . formatting . RulesProvider ( ) ; ruleProvider . ensureUpToDate ( options ) ; return ruleProvider ; } function applyEdits ( text , edits ) { let result = text ; for ( let i = edits . length - 1 ; i >= 0 ; i -- ) { let change = edits [ i ] ; let head = result . slice ( 0 , change . span . start ) ; let tail = result . slice ( change . span . start + change . span . length ) ; result = head + change . newText + tail ; } return result ; } function getDefaultOptions ( ) { return { IndentSize : 4 , TabSize : 4 , NewLineCharacter : '\r\n' , ConvertTabsToSpaces : true , IndentStyle : ts . IndentStyle . Block , InsertSpaceAfterCommaDelimiter : true , InsertSpaceAfterSemicolonInForStatements : true , InsertSpaceBeforeAndAfterBinaryOperators : true , InsertSpaceAfterKeywordsInControlFlowStatements : true , InsertSpaceAfterFunctionKeywordForAnonymousFunctions : false , InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis : false , InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets : false , InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces : true , PlaceOpenBraceOnNewLineForFunctions : false , PlaceOpenBraceOnNewLineForControlBlocks : false , } ; } } function createReplacer ( data ) { data = data || '' ; let rawDirectives = data . split ( ';' ) ; let directives = [ ] ; rawDirectives . forEach ( ( rawDirective ) => { if ( rawDirective . length === 0 ) { return ; } let pieces = rawDirective . split ( '=>' ) ; let findStr = pieces [ 0 ] ; let replaceStr = pieces [ 1 ] ; findStr = findStr . replace ( /[\-\\\{\}\*\+\?\|\^\$\.\,\[\]\(\)\#\s]/g , '\\$&' ) ; findStr = '\\b' + findStr + '\\b' ; directives . push ( [ new RegExp ( findStr , 'g' ) , replaceStr ] ) ; } ) ; return ( str ) => { for ( let i = 0 ; i < directives . length ; i ++ ) { str = str . replace ( directives [ i ] [ 0 ] , directives [ i ] [ 1 ] ) ; } return str ; } ; } function generateDeclarationFile ( out , recipe ) { let lines = recipe . split ( /\r\n|\n|\r/ ) ; let result = [ ] ; lines . forEach ( line => { let m1 = line . match ( /^\s*#include\(([^;)]*)(;[^)]*)?\)\:(.*)$/ ) ; if ( m1 ) { CURRENT_PROCESSING_RULE = line ; let moduleId = m1 [ 1 ] ; let sourceFile = getSourceFile ( out , moduleId ) ; if ( ! sourceFile ) { return ; } let replacer = createReplacer ( m1 [ 2 ] ) ; let typeNames = m1 [ 3 ] . split ( /,/ ) ; typeNames . forEach ( ( typeName ) => { typeName = typeName . trim ( ) ; if ( typeName . length === 0 ) { return ; } let declaration = getTopLevelDeclaration ( sourceFile , typeName ) ; if ( ! declaration ) { logErr ( 'Cannot find type ' + typeName ) ; return ; } result . push ( replacer ( getMassagedTopLevelDeclarationText ( sourceFile , declaration ) ) ) ; } ) ; return ; } let m2 = line . match ( /^\s*#includeAll\(([^;)]*)(;[^)]*)?\)\:(.*)$/ ) ; if ( m2 ) { CURRENT_PROCESSING_RULE = line ; let moduleId = m2 [ 1 ] ; let sourceFile = getSourceFile ( out , moduleId ) ; if ( ! sourceFile ) { return ; } let replacer = createReplacer ( m2 [ 2 ] ) ; let typeNames = m2 [ 3 ] . split ( /,/ ) ; let typesToExcludeMap = { } ; let typesToExcludeArr = [ ] ; typeNames . forEach ( ( typeName ) => { typeName = typeName . trim ( ) ; if ( typeName . length === 0 ) { return ; } typesToExcludeMap [ typeName ] = true ; typesToExcludeArr . push ( typeName ) ; } ) ; getAllTopLevelDeclarations ( sourceFile ) . forEach ( ( declaration ) => { if ( isDeclaration ( declaration ) ) { if ( typesToExcludeMap [ declaration . name . text ] ) { return ; } } else { let nodeText = getNodeText ( sourceFile , declaration ) ; for ( let i = 0 ; i < typesToExcludeArr . length ; i ++ ) { if ( nodeText . indexOf ( typesToExcludeArr [ i ] ) >= 0 ) { return ; } } } result . push ( replacer ( getMassagedTopLevelDeclarationText ( sourceFile , declaration ) ) ) ; } ) ; return ; } result . push ( line ) ; } ) ; let resultTxt = result . join ( '\n' ) ; resultTxt = resultTxt . replace ( /\bURI\b/g , 'Uri' ) ; resultTxt = resultTxt . replace ( /\bEvent</g , 'IEvent<' ) ; resultTxt = resultTxt . replace ( /\bTPromise</g , 'Promise<' ) ; resultTxt = format ( resultTxt ) ; resultTxt = resultTxt . replace ( /\r\n/g , '\n' ) ; return resultTxt ; } export function getFilesToWatch ( out ) { let recipe = fs . readFileSync ( RECIPE_PATH ) . toString ( ) ; let lines = recipe . split ( /\r\n|\n|\r/ ) ; let result = [ ] ; lines . forEach ( line => { let m1 = line . match ( /^\s*#include\(([^;)]*)(;[^)]*)?\)\:(.*)$/ ) ; if ( m1 ) { let moduleId = m1 [ 1 ] ; result . push ( moduleIdToPath ( out , moduleId ) ) ; return ; } let m2 = line . match ( /^\s*#includeAll\(([^;)]*)(;[^)]*)?\)\:(.*)$/ ) ; if ( m2 ) { let moduleId = m2 [ 1 ] ; result . push ( moduleIdToPath ( out , moduleId ) ) ; return ; } } ) ; return result ; } export interface IMonacoDeclarationResult { content : string ; filePath : string ; isTheSame : boolean ; } export function run ( out ) { log ( 'Starting monaco.d.ts generation' ) ; SOURCE_FILE_MAP = { } ; let recipe = fs . readFileSync ( RECIPE_PATH ) . toString ( ) ; let result = generateDeclarationFile ( out , recipe ) ; let currentContent = fs . readFileSync ( DECLARATION_PATH ) . toString ( ) ; log ( 'Finished monaco.d.ts generation' ) ; return { content : result , filePath : DECLARATION_PATH , isTheSame : currentContent === result } ; } export function complainErrors ( ) { logErr ( 'Not running monaco.d.ts generation due to compile errors' ) ; }