-
-
Notifications
You must be signed in to change notification settings - Fork 173
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve compiler error display for latest Chromium
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
1 parent
abe03ce
commit b16e136
Showing
15 changed files
with
248 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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({ | ||
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'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.