diff --git a/src/core/before-save.js b/src/core/before-save.js
index a2d58c10e7..e79d8a6515 100644
--- a/src/core/before-save.js
+++ b/src/core/before-save.js
@@ -38,8 +38,7 @@ function performTransformations(transforms, doc) {
const nameOrPosition = `\`${fn.name}\`` || `at position ${pos}`;
const msg = docLink`Function ${nameOrPosition}\` threw an error during processing of ${"[beforeSave]"}.`;
const hint = "See developer console.";
- showError(msg, name, { hint });
- console.error(err);
+ showError(msg, name, { hint, cause: err });
} finally {
pos++;
}
diff --git a/src/core/caniuse.js b/src/core/caniuse.js
index 7b4ad3b5a7..5d7f36612c 100644
--- a/src/core/caniuse.js
+++ b/src/core/caniuse.js
@@ -103,8 +103,7 @@ export async function run(conf) {
function handleError(err, options, featureURL) {
const msg = `Failed to retrieve feature "${options.feature}".`;
const hint = docLink`Please check the feature key on [caniuse.com](https://caniuse.com) and update ${"[caniuse]"}.`;
- showError(msg, name, { hint });
- console.error(err);
+ showError(msg, name, { hint, cause: err });
return html`caniuse.com`;
}
diff --git a/src/core/contrib.js b/src/core/contrib.js
index 0bd6673b89..621dd43a28 100644
--- a/src/core/contrib.js
+++ b/src/core/contrib.js
@@ -60,8 +60,7 @@ async function showContributors(editors, apiURL) {
);
} catch (error) {
const msg = "Error loading contributors from GitHub.";
- showError(msg, name);
- console.error(error);
+ showError(msg, name, { cause: error });
return null;
}
}
diff --git a/src/core/custom-elements/rs-changelog.js b/src/core/custom-elements/rs-changelog.js
index dd477328da..26d77aa908 100644
--- a/src/core/custom-elements/rs-changelog.js
+++ b/src/core/custom-elements/rs-changelog.js
@@ -34,7 +34,9 @@ export const element = class ChangelogElement extends HTMLElement {
${{
any: fetchCommits(from, to, filter)
.then(commits => toHTML(commits))
- .catch(error => showError(error.message, name, { elements: [this] }))
+ .catch(error =>
+ showError(error.message, name, { elements: [this], cause: error })
+ )
.finally(() => {
this.dispatchEvent(new CustomEvent("done"));
}),
@@ -70,8 +72,7 @@ async function fetchCommits(from, to, filter) {
commits = commits.filter(filter);
} catch (error) {
const msg = `Error loading commits from GitHub. ${error.message}`;
- console.error(error);
- throw new Error(msg);
+ throw new Error(msg, { cause: error });
}
return commits;
}
diff --git a/src/core/data-include.js b/src/core/data-include.js
index 49d4f1222f..55ffa0dbee 100644
--- a/src/core/data-include.js
+++ b/src/core/data-include.js
@@ -102,8 +102,7 @@ async function runIncludes(root, currentDepth) {
}
} catch (err) {
const msg = `\`data-include\` failed: \`${url}\` (${err.message}).`;
- console.error(msg, el, err);
- showError(msg, name, { elements: [el] });
+ showError(msg, name, { elements: [el], cause: err });
}
});
await Promise.all(promisesToInclude);
diff --git a/src/core/post-process.js b/src/core/post-process.js
index ad4a4ae338..20d0fc53f3 100644
--- a/src/core/post-process.js
+++ b/src/core/post-process.js
@@ -32,8 +32,7 @@ export async function run(config) {
} catch (err) {
const msg = `Function ${f.name} threw an error during \`postProcess\`.`;
const hint = "See developer console.";
- showError(msg, name, { hint });
- console.error(err);
+ showError(msg, name, { hint, cause: err });
}
});
await Promise.all(promises);
diff --git a/src/core/pre-process.js b/src/core/pre-process.js
index 5eb830a721..b9a389f920 100644
--- a/src/core/pre-process.js
+++ b/src/core/pre-process.js
@@ -31,8 +31,7 @@ export async function run(config) {
} catch (err) {
const msg = `Function ${f.name} threw an error during \`preProcess\`.`;
const hint = "See developer console.";
- showError(msg, name, { hint });
- console.error(err);
+ showError(msg, name, { hint, cause: err });
}
});
await Promise.all(promises);
diff --git a/src/core/utils.js b/src/core/utils.js
index 373e7fc66b..5cd3fb622e 100644
--- a/src/core/utils.js
+++ b/src/core/utils.js
@@ -294,8 +294,7 @@ export function runTransforms(content, flist, ...funcArgs) {
} catch (e) {
const msg = `call to \`${meth}()\` failed with: ${e}.`;
const hint = "See developer console for stack trace.";
- showWarning(msg, "utils/runTransforms", { hint });
- console.error(e);
+ showWarning(msg, "utils/runTransforms", { hint, cause: e });
}
}
}
@@ -850,7 +849,7 @@ export class RespecError extends Error {
* @param {Parameters[2] & { isWarning: boolean }} options
*/
constructor(message, plugin, options) {
- super(message);
+ super(message, { ...(options.cause && { cause: options.cause }) });
const name = options.isWarning ? "ReSpecWarning" : "ReSpecError";
Object.assign(this, { message, plugin, name, ...options });
if (options.elements) {
@@ -864,7 +863,23 @@ export class RespecError extends Error {
const { message, name, stack } = this;
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/26792
const { plugin, hint, elements, title, details } = this;
- return { message, name, plugin, hint, elements, title, details, stack };
+ return {
+ message,
+ name,
+ plugin,
+ hint,
+ elements,
+ title,
+ details,
+ stack,
+ ...(this.cause instanceof Error && {
+ cause: {
+ name: this.cause.name,
+ message: this.cause.message,
+ stack: this.cause.stack,
+ },
+ }),
+ };
}
}
@@ -876,6 +891,7 @@ export class RespecError extends Error {
* @param {HTMLElement[]} [options.elements] Offending elements.
* @param {string} [options.title] Title attribute for offending elements. Can be a shorter form of the message.
* @param {string} [options.details] Any further details/context.
+ * @param {Error} [options.cause] The error that caused this one.
*/
export function showError(message, pluginName, options = {}) {
const opts = { ...options, isWarning: false };
@@ -890,6 +906,7 @@ export function showError(message, pluginName, options = {}) {
* @param {HTMLElement[]} [options.elements] Offending elements.
* @param {string} [options.title] Title attribute for offending elements. Can be a shorter form of the message.
* @param {string} [options.details] Any further details/context.
+ * @param {Error} [options.cause] The error that caused this one.
*/
export function showWarning(message, pluginName, options = {}) {
const opts = { ...options, isWarning: true };
diff --git a/src/jsconfig.json b/src/jsconfig.json
index 248cbb30e2..d63e4b2e55 100644
--- a/src/jsconfig.json
+++ b/src/jsconfig.json
@@ -1,8 +1,8 @@
{
"compilerOptions": {
- "target": "es2019",
+ "target": "es2022",
"moduleResolution": "node",
"noImplicitThis": true,
- "module": "es2020"
+ "module": "es2022"
}
}
diff --git a/tools/respec2html.js b/tools/respec2html.js
index 6aa53ddf66..931217c94b 100755
--- a/tools/respec2html.js
+++ b/tools/respec2html.js
@@ -90,15 +90,32 @@ class Logger {
/** @param {import("./respecDocWriter").ReSpecError} rsError */
_printDetails(rsError) {
+ const shouldPrintStacktrace = this._shouldPrintStacktrace(rsError);
const print = (title, value) => {
if (!value) return;
- const padWidth = "Plugin".length + 1; // "Plugin" is the longest title
+ const longestTitle = shouldPrintStacktrace ? "Stacktrace" : "Plugin";
+ const padWidth = longestTitle.length + 1;
const paddedTitle = `${title}:`.padStart(padWidth);
console.error(" ", colors.bold(paddedTitle), this._formatMarkdown(value));
};
print("Count", rsError.elements && String(rsError.elements.length));
print("Plugin", rsError.plugin);
print("Hint", rsError.hint);
+ if (shouldPrintStacktrace) {
+ let stacktrace = `${rsError.stack}`;
+ if (rsError.cause) {
+ stacktrace += `\n ${colors.bold("Caused by:")} ${rsError.cause.stack.split("\n").join("\n ")}`;
+ }
+ print("Stacktrace", stacktrace);
+ }
+ }
+
+ _shouldPrintStacktrace(rsError) {
+ return (
+ this.verbose &&
+ !!rsError.stack &&
+ (!!rsError.cause?.stack || rsError.plugin === "unknown")
+ );
}
}
diff --git a/tools/respecDocWriter.js b/tools/respecDocWriter.js
index 3da3da07bd..e0454eb9c0 100644
--- a/tools/respecDocWriter.js
+++ b/tools/respecDocWriter.js
@@ -310,10 +310,19 @@ function handleConsoleMessages(page, onError, onWarning) {
// Old ReSpec versions might report errors as strings.
return JSON.stringify({ message: String(obj) });
} else if (obj instanceof Error && !obj.plugin) {
+ let cause;
+ if (obj.cause instanceof Error) {
+ cause = {
+ name: obj.cause.name,
+ message: obj.cause.message,
+ stack: obj.cause.stack,
+ };
+ }
return JSON.stringify({
message: obj.message,
plugin: "unknown",
name: obj.name,
+ cause,
stack: obj.stack?.replace(
obj.message,
`${obj.message.slice(0, 30)}…`