53
object: Object,
54
properties: Array<[string, string]>,
55
indent: number,
56
+ prefix: string,
57
): void {
58
for (const key in object) {
59
if (hasOwnProperty.call(object, key) && key[0] !== '_') {
60
const value = object[key];
60
- addValueToProperties(key, value, properties, indent);
61
+ addValueToProperties(key, value, properties, indent, prefix);
62
}
63
}
64
}
68
value: mixed,
69
properties: Array<[string, string]>,
70
indent: number,
71
+ prefix: string,
72
): void {
73
let desc;
74
switch (typeof value) {
96
break;
97
}
98
properties.push([
97
- '\xa0\xa0'.repeat(indent) + propertyName,
99
+ prefix + '\xa0\xa0'.repeat(indent) + propertyName,
100
'<' + typeName,
101
]);
102
if (key !== null) {
101
- addValueToProperties('key', key, properties, indent + 1);
103
+ addValueToProperties('key', key, properties, indent + 1, prefix);
104
}
105
let hasChildren = false;
106
for (const propKey in props) {
120
props[propKey],
121
properties,
122
indent + 1,
123
+ prefix,
124
);
125
}
126
}
140
desc = JSON.stringify(array);
141
break;
142
} else if (kind === ENTRIES_ARRAY) {
140
- properties.push(['\xa0\xa0'.repeat(indent) + propertyName, '']);
143
+ properties.push([
144
+ prefix + '\xa0\xa0'.repeat(indent) + propertyName,
145
+ '',
146
+ ]);
147
for (let i = 0; i < array.length; i++) {
148
const entry = array[i];
143
- addValueToProperties(entry[0], entry[1], properties, indent + 1);
149
+ addValueToProperties(
150
+ entry[0],
151
+ entry[1],
152
+ properties,
153
+ indent + 1,
154
+ prefix,
155
+ );
156
}
157
return;
158
}
198
}
199
}
200
properties.push([
189
- '\xa0\xa0'.repeat(indent) + propertyName,
201
+ prefix + '\xa0\xa0'.repeat(indent) + propertyName,
202
objectName === 'Object' ? (indent < 3 ? '' : '\u2026') : objectName,
203
]);
204
if (indent < 3) {
193
- addObjectToProperties(value, properties, indent + 1);
205
+ addObjectToProperties(value, properties, indent + 1, prefix);
206
}
207
return;
208
}
230
// eslint-disable-next-line react-internal/safe-string-coercion
231
desc = String(value);
232
}
221
- properties.push(['\xa0\xa0'.repeat(indent) + propertyName, desc]);
233
+ properties.push([prefix + '\xa0\xa0'.repeat(indent) + propertyName, desc]);
234
+}
235
+
236
+const REMOVED = '\u2013\xa0';
237
+const ADDED = '+\xa0';
238
+const UNCHANGED = '\u2007\xa0';
239
+
240
+export function addObjectDiffToProperties(
241
+ prev: Object,
242
+ next: Object,
243
+ properties: Array<[string, string]>,
244
+ indent: number,
245
+): void {
246
+ // Note: We diff even non-owned properties here but things that are shared end up just the same.
247
+ // If a property is added or removed, we just emit the property name and omit the value it had.
248
+ // Mainly for performance. We need to minimize to only relevant information.
249
+ for (const key in prev) {
250
+ if (!(key in next)) {
251
+ properties.push([REMOVED + '\xa0\xa0'.repeat(indent) + key, '\u2026']);
252
+ }
253
+ }
254
+ for (const key in next) {
255
+ if (key in prev) {
256
+ const prevValue = prev[key];
257
+ const nextValue = next[key];
258
+ if (prevValue !== nextValue) {
259
+ if (indent === 0 && key === 'children') {
260
+ // Omit any change inside the top level children prop since it's expected to change
261
+ // with any change to children of the component and their props will be logged
262
+ // elsewhere but still mark it as a cause of render.
263
+ const line = '\xa0\xa0'.repeat(indent) + key;
264
+ properties.push([REMOVED + line, '\u2026'], [ADDED + line, '\u2026']);
265
+ continue;
266
+ }
267
+ if (indent >= 3) {
268
+ // Just fallthrough to print the two values if we're deep.
269
+ // This will skip nested properties of the objects.
270
+ } else if (
271
+ typeof prevValue === 'object' &&
272
+ typeof nextValue === 'object' &&
273
+ prevValue !== null &&
274
+ nextValue !== null &&
275
+ prevValue.$$typeof === nextValue.$$typeof
276
+ ) {
277
+ if (nextValue.$$typeof === REACT_ELEMENT_TYPE) {
278
+ if (
279
+ prevValue.type === nextValue.type &&
280
+ prevValue.key === nextValue.key
281
+ ) {
282
+ // If the only thing that has changed is the props of a nested element, then
283
+ // we omit the props because it is likely to be represented as a diff elsewhere.
284
+ const typeName =
285
+ getComponentNameFromType(nextValue.type) || '\u2026';
286
+ const line = '\xa0\xa0'.repeat(indent) + key;
287
+ const desc = '<' + typeName + ' \u2026 />';
288
+ properties.push([REMOVED + line, desc], [ADDED + line, desc]);
289
+ continue;
290
+ }
291
+ } else {
292
+ // $FlowFixMe[method-unbinding]
293
+ const prevKind = Object.prototype.toString.call(prevValue);
294
+ // $FlowFixMe[method-unbinding]
295
+ const nextKind = Object.prototype.toString.call(nextValue);
296
+ if (
297
+ prevKind === nextKind &&
298
+ (nextKind === '[object Object]' || nextKind === '[object Array]')
299
+ ) {
300
+ // Diff nested object
301
+ const entry = [
302
+ UNCHANGED + '\xa0\xa0'.repeat(indent) + key,
303
+ nextKind === '[object Array]' ? 'Array' : '',
304
+ ];
305
+ properties.push(entry);
306
+ const prevLength = properties.length;
307
+ addObjectDiffToProperties(
308
+ prevValue,
309
+ nextValue,
310
+ properties,
311
+ indent + 1,
312
+ );
313
+ if (prevLength === properties.length) {
314
+ // Nothing notably changed inside the nested object. So this is only a change in reference
315
+ // equality. Let's note it.
316
+ entry[1] =
317
+ 'Referentially unequal but deeply equal objects. Consider memoization.';
318
+ }
319
+ continue;
320
+ }
321
+ }
322
+ } else if (
323
+ typeof prevValue === 'function' &&
324
+ typeof nextValue === 'function' &&
325
+ prevValue.name === nextValue.name &&
326
+ prevValue.length === nextValue.length
327
+ ) {
328
+ // $FlowFixMe[method-unbinding]
329
+ const prevSrc = Function.prototype.toString.call(prevValue);
330
+ // $FlowFixMe[method-unbinding]
331
+ const nextSrc = Function.prototype.toString.call(nextValue);
332
+ if (prevSrc === nextSrc) {
333
+ // This looks like it might be the same function but different closures.
334
+ let desc;
335
+ if (nextValue.name === '') {
336
+ desc = '() => {}';
337
+ } else {
338
+ desc = nextValue.name + '() {}';
339
+ }
340
+ properties.push([
341
+ UNCHANGED + '\xa0\xa0'.repeat(indent) + key,
342
+ desc +
343
+ ' Referentially unequal function closure. Consider memoization.',
344
+ ]);
345
+ continue;
346
+ }
347
+ }
348
+
349
+ // Otherwise, emit the change in property and the values.
350
+ addValueToProperties(key, prevValue, properties, indent, REMOVED);
351
+ addValueToProperties(key, nextValue, properties, indent, ADDED);
352
+ }
353
+ } else {
354
+ properties.push([ADDED + '\xa0\xa0'.repeat(indent) + key, '\u2026']);
355
+ }
356
+ }
357
}