Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debug protocol compile error update support #116

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions src/CompileErrorProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ export class CompileErrorProcessor {
private emitter = new EventEmitter();
public compileErrorTimer: NodeJS.Timeout;

private diagnostics: BSDebugDiagnostic[] = [];

public addDiagnostic(diagnostic: BSDebugDiagnostic) {
this.diagnostics.push(diagnostic);
}

public on(eventName: 'diagnostics', handler: (params: BSDebugDiagnostic[]) => void);
public on(eventName: string, handler: (payload: any) => void) {
this.emitter.on(eventName, handler);
Expand Down Expand Up @@ -88,7 +94,7 @@ export class CompileErrorProcessor {
}

public getErrors(lines: string[]) {
const result: BSDebugDiagnostic[] = [];
let result: BSDebugDiagnostic[] = [];
//clone the lines so the parsers can manipulate them
lines = [...lines];
while (lines.length > 0) {
Expand All @@ -111,10 +117,13 @@ export class CompileErrorProcessor {
lines.shift();
}
}
return result.filter(x => {
result = result.filter(x => {
//throw out $livecompile errors (those are generated by REPL/eval code)
return x.path && !x.path.toLowerCase().includes('$livecompile');
});
//include any diagnostics added from external sources (i.e. debug protocol)
result.push(...this.diagnostics);
return result;
}

/**
Expand Down Expand Up @@ -147,7 +156,7 @@ export class CompileErrorProcessor {
* Parse the standard syntax and compile error format
*/
private parseSyntaxAndCompileErrors(line: string): BSDebugDiagnostic[] {
let [, message, errorType, code, trailingInfo] = this.execAndTrim(
let [, message, , code, trailingInfo] = this.execAndTrim(
// https://regex101.com/r/HHZ6dE/3
/(.*?)(?:\(((?:syntax|compile)\s+error)\s+(&h[\w\d]+)?\s*\))\s*in\b\s+(.+)/ig,
line
Expand Down Expand Up @@ -373,10 +382,9 @@ export interface BSDebugDiagnostic extends Diagnostic {
*/
path: string;
/**
* The name of the component library this diagnostic was emitted from. Should be undefined if diagnostic originated from the
* main app.
* The name of the library (i.e. component library) this diagnostic was emitted from. Should be undefined if diagnostic originated from the main app.
*/
componentLibraryName?: string;
libraryName?: string;
}

export enum CompileStatus {
Expand Down
21 changes: 20 additions & 1 deletion src/adapters/DebugProtocolAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type { AdapterOptions, HighLevelType, RokuAdapterEvaluateResponse } from
import type { BreakpointManager } from '../managers/BreakpointManager';
import type { ProjectManager } from '../managers/ProjectManager';
import { ActionQueue } from '../managers/ActionQueue';
import { DiagnosticSeverity, util as bscUtil } from 'brighterscript';

/**
* A class that connects to a Roku device over telnet debugger port and provides a standardized way of interacting with it.
Expand Down Expand Up @@ -246,6 +247,24 @@ export class DebugProtocolAdapter {
});
});

this.socketDebugger.on('compile-error', (response) => {
this.compileErrorProcessor.addDiagnostic({
message: response.message,
range: bscUtil.createRange(
//convert 1-based debug-protocol line to 0-based Range
response.lineNumber - 1,
0,
//convert 1-based debug-protocol line to 0-based Range
response.lineNumber - 1,
999
),
//all 'compile-error' events are actual errors
severity: DiagnosticSeverity.Error,
path: response.filePath,
libraryName: response.libraryName
});
});

this.socketDebugger.on('cannot-continue', () => {
this.emit('cannot-continue');
});
Expand Down Expand Up @@ -282,7 +301,7 @@ export class DebugProtocolAdapter {
try {
this.compileClient = new Socket();
this.compileErrorProcessor.on('diagnostics', (errors) => {
this.compileClient.end();
this.compileClient?.end();
this.emit('diagnostics', errors);
});

Expand Down
8 changes: 7 additions & 1 deletion src/debugProtocol/Debugger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { AddBreakpointsResponse } from './responses/AddBreakpointsResponse';
import { RemoveBreakpointsResponse } from './responses/RemoveBreakpointsResponse';
import { util } from '../util';
import { BreakpointErrorUpdateResponse } from './responses/BreakpointErrorUpdateResponse';
import { CompileErrorUpdateResponse } from './responses/CompileErrorUpdateResponse';

export class Debugger {

Expand Down Expand Up @@ -120,6 +121,7 @@ export class Debugger {
public on(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'start', handler: () => void);
public on(eventName: 'data', handler: (data: any) => void);
public on(eventName: 'runtime-error' | 'suspend', handler: (data: UpdateThreadsResponse) => void);
public on(eventName: 'compile-error', handler: (data: CompileErrorUpdateResponse) => void);
public on(eventName: 'connected', handler: (connected: boolean) => void);
public on(eventName: 'io-output', handler: (output: string) => void);
public on(eventName: 'protocol-version', handler: (data: ProtocolVersionDetails) => void);
Expand All @@ -134,6 +136,7 @@ export class Debugger {
}

private emit(eventName: 'suspend' | 'runtime-error', data: UpdateThreadsResponse);
private emit(eventName: 'compile-error', data: CompileErrorUpdateResponse);
private emit(eventName: 'app-exit' | 'cannot-continue' | 'close' | 'connected' | 'data' | 'handshake-verified' | 'io-output' | 'protocol-version' | 'start', data?);
private emit(eventName: string, data?) {
//emit these events on next tick, otherwise they will be processed immediately which could cause issues
Expand Down Expand Up @@ -496,7 +499,10 @@ export class Debugger {
//we do nothing with breakpoint errors at this time.
return this.checkResponse(response, buffer, packetLength);
case UPDATE_TYPES.COMPILE_ERROR:
return this.checkResponse(new UndefinedResponse(slicedBuffer), buffer, packetLength);
const compileError = new CompileErrorUpdateResponse(slicedBuffer);
//emit as an event for the adapter to handle
this.emit('compile-error', compileError);
return this.checkResponse(compileError, buffer, packetLength);
default:
return this.checkResponse(new UndefinedResponse(slicedBuffer), buffer, packetLength);
}
Expand Down
68 changes: 68 additions & 0 deletions src/debugProtocol/responses/CompileErrorUpdateResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { SmartBuffer } from 'smart-buffer';
import { util } from '../../util';
import { UPDATE_TYPES } from '../Constants';

/**
* Data sent as the data segment of message type: COMPILE_ERROR
```
struct CompileErrorUpdateData {
uint32 flags; // Always 0, reserved for future use
utf8z error_string;
utf8z file_spec;
uint32 line_number;
utf8z library_name;
}
```
*/
export class CompileErrorUpdateResponse {

constructor(buffer: Buffer) {
// The minimum size of a undefined response
if (buffer.byteLength >= 12) {
let bufferReader = SmartBuffer.fromBuffer(buffer);
this.requestId = bufferReader.readUInt32LE();

// Updates will always have an id of zero because we didn't ask for this information
if (this.requestId === 0) {
this.errorCode = bufferReader.readUInt32LE();
this.updateType = bufferReader.readUInt32LE();
}
if (this.updateType === UPDATE_TYPES.COMPILE_ERROR) {
try {
this.flags = bufferReader.readUInt32LE(); // flags - always 0, reserved for future use
this.message = util.readStringNT(bufferReader); // error_string
this.filePath = util.readStringNT(bufferReader); // file_spec
this.lineNumber = bufferReader.readUInt32LE(); //line_number
this.libraryName = util.readStringNT(bufferReader); //library_name

} catch (error) {
// Could not process
}
}
}
}
public success = false;
public readOffset = 0;
public requestId = -1;
public errorCode = -1;
public updateType = -1;

public flags: number;

/**
* The message for this compile error
*/
public message: string;
/**
* The file path where the compile error occurred. (in the form `/source/file.brs` or `/components/a/b/c.xml`)
*/
public filePath: string;
/**
* The 1-based line number where the compile error occurred
*/
public lineNumber: number;
/**
* The name of the library where this compile error occurred. (is empty string if for the main app)
*/
public libraryName: string;
}
26 changes: 22 additions & 4 deletions src/debugSession/BrightScriptDebugSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import type { AugmentedSourceBreakpoint } from '../managers/BreakpointManager';
import { BreakpointManager } from '../managers/BreakpointManager';
import type { LogMessage } from '../logging';
import { logger, debugServerLogOutputEventTransport } from '../logging';
import { waitForDebugger } from 'inspector';

export class BrightScriptDebugSession extends BaseDebugSession {
public constructor() {
Expand Down Expand Up @@ -372,7 +371,8 @@ export class BrightScriptDebugSession extends BaseDebugSession {
* Anytime a roku adapter emits diagnostics, this methid is called to handle it.
*/
private async handleDiagnostics(diagnostics: BSDebugDiagnostic[]) {
// Roku device and sourcemap work with 1-based line numbers, VSCode expects 0-based lines.
const result = new Map<string, BSDebugDiagnostic>();

for (let diagnostic of diagnostics) {
let sourceLocation = await this.projectManager.getSourceLocation(diagnostic.path, diagnostic.range.start.line + 1);
if (sourceLocation) {
Expand All @@ -383,9 +383,27 @@ export class BrightScriptDebugSession extends BaseDebugSession {
// TODO: may need to add a custom event if the source location could not be found by the ProjectManager
diagnostic.path = fileUtils.removeLeadingSlash(util.removeFileScheme(diagnostic.path));
}
//override all diagnostic sources to be from `bsdebug`
diagnostic.source = `brs-debug`;
const key = [
s`${diagnostic.path}`,
diagnostic.range.start.line,
diagnostic.range.start.character,
diagnostic.range.end.line,
diagnostic.range.end.character,
diagnostic.code,
diagnostic.message.trim(),
diagnostic.libraryName
].join('-');
//only keep one diagnosic per unique key
result.set(key, diagnostic);
}

this.sendEvent(new DiagnosticsEvent(diagnostics));
this.sendEvent(new DiagnosticsEvent([...result.values()]));

//small timeout before killing the adapter so that the diagnostics can be properly sent to the client
await util.sleep(500);

//stop the roku adapter and exit the channel
void this.rokuAdapter.destroy();
void this.rokuDeploy.pressHomeButton(this.launchConfiguration.host, this.launchConfiguration.remotePort);
Expand Down Expand Up @@ -1092,7 +1110,7 @@ export class BrightScriptDebugSession extends BaseDebugSession {

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

//make the connection
Expand Down