• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokucommunity / roku-debug / #2026

02 Nov 2022 02:41PM UTC coverage: 56.194% (+7.0%) from 49.18%
#2026

push

TwitchBronBron
0.17.0

997 of 1898 branches covered (52.53%)

Branch coverage included in aggregate %.

2074 of 3567 relevant lines covered (58.14%)

15.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

35.42
/src/debugSession/BrightScriptDebugSession.ts
1
import * as fsExtra from 'fs-extra';
1✔
2
import { orderBy } from 'natural-orderby';
1✔
3
import * as path from 'path';
1✔
4
import * as request from 'request';
1✔
5
import { rokuDeploy, CompileError } from 'roku-deploy';
1✔
6
import type { RokuDeploy, RokuDeployOptions } from 'roku-deploy';
7
import {
1✔
8
    BreakpointEvent,
9
    DebugSession as BaseDebugSession,
10
    Handles,
11
    InitializedEvent,
12
    InvalidatedEvent,
13
    OutputEvent,
14
    Scope,
15
    Source,
16
    StackFrame,
17
    StoppedEvent,
18
    TerminatedEvent,
19
    Thread,
20
    Variable
21
} from 'vscode-debugadapter';
22
import type { SceneGraphCommandResponse } from '../SceneGraphDebugCommandController';
23
import { SceneGraphDebugCommandController } from '../SceneGraphDebugCommandController';
1✔
24
import type { DebugProtocol } from 'vscode-debugprotocol';
25
import { defer, util } from '../util';
1✔
26
import { fileUtils, standardizePath as s } from '../FileUtils';
1✔
27
import { ComponentLibraryServer } from '../ComponentLibraryServer';
1✔
28
import { ProjectManager, Project, ComponentLibraryProject } from '../managers/ProjectManager';
1✔
29
import type { EvaluateContainer } from '../adapters/DebugProtocolAdapter';
30
import { DebugProtocolAdapter } from '../adapters/DebugProtocolAdapter';
1✔
31
import { TelnetAdapter } from '../adapters/TelnetAdapter';
1✔
32
import type { BSDebugDiagnostic } from '../CompileErrorProcessor';
33
import {
1✔
34
    LaunchStartEvent,
35
    LogOutputEvent,
36
    RendezvousEvent,
37
    DiagnosticsEvent,
38
    StoppedEventReason,
39
    ChanperfEvent,
40
    DebugServerLogOutputEvent,
41
    ChannelPublishedEvent,
42
    PopupMessageEvent
43
} from './Events';
44
import type { LaunchConfiguration, ComponentLibraryConfiguration } from '../LaunchConfiguration';
45
import { FileManager } from '../managers/FileManager';
1✔
46
import { SourceMapManager } from '../managers/SourceMapManager';
1✔
47
import { LocationManager } from '../managers/LocationManager';
1✔
48
import type { AugmentedSourceBreakpoint } from '../managers/BreakpointManager';
49
import { BreakpointManager } from '../managers/BreakpointManager';
1✔
50
import type { LogMessage } from '../logging';
51
import { logger, debugServerLogOutputEventTransport } from '../logging';
1✔
52
import { waitForDebugger } from 'inspector';
53

54
export class BrightScriptDebugSession extends BaseDebugSession {
1✔
55
    public constructor() {
56
        super();
23✔
57

58
        // this debugger uses one-based lines and columns
59
        this.setDebuggerLinesStartAt1(true);
23✔
60
        this.setDebuggerColumnsStartAt1(true);
23✔
61

62
        //give util a reference to this session to assist in logging across the entire module
63
        util._debugSession = this;
23✔
64
        this.fileManager = new FileManager();
23✔
65
        this.sourceMapManager = new SourceMapManager();
23✔
66
        this.locationManager = new LocationManager(this.sourceMapManager);
23✔
67
        this.breakpointManager = new BreakpointManager(this.sourceMapManager, this.locationManager);
23✔
68
        //send newly-verified breakpoints to vscode
69
        this.breakpointManager.on('breakpoints-verified', (data) => this.onDeviceVerifiedBreakpoints(data));
23✔
70
        this.projectManager = new ProjectManager(this.breakpointManager, this.locationManager);
23✔
71
    }
72

73
    private onDeviceVerifiedBreakpoints(data: { breakpoints: AugmentedSourceBreakpoint[] }) {
74
        this.logger.info('Sending verified device breakpoints to client', data);
2✔
75
        //send all verified breakpoints to the client
76
        for (const breakpoint of data.breakpoints) {
2✔
77
            const event: DebugProtocol.Breakpoint = {
2✔
78
                line: breakpoint.line,
79
                column: breakpoint.column,
80
                verified: true,
81
                id: breakpoint.id,
82
                source: {
83
                    path: breakpoint.srcPath
84
                }
85
            };
86
            this.sendEvent(new BreakpointEvent('changed', event));
2✔
87
        }
88
    }
89

90
    public logger = logger.createLogger(`[${BrightScriptDebugSession.name}]`);
23✔
91

92
    /**
93
     * A sequence used to help identify log statements for requests
94
     */
95
    private idCounter = 1;
23✔
96

97
    public fileManager: FileManager;
98

99
    public projectManager: ProjectManager;
100

101
    public breakpointManager: BreakpointManager;
102

103
    public locationManager: LocationManager;
104

105
    public sourceMapManager: SourceMapManager;
106

107
    //set imports as class properties so they can be spied upon during testing
108
    public rokuDeploy = rokuDeploy as unknown as RokuDeploy;
23✔
109

110
    private componentLibraryServer = new ComponentLibraryServer();
23✔
111

112
    private rokuAdapterDeferred = defer<DebugProtocolAdapter | TelnetAdapter>();
23✔
113
    /**
114
     * A promise that is resolved whenever the app has started running for the first time
115
     */
116
    private firstRunDeferred = defer<void>();
23✔
117

118
    private evaluateRefIdLookup: Record<string, number> = {};
23✔
119
    private evaluateRefIdCounter = 1;
23✔
120

121
    private variables: Record<number, AugmentedVariable> = {};
23✔
122

123
    private variableHandles = new Handles<string>();
23✔
124

125
    private rokuAdapter: DebugProtocolAdapter | TelnetAdapter;
126
    private get enableDebugProtocol() {
127
        return this.launchConfiguration.enableDebugProtocol;
21✔
128
    }
129

130
    private getRokuAdapter() {
131
        return this.rokuAdapterDeferred.promise;
×
132
    }
133

134
    private launchConfiguration: LaunchConfiguration;
135
    private initRequestArgs: DebugProtocol.InitializeRequestArguments;
136

137
    /**
138
     * The 'initialize' request is the first request called by the frontend
139
     * to interrogate the features the debug adapter provides.
140
     */
141
    public initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
142
        this.initRequestArgs = args;
1✔
143
        this.logger.log('initializeRequest');
1✔
144
        // since this debug adapter can accept configuration requests like 'setBreakpoint' at any time,
145
        // we request them early by sending an 'initializeRequest' to the frontend.
146
        // The frontend will end the configuration sequence by calling 'configurationDone' request.
147
        this.sendEvent(new InitializedEvent());
1✔
148

149
        response.body = response.body || {};
1✔
150

151
        // This debug adapter implements the configurationDoneRequest.
152
        response.body.supportsConfigurationDoneRequest = true;
1✔
153

154
        // The debug adapter supports the 'restart' request. In this case a client should not implement 'restart' by terminating and relaunching the adapter but by calling the RestartRequest.
155
        response.body.supportsRestartRequest = true;
1✔
156

157
        // make VS Code to use 'evaluate' when hovering over source
158
        response.body.supportsEvaluateForHovers = true;
1✔
159

160
        // make VS Code to show a 'step back' button
161
        response.body.supportsStepBack = false;
1✔
162

163
        // This debug adapter supports conditional breakpoints
164
        response.body.supportsConditionalBreakpoints = true;
1✔
165

166
        // This debug adapter supports breakpoints that break execution after a specified number of hits
167
        response.body.supportsHitConditionalBreakpoints = true;
1✔
168

169
        // This debug adapter supports log points by interpreting the 'logMessage' attribute of the SourceBreakpoint
170
        response.body.supportsLogPoints = true;
1✔
171

172
        this.sendResponse(response);
1✔
173

174
        //register the debug output log transport writer
175
        debugServerLogOutputEventTransport.setWriter((message: LogMessage) => {
1✔
176
            this.sendEvent(
2✔
177
                new DebugServerLogOutputEvent(
178
                    message.logger.formatMessage(message, false)
179
                )
180
            );
181
        });
182
        this.logger.log('initializeRequest finished');
1✔
183
    }
184

185
    private showPopupMessage(message: string, severity: 'error' | 'warn' | 'info') {
186
        this.sendEvent(new PopupMessageEvent(message, severity));
×
187
    }
188

189
    public async launchRequest(response: DebugProtocol.LaunchResponse, config: LaunchConfiguration) {
190
        this.logger.log('[launchRequest] begin');
×
191
        this.launchConfiguration = config;
×
192

193
        //set the logLevel provided by the launch config
194
        if (this.launchConfiguration.logLevel) {
×
195
            logger.logLevel = this.launchConfiguration.logLevel;
×
196
        }
197

198
        //do a DNS lookup for the host to fix issues with roku rejecting ECP
199
        try {
×
200
            this.launchConfiguration.host = await util.dnsLookup(this.launchConfiguration.host);
×
201
        } catch (e) {
202
            const errorMessage = `Could not resolve ip address for "${this.launchConfiguration.host}"`;
×
203
            this.showPopupMessage(errorMessage, 'error');
×
204
            throw e;
×
205
        }
206

207
        this.projectManager.launchConfiguration = this.launchConfiguration;
×
208
        this.breakpointManager.launchConfiguration = this.launchConfiguration;
×
209

210
        this.sendEvent(new LaunchStartEvent(this.launchConfiguration));
×
211

212
        let error: Error;
213
        this.logger.log('[launchRequest] Packaging and deploying to roku');
×
214
        try {
×
215
            const start = Date.now();
×
216
            //build the main project and all component libraries at the same time
217
            await Promise.all([
×
218
                this.prepareMainProject(),
219
                this.prepareAndHostComponentLibraries(this.launchConfiguration.componentLibraries, this.launchConfiguration.componentLibrariesPort)
220
            ]);
221
            this.logger.log(`Packaging projects took: ${(util.formatTime(Date.now() - start))}`);
×
222

223
            util.log(`Connecting to Roku via ${this.enableDebugProtocol ? 'the BrightScript debug protocol' : 'telnet'} at ${this.launchConfiguration.host}`);
×
224

225
            this.createRokuAdapter(this.launchConfiguration.host);
×
226
            if (!this.enableDebugProtocol) {
×
227
                //connect to the roku debug via telnet
228
                if (!this.rokuAdapter.connected) {
×
229
                    await this.connectRokuAdapter();
×
230
                }
231
            } else {
232
                await (this.rokuAdapter as DebugProtocolAdapter).watchCompileOutput();
×
233
            }
234

235
            await this.runAutomaticSceneGraphCommands(this.launchConfiguration.autoRunSgDebugCommands);
×
236

237
            //press the home button to ensure we're at the home screen
238
            await this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort);
×
239

240
            //pass the debug functions used to locate the client files and lines thought the adapter to the RendezvousTracker
241
            this.rokuAdapter.registerSourceLocator(async (debuggerPath: string, lineNumber: number) => {
×
242
                return this.projectManager.getSourceLocation(debuggerPath, lineNumber);
×
243
            });
244

245
            //pass the log level down thought the adapter to the RendezvousTracker and ChanperfTracker
246
            this.rokuAdapter.setConsoleOutput(this.launchConfiguration.consoleOutput);
×
247

248
            //pass along the console output
249
            if (this.launchConfiguration.consoleOutput === 'full') {
×
250
                this.rokuAdapter.on('console-output', (data) => {
×
251
                    this.sendLogOutput(data);
×
252
                });
253
            } else {
254
                this.rokuAdapter.on('unhandled-console-output', (data) => {
×
255
                    this.sendLogOutput(data);
×
256
                });
257
            }
258

259
            // Send chanperf events to the extension
260
            this.rokuAdapter.on('chanperf', (output) => {
×
261
                this.sendEvent(new ChanperfEvent(output));
×
262
            });
263

264
            // Send rendezvous events to the extension
265
            this.rokuAdapter.on('rendezvous', (output) => {
×
266
                this.sendEvent(new RendezvousEvent(output));
×
267
            });
268

269
            //listen for a closed connection (shut down when received)
270
            this.rokuAdapter.on('close', (reason = '') => {
×
271
                if (reason === 'compileErrors') {
×
272
                    error = new Error('compileErrors');
×
273
                } else {
274
                    error = new Error('Unable to connect to Roku. Is another device already connected?');
×
275
                }
276
            });
277

278
            // handle any compile errors
279
            this.rokuAdapter.on('diagnostics', (diagnostics: BSDebugDiagnostic[]) => {
×
280
                void this.handleDiagnostics(diagnostics);
×
281
            });
282

283
            // close disconnect if required when the app is exited
284
            // eslint-disable-next-line @typescript-eslint/no-misused-promises
285
            this.rokuAdapter.on('app-exit', async () => {
×
286
                if (this.launchConfiguration.stopDebuggerOnAppExit || !this.rokuAdapter.supportsMultipleRuns) {
×
287
                    let message = `App exit event detected${this.rokuAdapter.supportsMultipleRuns ? ' and launchConfiguration.stopDebuggerOnAppExit is true' : ''}`;
×
288
                    message += ' - shutting down debug session';
×
289

290
                    this.logger.log('on app-exit', message);
×
291
                    this.sendEvent(new LogOutputEvent(message));
×
292
                    if (this.rokuAdapter) {
×
293
                        void this.rokuAdapter.destroy();
×
294
                    }
295
                    //return to the home screen
296
                    await this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort);
×
297
                    this.shutdown();
×
298
                    this.sendEvent(new TerminatedEvent());
×
299
                } else {
300
                    const message = 'App exit detected; but launchConfiguration.stopDebuggerOnAppExit is set to false, so keeping debug session running.';
×
301
                    this.logger.log('[launchRequest]', message);
×
302
                    this.sendEvent(new LogOutputEvent(message));
×
303
                }
304
            });
305

306
            //ignore the compile error failure from within the publish
307
            (this.launchConfiguration as any).failOnCompileError = false;
×
308
            // Set the remote debug flag on the args to be passed to roku deploy so the socket debugger can be started if needed.
309
            (this.launchConfiguration as any).remoteDebug = this.enableDebugProtocol;
×
310

311
            await this.connectAndPublish();
×
312

313
            this.sendEvent(new ChannelPublishedEvent(
×
314
                this.launchConfiguration
315
            ));
316

317
            //tell the adapter adapter that the channel has been launched.
318
            await this.rokuAdapter.activate();
×
319

320
            if (!error) {
×
321
                if (this.rokuAdapter.connected) {
×
322
                    this.logger.info('Host connection was established before the main public process was completed');
×
323
                    this.logger.log(`deployed to Roku@${this.launchConfiguration.host}`);
×
324
                    this.sendResponse(response);
×
325
                } else {
326
                    this.logger.info('Main public process was completed but we are still waiting for a connection to the host');
×
327
                    this.rokuAdapter.on('connected', (status) => {
×
328
                        if (status) {
×
329
                            this.logger.log(`deployed to Roku@${this.launchConfiguration.host}`);
×
330
                            this.sendResponse(response);
×
331
                        }
332
                    });
333
                }
334
            } else {
335
                throw error;
×
336
            }
337
        } catch (e) {
338
            //if the message is anything other than compile errors, we want to display the error
339
            if (!(e instanceof CompileError)) {
×
340
                util.log('Encountered an issue during the publish process');
×
341
                util.log((e as Error).message);
×
342
                this.sendErrorResponse(response, -1, (e as Error).message);
×
343
            }
344

345
            //send any compile errors to the client
346
            await this.rokuAdapter.sendErrors();
×
347
            this.logger.error('Error. Shutting down.', e);
×
348
            this.shutdown();
×
349
            return;
×
350
        }
351

352
        //at this point, the project has been deployed. If we need to use a deep link, launch it now.
353
        if (this.launchConfiguration.deepLinkUrl) {
×
354
            //wait until the first entry breakpoint has been hit
355
            await this.firstRunDeferred.promise;
×
356
            //if we are at a breakpoint, continue
357
            await this.rokuAdapter.continue();
×
358
            //kill the app on the roku
359
            await this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort);
×
360
            //convert a hostname to an ip address
361
            const deepLinkUrl = await util.resolveUrl(this.launchConfiguration.deepLinkUrl);
×
362
            //send the deep link http request
363
            await new Promise((resolve, reject) => {
×
364
                request.post(deepLinkUrl, (err, response) => {
×
365
                    return err ? reject(err) : resolve(response);
×
366
                });
367
            });
368
        }
369
    }
370

371
    /**
372
     * Anytime a roku adapter emits diagnostics, this methid is called to handle it.
373
     */
374
    private async handleDiagnostics(diagnostics: BSDebugDiagnostic[]) {
375
        // Roku device and sourcemap work with 1-based line numbers, VSCode expects 0-based lines.
376
        for (let diagnostic of diagnostics) {
1✔
377
            let sourceLocation = await this.projectManager.getSourceLocation(diagnostic.path, diagnostic.range.start.line + 1);
1✔
378
            if (sourceLocation) {
1!
379
                diagnostic.path = sourceLocation.filePath;
1✔
380
                diagnostic.range.start.line = sourceLocation.lineNumber - 1; //sourceLocation is 1-based, but we need 0-based
1✔
381
                diagnostic.range.end.line = sourceLocation.lineNumber - 1; //sourceLocation is 1-based, but we need 0-based
1✔
382
            } else {
383
                // TODO: may need to add a custom event if the source location could not be found by the ProjectManager
384
                diagnostic.path = fileUtils.removeLeadingSlash(util.removeFileScheme(diagnostic.path));
×
385
            }
386
        }
387

388
        this.sendEvent(new DiagnosticsEvent(diagnostics));
1✔
389
        //stop the roku adapter and exit the channel
390
        void this.rokuAdapter.destroy();
1✔
391
        void this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort);
1✔
392
    }
393

394
    private async connectAndPublish() {
395
        let connectPromise: Promise<any>;
396
        //connect to the roku debug via sockets
397
        if (this.enableDebugProtocol) {
×
398
            connectPromise = this.connectRokuAdapter().catch(e => this.logger.error(e));
×
399
        }
400

401
        let packageIsPublished = false;
×
402
        //publish the package to the target Roku
403
        const publishPromise = this.rokuDeploy.publish({
×
404
            ...this.launchConfiguration,
405
            failOnCompileError: true
406
        } as any as RokuDeployOptions).then(() => {
407
            packageIsPublished = true;
×
408
        });
409

410
        await publishPromise;
×
411

412
        //the channel has been deployed. Wait for the adapter to finish connecting.
413
        //if it hasn't connected after 5 seconds, it probably will never connect.
414
        await Promise.race([
×
415
            connectPromise,
416
            util.sleep(5000)
417
        ]);
418
        this.logger.log('Finished racing promises');
×
419
        //if the adapter is still not connected, then it will probably never connect. Abort.
420
        if (packageIsPublished && !this.rokuAdapter.connected) {
×
421
            //kill the session cuz it won't ever come back
422
            await this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort);
×
423
            const message = 'Debug session cancelled: failed to connect to debug protocol control port.';
×
424
            this.showPopupMessage(message, 'error');
×
425
            this.logger.error(message);
×
426
            this.shutdown();
×
427
            this.sendEvent(new TerminatedEvent());
×
428
        }
429
    }
430

431
    /**
432
     * Send log output to the "client" (i.e. vscode)
433
     * @param logOutput
434
     */
435
    private sendLogOutput(logOutput: string) {
436
        const lines = logOutput.split(/\r?\n/g);
×
437
        for (let line of lines) {
×
438
            line += '\n';
×
439
            this.sendEvent(new OutputEvent(line, 'stdout'));
×
440
            this.sendEvent(new LogOutputEvent(line));
×
441
        }
442
    }
443

444
    private async runAutomaticSceneGraphCommands(commands: string[]) {
445
        if (commands) {
×
446
            let connection = new SceneGraphDebugCommandController(this.launchConfiguration.host);
×
447

448
            try {
×
449
                await connection.connect();
×
450
                for (let command of this.launchConfiguration.autoRunSgDebugCommands) {
×
451
                    let response: SceneGraphCommandResponse;
452
                    switch (command) {
×
453
                        case 'chanperf':
454
                            util.log('Enabling Chanperf Tracking');
×
455
                            response = await connection.chanperf({ interval: 1 });
×
456
                            if (!response.error) {
×
457
                                util.log(response.result.rawResponse);
×
458
                            }
459
                            break;
×
460

461
                        case 'fpsdisplay':
462
                            util.log('Enabling FPS Display');
×
463
                            response = await connection.fpsDisplay('on');
×
464
                            if (!response.error) {
×
465
                                util.log(response.result.data as string);
×
466
                            }
467
                            break;
×
468

469
                        case 'logrendezvous':
470
                            util.log('Enabling Rendezvous Logging:');
×
471
                            response = await connection.logrendezvous('on');
×
472
                            if (!response.error) {
×
473
                                util.log(response.result.rawResponse);
×
474
                            }
475
                            break;
×
476

477
                        default:
478
                            util.log(`Running custom SceneGraph debug command on port 8080 '${command}':`);
×
479
                            response = await connection.exec(command);
×
480
                            if (!response.error) {
×
481
                                util.log(response.result.rawResponse);
×
482
                            }
483
                            break;
×
484
                    }
485
                }
486
                await connection.end();
×
487
            } catch (error) {
488
                util.log(`Error connecting to port 8080: ${error.message}`);
×
489
            }
490
        }
491
    }
492

493
    /**
494
     * Stage, insert breakpoints, and package the main project
495
     */
496
    public async prepareMainProject() {
497
        //add the main project
498
        this.projectManager.mainProject = new Project({
1✔
499
            rootDir: this.launchConfiguration.rootDir,
500
            files: this.launchConfiguration.files,
501
            outDir: this.launchConfiguration.outDir,
502
            sourceDirs: this.launchConfiguration.sourceDirs,
503
            bsConst: this.launchConfiguration.bsConst,
504
            injectRaleTrackerTask: this.launchConfiguration.injectRaleTrackerTask,
505
            raleTrackerTaskFileLocation: this.launchConfiguration.raleTrackerTaskFileLocation,
506
            injectRdbOnDeviceComponent: this.launchConfiguration.injectRdbOnDeviceComponent,
507
            rdbFilesBasePath: this.launchConfiguration.rdbFilesBasePath,
508
            stagingFolderPath: this.launchConfiguration.stagingFolderPath
509
        });
510

511
        util.log('Moving selected files to staging area');
1✔
512
        await this.projectManager.mainProject.stage();
1✔
513

514
        //add the entry breakpoint if stopOnEntry is true
515
        await this.handleEntryBreakpoint();
1✔
516

517
        //add breakpoint lines to source files and then publish
518
        util.log('Adding stop statements for active breakpoints');
1✔
519

520
        //write the `stop` statements to every file that has breakpoints (do for telnet, skip for debug protocol)
521
        if (!this.enableDebugProtocol) {
1!
522

523
            await this.breakpointManager.writeBreakpointsForProject(this.projectManager.mainProject);
1✔
524
        }
525

526
        //create zip package from staging folder
527
        util.log('Creating zip archive from project sources');
1✔
528
        await this.projectManager.mainProject.zipPackage({ retainStagingFolder: true });
1✔
529
    }
530

531
    /**
532
     * Accepts custom events and requests from the extension
533
     * @param command name of the command to execute
534
     */
535
    protected customRequest(command: string) {
536
        if (command === 'rendezvous.clearHistory') {
×
537
            this.rokuAdapter.clearRendezvousHistory();
×
538
        }
539

540
        if (command === 'chanperf.clearHistory') {
×
541
            this.rokuAdapter.clearChanperfHistory();
×
542
        }
543
    }
544

545
    /**
546
     * Stores the path to the staging folder for each component library
547
     */
548
    protected async prepareAndHostComponentLibraries(componentLibraries: ComponentLibraryConfiguration[], port: number) {
549
        if (componentLibraries && componentLibraries.length > 0) {
×
550
            let componentLibrariesOutDir = s`${this.launchConfiguration.outDir}/component-libraries`;
×
551
            //make sure this folder exists (and is empty)
552
            await fsExtra.ensureDir(componentLibrariesOutDir);
×
553
            await fsExtra.emptyDir(componentLibrariesOutDir);
×
554

555
            //create a ComponentLibraryProject for each component library
556
            for (let libraryIndex = 0; libraryIndex < componentLibraries.length; libraryIndex++) {
×
557
                let componentLibrary = componentLibraries[libraryIndex];
×
558

559
                this.projectManager.componentLibraryProjects.push(
×
560
                    new ComponentLibraryProject({
561
                        rootDir: componentLibrary.rootDir,
562
                        files: componentLibrary.files,
563
                        outDir: componentLibrariesOutDir,
564
                        outFile: componentLibrary.outFile,
565
                        sourceDirs: componentLibrary.sourceDirs,
566
                        bsConst: componentLibrary.bsConst,
567
                        injectRaleTrackerTask: componentLibrary.injectRaleTrackerTask,
568
                        raleTrackerTaskFileLocation: componentLibrary.raleTrackerTaskFileLocation,
569
                        libraryIndex: libraryIndex
570
                    })
571
                );
572
            }
573

574
            //prepare all of the libraries in parallel
575
            let compLibPromises = this.projectManager.componentLibraryProjects.map(async (compLibProject) => {
×
576

577
                await compLibProject.stage();
×
578

579
                // Add breakpoint lines to the staging files and before publishing
580
                util.log('Adding stop statements for active breakpoints in Component Libraries');
×
581

582
                //write the `stop` statements to every file that has breakpoints (do for telnet, skip for debug protocol)
583
                if (!this.enableDebugProtocol) {
×
584
                    await this.breakpointManager.writeBreakpointsForProject(compLibProject);
×
585
                }
586

587
                await compLibProject.postfixFiles();
×
588

589
                await compLibProject.zipPackage({ retainStagingFolder: true });
×
590
            });
591

592
            let hostingPromise: Promise<any>;
593
            if (compLibPromises) {
×
594
                // prepare static file hosting
595
                hostingPromise = this.componentLibraryServer.startStaticFileHosting(componentLibrariesOutDir, port, (message: string) => {
×
596
                    util.log(message);
×
597
                });
598
            }
599

600
            //wait for all component libaries to finish building, and the file hosting to start up
601
            await Promise.all([
×
602
                ...compLibPromises,
603
                hostingPromise
604
            ]);
605
        }
606
    }
607

608
    protected sourceRequest(response: DebugProtocol.SourceResponse, args: DebugProtocol.SourceArguments) {
609
        this.logger.log('sourceRequest');
×
610
        let old = this.sendResponse;
×
611
        this.sendResponse = function sendResponse(...args) {
×
612
            old.apply(this, args);
×
613
            this.sendResponse = old;
×
614
        };
615
        super.sourceRequest(response, args);
×
616
    }
617

618
    protected configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments) {
619
        this.logger.log('configurationDoneRequest');
×
620
    }
621

622
    /**
623
     * Called every time a breakpoint is created, modified, or deleted, for each file. This receives the entire list of breakpoints every time.
624
     */
625
    public async setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments) {
626
        let sanitizedBreakpoints = this.breakpointManager.replaceBreakpoints(args.source.path, args.breakpoints);
5✔
627
        //sort the breakpoints
628
        let sortedAndFilteredBreakpoints = orderBy(sanitizedBreakpoints, [x => x.line, x => x.column]);
5✔
629

630
        response.body = {
5✔
631
            breakpoints: sortedAndFilteredBreakpoints
632
        };
633
        this.sendResponse(response);
5✔
634

635
        await this.rokuAdapter?.syncBreakpoints();
5!
636
    }
637

638
    protected exceptionInfoRequest(response: DebugProtocol.ExceptionInfoResponse, args: DebugProtocol.ExceptionInfoArguments) {
639
        this.logger.log('exceptionInfoRequest');
×
640
    }
641

642
    protected async threadsRequest(response: DebugProtocol.ThreadsResponse) {
643
        this.logger.log('threadsRequest');
×
644
        //wait for the roku adapter to load
645
        await this.getRokuAdapter();
×
646

647
        let threads = [];
×
648

649
        //only send the threads request if we are at the debugger prompt
650
        if (this.rokuAdapter.isAtDebuggerPrompt) {
×
651
            let rokuThreads = await this.rokuAdapter.getThreads();
×
652

653
            for (let thread of rokuThreads) {
×
654
                threads.push(
×
655
                    new Thread(thread.threadId, `Thread ${thread.threadId}`)
656
                );
657
            }
658
        } else {
659
            this.logger.log('Skipped getting threads because the RokuAdapter is not accepting input at this time.');
×
660
        }
661

662
        response.body = {
×
663
            threads: threads
664
        };
665

666
        this.sendResponse(response);
×
667
    }
668

669
    protected async stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments) {
670
        try {
1✔
671
            this.logger.log('stackTraceRequest');
1✔
672
            let frames = [];
1✔
673

674
            if (this.rokuAdapter.isAtDebuggerPrompt) {
1!
675
                let stackTrace = await this.rokuAdapter.getStackTrace(args.threadId);
1✔
676

677
                for (let debugFrame of stackTrace) {
1✔
678
                    let sourceLocation = await this.projectManager.getSourceLocation(debugFrame.filePath, debugFrame.lineNumber);
3✔
679

680
                    //the stacktrace returns function identifiers in all lower case. Try to get the actual case
681
                    //load the contents of the file and get the correct casing for the function identifier
682
                    try {
3✔
683
                        let functionName = this.fileManager.getCorrectFunctionNameCase(sourceLocation?.filePath, debugFrame.functionIdentifier);
3✔
684
                        if (functionName) {
3!
685

686
                            //search for original function name if this is an anonymous function.
687
                            //anonymous function names are prefixed with $ in the stack trace (i.e. $anon_1 or $functionname_40002)
688
                            if (functionName.startsWith('$')) {
3!
689
                                functionName = this.fileManager.getFunctionNameAtPosition(
×
690
                                    sourceLocation.filePath,
691
                                    sourceLocation.lineNumber - 1,
692
                                    functionName
693
                                );
694
                            }
695
                            debugFrame.functionIdentifier = functionName;
3✔
696
                        }
697
                    } catch (error) {
698
                        this.logger.error('Error correcting function identifier case', { error, sourceLocation, debugFrame });
×
699
                    }
700
                    const filePath = sourceLocation?.filePath ?? debugFrame.filePath;
3✔
701

702
                    const frame: DebugProtocol.StackFrame = new StackFrame(
3✔
703
                        debugFrame.frameId,
704
                        `${debugFrame.functionIdentifier}`,
705
                        new Source(path.basename(filePath), filePath),
706
                        sourceLocation?.lineNumber ?? debugFrame.lineNumber,
18✔
707
                        1
708
                    );
709
                    if (!sourceLocation) {
3✔
710
                        frame.presentationHint = 'subtle';
1✔
711
                    }
712
                    frames.push(frame);
3✔
713
                }
714
            } else {
715
                this.logger.log('Skipped calculating stacktrace because the RokuAdapter is not accepting input at this time');
×
716
            }
717
            response.body = {
1✔
718
                stackFrames: frames,
719
                totalFrames: frames.length
720
            };
721
            this.sendResponse(response);
1✔
722
        } catch (error) {
723
            this.logger.error('Error getting stacktrace', { error, args });
×
724
        }
725
    }
726

727
    protected async scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments) {
728
        const logger = this.logger.createLogger(`scopesRequest ${this.idCounter}`);
×
729
        logger.info('begin', { args });
×
730
        try {
×
731
            const scopes = new Array<Scope>();
×
732

733
            if (this.enableDebugProtocol) {
×
734
                let refId = this.getEvaluateRefId('', args.frameId);
×
735
                let v: AugmentedVariable;
736
                //if we already looked this item up, return it
737
                if (this.variables[refId]) {
×
738
                    v = this.variables[refId];
×
739
                } else {
740
                    let result = await this.rokuAdapter.getVariable('', args.frameId, true);
×
741
                    if (!result) {
×
742
                        throw new Error(`Could not get scopes`);
×
743
                    }
744
                    v = this.getVariableFromResult(result, args.frameId);
×
745
                    //TODO - testing something, remove later
746
                    // eslint-disable-next-line camelcase
747
                    v.request_seq = response.request_seq;
×
748
                    v.frameId = args.frameId;
×
749
                }
750

751
                let scope = new Scope('Local', refId, false);
×
752
                scopes.push(scope);
×
753
            } else {
754
                // NOTE: Legacy telnet support
755
                scopes.push(new Scope('Local', this.variableHandles.create('local'), false));
×
756
            }
757

758
            response.body = {
×
759
                scopes: scopes
760
            };
761
            logger.debug('send response', { response });
×
762
            this.sendResponse(response);
×
763
            logger.info('end');
×
764
        } catch (error) {
765
            logger.error('Error getting scopes', { error, args });
×
766
        }
767
    }
768

769
    protected async continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments) {
770
        this.logger.log('continueRequest');
×
771
        await this.rokuAdapter.continue();
×
772
        this.sendResponse(response);
×
773
    }
774

775
    protected async pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments) {
776
        this.logger.log('pauseRequest');
×
777
        await this.rokuAdapter.pause();
×
778
        this.sendResponse(response);
×
779
    }
780

781
    protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse, args: DebugProtocol.ReverseContinueArguments) {
782
        this.logger.log('reverseContinueRequest');
×
783
        this.sendResponse(response);
×
784
    }
785

786
    /**
787
     * Clicked the "Step Over" button
788
     * @param response
789
     * @param args
790
     */
791
    protected async nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments) {
792
        this.logger.log('[nextRequest] begin');
×
793
        try {
×
794
            await this.rokuAdapter.stepOver(args.threadId);
×
795
            this.logger.info('[nextRequest] end');
×
796
        } catch (error) {
797
            this.logger.error(`[nextRequest] Error running '${BrightScriptDebugSession.prototype.nextRequest.name}()'`, error);
×
798
        }
799
        this.sendResponse(response);
×
800
    }
801

802
    protected async stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments) {
803
        this.logger.log('[stepInRequest]');
×
804
        await this.rokuAdapter.stepInto(args.threadId);
×
805
        this.sendResponse(response);
×
806
        this.logger.info('[stepInRequest] end');
×
807
    }
808

809
    protected async stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments) {
810
        this.logger.log('[stepOutRequest] begin');
×
811
        await this.rokuAdapter.stepOut(args.threadId);
×
812
        this.sendResponse(response);
×
813
        this.logger.info('[stepOutRequest] end');
×
814
    }
815

816
    protected stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments) {
817
        this.logger.log('[stepBackRequest] begin');
×
818
        this.sendResponse(response);
×
819
        this.logger.info('[stepBackRequest] end');
×
820
    }
821

822
    public async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments) {
823
        const logger = this.logger.createLogger('[variablesRequest]');
×
824
        try {
×
825
            logger.log('begin', { args });
×
826

827
            let childVariables: AugmentedVariable[] = [];
×
828
            //wait for any `evaluate` commands to finish so we have a higher likely hood of being at a debugger prompt
829
            await this.evaluateRequestPromise;
×
830
            if (this.rokuAdapter.isAtDebuggerPrompt) {
×
831
                const reference = this.variableHandles.get(args.variablesReference);
×
832
                if (reference) {
×
833
                    logger.log('reference', reference);
×
834
                    // NOTE: Legacy telnet support for local vars
835
                    if (this.launchConfiguration.enableVariablesPanel) {
×
836
                        const vars = await (this.rokuAdapter as TelnetAdapter).getScopeVariables(reference);
×
837

838
                        for (const varName of vars) {
×
839
                            let result = await this.rokuAdapter.getVariable(varName, -1);
×
840
                            let tempVar = this.getVariableFromResult(result, -1);
×
841
                            childVariables.push(tempVar);
×
842
                        }
843
                    } else {
844
                        childVariables.push(new Variable('variables disabled by launch.json setting', 'enableVariablesPanel: false'));
×
845
                    }
846
                } else {
847
                    //find the variable with this reference
848
                    let v = this.variables[args.variablesReference];
×
849
                    logger.log('variable', v);
×
850
                    //query for child vars if we haven't done it yet.
851
                    if (v.childVariables.length === 0) {
×
852
                        let result = await this.rokuAdapter.getVariable(v.evaluateName, v.frameId);
×
853
                        let tempVar = this.getVariableFromResult(result, v.frameId);
×
854
                        tempVar.frameId = v.frameId;
×
855
                        v.childVariables = tempVar.childVariables;
×
856
                    }
857
                    childVariables = v.childVariables;
×
858
                }
859

860
                //if the variable is an array, send only the requested range
861
                if (Array.isArray(childVariables) && args.filter === 'indexed') {
×
862
                    //only send the variable range requested by the debugger
863
                    childVariables = childVariables.slice(args.start, args.start + args.count);
×
864
                }
865
                response.body = {
×
866
                    variables: childVariables
867
                };
868
            } else {
869
                logger.log('Skipped getting variables because the RokuAdapter is not accepting input at this time');
×
870
            }
871
            logger.info('end', { response });
×
872
            this.sendResponse(response);
×
873
        } catch (error) {
874
            logger.error('Error during variablesRequest', error, { args });
×
875
        }
876
    }
877

878
    private evaluateRequestPromise = Promise.resolve();
23✔
879

880
    public async evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments) {
881
        let deferred = defer<void>();
9✔
882
        if (args.context === 'repl' && !this.enableDebugProtocol && args.expression.trim().startsWith('>')) {
9!
883
            this.clearState();
×
884
            const expression = args.expression.replace(/^\s*>\s*/, '');
×
885
            this.logger.log('Sending raw telnet command...I sure hope you know what you\'re doing', { expression });
×
886
            (this.rokuAdapter as TelnetAdapter).requestPipeline.client.write(`${expression}\r\n`);
×
887
            this.sendResponse(response);
×
888
            return deferred.promise;
×
889
        }
890

891
        try {
9✔
892
            this.evaluateRequestPromise = this.evaluateRequestPromise.then(() => {
9✔
893
                return deferred.promise;
9✔
894
            });
895

896
            //fix vscode hover bug that excludes closing quotemark sometimes.
897
            if (args.context === 'hover') {
9✔
898
                args.expression = util.ensureClosingQuote(args.expression);
4✔
899
            }
900

901
            if (!this.rokuAdapter.isAtDebuggerPrompt) {
9✔
902
                let message = 'Skipped evaluate request because RokuAdapter is not accepting requests at this time';
1✔
903
                if (args.context === 'repl') {
1!
904
                    this.sendEvent(new OutputEvent(message, 'stderr'));
1✔
905
                    response.body = {
1✔
906
                        result: 'invalid',
907
                        variablesReference: 0
908
                    };
909
                } else {
910
                    throw new Error(message);
×
911
                }
912

913
                //is at debugger prompt
914
            } else {
915
                const variablePath = util.getVariablePath(args.expression);
8✔
916
                //if we found a variable path (e.g. ['a', 'b', 'c']) then do a variable lookup because it's faster and more widely supported than `evaluate`
917
                if (variablePath) {
8✔
918
                    let refId = this.getEvaluateRefId(args.expression, args.frameId);
5✔
919
                    let v: AugmentedVariable;
920
                    //if we already looked this item up, return it
921
                    if (this.variables[refId]) {
5✔
922
                        v = this.variables[refId];
1✔
923
                    } else {
924
                        let result = await this.rokuAdapter.getVariable(args.expression, args.frameId, true);
4✔
925
                        if (!result) {
4!
926
                            throw new Error(`bad variable request "${args.expression}"`);
×
927
                        }
928
                        v = this.getVariableFromResult(result, args.frameId);
4✔
929
                        //TODO - testing something, remove later
930
                        // eslint-disable-next-line camelcase
931
                        v.request_seq = response.request_seq;
4✔
932
                        v.frameId = args.frameId;
4✔
933
                    }
934
                    response.body = {
5✔
935
                        result: v.value,
936
                        type: v.type,
937
                        variablesReference: v.variablesReference,
938
                        namedVariables: v.namedVariables || 0,
9✔
939
                        indexedVariables: v.indexedVariables || 0
9✔
940
                    };
941

942
                    //run an `evaluate` call
943
                } else {
944
                    if (args.context === 'repl' || !this.enableDebugProtocol) {
3!
945
                        let commandResults = await this.rokuAdapter.evaluate(args.expression, args.frameId);
3✔
946

947
                        commandResults.message = util.trimDebugPrompt(commandResults.message);
3✔
948
                        if (args.context !== 'watch') {
3!
949
                            //clear variable cache since this action could have side-effects
950
                            this.clearState();
3✔
951
                            this.sendInvalidatedEvent(null, args.frameId);
3✔
952
                        }
953
                        //if the adapter captured output (probably only telnet), print it to the vscode debug console
954
                        if (typeof commandResults.message === 'string') {
3!
955
                            this.sendEvent(new OutputEvent(commandResults.message, commandResults.type === 'error' ? 'stderr' : 'stdio'));
×
956
                        }
957

958
                        if (this.enableDebugProtocol || (typeof commandResults.message !== 'string')) {
3!
959
                            response.body = {
3✔
960
                                result: 'invalid',
961
                                variablesReference: 0
962
                            };
963
                        } else {
964
                            response.body = {
×
965
                                result: commandResults.message === '\r\n' ? 'invalid' : commandResults.message,
×
966
                                variablesReference: 0
967
                            };
968
                        }
969
                    } else {
970
                        response.body = {
×
971
                            result: 'invalid',
972
                            variablesReference: 0
973
                        };
974
                    }
975
                }
976
            }
977
        } catch (error) {
978
            this.logger.error('Error during variables request', error);
×
979
        }
980
        //
981
        try {
9✔
982
            this.sendResponse(response);
9✔
983
        } catch { }
984
        deferred.resolve();
9✔
985
    }
986

987
    /**
988
     * Called when the host stops debugging
989
     * @param response
990
     * @param args
991
     */
992
    protected async disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments, request?: DebugProtocol.Request) {
993
        if (this.rokuAdapter) {
×
994
            await this.rokuAdapter.destroy();
×
995
        }
996
        //return to the home screen
997
        if (!this.enableDebugProtocol) {
×
998
            await this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort);
×
999
        }
1000
        this.componentLibraryServer.stop();
×
1001
        this.sendResponse(response);
×
1002
    }
1003

1004
    private createRokuAdapter(host: string) {
1005
        if (this.enableDebugProtocol) {
×
1006
            this.rokuAdapter = new DebugProtocolAdapter(this.launchConfiguration, this.projectManager, this.breakpointManager);
×
1007
        } else {
1008
            this.rokuAdapter = new TelnetAdapter(this.launchConfiguration);
×
1009
        }
1010
    }
1011

1012
    protected async restartRequest(response: DebugProtocol.RestartResponse, args: DebugProtocol.RestartArguments, request?: DebugProtocol.Request) {
1013
        this.logger.log('[restartRequest] begin');
×
1014
        if (this.rokuAdapter) {
×
1015
            if (!this.enableDebugProtocol) {
×
1016
                this.rokuAdapter.removeAllListeners();
×
1017
            }
1018
            await this.rokuAdapter.destroy();
×
1019
            this.rokuAdapterDeferred = defer();
×
1020
        }
1021
        await this.launchRequest(response, args.arguments as LaunchConfiguration);
×
1022
    }
1023

1024
    /**
1025
     * Used to track whether the entry breakpoint has already been handled
1026
     */
1027
    private entryBreakpointWasHandled = false;
23✔
1028

1029
    /**
1030
     * Registers the main events for the RokuAdapter
1031
     */
1032
    private async connectRokuAdapter() {
1033
        this.rokuAdapter.on('start', () => {
×
1034
            if (!this.firstRunDeferred.isCompleted) {
×
1035
                this.firstRunDeferred.resolve();
×
1036
            }
1037
        });
1038

1039
        //when the debugger suspends (pauses for debugger input)
1040
        // eslint-disable-next-line @typescript-eslint/no-misused-promises
1041
        this.rokuAdapter.on('suspend', async () => {
×
1042
            //sync breakpoints
1043
            await this.rokuAdapter?.syncBreakpoints();
×
1044
            this.logger.info('received "suspend" event from adapter');
×
1045

1046
            const threads = await this.rokuAdapter.getThreads();
×
1047
            const activeThread = threads.find(x => x.isSelected);
×
1048

1049
            //TODO remove this once Roku fixes their threads off-by-one line number issues
1050
            //look up the correct line numbers for each thread from the StackTrace
1051
            await Promise.all(
×
1052
                threads.map(async (thread) => {
1053
                    const stackTrace = await this.rokuAdapter.getStackTrace(thread.threadId);
×
1054
                    const stackTraceLineNumber = stackTrace[0]?.lineNumber;
×
1055
                    if (stackTraceLineNumber !== thread.lineNumber) {
×
1056
                        this.logger.warn(`Thread ${thread.threadId} reported incorrect line (${thread.lineNumber}). Using line from stack trace instead (${stackTraceLineNumber})`, thread, stackTrace);
×
1057
                        thread.lineNumber = stackTraceLineNumber;
×
1058
                    }
1059
                })
1060
            );
1061

1062
            //if !stopOnEntry, and we haven't encountered a suspend yet, THIS is the entry breakpoint. auto-continue
1063
            if (!this.entryBreakpointWasHandled && !this.launchConfiguration.stopOnEntry) {
×
1064
                this.entryBreakpointWasHandled = true;
×
1065
                //if there's a user-defined breakpoint at this exact position, it needs to be handled like a regular breakpoint (i.e. suspend). So only auto-continue if there's no breakpoint here
1066
                if (!await this.breakpointManager.lineHasBreakpoint(this.projectManager.getAllProjects(), activeThread.filePath, activeThread.lineNumber - 1)) {
×
1067
                    this.logger.info('Encountered entry breakpoint and `stopOnEntry` is disabled. Continuing...');
×
1068
                    return this.rokuAdapter.continue();
×
1069
                }
1070
            }
1071

1072
            this.clearState();
×
1073
            const event: StoppedEvent = new StoppedEvent(
×
1074
                StoppedEventReason.breakpoint,
1075
                //Not sure why, but sometimes there is no active thread. Just pick thread 0 to prevent the app from totally crashing
1076
                activeThread?.threadId ?? 0,
×
1077
                '' //exception text
1078
            );
1079
            // Socket debugger will always stop all threads and supports multi thread inspection.
1080
            (event.body as any).allThreadsStopped = this.enableDebugProtocol;
×
1081
            this.sendEvent(event);
×
1082
        });
1083

1084
        //anytime the adapter encounters an exception on the roku,
1085
        // eslint-disable-next-line @typescript-eslint/no-misused-promises
1086
        this.rokuAdapter.on('runtime-error', async (exception) => {
×
1087
            let rokuAdapter = await this.getRokuAdapter();
×
1088
            let threads = await rokuAdapter.getThreads();
×
1089
            let threadId = threads[0]?.threadId;
×
1090
            this.sendEvent(new StoppedEvent(StoppedEventReason.exception, threadId, exception.message));
×
1091
        });
1092

1093
        // If the roku says it can't continue, we are no longer able to debug, so kill the debug session
1094
        this.rokuAdapter.on('cannot-continue', () => {
×
1095
            this.sendEvent(new TerminatedEvent());
×
1096
        });
1097

1098
        //make the connection
1099
        await this.rokuAdapter.connect();
×
1100
        this.rokuAdapterDeferred.resolve(this.rokuAdapter);
×
1101
        return this.rokuAdapter;
×
1102
    }
1103

1104
    private getVariableFromResult(result: EvaluateContainer, frameId: number) {
1105
        let v: AugmentedVariable;
1106

1107
        if (result) {
8!
1108
            if (this.enableDebugProtocol) {
8!
1109
                let refId = this.getEvaluateRefId(result.evaluateName, frameId);
×
1110
                if (result.keyType) {
×
1111
                    // check to see if this is an dictionary or a list
1112
                    if (result.keyType === 'Integer') {
×
1113
                        // list type
1114
                        v = new Variable(result.name, result.type, refId, result.elementCount, 0);
×
1115
                        this.variables[refId] = v;
×
1116
                    } else if (result.keyType === 'String') {
×
1117
                        // dictionary type
1118
                        v = new Variable(result.name, result.type, refId, 0, result.elementCount);
×
1119
                    }
1120
                } else {
1121
                    v = new Variable(result.name, `${result.value}`);
×
1122
                }
1123
                this.variables[refId] = v;
×
1124
            } else {
1125
                if (result.highLevelType === 'primative' || result.highLevelType === 'uninitialized') {
8✔
1126
                    v = new Variable(result.name, `${result.value}`);
6✔
1127
                } else if (result.highLevelType === 'array') {
2✔
1128
                    let refId = this.getEvaluateRefId(result.evaluateName, frameId);
1✔
1129
                    v = new Variable(result.name, result.type, refId, result.children?.length ?? 0, 0);
1!
1130
                    this.variables[refId] = v;
1✔
1131
                } else if (result.highLevelType === 'object') {
1!
1132
                    let refId = this.getEvaluateRefId(result.evaluateName, frameId);
1✔
1133
                    v = new Variable(result.name, result.type, refId, 0, result.children?.length ?? 0);
1!
1134
                    this.variables[refId] = v;
1✔
1135
                } else if (result.highLevelType === 'function') {
×
1136
                    v = new Variable(result.name, result.value);
×
1137
                } else {
1138
                    //all other cases, but mostly for HighLevelType.unknown
1139
                    v = new Variable(result.name, result.value);
×
1140
                }
1141
            }
1142

1143
            v.type = result.type;
8✔
1144
            v.evaluateName = result.evaluateName;
8✔
1145
            v.frameId = frameId;
8✔
1146
            v.type = result.type;
8✔
1147
            v.presentationHint = result.presentationHint ? { kind: result.presentationHint } : undefined;
8!
1148

1149
            if (result.children) {
8✔
1150
                let childVariables = [];
2✔
1151
                for (let childContainer of result.children) {
2✔
1152
                    let childVar = this.getVariableFromResult(childContainer, frameId);
4✔
1153
                    childVariables.push(childVar);
4✔
1154
                }
1155
                v.childVariables = childVariables;
2✔
1156
            }
1157
        }
1158
        return v;
8✔
1159
    }
1160

1161

1162
    private getEvaluateRefId(expression: string, frameId: number) {
1163
        let evaluateRefId = `${expression}-${frameId}`;
9✔
1164
        if (!this.evaluateRefIdLookup[evaluateRefId]) {
9✔
1165
            this.evaluateRefIdLookup[evaluateRefId] = this.evaluateRefIdCounter++;
6✔
1166
        }
1167
        return this.evaluateRefIdLookup[evaluateRefId];
9✔
1168
    }
1169

1170
    private clearState() {
1171
        //erase all cached variables
1172
        this.variables = {};
3✔
1173
    }
1174

1175
    /**
1176
     * Tells the client to re-request all variables because we've invalidated them
1177
     * @param threadId
1178
     * @param stackFrameId
1179
     */
1180
    private sendInvalidatedEvent(threadId?: number, stackFrameId?: number) {
1181
        //if the client supports this request, send it
1182
        if (this.initRequestArgs.supportsInvalidatedEvent) {
3✔
1183
            this.sendEvent(new InvalidatedEvent(['variables'], threadId, stackFrameId));
1✔
1184
        }
1185
    }
1186

1187
    /**
1188
     * If `stopOnEntry` is enabled, register the entry breakpoint.
1189
     */
1190
    public async handleEntryBreakpoint() {
1191
        if (!this.enableDebugProtocol) {
3!
1192
            this.entryBreakpointWasHandled = true;
3✔
1193
            if (this.launchConfiguration.stopOnEntry || this.launchConfiguration.deepLinkUrl) {
3✔
1194
                await this.projectManager.registerEntryBreakpoint(this.projectManager.mainProject.stagingFolderPath);
1✔
1195
            }
1196
        }
1197
    }
1198

1199
    /**
1200
     * Called when the debugger is terminated
1201
     */
1202
    public shutdown() {
1203
        //if configured, delete the staging directory
1204
        if (!this.launchConfiguration.retainStagingFolder) {
1!
1205
            let stagingFolderPaths = this.projectManager.getStagingFolderPaths();
1✔
1206
            for (let stagingFolderPath of stagingFolderPaths) {
1✔
1207
                try {
2✔
1208
                    fsExtra.removeSync(stagingFolderPath);
2✔
1209
                } catch (e) {
1210
                    util.log(`Error removing staging directory '${stagingFolderPath}': ${JSON.stringify(e)}`);
×
1211
                }
1212
            }
1213
        }
1214
        super.shutdown();
1✔
1215
    }
1216
}
1217

1218
interface AugmentedVariable extends DebugProtocol.Variable {
1219
    childVariables?: AugmentedVariable[];
1220
    // eslint-disable-next-line camelcase
1221
    request_seq?: number;
1222
    frameId?: number;
1223
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc