Skip to content

Commit bcc1e1a

Browse files
committed
Add opaqueOverride option to prop object handling
1 parent b00e917 commit bcc1e1a

File tree

8 files changed

+395
-19
lines changed

8 files changed

+395
-19
lines changed

src/calculateDeepEqualDiffs.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,19 @@ function isGetter(obj, prop) {
4242

4343
export const dependenciesMap = new WeakMap();
4444

45-
function accumulateDeepEqualDiffs(a, b, diffsAccumulator, pathString = '', {detailed}) {
45+
function accumulateDeepEqualDiffs(a, b, diffsAccumulator, pathString = '', {detailed, opaqueOverride}) {
46+
// Check if either value should be treated as opaque
47+
if (opaqueOverride) {
48+
const aIsOpaque = opaqueOverride(a);
49+
const bIsOpaque = opaqueOverride(b);
50+
if (aIsOpaque || bIsOpaque) {
51+
// If either is opaque, only compare by reference
52+
return a === b ?
53+
trackDiff(a, b, diffsAccumulator, pathString, diffTypes.same) :
54+
trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different);
55+
}
56+
}
57+
4658
if (a === b) {
4759
if (detailed) {
4860
trackDiff(a, b, diffsAccumulator, pathString, diffTypes.same);
@@ -63,7 +75,7 @@ function accumulateDeepEqualDiffs(a, b, diffsAccumulator, pathString = '', {deta
6375
const arrayItemDiffs = [];
6476
let numberOfDeepEqualsItems = 0;
6577
for (let i = arrayLength; i--; i > 0) {
66-
const diffEquals = accumulateDeepEqualDiffs(a[i], b[i], arrayItemDiffs, `${pathString}[${i}]`, {detailed});
78+
const diffEquals = accumulateDeepEqualDiffs(a[i], b[i], arrayItemDiffs, `${pathString}[${i}]`, {detailed, opaqueOverride});
6779
if (diffEquals) {
6880
numberOfDeepEqualsItems++;
6981
}
@@ -116,7 +128,7 @@ function accumulateDeepEqualDiffs(a, b, diffsAccumulator, pathString = '', {deta
116128
}
117129

118130
const reactElementPropsAreDeepEqual =
119-
accumulateDeepEqualDiffs(a.props, b.props, [], `${pathString}.props`, {detailed});
131+
accumulateDeepEqualDiffs(a.props, b.props, [], `${pathString}.props`, {detailed, opaqueOverride});
120132

121133
return reactElementPropsAreDeepEqual ?
122134
trackDiff(a, b, diffsAccumulator, pathString, diffTypes.reactElement) :
@@ -133,7 +145,7 @@ function accumulateDeepEqualDiffs(a, b, diffsAccumulator, pathString = '', {deta
133145

134146
if (aDependenciesObj && bDependenciesObj) {
135147
const dependenciesAreDeepEqual =
136-
accumulateDeepEqualDiffs(aDependenciesObj.deps, bDependenciesObj.deps, diffsAccumulator, `${pathString}:parent-hook-${aDependenciesObj.hookName}-deps`, {detailed});
148+
accumulateDeepEqualDiffs(aDependenciesObj.deps, bDependenciesObj.deps, diffsAccumulator, `${pathString}:parent-hook-${aDependenciesObj.hookName}-deps`, {detailed, opaqueOverride});
137149

138150
return dependenciesAreDeepEqual ?
139151
trackDiff(a, b, diffsAccumulator, pathString, diffTypes.function) :
@@ -144,6 +156,7 @@ function accumulateDeepEqualDiffs(a, b, diffsAccumulator, pathString = '', {deta
144156
}
145157

146158
if (typeof a === 'object' && typeof b === 'object' && Object.getPrototypeOf(a) === Object.getPrototypeOf(b)) {
159+
147160
const aKeys = Object.getOwnPropertyNames(a);
148161
const bKeys = Object.getOwnPropertyNames(b);
149162

@@ -183,7 +196,7 @@ function accumulateDeepEqualDiffs(a, b, diffsAccumulator, pathString = '', {deta
183196
let numberOfDeepEqualsObjectValues = 0;
184197
for (let i = keysLength; i--; i > 0) {
185198
const key = relevantKeys[i];
186-
const deepEquals = accumulateDeepEqualDiffs(a[key], b[key], objectValuesDiffs, `${pathString}.${key}`, {detailed});
199+
const deepEquals = accumulateDeepEqualDiffs(a[key], b[key], objectValuesDiffs, `${pathString}.${key}`, {detailed, opaqueOverride});
187200
if (deepEquals) {
188201
numberOfDeepEqualsObjectValues++;
189202
}
@@ -203,10 +216,10 @@ function accumulateDeepEqualDiffs(a, b, diffsAccumulator, pathString = '', {deta
203216
return trackDiff(a, b, diffsAccumulator, pathString, diffTypes.different);
204217
}
205218

206-
export default function calculateDeepEqualDiffs(a, b, initialPathString, {detailed = false} = {}) {
219+
export default function calculateDeepEqualDiffs(a, b, initialPathString, {detailed = false, opaqueOverride} = {}) {
207220
try {
208221
const diffs = [];
209-
accumulateDeepEqualDiffs(a, b, diffs, initialPathString, {detailed});
222+
accumulateDeepEqualDiffs(a, b, diffs, initialPathString, {detailed, opaqueOverride});
210223
return diffs;
211224
} catch (error) {
212225
if ((error.message && error.message.match(/stack|recursion/i)) || (error.number === -2146828260)) {

src/defaultNotifier.js

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,44 @@ const moreInfoHooksUrl = 'http://bit.ly/wdyr3';
88

99
let inHotReload = false;
1010

11+
// Wrap opaque values to prevent console from accessing their properties
12+
function safeValue(value) {
13+
if (!wdyrStore.options?.opaqueOverride) {
14+
return value;
15+
}
16+
17+
const opaqueOverride = wdyrStore.options.opaqueOverride;
18+
19+
// Check if the value itself is opaque
20+
if (opaqueOverride(value)) {
21+
// Return a safe representation that won't trigger property access
22+
return `[Opaque Reference ${typeof value}]`;
23+
}
24+
25+
// For objects and arrays, recursively check for opaque values
26+
if (value && typeof value === 'object') {
27+
// Don't modify the original, and be careful with property access
28+
try {
29+
if (Array.isArray(value)) {
30+
return value.map(item => safeValue(item));
31+
}
32+
33+
const safeObj = {};
34+
for (const key in value) {
35+
if (Object.prototype.hasOwnProperty.call(value, key)) {
36+
safeObj[key] = safeValue(value[key]);
37+
}
38+
}
39+
return safeObj;
40+
} catch {
41+
// If we can't safely traverse, return a placeholder
42+
return '[Complex object with potential opaque values]';
43+
}
44+
}
45+
46+
return value;
47+
}
48+
1149
function shouldLog(reason, Component) {
1250
if (inHotReload) {
1351
return false;
@@ -51,7 +89,7 @@ function logDifference({Component, displayName, hookName, prefixMessage, diffObj
5189
wdyrStore.options.consoleLog(
5290
`${diffTypesDescriptions[diffType]}. (more info at ${hookName ? moreInfoHooksUrl : moreInfoUrl})`,
5391
);
54-
wdyrStore.options.consoleLog({[`prev ${pathString}`]: prevValue}, '!==', {[`next ${pathString}`]: nextValue});
92+
wdyrStore.options.consoleLog({[`prev ${pathString}`]: safeValue(prevValue)}, '!==', {[`next ${pathString}`]: safeValue(nextValue)});
5593
if (diffType === diffTypes.deepEquals) {
5694
wdyrStore.options.consoleLog({'For detailed diff, right click the following fn, save as global, and run: ': diffFn});
5795
}
@@ -67,7 +105,7 @@ function logDifference({Component, displayName, hookName, prefixMessage, diffObj
67105
'This usually means this component called setState when no changes in its state actually occurred.',
68106
`More info at ${moreInfoUrl}`
69107
);
70-
wdyrStore.options.consoleLog(`prev ${diffObjType}:`, values.prev, ' !== ', values.next, `:next ${diffObjType}`);
108+
wdyrStore.options.consoleLog(`prev ${diffObjType}:`, safeValue(values.prev), ' !== ', safeValue(values.next), `:next ${diffObjType}`);
71109
}
72110
}
73111

src/findObjectsDifferences.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,28 @@ import calculateDeepEqualDiffs from './calculateDeepEqualDiffs';
33

44
const emptyObject = {};
55

6-
export default function findObjectsDifferences(userPrevObj, userNextObj, {shallow = true} = {}) {
6+
export default function findObjectsDifferences(userPrevObj, userNextObj, {shallow = true, opaqueOverride} = {}) {
77
if (userPrevObj === userNextObj) {
88
return false;
99
}
1010

11+
// Check if either object should be treated as opaque
12+
if (opaqueOverride) {
13+
const prevIsOpaque = opaqueOverride(userPrevObj);
14+
const nextIsOpaque = opaqueOverride(userNextObj);
15+
if (prevIsOpaque || nextIsOpaque) {
16+
// If either is opaque, they're different unless they're the same reference (which we already checked)
17+
return [{
18+
pathString: '',
19+
diffType: 'different',
20+
prevValue: userPrevObj,
21+
nextValue: userNextObj,
22+
}];
23+
}
24+
}
25+
1126
if (!shallow) {
12-
return calculateDeepEqualDiffs(userPrevObj, userNextObj);
27+
return calculateDeepEqualDiffs(userPrevObj, userNextObj, '', {opaqueOverride});
1328
}
1429

1530
const prevObj = userPrevObj || emptyObject;
@@ -18,7 +33,7 @@ export default function findObjectsDifferences(userPrevObj, userNextObj, {shallo
1833
const keysOfBothObjects = Object.keys({...prevObj, ...nextObj});
1934

2035
return reduce(keysOfBothObjects, (result, key) => {
21-
const deepEqualDiffs = calculateDeepEqualDiffs(prevObj[key], nextObj[key], key);
36+
const deepEqualDiffs = calculateDeepEqualDiffs(prevObj[key], nextObj[key], key, {opaqueOverride});
2237
if (deepEqualDiffs) {
2338
result = [
2439
...result,

src/getUpdateInfo.js

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ function getOwnerDifferences(prevOwner, nextOwner) {
4646
// Both exist, compare them
4747
let differences;
4848
try {
49-
differences = findObjectsDifferences(prev.result, next.result, {shallow: false});
49+
differences = findObjectsDifferences(prev.result, next.result, {
50+
shallow: false,
51+
opaqueOverride: wdyrStore.options?.opaqueOverride
52+
});
5053
} catch {
5154
differences = {error: 'diff_failed'};
5255
}
@@ -58,8 +61,12 @@ function getOwnerDifferences(prevOwner, nextOwner) {
5861
}
5962

6063
return {
61-
propsDifferences: findObjectsDifferences(prevOwnerData.props, nextOwnerData.props),
62-
stateDifferences: findObjectsDifferences(prevOwnerData.state, nextOwnerData.state),
64+
propsDifferences: findObjectsDifferences(prevOwnerData.props, nextOwnerData.props, {
65+
opaqueOverride: wdyrStore.options?.opaqueOverride
66+
}),
67+
stateDifferences: findObjectsDifferences(prevOwnerData.state, nextOwnerData.state, {
68+
opaqueOverride: wdyrStore.options?.opaqueOverride
69+
}),
6370
hookDifferences: hookDifferences.length > 0 ? hookDifferences : false,
6471
};
6572
}
@@ -78,9 +85,16 @@ function getOwnerDifferences(prevOwner, nextOwner) {
7885

7986
function getUpdateReason(prevOwner, prevProps, prevState, prevHookResult, nextOwner, nextProps, nextState, nextHookResult) {
8087
return {
81-
propsDifferences: findObjectsDifferences(prevProps, nextProps),
82-
stateDifferences: findObjectsDifferences(prevState, nextState),
83-
hookDifferences: findObjectsDifferences(prevHookResult, nextHookResult, {shallow: false}),
88+
propsDifferences: findObjectsDifferences(prevProps, nextProps, {
89+
opaqueOverride: wdyrStore.options?.opaqueOverride
90+
}),
91+
stateDifferences: findObjectsDifferences(prevState, nextState, {
92+
opaqueOverride: wdyrStore.options?.opaqueOverride
93+
}),
94+
hookDifferences: findObjectsDifferences(prevHookResult, nextHookResult, {
95+
shallow: false,
96+
opaqueOverride: wdyrStore.options?.opaqueOverride
97+
}),
8498
ownerDifferences: getOwnerDifferences(prevOwner, nextOwner),
8599
};
86100
}

src/normalizeOptions.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export default function normalizeOptions(userOptions = {}) {
3838
textBackgroundColor: 'white',
3939
trackExtraHooks: [],
4040
trackAllPureComponents: false,
41+
opaqueOverride: null,
4142
...userOptions,
4243
};
4344
}

src/printDiff.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import {sortBy, groupBy} from 'lodash';
22

3+
import wdyrStore from './wdyrStore';
34
import calculateDeepEqualDiffs from './calculateDeepEqualDiffs';
45
import {diffTypesDescriptions} from './consts';
56

67
export default function printDiff(value1, value2, {pathString, consoleLog}) {
7-
const diffs = calculateDeepEqualDiffs(value1, value2, pathString, {detailed: true});
8+
const diffs = calculateDeepEqualDiffs(value1, value2, pathString, {
9+
detailed: true,
10+
opaqueOverride: wdyrStore.options?.opaqueOverride
11+
});
812

913
const keysLength = Math.max(...diffs.map(diff => diff.pathString.length)) + 2;
1014

0 commit comments

Comments
 (0)