-
-
Notifications
You must be signed in to change notification settings - Fork 187
Commit
This commit addresses the issue of Chromium v126 and later not displaying error messages correctly when the error object's `message` property uses a getter. It refactors the code to utilize an immutable Error object with recursive context, improves error message formatting and leverages the `cause` property. Changes: - Refactor error wrapping internals to use an immutable error object, eliminating `message` getters. - Utilize the `cause` property in contextual errors for enhanced error display in the console. - Enhance message formatting with better indentation and listing. - Improve clarity by renaming values thrown during validations.
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,42 +1,116 @@ | ||
import { CustomError } from '@/application/Common/CustomError'; | ||
import { indentText } from '@/application/Common/Text/IndentText'; | ||
|
||
export interface ErrorWithContextWrapper { | ||
( | ||
error: Error, | ||
innerError: Error, | ||
additionalContext: string, | ||
): Error; | ||
} | ||
|
||
export const wrapErrorWithAdditionalContext: ErrorWithContextWrapper = ( | ||
error: Error, | ||
additionalContext: string, | ||
innerError, | ||
additionalContext, | ||
) => { | ||
return (error instanceof ContextualError ? error : new ContextualError(error)) | ||
.withAdditionalContext(additionalContext); | ||
if (!additionalContext) { | ||
throw new Error('Missing additional context'); | ||
} | ||
return new ContextualError({ | ||
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (ubuntu)../../tests/integration/composite/DependencyResolution.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (ubuntu)../../tests/integration/presentation/components/Scripts/View/Tree/Shared/OperatingSystemNames.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (ubuntu)../../tests/integration/presentation/components/Scripts/View/Tree/NodeContent/Markdown/CompositeMarkdownRenderer.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-check../../tests/checks/external-urls/main.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (macos)../../tests/integration/composite/DependencyResolution.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (macos)../../tests/integration/presentation/components/Scripts/View/Tree/Shared/OperatingSystemNames.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (macos)../../tests/integration/presentation/components/Scripts/View/Tree/NodeContent/Markdown/CompositeMarkdownRenderer.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (windows)../../tests/integration/composite/DependencyResolution.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (windows)../../tests/integration/presentation/components/Scripts/View/Tree/Shared/OperatingSystemNames.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (windows)../../tests/integration/presentation/components/Scripts/View/Tree/NodeContent/Markdown/CompositeMarkdownRenderer.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-check../../tests/checks/external-urls/main.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (ubuntu)../../tests/integration/composite/DependencyResolution.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (ubuntu)../../tests/integration/presentation/components/Scripts/View/Tree/Shared/OperatingSystemNames.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (ubuntu)../../tests/integration/presentation/components/Scripts/View/Tree/NodeContent/Markdown/CompositeMarkdownRenderer.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (macos)../../tests/integration/composite/DependencyResolution.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (macos)../../tests/integration/presentation/components/Scripts/View/Tree/Shared/OperatingSystemNames.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (macos)../../tests/integration/presentation/components/Scripts/View/Tree/NodeContent/Markdown/CompositeMarkdownRenderer.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (windows)../../tests/integration/composite/DependencyResolution.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (windows)../../tests/integration/presentation/components/Scripts/View/Tree/Shared/OperatingSystemNames.spec.ts
Check failure on line 18 in src/application/Parser/Common/ContextualError.ts GitHub Actions / run-tests (windows)../../tests/integration/presentation/components/Scripts/View/Tree/NodeContent/Markdown/CompositeMarkdownRenderer.spec.ts
|
||
innerError, | ||
additionalContext, | ||
}); | ||
}; | ||
|
||
/* AggregateError is similar but isn't well-serialized or displayed by browsers */ | ||
/** | ||
* Class for building a detailed error trace. | ||
* | ||
* Alternatives considered: | ||
* - `AggregateError`: | ||
* Similar but not well-serialized or displayed by browsers such as Chromium (last tested v126). | ||
* - `cause` property: | ||
* Not displayed by all browsers (last tested v126). | ||
* Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause | ||
* | ||
* This is immutable where the constructor sets the values because using getter functions such as | ||
* `get cause()`, `get message()` does not work on Chromium (last tested v126), but works fine on | ||
* Firefox (last tested v127). | ||
*/ | ||
class ContextualError extends CustomError { | ||
private readonly additionalContext = new Array<string>(); | ||
constructor(public readonly context: ErrorContext) { | ||
super( | ||
generateDetailedErrorMessageWithContext(context), | ||
{ | ||
cause: context.innerError, | ||
}, | ||
); | ||
} | ||
} | ||
|
||
constructor( | ||
public readonly innerError: Error, | ||
) { | ||
super(); | ||
interface ErrorContext { | ||
readonly innerError: Error; | ||
readonly additionalContext: string; | ||
} | ||
|
||
function generateDetailedErrorMessageWithContext( | ||
context: ErrorContext, | ||
): string { | ||
return [ | ||
'\n', | ||
// Display the current error message first, then the root cause. | ||
// This prevents repetitive main messages for errors with a `cause:` chain, | ||
// aligning with browser error display conventions. | ||
context.additionalContext, | ||
'\n', | ||
'Error Trace (starting from root cause):', | ||
indentText( | ||
formatErrorTrace( | ||
// Displaying contexts from the top frame (deepest, most recent) aligns with | ||
// common debugger/compiler standard. | ||
extractErrorTraceAscendingFromDeepest(context), | ||
), | ||
), | ||
'\n', | ||
].join('\n'); | ||
} | ||
|
||
function extractErrorTraceAscendingFromDeepest( | ||
context: ErrorContext, | ||
): string[] { | ||
const originalError = findRootError(context.innerError); | ||
const contextsDescendingFromMostRecent: string[] = [ | ||
context.additionalContext, | ||
...gatherContextsFromErrorChain(context.innerError), | ||
originalError.toString(), | ||
]; | ||
const contextsAscendingFromDeepest = contextsDescendingFromMostRecent.reverse(); | ||
return contextsAscendingFromDeepest; | ||
} | ||
|
||
function findRootError(error: Error): Error { | ||
if (error instanceof ContextualError) { | ||
return findRootError(error.context.innerError); | ||
} | ||
return error; | ||
} | ||
|
||
public withAdditionalContext(additionalContext: string): this { | ||
this.additionalContext.push(additionalContext); | ||
return this; | ||
function gatherContextsFromErrorChain( | ||
error: Error, | ||
accumulatedContexts: string[] = [], | ||
): string[] { | ||
if (error instanceof ContextualError) { | ||
accumulatedContexts.push(error.context.additionalContext); | ||
return gatherContextsFromErrorChain(error.context.innerError, accumulatedContexts); | ||
} | ||
return accumulatedContexts; | ||
} | ||
|
||
public get message(): string { // toString() is not used when Chromium logs it on console | ||
return [ | ||
'\n', | ||
this.innerError.message, | ||
'\n', | ||
'Additional context:', | ||
...this.additionalContext.map((context, index) => `${index + 1}: ${context}`), | ||
].join('\n'); | ||
function formatErrorTrace( | ||
errorMessages: readonly string[], | ||
): string { | ||
if (errorMessages.length === 1) { | ||
return errorMessages[0]; | ||
} | ||
return errorMessages | ||
.map((context, index) => `${index + 1}.${indentText(context)}`) | ||
.join('\n'); | ||
} |