From 1d921de3907e8052ebe405d0edb8279a1be4fd32 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 21 Dec 2017 17:10:40 +0100 Subject: [PATCH] [html] catch exceptions in handlers --- extensions/html/server/src/htmlServerMain.ts | 257 +++++++++++-------- extensions/html/server/src/utils/errors.ts | 33 +++ 2 files changed, 179 insertions(+), 111 deletions(-) create mode 100644 extensions/html/server/src/utils/errors.ts diff --git a/extensions/html/server/src/htmlServerMain.ts b/extensions/html/server/src/htmlServerMain.ts index 8ce00331d83..85383529fd4 100644 --- a/extensions/html/server/src/htmlServerMain.ts +++ b/extensions/html/server/src/htmlServerMain.ts @@ -16,6 +16,7 @@ import { format } from './modes/formatting'; import { pushAll } from './utils/arrays'; import { getDocumentContext } from './utils/documentContext'; import uri from 'vscode-uri'; +import { formatError, runSafe } from './utils/errors'; import * as nls from 'vscode-nls'; @@ -31,6 +32,10 @@ let connection: IConnection = createConnection(); console.log = connection.console.log.bind(connection.console); console.error = connection.console.error.bind(connection.console); +process.on('unhandledRejection', (e: any) => { + connection.console.error(formatError(`Unhandled exception`, e)); +}); + // Create a simple text document manager. The text document manager // supports full document sync only let documents: TextDocuments = new TextDocuments(); @@ -208,165 +213,195 @@ function isValidationEnabled(languageId: string, settings: Settings = globalSett } async function validateTextDocument(textDocument: TextDocument) { - let diagnostics: Diagnostic[] = []; - if (textDocument.languageId === 'html') { - let modes = languageModes.getAllModesInDocument(textDocument); - let settings = await getDocumentSettings(textDocument, () => modes.some(m => !!m.doValidation)); - modes.forEach(mode => { - if (mode.doValidation && isValidationEnabled(mode.getId(), settings)) { - pushAll(diagnostics, mode.doValidation(textDocument, settings)); - } - }); + try { + let diagnostics: Diagnostic[] = []; + if (textDocument.languageId === 'html') { + let modes = languageModes.getAllModesInDocument(textDocument); + let settings = await getDocumentSettings(textDocument, () => modes.some(m => !!m.doValidation)); + modes.forEach(mode => { + if (mode.doValidation && isValidationEnabled(mode.getId(), settings)) { + pushAll(diagnostics, mode.doValidation(textDocument, settings)); + } + }); + } + connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); + } catch (e) { + connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e)); } - connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); } connection.onCompletion(async textDocumentPosition => { - let document = documents.get(textDocumentPosition.textDocument.uri); - let mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); - if (mode && mode.doComplete) { - let doComplete = mode.doComplete; - if (mode.getId() !== 'html') { - connection.telemetry.logEvent({ key: 'html.embbedded.complete', value: { languageId: mode.getId() } }); + return runSafe(async () => { + let document = documents.get(textDocumentPosition.textDocument.uri); + let mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); + if (mode && mode.doComplete) { + let doComplete = mode.doComplete; + if (mode.getId() !== 'html') { + connection.telemetry.logEvent({ key: 'html.embbedded.complete', value: { languageId: mode.getId() } }); + } + let settings = await getDocumentSettings(document, () => doComplete.length > 2); + return doComplete(document, textDocumentPosition.position, settings); } - let settings = await getDocumentSettings(document, () => doComplete.length > 2); - return doComplete(document, textDocumentPosition.position, settings); - } - return { isIncomplete: true, items: [] }; + return { isIncomplete: true, items: [] }; + }, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`); }); connection.onCompletionResolve(item => { - let data = item.data; - if (data && data.languageId && data.uri) { - let mode = languageModes.getMode(data.languageId); - let document = documents.get(data.uri); - if (mode && mode.doResolve && document) { - return mode.doResolve(document, item); + return runSafe(() => { + let data = item.data; + if (data && data.languageId && data.uri) { + let mode = languageModes.getMode(data.languageId); + let document = documents.get(data.uri); + if (mode && mode.doResolve && document) { + return mode.doResolve(document, item); + } } - } - return item; + return item; + }, null, `Error while resolving completion proposal`); }); connection.onHover(textDocumentPosition => { - let document = documents.get(textDocumentPosition.textDocument.uri); - let mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); - if (mode && mode.doHover) { - return mode.doHover(document, textDocumentPosition.position); - } - return null; + return runSafe(() => { + let document = documents.get(textDocumentPosition.textDocument.uri); + let mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); + if (mode && mode.doHover) { + return mode.doHover(document, textDocumentPosition.position); + } + return null; + }, null, `Error while computing hover for ${textDocumentPosition.textDocument.uri}`); }); connection.onDocumentHighlight(documentHighlightParams => { - let document = documents.get(documentHighlightParams.textDocument.uri); - let mode = languageModes.getModeAtPosition(document, documentHighlightParams.position); - if (mode && mode.findDocumentHighlight) { - return mode.findDocumentHighlight(document, documentHighlightParams.position); - } - return []; + return runSafe(() => { + let document = documents.get(documentHighlightParams.textDocument.uri); + let mode = languageModes.getModeAtPosition(document, documentHighlightParams.position); + if (mode && mode.findDocumentHighlight) { + return mode.findDocumentHighlight(document, documentHighlightParams.position); + } + return []; + }, [], `Error while computing document highlights for ${documentHighlightParams.textDocument.uri}`); }); connection.onDefinition(definitionParams => { - let document = documents.get(definitionParams.textDocument.uri); - let mode = languageModes.getModeAtPosition(document, definitionParams.position); - if (mode && mode.findDefinition) { - return mode.findDefinition(document, definitionParams.position); - } - return []; + return runSafe(() => { + let document = documents.get(definitionParams.textDocument.uri); + let mode = languageModes.getModeAtPosition(document, definitionParams.position); + if (mode && mode.findDefinition) { + return mode.findDefinition(document, definitionParams.position); + } + return []; + }, null, `Error while computing definitions for ${definitionParams.textDocument.uri}`); }); connection.onReferences(referenceParams => { - let document = documents.get(referenceParams.textDocument.uri); - let mode = languageModes.getModeAtPosition(document, referenceParams.position); - if (mode && mode.findReferences) { - return mode.findReferences(document, referenceParams.position); - } - return []; + return runSafe(() => { + let document = documents.get(referenceParams.textDocument.uri); + let mode = languageModes.getModeAtPosition(document, referenceParams.position); + if (mode && mode.findReferences) { + return mode.findReferences(document, referenceParams.position); + } + return []; + }, [], `Error while computing references for ${referenceParams.textDocument.uri}`); }); connection.onSignatureHelp(signatureHelpParms => { - let document = documents.get(signatureHelpParms.textDocument.uri); - let mode = languageModes.getModeAtPosition(document, signatureHelpParms.position); - if (mode && mode.doSignatureHelp) { - return mode.doSignatureHelp(document, signatureHelpParms.position); - } - return null; + return runSafe(() => { + let document = documents.get(signatureHelpParms.textDocument.uri); + let mode = languageModes.getModeAtPosition(document, signatureHelpParms.position); + if (mode && mode.doSignatureHelp) { + return mode.doSignatureHelp(document, signatureHelpParms.position); + } + return null; + }, null, `Error while computing signature help for ${signatureHelpParms.textDocument.uri}`); }); connection.onDocumentRangeFormatting(async formatParams => { - let document = documents.get(formatParams.textDocument.uri); - let settings = await getDocumentSettings(document, () => true); - if (!settings) { - settings = globalSettings; - } - let unformattedTags: string = settings && settings.html && settings.html.format && settings.html.format.unformatted || ''; - let enabledModes = { css: !unformattedTags.match(/\bstyle\b/), javascript: !unformattedTags.match(/\bscript\b/) }; + return runSafe(async () => { + let document = documents.get(formatParams.textDocument.uri); + let settings = await getDocumentSettings(document, () => true); + if (!settings) { + settings = globalSettings; + } + let unformattedTags: string = settings && settings.html && settings.html.format && settings.html.format.unformatted || ''; + let enabledModes = { css: !unformattedTags.match(/\bstyle\b/), javascript: !unformattedTags.match(/\bscript\b/) }; - return format(languageModes, document, formatParams.range, formatParams.options, settings, enabledModes); + return format(languageModes, document, formatParams.range, formatParams.options, settings, enabledModes); + }, [], `Error while formatting range for ${formatParams.textDocument.uri}`); }); connection.onDocumentLinks(documentLinkParam => { - let document = documents.get(documentLinkParam.textDocument.uri); - let links: DocumentLink[] = []; - if (document) { - let documentContext = getDocumentContext(document.uri, workspaceFolders); - languageModes.getAllModesInDocument(document).forEach(m => { - if (m.findDocumentLinks) { - pushAll(links, m.findDocumentLinks(document, documentContext)); - } - }); - } - return links; + return runSafe(() => { + let document = documents.get(documentLinkParam.textDocument.uri); + let links: DocumentLink[] = []; + if (document) { + let documentContext = getDocumentContext(document.uri, workspaceFolders); + languageModes.getAllModesInDocument(document).forEach(m => { + if (m.findDocumentLinks) { + pushAll(links, m.findDocumentLinks(document, documentContext)); + } + }); + } + return links; + }, [], `Error while document links for ${documentLinkParam.textDocument.uri}`); }); connection.onDocumentSymbol(documentSymbolParms => { - let document = documents.get(documentSymbolParms.textDocument.uri); - let symbols: SymbolInformation[] = []; - languageModes.getAllModesInDocument(document).forEach(m => { - if (m.findDocumentSymbols) { - pushAll(symbols, m.findDocumentSymbols(document)); - } - }); - return symbols; + return runSafe(() => { + let document = documents.get(documentSymbolParms.textDocument.uri); + let symbols: SymbolInformation[] = []; + languageModes.getAllModesInDocument(document).forEach(m => { + if (m.findDocumentSymbols) { + pushAll(symbols, m.findDocumentSymbols(document)); + } + }); + return symbols; + }, [], `Error while computing document symbols for ${documentSymbolParms.textDocument.uri}`); }); connection.onRequest(DocumentColorRequest.type, params => { - let infos: ColorInformation[] = []; - let document = documents.get(params.textDocument.uri); - if (document) { - languageModes.getAllModesInDocument(document).forEach(m => { - if (m.findDocumentColors) { - pushAll(infos, m.findDocumentColors(document)); - } - }); - } - return infos; + return runSafe(() => { + let infos: ColorInformation[] = []; + let document = documents.get(params.textDocument.uri); + if (document) { + languageModes.getAllModesInDocument(document).forEach(m => { + if (m.findDocumentColors) { + pushAll(infos, m.findDocumentColors(document)); + } + }); + } + return infos; + }, [], `Error while computing document colors for ${params.textDocument.uri}`); }); connection.onRequest(ColorPresentationRequest.type, params => { - let document = documents.get(params.textDocument.uri); - if (document) { - let mode = languageModes.getModeAtPosition(document, params.range.start); - if (mode && mode.getColorPresentations) { - return mode.getColorPresentations(document, params.color, params.range); + return runSafe(() => { + let document = documents.get(params.textDocument.uri); + if (document) { + let mode = languageModes.getModeAtPosition(document, params.range.start); + if (mode && mode.getColorPresentations) { + return mode.getColorPresentations(document, params.color, params.range); + } } - } - return []; + return []; + }, [], `Error while computing color presentations for ${params.textDocument.uri}`); }); connection.onRequest(TagCloseRequest.type, params => { - let document = documents.get(params.textDocument.uri); - if (document) { - let pos = params.position; - if (pos.character > 0) { - let mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1)); - if (mode && mode.doAutoClose) { - return mode.doAutoClose(document, pos); + return runSafe(() => { + let document = documents.get(params.textDocument.uri); + if (document) { + let pos = params.position; + if (pos.character > 0) { + let mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1)); + if (mode && mode.doAutoClose) { + return mode.doAutoClose(document, pos); + } } } - } - return null; + return null; + }, null, `Error while computing tag close actions for ${params.textDocument.uri}`); }); diff --git a/extensions/html/server/src/utils/errors.ts b/extensions/html/server/src/utils/errors.ts new file mode 100644 index 00000000000..d5a0c8e7d05 --- /dev/null +++ b/extensions/html/server/src/utils/errors.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +export function formatError(message: string, err: any): string { + if (err instanceof Error) { + let error = err; + return `${message}: ${error.message}\n${error.stack}`; + } else if (typeof err === 'string') { + return `${message}: ${err}`; + } else if (err) { + return `${message}: ${err.toString()}`; + } + return message; +} + +export function runSafe(func: () => Thenable | T, errorVal: T, errorMessage: string): Thenable | T { + try { + let t = func(); + if (t instanceof Promise) { + return t.then(void 0, e => { + console.error(formatError(errorMessage, e)); + return errorVal; + }); + } + return t; + } catch (e) { + console.error(formatError(errorMessage, e)); + return errorVal; + } +} \ No newline at end of file