From a1a320d3d1bd73b150094bfcd0558cf5c9d4ad4c Mon Sep 17 00:00:00 2001 From: Philip Chimento Date: Tue, 2 Jan 2024 16:16:00 -0800 Subject: [PATCH] js: Add utility to improve error message formatting Previously, when we formatted SyntaxErrors with toString(), they wouldn't display the file/line/column where the syntax error occurred. This adds a utility function that performs a more comprehensive formatting that displays location information for SyntaxErrors, as well as the .cause property of the error if it is present. This formatting is equivalent to what we do in gjs-console when logging an error. Part-of: --- js/js-resources.gresource.xml | 1 + js/misc/errorUtils.js | 62 +++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 js/misc/errorUtils.js diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index 5351390a8..f6b8b7e78 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -19,6 +19,7 @@ misc/dateUtils.js misc/dbusUtils.js misc/dependencies.js + misc/errorUtils.js misc/extensionUtils.js misc/fileUtils.js misc/gnomeSession.js diff --git a/js/misc/errorUtils.js b/js/misc/errorUtils.js new file mode 100644 index 000000000..c2bc1c794 --- /dev/null +++ b/js/misc/errorUtils.js @@ -0,0 +1,62 @@ +// Common code for displaying errors to the user in various dialogs + +function formatSyntaxErrorLocation(error) { + const {fileName = '', lineNumber = 0, columnNumber = 0} = error; + return ` @ ${fileName}:${lineNumber}:${columnNumber}`; +} + +function formatExceptionStack(error) { + const {stack} = error; + if (!stack) + return '\n\n(No stack trace)'; + + const indentedStack = stack.split('\n').map(line => ` ${line}`).join('\n'); + return `\n\nStack trace:\n${indentedStack}`; +} + +function formatExceptionWithCause(error, seenCauses) { + let fmt = formatExceptionStack(error); + + const {cause} = error; + if (!cause) + return fmt; + + fmt += `\nCaused by: ${cause}`; + + if (cause !== null && typeof cause === 'object') { + if (seenCauses.has(cause)) + return fmt; // avoid recursion + seenCauses.add(cause); + + fmt += formatExceptionWithCause(cause, seenCauses); + } + + return fmt; +} + +/** + * Formats a thrown exception into a string, including the stack, taking the + * location where a SyntaxError was thrown into account. + * + * @param {Error} error The error to format + * @returns {string} The formatted string + */ +export function formatError(error) { + try { + let fmt = `${error}`; + if (error === null || typeof error !== 'object') + return fmt; + + if (error instanceof SyntaxError) { + fmt += formatSyntaxErrorLocation(error); + fmt += formatExceptionStack(error); + return fmt; + } + + const seenCauses = new Set([error]); + fmt += formatExceptionWithCause(error, seenCauses); + return fmt; + } catch (e) { + return `(could not display error: ${e})`; + } +}