From cab49d90471c247f1d309c68856dd35c2bbc92d8 Mon Sep 17 00:00:00 2001 From: Bronley Date: Tue, 9 Mar 2021 21:07:58 -0500 Subject: [PATCH] Append stack trace to every language server error --- src/LanguageServer.ts | 51 +++++++++++++++++++++++++++++++++++++++++-- tsconfig.json | 1 + 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/LanguageServer.ts b/src/LanguageServer.ts index fc00c95f6..0906a7e29 100644 --- a/src/LanguageServer.ts +++ b/src/LanguageServer.ts @@ -178,6 +178,7 @@ export class LanguageServer { * Called when the client starts initialization * @param params */ + @AddStackToErrorMessage public onInitialize(params: InitializeParams) { let clientCapabilities = params.capabilities; @@ -223,6 +224,7 @@ export class LanguageServer { * Called when the client has finished initializing * @param params */ + @AddStackToErrorMessage private async onInitialized() { let workspaceCreatedDeferred = new Deferred(); this.initialWorkspacesCreated = workspaceCreatedDeferred.promise; @@ -515,6 +517,7 @@ export class LanguageServer { * Provide a list of completion items based on the current cursor position * @param textDocumentPosition */ + @AddStackToErrorMessage private async onCompletion(uri: string, position: Position) { //ensure programs are initialized await this.waitAllProgramFirstRuns(); @@ -539,6 +542,7 @@ export class LanguageServer { * Provide a full completion item from the selection * @param item */ + @AddStackToErrorMessage private onCompletionResolve(item: CompletionItem): CompletionItem { if (item.data === 1) { item.detail = 'TypeScript details'; @@ -550,6 +554,7 @@ export class LanguageServer { return item; } + @AddStackToErrorMessage private async onCodeAction(params: CodeActionParams) { //ensure programs are initialized await this.waitAllProgramFirstRuns(); @@ -665,6 +670,8 @@ export class LanguageServer { await workspace.firstRunPromise; } } + + @AddStackToErrorMessage private async onDidChangeConfiguration() { if (this.hasConfigurationCapability) { await this.reloadWorkspaces(); @@ -683,6 +690,7 @@ export class LanguageServer { * file types are watched (.brs,.bs,.xml,manifest, and any json/text/image files) * @param params */ + @AddStackToErrorMessage private async onDidChangeWatchedFiles(params: DidChangeWatchedFilesParams) { //ensure programs are initialized await this.waitAllProgramFirstRuns(); @@ -854,6 +862,7 @@ export class LanguageServer { } } + @AddStackToErrorMessage private async onHover(params: TextDocumentPositionParams) { //ensure programs are initialized await this.waitAllProgramFirstRuns(); @@ -881,6 +890,7 @@ export class LanguageServer { return hover; } + @AddStackToErrorMessage private async onDocumentClose(textDocument: TextDocument): Promise { let filePath = URI.parse(textDocument.uri).fsPath; let standaloneFileWorkspace = this.standaloneFileWorkspaces[filePath]; @@ -893,6 +903,7 @@ export class LanguageServer { } } + @AddStackToErrorMessage private async validateTextDocument(textDocument: TextDocument): Promise { //ensure programs are initialized await this.waitAllProgramFirstRuns(); @@ -923,7 +934,6 @@ export class LanguageServer { // validate all workspaces await this.validateAllThrottled(); } catch (e) { - this.connection.tracer.log(e); this.sendCriticalFailure(`Critical error parsing/ validating ${filePath}: ${e.message}`); } } @@ -949,6 +959,7 @@ export class LanguageServer { this.connection.sendNotification('build-status', 'success'); } + @AddStackToErrorMessage public async onWorkspaceSymbol(params: WorkspaceSymbolParams) { await this.waitAllProgramFirstRuns(); @@ -968,6 +979,7 @@ export class LanguageServer { return allSymbols as SymbolInformation[]; } + @AddStackToErrorMessage public async onDocumentSymbol(params: DocumentSymbolParams) { await this.waitAllProgramFirstRuns(); @@ -982,6 +994,7 @@ export class LanguageServer { } } + @AddStackToErrorMessage private async onDefinition(params: TextDocumentPositionParams) { await this.waitAllProgramFirstRuns(); @@ -996,6 +1009,7 @@ export class LanguageServer { return results; } + @AddStackToErrorMessage private async onSignatureHelp(params: SignatureHelpParams) { await this.waitAllProgramFirstRuns(); @@ -1020,7 +1034,7 @@ export class LanguageServer { }; return results; } catch (e) { - this.connection.console.error(`error in onSignatureHelp: ${e.message}${e.stack ?? ''}`); + this.connection.console.error(`error in onSignatureHelp: ${e.stack ?? e.message ?? e}`); return { signatures: [], activeSignature: 0, @@ -1029,6 +1043,7 @@ export class LanguageServer { } } + @AddStackToErrorMessage private async onReferences(params: ReferenceParams) { await this.waitAllProgramFirstRuns(); @@ -1069,6 +1084,7 @@ export class LanguageServer { } } + @AddStackToErrorMessage public async onExecuteCommand(params: ExecuteCommandParams) { await this.waitAllProgramFirstRuns(); if (params.command === CustomCommands.TranspileFile) { @@ -1107,3 +1123,34 @@ export interface Workspace { export enum CustomCommands { TranspileFile = 'TranspileFile' } + +/** + * Wraps a method. If there's an error (either sync or via a promise), + * this appends the error's stack trace at the end of the error message so that the connection will + */ +function AddStackToErrorMessage(target: any, propertyKey: string, descriptor: PropertyDescriptor) { + let originalMethod = descriptor.value; + + //wrapping the original method + descriptor.value = function value(...args: any[]) { + try { + let result = originalMethod.apply(this, args); + //if the result looks like a promise, log if there's a rejection + if (result?.then) { + return Promise.resolve(result).catch((e: Error) => { + if (e?.stack) { + e.message = e.stack; + } + return Promise.reject(e); + }); + } else { + return result; + } + } catch (e) { + if (e?.stack) { + e.message = e.stack; + } + throw e; + } + }; +} diff --git a/tsconfig.json b/tsconfig.json index 65d1e6e48..fce17e636 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "strict": true, "strictNullChecks": false, "forceConsistentCasingInFileNames": true, + "experimentalDecorators": true, "preserveConstEnums": true, "downlevelIteration": true, "noUnusedLocals": true,