-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
error.ts
174 lines (155 loc) · 5.54 KB
/
error.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import { diff } from './diff'
import { format } from './display'
import { deepClone, getOwnProperties, getType } from './helpers'
import { stringify } from './stringify'
const IS_RECORD_SYMBOL = '@@__IMMUTABLE_RECORD__@@'
const IS_COLLECTION_SYMBOL = '@@__IMMUTABLE_ITERABLE__@@'
function isImmutable(v: any) {
return v && (v[IS_COLLECTION_SYMBOL] || v[IS_RECORD_SYMBOL])
}
const OBJECT_PROTO = Object.getPrototypeOf({})
function getUnserializableMessage(err: unknown) {
if (err instanceof Error)
return `<unserializable>: ${err.message}`
if (typeof err === 'string')
return `<unserializable>: ${err}`
return '<unserializable>'
}
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
export function serializeError(val: any, seen = new WeakMap()): any {
if (!val || typeof val === 'string')
return val
if (typeof val === 'function')
return `Function<${val.name || 'anonymous'}>`
if (typeof val === 'symbol')
return val.toString()
if (typeof val !== 'object')
return val
// cannot serialize immutables as immutables
if (isImmutable(val))
return serializeError(val.toJSON(), seen)
if (val instanceof Promise || (val.constructor && val.constructor.prototype === 'AsyncFunction'))
return 'Promise'
if (typeof Element !== 'undefined' && val instanceof Element)
return val.tagName
if (typeof val.asymmetricMatch === 'function')
return `${val.toString()} ${format(val.sample)}`
if (seen.has(val))
return seen.get(val)
if (Array.isArray(val)) {
const clone: any[] = new Array(val.length)
seen.set(val, clone)
val.forEach((e, i) => {
try {
clone[i] = serializeError(e, seen)
}
catch (err) {
clone[i] = getUnserializableMessage(err)
}
})
return clone
}
else {
// Objects with `Error` constructors appear to cause problems during worker communication
// using `MessagePort`, so the serialized error object is being recreated as plain object.
const clone = Object.create(null)
seen.set(val, clone)
let obj = val
while (obj && obj !== OBJECT_PROTO) {
Object.getOwnPropertyNames(obj).forEach((key) => {
if (key in clone)
return
try {
clone[key] = serializeError(val[key], seen)
}
catch (err) {
// delete in case it has a setter from prototype that might throw
delete clone[key]
clone[key] = getUnserializableMessage(err)
}
})
obj = Object.getPrototypeOf(obj)
}
return clone
}
}
function normalizeErrorMessage(message: string) {
return message.replace(/__vite_ssr_import_\d+__\./g, '')
}
export function processError(err: any) {
if (!err || typeof err !== 'object')
return { message: err }
// stack is not serialized in worker communication
// we stringify it first
if (err.stack)
err.stackStr = String(err.stack)
if (err.name)
err.nameStr = String(err.name)
if (err.showDiff || (err.showDiff === undefined && err.expected !== undefined && err.actual !== undefined)) {
const clonedActual = deepClone(err.actual, { forceWritable: true })
const clonedExpected = deepClone(err.expected, { forceWritable: true })
const { replacedActual, replacedExpected } = replaceAsymmetricMatcher(clonedActual, clonedExpected)
err.diff = diff(replacedExpected, replacedActual)
}
if (typeof err.expected !== 'string')
err.expected = stringify(err.expected, 10)
if (typeof err.actual !== 'string')
err.actual = stringify(err.actual, 10)
// some Error implementations don't allow rewriting message
try {
if (typeof err.message === 'string')
err.message = normalizeErrorMessage(err.message)
if (typeof err.cause === 'object' && typeof err.cause.message === 'string')
err.cause.message = normalizeErrorMessage(err.cause.message)
}
catch {}
try {
return serializeError(err)
}
catch (e: any) {
return serializeError(new Error(`Failed to fully serialize error: ${e?.message}\nInner error message: ${err?.message}`))
}
}
function isAsymmetricMatcher(data: any) {
const type = getType(data)
return type === 'Object' && typeof data.asymmetricMatch === 'function'
}
function isReplaceable(obj1: any, obj2: any) {
const obj1Type = getType(obj1)
const obj2Type = getType(obj2)
return obj1Type === obj2Type && obj1Type === 'Object'
}
export function replaceAsymmetricMatcher(actual: any, expected: any, actualReplaced = new WeakSet(), expectedReplaced = new WeakSet()) {
if (!isReplaceable(actual, expected))
return { replacedActual: actual, replacedExpected: expected }
if (actualReplaced.has(actual) || expectedReplaced.has(expected))
return { replacedActual: actual, replacedExpected: expected }
actualReplaced.add(actual)
expectedReplaced.add(expected)
getOwnProperties(expected).forEach((key) => {
const expectedValue = expected[key]
const actualValue = actual[key]
if (isAsymmetricMatcher(expectedValue)) {
if (expectedValue.asymmetricMatch(actualValue))
actual[key] = expectedValue
}
else if (isAsymmetricMatcher(actualValue)) {
if (actualValue.asymmetricMatch(expectedValue))
expected[key] = actualValue
}
else if (isReplaceable(actualValue, expectedValue)) {
const replaced = replaceAsymmetricMatcher(
actualValue,
expectedValue,
actualReplaced,
expectedReplaced,
)
actual[key] = replaced.replacedActual
expected[key] = replaced.replacedExpected
}
})
return {
replacedActual: actual,
replacedExpected: expected,
}
}