import { DebugSession , Thread , Source , StackFrame , Scope , Variable , Breakpoint , TerminatedEvent , InitializedEvent , StoppedEvent , ContinuedEvent , OutputEvent , Handles } from "vscode-debugadapter" ; import { DebugProtocol } from "vscode-debugprotocol" ; import * as Path from "path" ; import * as FS from "fs" ; import * as assert from "assert" ; import { SourceMaps , SourceMap , Bias , MappingResult } from "./sourceMaps" ; import * as Promise from "bluebird" ; import { DukConnection , DukVersion } from "./DukConnection" ; import { DukDbgProtocol , DukEvent , DukStatusState , DukScopeMask , DukStatusNotification , DukPrintNotification , DukAlertNotification , DukLogNotification , DukThrowNotification , DukAppNotification , DukListBreakResponse , DukAddBreakResponse , DukGetCallStackResponse , DukGetLocalsResponse , DukEvalResponse , DukGetHeapObjInfoResponse , DukGetObjPropDescRangeResponse , DukGetClosureResponse } from "./DukDbgProtocol" ; import * as Duk from "./DukBase" ; export interface CommonArguments { /** comma separated list of trace selectors. Supported:
     * 'all': all
     * 'la': launch/attach
     * 'bp': breakpoints
     * 'sm': source maps
     * */ trace ? : string ; /** Automatically stop target after launch. If not specified, target does not stop. */ stopOnEntry ? : boolean ; /** Configure source maps. By default source maps are disabled. */ sourceMaps ? : boolean ; /** Where to look for the generated code. Only used if sourceMaps is true. */ outDir ? : string ; /** Do show '__artificial' property while inspecting object or not */ artificial ? : boolean ; debugLog ? : boolean ; isMusashi ? : boolean ; } type HObjectClassID = number ; export interface LaunchRequestArguments extends DebugProtocol . LaunchRequestArguments , CommonArguments { /** An absolute path to the program to debug. */ program : string ; /** Optional arguments passed to the debuggee. */ args ? : string [ ] ; /** Launch the debuggee in this working directory (specified as an absolute path). If omitted the debuggee is lauched in its own directory. */ cwd ? : string ; /** Absolute path to the runtime executable to be used. Default is the runtime executable on the PATH. */ runtimeExecutable ? : string ; /** Optional arguments passed to the runtime executable. */ runtimeArgs ? : string [ ] ; /** Optional environment variables to pass to the debuggee. The string valued properties of the 'environmentVariables' are used as key/value pairs. */ env ? : { [ key ] : string ; } ; /** If true launch the target in an external console. */ externalConsole ? : boolean ; } export interface AttachRequestArguments extends DebugProtocol . AttachRequestArguments , CommonArguments { /** The debug port to attach to. */ port : number ; /** The TCP/IP address of the port (remote addresses only supported for node >= 5.0). */ address ? : string ; /** Retry for this number of milliseconds to connect to the node runtime. */ timeout ? : number ; /** Node's root directory. */ remoteRoot ? : string ; /** VS Code's root directory. */ localRoot ? : string ; /** VS Code's root directories. */ localRoots ? : string [ ] ; /** Duktape root directory */ dukRemoteRoot ? : string ; } class ArrayX { public static firstOrNull < T > ( target , comparer ) { for ( let i = 0 ; i < target . length ; i ++ ) { if ( comparer ( target [ i ] ) ) { return target [ i ] ; } } return null ; } public static convert < T , U > ( target , converter ) { let result = new Array < U > ( target . length ) ; for ( let i = 0 ; i < target . length ; i ++ ) { result [ i ] = converter ( target [ i ] ) ; } return result ; } } enum LaunchType { Launch = 0 , Attach } class DukBreakPoint { public filePath : string ; public dukIdx : number ; public line : number ; constructor ( filePath , line ) { this . filePath = filePath ; this . line = line ; } } class BreakPointMap { public _breakpoints : DukBreakPoint [ ] = [ ] ; find ( filePath , line ) { return ArrayX . firstOrNull ( this . _breakpoints , b => b . filePath === filePath && b . line === line ) ; } getBreakpointsForFile ( filePath ) { filePath = Path . normalize ( filePath ) ; let bps = this . _breakpoints ; let len = bps . length ; let result = [ ] ; for ( let i = 0 ; i < len ; i ++ ) { if ( bps [ i ] . filePath === filePath ) { result . push ( bps [ i ] ) ; } } return result ; } removeBreakpoints ( remList ) { let bps = this . _breakpoints ; remList . forEach ( b => { for ( let i = 0 ; i < bps . length ; i ++ ) { if ( bps [ i ] . dukIdx === b . dukIdx ) { bps [ i ] = null ; break ; } } } ) ; for ( let i = bps . length - 1 ; i >= 0 ; i -- ) { if ( bps [ i ] === null ) { bps . splice ( i , 1 ) ; } } for ( let i = 0 ; i < bps . length ; i ++ ) { bps [ i ] . dukIdx = i ; } } addBreakpoints ( remList ) { let bps = this . _breakpoints ; remList . forEach ( b => bps . push ( b ) ) ; for ( let i = 0 ; i < bps . length ; i ++ ) { bps [ i ] . dukIdx = i ; } } } class SourceFilePosition { public path : string ; public fileName : string ; public line : number ; } class SourceFile { public id : number ; public name : string ; public path : string ; public srcMapPath : string ; public srcMap : SourceMap ; constructor ( ) { } public generated2Source ( line ) { if ( this . srcMap ) { let pos = this . srcMap . originalPositionFor ( line , 0 , Bias . LEAST_UPPER_BOUND ) ; if ( pos && pos . line !== null ) { return { path : pos . source , fileName : Path . basename ( pos . source ) , line : pos . line } ; } } return { path : this . path , fileName : this . name , line : line } ; } public source2Generated ( absSourcePath , line ) { if ( this . srcMap ) { let pos = this . srcMap . generatedPositionFor ( absSourcePath , line , 0 , Bias . LEAST_UPPER_BOUND ) ; if ( pos && pos . line !== null ) { return { path : this . path , fileName : this . name , line : pos . line } ; } } return { path : this . path , fileName : this . name , line : line } ; } } enum PropertySetType { Scope = 0 , Object , Artificials } class PropertySet { public type : PropertySetType ; public handle : number ; public parent ? : PropertySet ; public scope : DukScope ; public displayName : string ; public heapPtr : Duk . TValPointer ; public variables : Variable [ ] ; public classType : HObjectClassID = 0 ; public constructor ( type ) { this . type = type ; } } class DukScope { public handle : number ; public name : string ; public stackFrame : DukStackFrame ; public properties : PropertySet ; public constructor ( name , stackFrame , properties ) { this . name = name ; this . stackFrame = stackFrame ; this . properties = properties ; } } class DukStackFrame { public handle : number ; public source : SourceFile ; public fileName : string ; public filePath : string ; public funcName : string ; public lineNumber : number ; public pc : number ; public depth : number ; public klass : string ; public scopes : DukScope [ ] ; public constructor ( source , fileName , filePath , funcName , lineNumber , pc , depth , scopes ) { this . source = source ; this . fileName = fileName ; this . filePath = filePath ; this . funcName = funcName ; this . lineNumber = lineNumber ; this . pc = pc ; this . depth = depth ; this . scopes = scopes ; } } class PtrPropDict { [ key ] : PropertySet ; } class DbgClientState { public ptrHandles : PtrPropDict ; public varHandles : Handles < PropertySet > ; public stackFrames : Handles < DukStackFrame > ; public scopes : Handles < DukScope > ; public reset ( ) { this . ptrHandles = new PtrPropDict ( ) ; this . varHandles = new Handles < PropertySet > ( ) ; this . stackFrames = new Handles < DukStackFrame > ( ) ; this . scopes = new Handles < DukScope > ( ) ; } } class ErrorCode { public static RequestFailed = 100 ; } export class DukDebugSession extends DebugSession { private static THREAD_ID = 1 ; private _args : AttachRequestArguments | LaunchRequestArguments ; private _nextSourceID : number = 1 ; private _sources : { } ; private _sourceMaps : SourceMaps ; private _sourceToGen : { } ; private _breakpoints : BreakPointMap = new BreakPointMap ( ) ; private _launchType : LaunchType ; private _targetProgram : string ; private _sourceRoots : string [ ] ; private _outDir : string ; private _remoteRoot : string ; private _stopOnEntry : boolean ; private _dukProto : DukDbgProtocol ; private _dbgState : DbgClientState ; private _expectingBreak : string = "debugger" ; private _expectingContinue : boolean = false ; private _isDisconnecting : boolean = false ; private _scopeMask : DukScopeMask = DukScopeMask . AllButGlobals ; private _dbgLog : boolean = false ; public constructor ( ) { super ( ) ; this . setDebuggerLinesStartAt1 ( true ) ; this . setDebuggerColumnsStartAt1 ( true ) ; this . _dbgState = new DbgClientState ( ) ; this . _sources = { } ; this . _sourceToGen = { } ; this . _breakpoints . _breakpoints = [ ] ; } private initDukDbgProtocol ( conn , buf ) { this . _dukProto = new DukDbgProtocol ( msg => this . dbgLog ( msg ) ) ; this . _dukProto . on ( DukEvent [ DukEvent . nfy_status ] , ( status ) => { if ( status . state === DukStatusState . Paused ) { this . dbgLog ( "Status Notification: PAUSE" ) ; } this . processStatus ( status ) ; } ) ; this . _dukProto . once ( DukEvent [ DukEvent . disconnected ] , ( reason ) => { this . logToClient ( `Disconnected: ${ this . _isDisconnecting ? "Client disconnected" : reason }\n` ) ; this . sendEvent ( new TerminatedEvent ( ) ) ; } ) ; this . _dukProto . on ( DukEvent [ DukEvent . nfy_print ] , ( e ) => { this . logToClient ( e . message , "stdout" ) ; } ) ; this . _dukProto . on ( DukEvent [ DukEvent . nfy_alert ] , ( e ) => { this . logToClient ( e . message , "console" ) ; } ) ; this . _dukProto . on ( DukEvent [ DukEvent . nfy_log ] , ( e ) => { this . logToClient ( e . message , "stdout" ) ; } ) ; this . _dukProto . on ( DukEvent [ DukEvent . nfy_throw ] , ( e ) => { this . _expectingBreak = "Exception" ; const sendEvent = function ( ) { const sourceFile = this . mapSourceFile ( e . fileName ) ; const source = sourceFile ? new Source ( sourceFile . name , sourceFile . path ) : new Source ( e . fileName , Path . resolve ( this . _outDir , e . fileName ) ) ; const outputEventOptions = { source : source , line : e . lineNumber , column : 1 } ; this . logToClient ( `Exception thrown: ${ e . message }\n` , "stderr" , outputEventOptions ) ; } . bind ( this ) ; if ( this . _sourceMaps ) { const sourceMap = this . _sourceMaps . FindSourceToGeneratedMapping ( Path . resolve ( this . _outDir , e . fileName ) , this . _sourceRoots ) ; if ( sourceMap && sourceMap . _loading ) { sourceMap . _loading . then ( ( ) => { const mappingResult = this . _sourceMaps . MapToSource ( Path . resolve ( this . _outDir , e . fileName ) , e . lineNumber , 0 , this . _sourceRoots ) ; if ( ! mappingResult ) { sendEvent ( ) ; return ; } const source = new Source ( e . fileName , mappingResult . path ) ; const outputEventOptions = { source : source , line : mappingResult . line , column : mappingResult . column } ; this . logToClient ( `Exception thrown: ${ e . message }\n` , "stderr" , outputEventOptions ) ; } ) ; return ; } } sendEvent ( ) ; } ) ; this . _dukProto . on ( DukEvent [ DukEvent . nfy_appmsg ] , ( e ) => { this . logToClient ( e . messages . join ( " " ) + "\n" ) ; } ) ; this . _dukProto . connect ( conn , buf ) ; } private beginInit ( response ) { let args = < AttachRequestArguments > this . _args ; try { const tmOut = args . timeout === undefined ? 10000 : 0 ; const conn = DukConnection . connect ( args . address , args . port , tmOut ) ; conn . once ( "connected" , ( buf , version ) => { this . logToClient ( "Attached to duktape debugger.\n" ) ; conn . removeAllListeners ( ) ; this . logToClient ( `Protocol ID: ${ version . id }\n` ) ; if ( version . major === 2 || ( version . major === 1 && version . minor >= 5 ) ) { this . initDukDbgProtocol ( conn , buf ) ; this . finalizeInit ( response ) ; } else { conn . closeSocket ( ) ; this . sendErrorResponse ( response , 0 , `Unsupported duktape version: ${ version . dukVersion }` ) ; } } ) ; conn . once ( "error" , err => { this . sendErrorResponse ( response , 0 , "Attach failed with error: " + err ) ; } ) ; } catch ( err ) { this . sendErrorResponse ( response , 0 , "Failed to perform attach with error: " + err ) ; } } private finalizeInit ( response ) { this . dbgLog ( "Finalized Initialization." ) ; if ( this . _args . sourceMaps ) { this . _sourceMaps = new SourceMaps ( this . _outDir ) ; } this . removeAllTargetBreakpoints ( ) . catch ( ( ) => { } ) . then ( ( ) => { this . sendResponse ( response ) ; this . sendEvent ( new InitializedEvent ( ) ) ; if ( this . _args . stopOnEntry ) { this . _dukProto . requestPause ( ) . then ( ( ) => { this . sendEvent ( new StoppedEvent ( "start" , DukDebugSession . THREAD_ID ) ) ; } ) ; } else { this . _dukProto . requestResume ( ) . then ( ( ) => { this . sendEvent ( new ContinuedEvent ( DukDebugSession . THREAD_ID , true ) ) ; } ) ; } } ) . catch ( ( ) => { } ) ; } private processStatus ( status ) { if ( status . state === DukStatusState . Paused ) { let sourceFile = this . mapSourceFile ( status . filename ) ; if ( sourceFile ) { let line = this . convertDebuggerLineToClient ( status . linenumber ) ; let pos = sourceFile . generated2Source ( line ) ; let bp = this . _breakpoints . find ( pos . fileName , pos . line ) ; if ( bp ) { this . _expectingBreak = "breakpoint" ; } } this . _dbgState . reset ( ) ; this . sendEvent ( new StoppedEvent ( this . _expectingBreak , DukDebugSession . THREAD_ID ) ) ; this . _expectingBreak = "debugger" ; } else { this . sendEvent ( new ContinuedEvent ( DukDebugSession . THREAD_ID , true ) ) ; } } protected initializeRequest ( response , args ) { this . dbgLog ( "[FE] initializeRequest." ) ; response . body . supportsConfigurationDoneRequest = true ; response . body . supportsFunctionBreakpoints = false ; response . body . supportsEvaluateForHovers = true ; response . body . supportsStepBack = false ; response . body . supportsSetVariable = true ; this . sendResponse ( response ) ; } protected launchRequest ( response , args ) { this . dbgLog ( "[FE] launchRequest" ) ; this . sendErrorResponse ( response , 0 , "Launching is not currently supported. Use Attach." ) ; } protected attachRequest ( response , args ) { this . dbgLog ( "[FE] attachRequest" ) ; if ( ( ! args . localRoot || args . localRoot === "" ) && ( ! args . localRoots || args . localRoots . length === 0 ) ) { this . sendErrorResponse ( response , 0 , "Must specify `localRoot` or `localRoots`" ) ; return ; } if ( args . sourceMaps && ( ! args . outDir || args . outDir === "" ) ) { this . sendErrorResponse ( response , 0 , "Must specify an 'outDir' when 'sourceMaps' is enabled.`" ) ; return ; } this . _args = args ; this . _launchType = LaunchType . Attach ; this . _sourceRoots = new Array < string > ( ) ; if ( args . localRoot ) { this . _sourceRoots = this . _sourceRoots . concat ( this . normPath ( args . localRoot ) ) ; } if ( args . localRoots ) { this . _sourceRoots = this . _sourceRoots . concat ( args . localRoots . map ( path => { return this . normPath ( path ) ; } ) ) ; } if ( args . dukRemoteRoot ) { this . _remoteRoot = this . processDukRemoteRoot ( args . dukRemoteRoot ) ; } this . _outDir = this . normPath ( args . outDir ) ; this . _dbgLog = args . debugLog || false ; this . beginInit ( response ) ; } private processDukRemoteRoot ( remoteRoot ) { const sep = Path . sep ; return remoteRoot . endsWith ( sep ) ? remoteRoot : remoteRoot + sep ; } protected disconnectRequest ( response , args ) { this . dbgLog ( "[FE] disconnectRequest" ) ; this . _isDisconnecting = true ; if ( ! this . _dukProto . isConnected ) { this . sendResponse ( response ) ; return ; } const TIMEOUT_MS = 2000 ; const doDisconnect = ( ) => { clearTimeout ( timeoutID ) ; this . _dukProto . disconnect ( "Client disconnected." ) ; this . sendResponse ( response ) ; } ; const timeoutID = setTimeout ( ( ) => { this . dbgLog ( "Detach request took too long. Forcefully disconnecting." ) ; doDisconnect ( ) ; } , TIMEOUT_MS ) ; this . _breakpoints . _breakpoints = [ ] ; this . dbgLog ( "Clearing breakpoints on target." ) ; this . removeAllTargetBreakpoints ( ) . catch ( ( ) => { } ) . then ( ( ) => { const isConnected = this . _dukProto . isConnected ; ( ( isConnected && this . _dukProto . requestDetach ( ) ) || Promise . resolve ( ) ) . catch ( ( ) => { } ) . then ( ( ) => doDisconnect ( ) ) ; } ) . catch ( ( ) => { } ) ; } protected setBreakPointsRequest ( response , args ) { this . dbgLog ( "[FE] setBreakPointsRequest" ) ; let filePath = Path . normalize ( args . source . path ) ; let inBreaks = args . breakpoints ; let addBPs = [ ] ; let remBPs = [ ] ; let fileBPs = this . _breakpoints . getBreakpointsForFile ( filePath ) ; let addedBPs = [ ] ; let removedBPs = [ ] ; let persistBPs = [ ] ; inBreaks . forEach ( b => ( b . line = this . convertClientLineToDebugger ( b . line ) ) ) ; inBreaks . forEach ( a => { if ( ArrayX . firstOrNull ( fileBPs , b => a . line === b . line ) === null ) { addBPs . push ( new DukBreakPoint ( filePath , a . line ) ) ; } } ) ; fileBPs . forEach ( a => { if ( ArrayX . firstOrNull ( inBreaks , b => a . line === b . line ) === null ) { remBPs . push ( a ) ; } } ) ; fileBPs . forEach ( a => { if ( ! ArrayX . firstOrNull ( remBPs , b => a . line === b . line ) ) { persistBPs . push ( a ) ; } } ) ; let doRemoveBreakpoints ; let doAddBreakpoints ; let doFindSourceFile ; doFindSourceFile = ( bp ) => { let src = this . unmapSourceFile ( filePath ) ; if ( ! src ) { let log = "Unknown source file: " + filePath ; this . dbgLog ( log ) ; this . sendErrorResponse ( response , 0 , "SetBreakPoint failed" ) ; return Promise . reject ( log ) ; } if ( ! src . srcMap ) { return Promise . resolve ( src ) ; } return new Promise ( ( resolve , reject ) => { src . srcMap . _loading . then ( ( ) => resolve ( src ) ) . catch ( e => reject ( e ) ) ; } ) ; } ; doRemoveBreakpoints = ( i ) => { if ( i >= remBPs . length ) { return Promise . resolve ( ) ; } let bp = remBPs [ i ] ; return this . _dukProto . requestRemoveBreakpoint ( remBPs [ i ] . dukIdx ) . then ( ( ) => { this . _breakpoints . removeBreakpoints ( [ bp ] ) ; removedBPs . push ( bp ) ; } ) . catch ( ( ) => { } ) . then ( ( ) => { return doRemoveBreakpoints ( i + 1 ) ; } ) ; } ; doAddBreakpoints = ( i ) => { if ( i >= addBPs . length ) { return Promise . resolve ( ) ; } let bp = addBPs [ i ] ; let line = bp . line ; let generatedName = null ; return doFindSourceFile ( bp ) . then ( src => { if ( src . srcMap ) { let pos = src . source2Generated ( filePath , line ) ; generatedName = pos . fileName ; line = pos . line ; } else { generatedName = this . getSourceNameByPath ( args . source . path ) || args . source . name ; } if ( ! generatedName ) { return doAddBreakpoints ( i + 1 ) ; } if ( this . _remoteRoot ) { generatedName = Path . join ( this . _remoteRoot , generatedName ) ; } return this . _dukProto . requestSetBreakpoint ( generatedName , line ) . then ( ( r ) => { this . _breakpoints . addBreakpoints ( [ bp ] ) ; addedBPs . push ( bp ) ; } ) . catch ( ( ) => { } ) . then ( ( ) => { return doAddBreakpoints ( i + 1 ) ; } ) ; } ) ; } ; doRemoveBreakpoints ( 0 ) . then ( ( ) => doAddBreakpoints ( 0 ) ) . catch ( e => { } ) . then ( ( ) => { addedBPs = persistBPs . concat ( addedBPs ) ; let outBreaks = new Array < Breakpoint > ( addedBPs . length ) ; for ( let i = 0 ; i < addedBPs . length ; i ++ ) { outBreaks [ i ] = new Breakpoint ( true , addedBPs [ i ] . line ) ; } response . body = { breakpoints : outBreaks } ; this . sendResponse ( response ) ; } ) ; } protected setFunctionBreakPointsRequest ( response , args ) { this . dbgLog ( "[FE] setFunctionBreakPointsRequest" ) ; this . sendResponse ( response ) ; } protected setExceptionBreakPointsRequest ( response , args ) { this . dbgLog ( "[FE] setExceptionBreakPointsRequest" ) ; this . sendResponse ( response ) ; } protected configurationDoneRequest ( response , args ) { this . dbgLog ( "[FE] configurationDoneRequest" ) ; this . sendResponse ( response ) ; } protected continueRequest ( response , args ) { this . dbgLog ( "[FE] continueRequest" ) ; this . _dukProto . requestResume ( ) . then ( ( ) => { this . sendResponse ( response ) ; } ) . catch ( ( ) => { this . requestFailedResponse ( response ) ; } ) ; } protected nextRequest ( response , args ) { this . dbgLog ( "[FE] nextRequest" ) ; this . _expectingBreak = "step" ; this . _dukProto . requestStepOver ( ) . then ( ( ) => { this . sendResponse ( response ) ; } ) . catch ( ( ) => { this . requestFailedResponse ( response ) ; } ) ; } protected stepInRequest ( response , args ) { this . dbgLog ( "[FE] stepInRequest" ) ; this . _expectingBreak = "stepin" ; this . _dukProto . requestStepInto ( ) . then ( ( ) => { this . sendResponse ( response ) ; } ) . catch ( ( ) => { this . requestFailedResponse ( response ) ; } ) ; } protected stepOutRequest ( response , args ) { this . dbgLog ( "[FE] stepOutRequest" ) ; this . _expectingBreak = "stepout" ; this . _dukProto . requestStepOut ( ) . then ( ( ) => { this . sendResponse ( response ) ; } ) . catch ( ( ) => { this . requestFailedResponse ( response ) ; } ) ; } protected pauseRequest ( response , args ) { this . dbgLog ( "[FE] pauseRequest" ) ; this . _expectingBreak = "pause" ; this . _dukProto . requestPause ( ) . then ( ( ) => { this . sendResponse ( response ) ; } ) . catch ( ( ) => { this . requestFailedResponse ( response , "Error pausing." ) ; } ) ; } protected sourceRequest ( response , args ) { this . dbgLog ( "[FE] sourceRequest" ) ; let ref = args . sourceReference ; response . body = { content : "Unknown Source\n" } ; this . sendResponse ( response ) ; } protected threadsRequest ( response ) { this . dbgLog ( "[FE] threadsRequest" ) ; response . body = { threads : [ new Thread ( DukDebugSession . THREAD_ID , "Main Thread" ) ] } ; this . sendResponse ( response ) ; } protected stackTraceRequest ( response , args ) { this . dbgLog ( "[FE] stackTraceRequest" ) ; const dukframes = new Array < DukStackFrame > ( ) ; const doRespond = ( ) => { let frames = [ ] ; frames . length = dukframes . length ; for ( let i = 0 , len = frames . length ; i < len ; i ++ ) { let frame = dukframes [ i ] ; let srcFile = frame . source ; let src = null ; if ( srcFile ) { src = new Source ( frame . fileName , frame . filePath , 0 ) ; } let klsName = frame . klass === "" ? "" : frame . klass + "." ; let funcName = frame . funcName === "" ? "(anonymous function)" : frame . funcName + "()" ; frames [ i ] = new StackFrame ( frame . handle , klsName + funcName + " : " + frame . pc , src , frame . lineNumber , frame . pc ) ; } response . body = { stackFrames : frames } ; this . sendResponse ( response ) ; } ; const doApplyConstructors = ( index ) => { if ( index >= dukframes . length ) { doRespond ( ) ; return ; } this . getObjectConstructorByName ( "this" , dukframes [ index ] . depth ) . then ( ( c ) => { dukframes [ index ] . klass = c ; doApplyConstructors ( index + 1 ) ; } ) ; } ; this . _dukProto . requestCallStack ( ) . then ( ( val ) => { dukframes . length = val . callStack . length ; for ( let i = 0 , len = dukframes . length ; i < len ; i ++ ) { let entry = val . callStack [ i ] ; let srcFile = this . mapSourceFile ( entry . fileName ) ; let line = this . convertDebuggerLineToClient ( entry . lineNumber ) ; let srcPos = srcFile ? srcFile . generated2Source ( line ) : { path : entry . fileName , fileName : entry . fileName , line : line } ; let frame = new DukStackFrame ( srcFile , srcPos . fileName , srcPos . path , entry . funcName , srcPos . line , entry . pc , - i - 1 , null ) ; frame . handle = this . _dbgState . stackFrames . create ( frame ) ; dukframes [ i ] = frame ; } doApplyConstructors ( 0 ) ; } ) . catch ( err => { this . dbgLog ( "Stack trace failed: " + err ) ; response . body = { stackFrames : [ ] } ; this . sendResponse ( response ) ; } ) ; } protected scopesRequest ( response , args ) { this . dbgLog ( "[FE] scopesRequest" ) ; if ( ! this . _args . isMusashi ) { this . scopeRequestForLocals ( args . frameId , response , args ) ; } else { this . scopeRequestForMultiple ( args . frameId , response , args ) ; } } protected variablesRequest ( response , args ) { this . dbgLog ( "[FE] variablesRequest" ) ; assert ( args . variablesReference !== 0 ) ; const properties = this . _dbgState . varHandles . get ( args . variablesReference ) ; if ( ! properties ) { response . body = { variables : [ ] } ; this . sendResponse ( response ) ; return ; } const scope = properties . scope ; const returnVars = ( vars ) => { if ( properties . type !== PropertySetType . Artificials ) { vars . sort ( ( a , b ) => { let aNum = Number ( a . name ) ; let bNum = Number ( b . name ) ; let aIsNum = ! isNaN ( aNum ) ; let bIsNum = ! isNaN ( bNum ) ; if ( ! aIsNum && bIsNum ) { return - 1 ; } else if ( aIsNum && ! bIsNum ) { return 1 ; } else if ( aIsNum && bIsNum ) { return aNum < bNum ? - 1 : aNum > bNum ? 1 : 0 ; } if ( a . name [ 0 ] === "_" ) { return - 1 ; } if ( b . name [ 0 ] === "_" ) { return 1 ; } if ( a . name === "this" ) { return - 1 ; } if ( b . name === "this" ) { return 1 ; } return a . name < b . name ? - 1 : a . name > b . name ? 1 : 0 ; } ) ; } response . body = { variables : vars } ; this . sendResponse ( response ) ; } ; if ( properties . type === PropertySetType . Scope ) { returnVars ( scope . properties . variables ) ; } else if ( properties . type >= PropertySetType . Object ) { this . expandPropertySubset ( properties ) . then ( objVars => { returnVars ( objVars ) ; } ) ; } } private getVariableName ( argName , properties ) { let getVariableNamePrefix = function ( properties ) : Array < string > { if ( ! properties . parent ) { return [ ] ; } let variable = properties . parent . variables . find ( parentVar => { return parentVar . variablesReference === properties . handle ; } ) ; return getVariableNamePrefix ( properties . parent ) . concat ( variable . name ) ; } ; return getVariableNamePrefix ( properties ) . concat ( argName ) . join ( "." ) ; } protected setVariableRequest ( response , args ) { this . dbgLog ( "[FE] setVariableRequest" ) ; const properties = this . _dbgState . varHandles . get ( args . variablesReference ) ; if ( ! properties ) { this . requestFailedResponse ( response , "Failed to find variable: " + args . variablesReference ) ; return ; } const frame = properties . scope . stackFrame ; let variableName = this . getVariableName ( args . name , properties ) ; let expression = variableName + " = " + args . value ; this . _dukProto . requestEval ( expression , frame . depth ) . then ( resp => { let r = < DukEvalResponse > resp ; if ( ! r . success ) { this . requestFailedResponse ( response , "Eval failed: " + r . result ) ; return ; } else { this . resolveObject ( expression , r . result , frame . scopes [ 0 ] ) . then ( v => { response . body = { value : v . value , variablesReference : v . variablesReference } ; this . sendResponse ( response ) ; } ) ; } } ) . catch ( err => { this . requestFailedResponse ( response , "Eval request failed: " + err ) ; } ) ; } protected evaluateRequest ( response , args ) { this . dbgLog ( "[FE] evaluateRequest" ) ; let x = args . expression ; if ( x . indexOf ( "cmd:" ) === 0 ) { this . handleCommandLine ( response , args ) ; } else { let frame = this . _dbgState . stackFrames . get ( args . frameId ) ; if ( ! frame ) { this . requestFailedResponse ( response , "Failed to find stack frame: " + args . frameId ) ; return ; } if ( ! args . expression || args . expression . length < 1 ) { this . requestFailedResponse ( response , "Invalid expression" ) ; return ; } this . dbgLog ( `Expression: ${ args . expression }` ) ; this . _dukProto . requestEval ( args . expression , frame . depth ) . then ( resp => { let r = < DukEvalResponse > resp ; if ( ! r . success ) { this . requestFailedResponse ( response , "Eval failed: " + r . result ) ; return ; } else { this . resolveObject ( args . expression , r . result , frame . scopes [ 0 ] ) . then ( v => { response . body = { result : v . value , variablesReference : v . variablesReference } ; this . sendResponse ( response ) ; } ) ; } } ) . catch ( err => { this . requestFailedResponse ( response , "Eval request failed: " + err ) ; } ) ; return ; } } private scopeRequestForLocals ( stackFrameHdl , response , args ) { let stackFrame = this . _dbgState . stackFrames . get ( stackFrameHdl ) ; let dukScope = new DukScope ( "Local" , stackFrame , null ) ; dukScope . handle = this . _dbgState . scopes . create ( dukScope ) ; stackFrame . scopes = [ dukScope ] ; const scopes = [ ] ; this . _dukProto . requestLocalVariables ( stackFrame . depth ) . then ( ( r ) => { let keys = ArrayX . convert ( r . vars , v => v . name ) ; return this . isGlobalObjectByName ( "this" , stackFrame . depth ) . then ( ( isGlobal ) => { if ( ! isGlobal ) { keys . unshift ( "this" ) ; } return this . expandScopeProperties ( keys , dukScope ) . then ( ( props ) => { scopes . push ( new Scope ( props . scope . name , props . handle , false ) ) ; } ) ; } ) ; } ) . then ( ( ) => { response . body = { scopes : scopes } ; this . sendResponse ( response ) ; } ) . catch ( err => { this . dbgLog ( "scopesRequest (Locals) failed: " + err ) ; response . body = { scopes : [ ] } ; } ) ; } private scopeRequestForMultiple ( stackFrameHdl , response , args ) { let stackFrame = this . _dbgState . stackFrames . get ( stackFrameHdl ) ; const names = [ "Local" , "Closure" , "Global" ] ; let dukScopes = new Array < DukScope > ( names . length ) ; for ( let i = 0 ; i < names . length ; i ++ ) { let scope = new DukScope ( names [ i ] , stackFrame , null ) ; scope . handle = this . _dbgState . scopes . create ( scope ) ; dukScopes [ i ] = scope ; } stackFrame . scopes = dukScopes ; const scopes = [ ] ; this . _dukProto . requestClosures ( this . _scopeMask , stackFrame . depth ) . then ( ( r ) => { let keys = [ r . local , r . closure , r . global ] ; let propPromises = [ ] ; return this . isGlobalObjectByName ( "this" , stackFrame . depth ) . then ( ( isGlobal ) => { if ( ! isGlobal ) { r . local . unshift ( "this" ) ; } for ( let i = 0 ; i < names . length ; i ++ ) { if ( keys [ i ] . length === 0 ) { continue ; } propPromises . push ( this . expandScopeProperties ( keys [ i ] , dukScopes [ i ] ) ) ; } if ( propPromises . length > 0 ) { return Promise . all ( propPromises ) . then ( ( results ) => { for ( let i = 0 ; i < results . length ; i ++ ) { scopes . push ( new Scope ( results [ i ] . scope . name , results [ i ] . handle , results [ i ] . scope . name === "Global" ) ) ; } } ) ; } } ) ; } ) . then ( ( ) => { response . body = { scopes : scopes } ; this . sendResponse ( response ) ; } ) . catch ( err => { this . dbgLog ( "scopesRequest failed: " + err ) ; response . body = { scopes : [ ] } ; this . sendResponse ( response ) ; } ) ; } private handleCommandLine ( response , feArgs ) { const x = feArgs . expression ; let args ; let cmd ; let result = "" ; let requireArg = ( i ) => { if ( i < 0 ) { i = args . length - i ; } if ( i < 0 || i >= args . length ) { throw new Error ( `Required arg at index ${ i }` ) ; return "" ; } return args [ i ] ; } ; let getBool = ( i ) => { let arg = requireArg ( i ) ; let narg = Number ( arg ) ; if ( ! isNaN ( narg ) ) { return narg === 0 ? false : true ; } return Boolean ( arg . toLowerCase ( ) ) ; } ; args = x . substr ( "cmd:" . length ) . split ( " " ) ; if ( args . length < 1 ) { this . requestFailedResponse ( response , "No command" ) ; return ; } try { cmd = requireArg ( 0 ) ; args . shift ( ) ; switch ( cmd ) { default : this . requestFailedResponse ( response , "Unknown command: " + cmd ) ; return ; case "breakpoints" : { this . _dukProto . requestListBreakpoints ( ) . then ( resp => { let r = < DukListBreakResponse > resp ; this . dbgLog ( "Breakpoints: " + r . breakpoints . length ) ; for ( let i = 0 ; i < r . breakpoints . length ; i ++ ) { let bp = r . breakpoints [ i ] ; let line = "[" + i + "] " + bp . fileName + ": " + bp . line ; this . dbgLog ( line ) ; result += line + "\n" ; } } ) . catch ( err => { this . requestFailedResponse ( response , "Failed: " + err ) ; } ) ; } break ; case "scopes_globals" : let enabled = getBool ( 0 ) ; this . logToClient ( `Scope Mask: ${ enabled ? "true" : "false" }\n` ) ; if ( enabled ) { this . _scopeMask |= DukScopeMask . Globals ; } else { this . _scopeMask &= ~ DukScopeMask . Globals ; } break ; } } catch ( err ) { this . requestFailedResponse ( response , `Cmd Failed: ${ String ( err ) }` ) ; return ; } response . body = { result : result , variablesReference : 0 } ; this . sendResponse ( response ) ; } private removeAllTargetBreakpoints ( ) { this . dbgLog ( "removeAllTargetBreakpoints" ) ; let numBreakpoints = 0 ; return this . _dukProto . requestListBreakpoints ( ) . then ( ( r ) => { numBreakpoints = r . breakpoints . length ; if ( numBreakpoints < 1 ) { return Promise . resolve ( [ ] ) ; } const promises = new Array < Promise < any > > ( ) ; promises . length = numBreakpoints ; numBreakpoints -- ; for ( let i = numBreakpoints ; i >= 0 ; i -- ) { promises [ i ] = this . _dukProto . requestRemoveBreakpoint ( numBreakpoints -- ) ; } return Promise . all ( promises ) ; } ) ; } private expandScopeProperties ( keys , scope ) { const propSet = new PropertySet ( PropertySetType . Scope ) ; propSet . handle = this . _dbgState . varHandles . create ( propSet ) ; propSet . scope = scope ; propSet . variables = [ ] ; scope . properties = propSet ; let evalPromises = new Array < Promise < any > > ( keys . length ) ; for ( let i = 0 ; i < keys . length ; i ++ ) { evalPromises [ i ] = this . _dukProto . requestEval ( keys [ i ] , scope . stackFrame . depth ) ; } return Promise . all ( evalPromises ) . then ( ( results ) => { let ctorPromises = [ ] ; let objVars = [ ] ; let pKeys = [ ] ; let pValues = [ ] ; for ( let i = 0 ; i < results . length ; i ++ ) { if ( ! results [ i ] . success ) { continue ; } pKeys . push ( keys [ i ] ) ; pValues . push ( results [ i ] . result ) ; } if ( pKeys . length < 1 ) { return propSet ; } return this . resolvePropertySetVariables ( pKeys , pValues , propSet ) ; } ) . catch ( err => { return propSet ; } ) ; } private expandPropertySubset ( propSet ) { if ( propSet . type === PropertySetType . Object ) { if ( propSet . variables ) { return Promise . resolve ( propSet . variables ) ; } propSet . variables = [ ] ; return this . _dukProto . requestInspectHeapObj ( propSet . heapPtr ) . then ( ( r ) => { let numArtificial = r . properties . length ; let props = r . properties ; if ( this . _args . artificial ) { let artificials = new PropertySet ( PropertySetType . Artificials ) ; artificials . handle = this . _dbgState . varHandles . create ( artificials ) ; artificials . scope = propSet . scope ; artificials . variables = new Array < Variable > ( numArtificial ) ; for ( let i = 0 ; i < numArtificial ; i ++ ) { let p = r . properties [ i ] ; artificials . variables [ i ] = new Variable ( < string > p . key , String ( p . value ) , 0 ) ; } propSet . variables . push ( new Variable ( "__artificial" , "{...}" , artificials . handle ) ) ; } let maxOwnProps = r . maxPropDescRange ; if ( maxOwnProps < 1 ) { return propSet . variables ; } return this . _dukProto . requestGetObjPropDescRange ( propSet . heapPtr , 0 , maxOwnProps ) . then ( ( r ) => { let props = [ ] ; for ( let i = 0 ; i < r . properties . length ; i ++ ) { if ( r . properties [ i ] . value !== undefined ) { props . push ( r . properties [ i ] ) ; } } return this . resolvePropertySetVariables ( ArrayX . convert ( props , v => String ( v . key ) ) , ArrayX . convert ( props , v => v . value ) , propSet ) . then ( p => propSet . variables ) ; } ) ; } ) ; } else if ( propSet . type === PropertySetType . Artificials ) { return Promise . resolve ( propSet . variables ) ; } return Promise . resolve ( [ ] ) ; } private resolveObject ( name , value , scope ) { if ( value instanceof Duk . TValObject ) { let ptrStr = ( < Duk . TValObject > value ) . ptr . toString ( ) ; let objPropSet = this . _dbgState . ptrHandles [ ptrStr ] ; if ( objPropSet ) { return Promise . resolve ( new Variable ( name , objPropSet . displayName , objPropSet . handle ) ) ; } else { return new Promise < Variable > ( ( resolve , reject ) => { objPropSet = new PropertySet ( PropertySetType . Object ) ; objPropSet . scope = scope ; objPropSet . heapPtr = ( < Duk . TValObject > value ) . ptr ; objPropSet . classType = ( < Duk . TValObject > value ) . classID ; objPropSet . displayName = "Object" ; objPropSet . handle = this . _dbgState . varHandles . create ( objPropSet ) ; let variable = new Variable ( name , objPropSet . displayName , objPropSet . handle ) ; this . _dbgState . ptrHandles [ ptrStr ] = objPropSet ; this . _dukProto . requestInspectHeapObj ( objPropSet . heapPtr ) . then ( ( r ) => { const clsName = ArrayX . firstOrNull ( r . properties , v => v . key === "class_name" ) ; if ( ! clsName || clsName . value === < any > "Object" ) { this . getConstructorNameByObject ( objPropSet . heapPtr ) . then ( className => { variable . value = className ; resolve ( variable ) ; } ) ; } else { objPropSet . displayName = < string > clsName . value ; variable . value = objPropSet . displayName ; resolve ( variable ) ; } } ) ; } ) ; } } else { return Promise . resolve ( new Variable ( name , typeof value === "string" ? `"${ value }"` : String ( value ) ) ) ; } } private resolvePropertySetVariables ( keys , values , propSet ) { let scope = propSet . scope ; let stackDepth = scope . stackFrame . depth ; let objClassPromises = [ ] ; let toStrPromises = [ ] ; let objVars = [ ] ; if ( ! propSet . variables ) { propSet . variables = [ ] ; } for ( let i = 0 ; i < keys . length ; i ++ ) { let key = keys [ i ] ; let value = values [ i ] ; let variable = new Variable ( key , "" , 0 ) ; propSet . variables . push ( variable ) ; if ( value instanceof Duk . TValObject ) { let ptrStr = ( < Duk . TValObject > value ) . ptr . toString ( ) ; let objPropSet = this . _dbgState . ptrHandles [ ptrStr ] ; if ( objPropSet ) { variable . variablesReference = objPropSet . handle ; if ( objPropSet . displayName ) { variable . value = objPropSet . displayName ; continue ; } } else { objPropSet = new PropertySet ( PropertySetType . Object ) ; objPropSet . scope = scope ; objPropSet . heapPtr = ( < Duk . TValObject > value ) . ptr ; objPropSet . classType = ( < Duk . TValObject > value ) . classID ; objPropSet . displayName = "Object" ; variable . value = objPropSet . displayName ; objPropSet . handle = this . _dbgState . varHandles . create ( objPropSet ) ; objPropSet . parent = propSet ; variable . variablesReference = objPropSet . handle ; this . _dbgState . ptrHandles [ ptrStr ] = objPropSet ; const objPromise = this . _dukProto . requestInspectHeapObj ( objPropSet . heapPtr ) . then ( ( r ) => { const clsName = ArrayX . firstOrNull ( r . properties , v => v . key === "class_name" ) ; if ( ! clsName || clsName . value === < any > "Object" ) { toStrPromises . push ( this . getConstructorNameByObject ( objPropSet . heapPtr ) ) ; objVars . push ( variable ) ; } else { objPropSet . displayName = < string > clsName . value ; variable . value = objPropSet . displayName ; } } ) ; objClassPromises . push ( objPromise ) ; } } else { variable . value = typeof value === "string" ? `"${ value }"` : String ( value ) ; } } return Promise . all ( objClassPromises ) . then ( ( ) => { return Promise . all ( toStrPromises ) . then ( ( toStrResults ) => { let ctorRequests = [ ] ; let ctorVars = [ ] ; for ( let i = 0 ; i < toStrResults . length ; i ++ ) { let rName = toStrResults [ i ] ; this . _dbgState . varHandles . get ( objVars [ i ] . variablesReference ) . displayName = rName ; objVars [ i ] . value = rName ; } if ( ctorRequests . length > 0 ) { return Promise . all ( ctorRequests ) . then ( ( ctorNameResp ) => { for ( let i = 0 ; i < ctorNameResp . length ; i ++ ) { if ( ctorNameResp [ i ] . success ) { ctorVars [ i ] . value = < string > ctorNameResp [ i ] . result ; } } return Promise . resolve ( propSet ) ; } ) ; } return Promise . resolve ( propSet ) ; } ) ; } ) . catch ( ( ) => { } ) . then ( ( ) => { return Promise . resolve ( propSet ) ; } ) ; } private getObjectConstructorByName ( prefix , stackDepth ) { let exp = "(" + prefix + ".constructor.toString().match(/\\w+/g)[1])" ; return this . isGlobalObjectByName ( prefix , stackDepth ) . then ( isGlobal => { if ( isGlobal ) { return "" ; } return this . _dukProto . requestEval ( exp , stackDepth ) . then ( resp => { let r = < DukEvalResponse > resp ; return r . success ? String ( r . result ) : "" ; } ) ; } ) . catch ( err => "" ) ; } private getConstructorNameByObject ( ptr ) { let protoPtr ; return this . _dukProto . requestInspectHeapObj ( ptr ) . then ( ( r ) => { let p = ArrayX . firstOrNull ( r . properties , n => n . key === "prototype" ) ; protoPtr = ( < Duk . TValObject > p . value ) . ptr ; return this . _dukProto . requestInspectHeapObj ( protoPtr ) ; } ) . then ( ( r ) => { return this . _dukProto . requestGetObjPropDescRange ( protoPtr , 0 , r . maxPropEntriesRange ) ; } ) . then ( ( r ) => { let p = ArrayX . firstOrNull ( r . properties , n => n . key === "constructor" ) ; let obj = < Duk . TValObject > p . value ; return this . _dukProto . requestGetObjPropDescRange ( ( < Duk . TValObject > p . value ) . ptr , 0 , 0x7fffffff ) ; } ) . then ( ( r ) => { let p = ArrayX . firstOrNull ( r . properties , n => n . key === "name" ) ; return < string > p . value ; } ) . catch ( err => { let errStr = String ( err ) ; this . dbgLog ( errStr ) ; return "Object" ; } ) ; } private isGlobalObjectByName ( prefix , stackDepth ) { let exp = "String(" + prefix + ")" ; return this . _dukProto . requestEval ( exp , stackDepth ) . then ( resp => { let r = < DukEvalResponse > resp ; if ( ! r . success ) { return Promise . reject ( "failed" ) ; } else { let isglob = < string > r . result === "[object global]" ? true : false ; return Promise . resolve ( isglob ) ; } } , err => { Promise . reject ( err ) ; } ) ; } private mapSourceFile ( name ) { this . dbgLog ( `[mapSourceFile] Mapping source file ${ name }` ) ; if ( ! name ) { return null ; } if ( this . _remoteRoot ) { name = name . replace ( new RegExp ( `^${ this . _remoteRoot }` ) , "" ) ; } this . dbgLog ( `[mapSourceFile] trimmed remote root ${ name }` ) ; name = this . normPath ( name ) ; let sources = this . _sources ; for ( let k in sources ) { let val = sources [ k ] ; if ( val . name === name ) { this . dbgLog ( `[mapSourceFile] Mapped source file ${ val . path }` ) ; return val ; } } let fpath ; if ( this . _args . sourceMaps ) { let candidatePath = this . normPath ( Path . join ( this . _outDir , name ) ) ; if ( FS . existsSync ( candidatePath ) ) { fpath = this . normPath ( Path . join ( this . _outDir , name ) ) ; } else { for ( let rpath of this . _sourceRoots ) { let candidatePath = this . normPath ( Path . join ( rpath , name ) ) ; if ( FS . existsSync ( candidatePath ) ) { fpath = candidatePath ; break ; } } } } else { for ( let rpath of this . _sourceRoots ) { let candidatePath = this . normPath ( Path . join ( rpath , name ) ) ; if ( FS . existsSync ( candidatePath ) ) { fpath = candidatePath ; break ; } } } if ( ! fpath ) { this . dbgLog ( `[mapSourceFile] failed to map source file` ) ; return null ; } if ( ! FS . existsSync ( fpath ) ) { this . dbgLog ( `[mapSourceFile] mapped source path doesn't exist ${ fpath }` ) ; return null ; } let src = new SourceFile ( ) ; src . id = this . _nextSourceID ++ ; src . name = name ; src . path = fpath ; sources [ src . id ] = src ; sources [ src . name ] = src ; try { this . checkForSourceMap ( src ) ; if ( src . srcMap ) { let srcMap = src . srcMap ; for ( let i = 0 ; i < srcMap . _sources . length ; i ++ ) { let srcPath = srcMap . _sources [ i ] ; if ( ! Path . isAbsolute ( srcPath ) ) { srcPath = Path . resolve ( Path . dirname ( this . normPath ( Path . join ( this . _outDir , name ) ) ) , srcPath ) ; } srcPath = Path . normalize ( srcPath ) ; this . _sourceToGen [ srcPath ] = src ; } } } catch ( err ) { this . dbgLog ( `[mapSourceFile] Failed to process source map for source: ${ src }` ) ; } return src ; } private unmapSourceFile ( path ) { this . dbgLog ( `[unmapSourceFile] Unmapping file: ${ path }` ) ; path = Path . normalize ( path ) ; let name = Path . basename ( path ) ; let pathUnderRoot = Path . dirname ( this . getSourceNameByPath ( path ) || "" ) ; this . dbgLog ( `[unmapSourceFile] Path under root: ${ pathUnderRoot }` ) ; if ( ! this . _sourceMaps ) { return this . mapSourceFile ( Path . join ( pathUnderRoot , name ) ) ; } let src2gen = this . _sourceToGen ; if ( src2gen [ path ] ) { return src2gen [ path ] ; } const scanDir = ( dirPath , rootPath ) => { let files ; try { files = FS . readdirSync ( dirPath ) ; } catch ( err ) { this . dbgLog ( `[scanDir] Error reading directory: ${ dirPath }` ) ; return ; } files = files . filter ( f => Path . extname ( f ) . toLocaleLowerCase ( ) === ".js" ) ; for ( let i = 0 ; i < files . length ; i ++ ) { let f = files [ i ] ; const stat = FS . lstatSync ( Path . join ( dirPath , f ) ) ; if ( stat . isDirectory ( ) ) { continue ; } const candidate = this . mapSourceFile ( Path . join ( rootPath , f ) ) ; if ( candidate . name === Path . join ( pathUnderRoot , name ) ) { this . dbgLog ( `[scanDir] Found matching file: ${ candidate . name }` ) ; return candidate ; } if ( ! candidate . srcMap ) { continue ; } for ( const candidateFile of candidate . srcMap . _sources ) { if ( candidateFile ) { if ( this . normPath ( Path . resolve ( this . _outDir , candidateFile ) ) === path ) { this . dbgLog ( `[scanDir] Found matching file: ${ candidate }` ) ; return candidate ; } for ( let rpath of this . _sourceRoots ) { if ( this . normPath ( Path . join ( rpath , candidateFile ) ) === path ) { this . dbgLog ( `[scanDir] Found matching file: ${ candidate }` ) ; return candidate ; } } } } } } ; let outDirToScan = Path . join ( this . _outDir , pathUnderRoot ) ; return scanDir ( outDirToScan , pathUnderRoot ) || scanDir ( this . _outDir , "" ) ; } private checkForSourceMap ( src ) { if ( ! this . _args . sourceMaps ) { return ; } src . srcMap = this . _sourceMaps . MapPathFromSource ( src . path , this . _sourceRoots ) ; src . srcMapPath = src . srcMap . generatedPath ( ) ; } private getSourceNameByPath ( fpath ) { fpath = this . normPath ( fpath ) ; for ( let rpath of this . _sourceRoots ) { if ( fpath . indexOf ( rpath ) === 0 ) { const sourceName = fpath . substr ( rpath . length + 1 ) ; this . dbgLog ( `[getSourceNameByPath] Path found in source root '${ rpath }' source name: ${ sourceName }` ) ; return sourceName ; } } return undefined ; } private requestFailedResponse ( response , msg ? ) { msg = msg ? msg . toString ( ) : "" ; msg = "Request failed: " + msg ; this . dbgLog ( "ERROR: " + msg ) ; this . sendErrorResponse ( response , ErrorCode . RequestFailed , msg ) ; } private normPath ( fpath ) { if ( ! fpath ) { fpath = "" ; } fpath = Path . normalize ( fpath ) ; fpath = fpath . replace ( /\\/g , "/" ) ; fpath = fpath . replace ( /^[A-Z]:\// , match => match . toLowerCase ( ) ) ; return fpath ; } private logToClient ( msg , category ? , outputEventOptions ? ) { const outputEvent = new OutputEvent ( msg , category ) ; Object . assign ( outputEvent . body , outputEventOptions ) ; this . sendEvent ( outputEvent ) ; console . log ( msg ) ; } private dbgLog ( msg ) { if ( this . _dbgLog && msg && msg . length > 0 ) { const buf = new Buffer ( msg ) ; for ( let i = 0 , len = buf . length ; i < len ; i ++ ) { if ( buf [ i ] > 0x7f ) { buf [ i ] = 0x3f ; } } msg = buf . toString ( "utf8" ) ; this . sendEvent ( new OutputEvent ( msg + "\n" ) ) ; console . log ( msg ) ; } } }