react-state-basis 0.6.6 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/{chunk-WMHY2C6D.mjs → chunk-KIXM6YRX.mjs} +179 -137
- package/dist/chunk-KIXM6YRX.mjs.map +1 -0
- package/dist/index.js +178 -136
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/integrations/zustand.js +173 -135
- package/dist/integrations/zustand.js.map +1 -1
- package/dist/integrations/zustand.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-WMHY2C6D.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -168,7 +168,7 @@ A detected pattern is **not automatically a bug**, and Basis does not know the i
|
|
|
168
168
|
|
|
169
169
|
Use the results as prompts for investigation rather than as rules for how React code should be written.
|
|
170
170
|
|
|
171
|
-
[See examples and possible fixes →](https://github.com/liovic/react-state-basis/wiki/
|
|
171
|
+
[See examples and possible fixes →](https://github.com/liovic/react-state-basis/wiki/Detected-patterns)
|
|
172
172
|
|
|
173
173
|
---
|
|
174
174
|
|
|
@@ -338,7 +338,7 @@ Basis is designed primarily as a development-time diagnostic tool.
|
|
|
338
338
|
|
|
339
339
|
Actual overhead depends on the application and instrumentation configuration.
|
|
340
340
|
|
|
341
|
-
[See benchmarks →](https://github.com/liovic/react-state-basis/wiki/Performance
|
|
341
|
+
[See benchmarks →](https://github.com/liovic/react-state-basis/wiki/Performance)
|
|
342
342
|
|
|
343
343
|
---
|
|
344
344
|
|
|
@@ -285,6 +285,10 @@ var shouldLog = (key) => {
|
|
|
285
285
|
return false;
|
|
286
286
|
};
|
|
287
287
|
var isBooleanLike = (name) => /^(is|has|can|should|did|will|show|hide)(?=[A-Z_])/.test(name);
|
|
288
|
+
var displayName = (raw) => {
|
|
289
|
+
const { name } = parseLabel(raw);
|
|
290
|
+
return name.replace(/:\d+$/, "");
|
|
291
|
+
};
|
|
288
292
|
var areSyncSignificant = (metaA, metaB) => {
|
|
289
293
|
const { kSync, densityA, densityB } = countOverlapsCircular(
|
|
290
294
|
metaA.buffer,
|
|
@@ -296,95 +300,80 @@ var areSyncSignificant = (metaA, metaB) => {
|
|
|
296
300
|
};
|
|
297
301
|
var getSuggestedFix = (issue, info) => {
|
|
298
302
|
if (issue.label.includes("Global Event")) {
|
|
299
|
-
return `
|
|
303
|
+
return `One interaction is updating state in several places. If that is really one transition, put it in one %cstore / reducer%c. If it is intentional, ignore this.`;
|
|
300
304
|
}
|
|
301
305
|
const violations = issue.violations || [];
|
|
302
306
|
const leaks = violations.filter((v) => v.type === "causal_leak");
|
|
303
307
|
const mirrors = violations.filter((v) => v.type === "context_mirror");
|
|
304
308
|
const duplicates = violations.filter((v) => v.type === "duplicate_state");
|
|
305
309
|
if (mirrors.length > 0) {
|
|
306
|
-
return `
|
|
310
|
+
return `A local hook is only tracking context or a store. If it is not a draft, delete it and read the %ccontext / store%c in render.`;
|
|
307
311
|
}
|
|
308
312
|
if (leaks.length > 0) {
|
|
309
|
-
const targetName =
|
|
313
|
+
const targetName = displayName(leaks[0].target);
|
|
310
314
|
if (issue.label.includes("effect")) {
|
|
311
|
-
return `
|
|
315
|
+
return `An effect is calling setState on ${targetName}, which paints again. If you can compute ${targetName} while rendering, drop the %ceffect%c.`;
|
|
312
316
|
}
|
|
313
|
-
return
|
|
317
|
+
return `${info.name} updates, then ${targetName} updates on the next frame. If they are one fact, write them in the same %csetState%c.`;
|
|
314
318
|
}
|
|
315
319
|
if (duplicates.length > 0) {
|
|
316
320
|
if (isBooleanLike(info.name)) {
|
|
317
|
-
return `
|
|
321
|
+
return `Several flags move together. One %cstatus%c value avoids impossible combinations.`;
|
|
318
322
|
}
|
|
319
|
-
return `
|
|
323
|
+
return `These hooks move together. If one is just the other in another shape, compute it while %crendering%c.`;
|
|
320
324
|
}
|
|
321
325
|
if (issue.metric === "density") {
|
|
322
|
-
return `
|
|
326
|
+
return `This hook updates faster than a frame. %cDebounce%c it or keep it in a ref if the UI does not need every pulse.`;
|
|
323
327
|
}
|
|
324
|
-
return `
|
|
328
|
+
return `Inspect ${info.name} and what updates with it.`;
|
|
325
329
|
};
|
|
326
330
|
var displayHealthReport = (history2, violationMap) => {
|
|
327
331
|
if (!isWeb) return;
|
|
328
332
|
const entries = Array.from(history2.entries());
|
|
329
333
|
if (entries.length === 0) return;
|
|
330
334
|
const topIssues = identifyTopIssues(instance.graph, history2, instance.redundantLabels, violationMap);
|
|
331
|
-
console.group(`%c
|
|
335
|
+
console.group(`%c BASIS | report `, STYLES.headerIdentity);
|
|
332
336
|
if (topIssues.length > 0) {
|
|
333
|
-
console.log(
|
|
334
|
-
`%c\u{1F3AF} REFACTOR PRIORITIES %c(PRIME MOVERS)`,
|
|
335
|
-
`font-weight: bold; color: ${THEME.identity}; margin-top: 10px;`,
|
|
336
|
-
`font-weight: normal; color: ${THEME.muted}; font-style: italic;`
|
|
337
|
-
);
|
|
337
|
+
console.log(`%cStart here`, `font-weight: bold; color: ${THEME.identity}; margin-top: 10px;`);
|
|
338
338
|
topIssues.forEach((issue, idx) => {
|
|
339
339
|
const info = parseLabel(issue.label);
|
|
340
|
-
const icon = issue.metric === "influence" ? "\
|
|
340
|
+
const icon = issue.metric === "influence" ? "\u2192" : "\u2022";
|
|
341
341
|
const pColor = idx === 0 ? THEME.problem : idx === 1 ? THEME.solution : THEME.identity;
|
|
342
|
-
let displayName = info.name;
|
|
343
|
-
let displayFile = info.file;
|
|
344
|
-
if (issue.label.includes("Global Event")) {
|
|
345
|
-
displayName = info.name;
|
|
346
|
-
displayFile = info.file;
|
|
347
|
-
}
|
|
348
342
|
console.group(
|
|
349
|
-
` %c${idx + 1}%c ${icon} ${displayName} %c(${
|
|
343
|
+
` %c${idx + 1}%c ${icon} ${displayName(issue.label)} %c(${info.file})`,
|
|
350
344
|
`background: ${pColor}; color: ${idx === 1 ? "black" : "white"}; border-radius: 50%; padding: 0 5px;`,
|
|
351
345
|
"font-family: monospace; font-weight: 700;",
|
|
352
|
-
`color: ${THEME.muted}; font-size: 10px; font-weight: normal
|
|
346
|
+
`color: ${THEME.muted}; font-size: 10px; font-weight: normal;`
|
|
353
347
|
);
|
|
354
|
-
console.log(`%c${issue.reason}`, `color: ${THEME.muted}
|
|
348
|
+
console.log(`%c${issue.reason}`, `color: ${THEME.muted};`);
|
|
355
349
|
if (issue.violations.length > 0) {
|
|
356
350
|
const byFile = /* @__PURE__ */ new Map();
|
|
357
351
|
issue.violations.forEach((v) => {
|
|
358
352
|
if (issue.label.includes("Global Event") && v.type === "context_mirror") return;
|
|
359
353
|
const { file, name } = parseLabel(v.target);
|
|
360
354
|
if (!byFile.has(file)) byFile.set(file, []);
|
|
361
|
-
byFile.get(file).push(name);
|
|
355
|
+
byFile.get(file).push(name.replace(/:\d+$/, ""));
|
|
362
356
|
});
|
|
363
357
|
const impactParts = [];
|
|
364
358
|
byFile.forEach((vars, file) => {
|
|
365
|
-
|
|
366
|
-
impactParts.push(`${file} (${varList})`);
|
|
359
|
+
impactParts.push(`${file} (${vars.join(", ")})`);
|
|
367
360
|
});
|
|
368
361
|
if (impactParts.length > 0) {
|
|
369
|
-
console.log(`%
|
|
362
|
+
console.log(`%cAlso updates: %c${impactParts.join(" \xB7 ")}`, STYLES.impactLabel, "");
|
|
370
363
|
}
|
|
371
364
|
}
|
|
372
365
|
const fix = getSuggestedFix(issue, info);
|
|
373
366
|
const fixParts = fix.split("%c");
|
|
374
367
|
if (fixParts.length === 3) {
|
|
375
368
|
console.log(
|
|
376
|
-
`%
|
|
369
|
+
`%cTry: %c${fixParts[0]}%c${fixParts[1]}%c${fixParts[2]}`,
|
|
377
370
|
STYLES.actionLabel,
|
|
378
371
|
"",
|
|
379
372
|
STYLES.actionPill,
|
|
380
373
|
""
|
|
381
374
|
);
|
|
382
375
|
} else {
|
|
383
|
-
console.log(
|
|
384
|
-
`%cSolution: %c${fix}`,
|
|
385
|
-
STYLES.actionLabel,
|
|
386
|
-
""
|
|
387
|
-
);
|
|
376
|
+
console.log(`%cTry: %c${fix}`, STYLES.actionLabel, "");
|
|
388
377
|
}
|
|
389
378
|
console.groupEnd();
|
|
390
379
|
});
|
|
@@ -408,135 +397,170 @@ var displayHealthReport = (history2, violationMap) => {
|
|
|
408
397
|
else independentCount++;
|
|
409
398
|
});
|
|
410
399
|
const totalVars = entries.length;
|
|
411
|
-
const redundancyScore = (independentCount + clusters.length) / totalVars * 100;
|
|
412
|
-
let internalEdges = 0;
|
|
413
|
-
instance.graph.forEach((targets, source) => {
|
|
414
|
-
if (source.startsWith("Event_Tick_")) return;
|
|
415
|
-
internalEdges += targets.size;
|
|
416
|
-
});
|
|
417
|
-
const causalPenalty = internalEdges / totalVars * 100;
|
|
418
|
-
let healthScore = redundancyScore - causalPenalty;
|
|
419
|
-
if (healthScore < 0) healthScore = 0;
|
|
420
|
-
const scoreColor = healthScore > 85 ? THEME.success : THEME.problem;
|
|
421
400
|
console.log(
|
|
422
|
-
`%
|
|
423
|
-
STYLES.
|
|
424
|
-
`color: ${scoreColor}; font-weight: bold;`
|
|
401
|
+
`%c${independentCount + clusters.length} of ${totalVars} instrumented hooks look independent in this window.`,
|
|
402
|
+
STYLES.subText
|
|
425
403
|
);
|
|
426
|
-
console.log(`%cSources of Truth: ${independentCount + clusters.length}/${totalVars} | Causal Leaks: ${internalEdges}`, STYLES.subText);
|
|
427
404
|
if (clusters.length > 0) {
|
|
428
|
-
console.log(
|
|
405
|
+
console.log(
|
|
406
|
+
`%c${clusters.length} group${clusters.length === 1 ? "" : "s"} that keep updating together:`,
|
|
407
|
+
`font-weight: bold; color: ${THEME.problem}; margin-top: 10px;`
|
|
408
|
+
);
|
|
429
409
|
clusters.forEach((cluster, idx) => {
|
|
430
410
|
const clusterMetas = cluster.map((l) => ({
|
|
431
411
|
label: l,
|
|
432
412
|
meta: history2.get(l),
|
|
433
|
-
name:
|
|
413
|
+
name: displayName(l)
|
|
434
414
|
}));
|
|
435
415
|
const hasCtx = clusterMetas.some(
|
|
436
416
|
(c) => c.meta.role === "context" /* CONTEXT */ || c.meta.role === "store" /* STORE */
|
|
437
417
|
);
|
|
438
|
-
const names = clusterMetas.map((c) =>
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
418
|
+
const names = clusterMetas.map((c) => c.name).join(", ");
|
|
419
|
+
console.group(
|
|
420
|
+
` %c${idx + 1}%c ${names}`,
|
|
421
|
+
`background: ${THEME.problem}; color: white; border-radius: 50%; padding: 0 5px;`,
|
|
422
|
+
"font-family: monospace; font-weight: bold;"
|
|
423
|
+
);
|
|
443
424
|
if (hasCtx) {
|
|
444
425
|
const hasStore = clusterMetas.some((c) => c.meta.role === "store" /* STORE */);
|
|
445
|
-
const sourceType = hasStore ? "
|
|
446
|
-
console.log(
|
|
447
|
-
console.log(
|
|
426
|
+
const sourceType = hasStore ? "a store" : "context";
|
|
427
|
+
console.log(`A local hook is only following ${sourceType}.`);
|
|
428
|
+
console.log(
|
|
429
|
+
`%cTry:%c Read ${sourceType} in render if the local value is not a draft.`,
|
|
430
|
+
STYLES.actionLabel,
|
|
431
|
+
""
|
|
432
|
+
);
|
|
448
433
|
} else {
|
|
449
|
-
const boolKeywords = [
|
|
434
|
+
const boolKeywords = [
|
|
435
|
+
"is",
|
|
436
|
+
"has",
|
|
437
|
+
"can",
|
|
438
|
+
"should",
|
|
439
|
+
"loading",
|
|
440
|
+
"success",
|
|
441
|
+
"error",
|
|
442
|
+
"active",
|
|
443
|
+
"enabled",
|
|
444
|
+
"open",
|
|
445
|
+
"visible"
|
|
446
|
+
];
|
|
450
447
|
const boolCount = clusterMetas.filter(
|
|
451
448
|
(c) => boolKeywords.some((kw) => c.name.toLowerCase().startsWith(kw))
|
|
452
449
|
).length;
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
console.log(
|
|
456
|
-
|
|
450
|
+
if (cluster.length > 2 && boolCount / cluster.length > 0.5) {
|
|
451
|
+
console.log(`These flags move together.`);
|
|
452
|
+
console.log(
|
|
453
|
+
`%cTry:%c One %cstatus%c instead of several booleans.`,
|
|
454
|
+
STYLES.actionLabel,
|
|
455
|
+
"",
|
|
456
|
+
STYLES.actionPill,
|
|
457
|
+
""
|
|
458
|
+
);
|
|
457
459
|
} else if (cluster.length > 2) {
|
|
458
|
-
console.log(
|
|
459
|
-
console.log(
|
|
460
|
+
console.log(`These hooks move on the same frames. Often the same click or fetch.`);
|
|
461
|
+
console.log(
|
|
462
|
+
`%cTry:%c Leave it if that is intentional. Otherwise one %creducer%c.`,
|
|
463
|
+
STYLES.actionLabel,
|
|
464
|
+
"",
|
|
465
|
+
STYLES.actionPill,
|
|
466
|
+
""
|
|
467
|
+
);
|
|
460
468
|
} else {
|
|
461
|
-
console.log(
|
|
462
|
-
console.log(
|
|
469
|
+
console.log(`These two hooks keep updating in the same frame.`);
|
|
470
|
+
console.log(
|
|
471
|
+
`%cTry:%c If one is derived, compute it while %crendering%c.`,
|
|
472
|
+
STYLES.actionLabel,
|
|
473
|
+
"",
|
|
474
|
+
STYLES.actionPill,
|
|
475
|
+
""
|
|
476
|
+
);
|
|
463
477
|
}
|
|
464
478
|
}
|
|
465
479
|
console.groupEnd();
|
|
466
480
|
});
|
|
467
481
|
} else {
|
|
468
|
-
console.log(
|
|
482
|
+
console.log(
|
|
483
|
+
"%cNo hooks were updating in lockstep in this window.",
|
|
484
|
+
`color: ${THEME.success}; font-weight: bold;`
|
|
485
|
+
);
|
|
469
486
|
}
|
|
470
487
|
console.groupEnd();
|
|
471
488
|
};
|
|
472
|
-
var displayRedundancyAlert = (labelA, metaA, labelB, metaB,
|
|
489
|
+
var displayRedundancyAlert = (labelA, metaA, labelB, metaB, overlap) => {
|
|
473
490
|
if (!isWeb || !shouldLog(`redundant-${labelA}-${labelB}`)) return;
|
|
474
491
|
const infoA = parseLabel(labelA);
|
|
475
|
-
const
|
|
492
|
+
const nameA = displayName(labelA);
|
|
493
|
+
const nameB = displayName(labelB);
|
|
476
494
|
const isContextMirror = metaA.role === "local" /* LOCAL */ && metaB.role === "context" /* CONTEXT */ || metaB.role === "local" /* LOCAL */ && metaA.role === "context" /* CONTEXT */;
|
|
477
495
|
const isStoreMirror = metaA.role === "local" /* LOCAL */ && metaB.role === "store" /* STORE */ || metaB.role === "local" /* LOCAL */ && metaA.role === "store" /* STORE */;
|
|
478
|
-
const alertType = isContextMirror ? "
|
|
479
|
-
|
|
480
|
-
console.
|
|
481
|
-
console.log(`%
|
|
496
|
+
const alertType = isContextMirror ? "local state follows context" : isStoreMirror ? "local state follows a store" : "hooks moving together";
|
|
497
|
+
const times = overlap.kSync === 1 ? "time" : "times";
|
|
498
|
+
console.group(`%c BASIS | ${alertType} `, STYLES.headerProblem);
|
|
499
|
+
console.log(`%c${infoA.file}`, STYLES.location);
|
|
500
|
+
console.log(
|
|
501
|
+
`%c${nameA}%c and %c${nameB}%c updated in the same frame ${overlap.kSync} ${times}.`,
|
|
502
|
+
STYLES.label,
|
|
503
|
+
"",
|
|
504
|
+
STYLES.label,
|
|
505
|
+
""
|
|
506
|
+
);
|
|
482
507
|
if (isContextMirror || isStoreMirror) {
|
|
483
|
-
const sourceType = isStoreMirror ? "
|
|
508
|
+
const sourceType = isStoreMirror ? "store" : "context";
|
|
484
509
|
console.log(
|
|
485
|
-
`%
|
|
510
|
+
`%cTry:%c If this is not a draft, delete the local hook and read the %c${sourceType}%c in render.`,
|
|
511
|
+
STYLES.bold,
|
|
512
|
+
"",
|
|
513
|
+
STYLES.actionPill,
|
|
514
|
+
""
|
|
515
|
+
);
|
|
516
|
+
} else if (isBooleanLike(nameA) || isBooleanLike(nameB)) {
|
|
517
|
+
console.log(
|
|
518
|
+
`%cTry:%c One %cstatus%c instead of several flags.`,
|
|
486
519
|
STYLES.bold,
|
|
487
520
|
"",
|
|
488
521
|
STYLES.actionPill,
|
|
489
522
|
""
|
|
490
523
|
);
|
|
491
524
|
} else {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
);
|
|
502
|
-
} else {
|
|
503
|
-
console.log(
|
|
504
|
-
`%cFix:%c Redundant State detected. Derive %c${infoB.name}%c from %c${infoA.name}%c during render, or use %cuseMemo%c.`,
|
|
505
|
-
STYLES.bold,
|
|
506
|
-
"",
|
|
507
|
-
STYLES.label,
|
|
508
|
-
"",
|
|
509
|
-
STYLES.label,
|
|
510
|
-
"",
|
|
511
|
-
STYLES.actionPill,
|
|
512
|
-
""
|
|
513
|
-
);
|
|
514
|
-
}
|
|
525
|
+
console.log(
|
|
526
|
+
`%cTry:%c If %c${nameB}%c is just %c${nameA}%c in another shape, compute it while rendering.`,
|
|
527
|
+
STYLES.bold,
|
|
528
|
+
"",
|
|
529
|
+
STYLES.label,
|
|
530
|
+
"",
|
|
531
|
+
STYLES.label,
|
|
532
|
+
""
|
|
533
|
+
);
|
|
515
534
|
}
|
|
516
535
|
console.groupEnd();
|
|
517
536
|
};
|
|
518
|
-
var displayCausalHint = (targetLabel,
|
|
537
|
+
var displayCausalHint = (targetLabel, _targetMeta, sourceLabel, sourceMeta) => {
|
|
519
538
|
if (!isWeb || !shouldLog(`causal-${sourceLabel}-${targetLabel}`)) return;
|
|
520
539
|
const target = parseLabel(targetLabel);
|
|
521
|
-
const
|
|
522
|
-
const
|
|
540
|
+
const sourceName = displayName(sourceLabel);
|
|
541
|
+
const targetName = displayName(targetLabel);
|
|
542
|
+
const headerType = sourceMeta.role === "context" /* CONTEXT */ ? "extra render from context" : sourceMeta.role === "store" /* STORE */ ? "extra render from a store" : "extra render";
|
|
523
543
|
const isEffect = sourceLabel.includes("effect") || sourceLabel.includes("useLayoutEffect");
|
|
524
|
-
console.groupCollapsed(`%c
|
|
525
|
-
console.log(`%c
|
|
526
|
-
console.log(
|
|
544
|
+
console.groupCollapsed(`%c BASIS | ${headerType} `, STYLES.headerProblem);
|
|
545
|
+
console.log(`%c${target.file}`, STYLES.location);
|
|
546
|
+
console.log(
|
|
547
|
+
`%c${sourceName}%c updates %c${targetName}%c on the next frame.`,
|
|
548
|
+
STYLES.label,
|
|
549
|
+
"",
|
|
550
|
+
STYLES.label,
|
|
551
|
+
""
|
|
552
|
+
);
|
|
527
553
|
if (isEffect) {
|
|
528
554
|
console.log(
|
|
529
|
-
`%
|
|
555
|
+
`%cTry:%c If %c${targetName}%c can be computed while rendering, drop the extra setState.`,
|
|
530
556
|
STYLES.bold,
|
|
531
557
|
"",
|
|
532
558
|
STYLES.label,
|
|
533
|
-
"",
|
|
534
|
-
STYLES.actionPill,
|
|
535
559
|
""
|
|
536
560
|
);
|
|
537
561
|
} else {
|
|
538
562
|
console.log(
|
|
539
|
-
`%
|
|
563
|
+
`%cTry:%c Write %c${targetName}%c in the same update as %c${sourceName}%c if they are one fact.`,
|
|
540
564
|
STYLES.bold,
|
|
541
565
|
"",
|
|
542
566
|
STYLES.label,
|
|
@@ -569,7 +593,7 @@ var displayGraphReport = (graph) => {
|
|
|
569
593
|
if (!isWeb) return;
|
|
570
594
|
if (graph.nodes.length === 0) {
|
|
571
595
|
console.log(
|
|
572
|
-
`%c
|
|
596
|
+
`%c BASIS | update graph %c(nothing recorded yet)`,
|
|
573
597
|
STYLES.headerIdentity,
|
|
574
598
|
`color: ${THEME.muted}; font-style: italic;`
|
|
575
599
|
);
|
|
@@ -588,17 +612,22 @@ var displayGraphReport = (graph) => {
|
|
|
588
612
|
occurrences: g.occurrences
|
|
589
613
|
}));
|
|
590
614
|
const groupedSourceIds = new Set(graph.eventGroups.flatMap((g) => g.sourceIds));
|
|
591
|
-
const nonEventGroups = Array.from(outgoing.keys()).filter((id) => !groupedSourceIds.has(id)).map((id) => ({
|
|
615
|
+
const nonEventGroups = Array.from(outgoing.keys()).filter((id) => !groupedSourceIds.has(id)).map((id) => ({
|
|
616
|
+
sourceIds: [id],
|
|
617
|
+
sourceNode: nodeById.get(id),
|
|
618
|
+
edges: outgoing.get(id),
|
|
619
|
+
occurrences: 1
|
|
620
|
+
}));
|
|
592
621
|
const groups = [...eventGroups, ...nonEventGroups].sort(
|
|
593
622
|
(a, b) => b.edges.length - a.edges.length || b.occurrences - a.occurrences
|
|
594
623
|
);
|
|
595
624
|
console.group(
|
|
596
|
-
`%c
|
|
625
|
+
`%c BASIS | update graph %c${graph.nodes.length} nodes \xB7 ${graph.edges.length} edges \xB7 ${groups.length} sources \xB7 last ${graph.bufferWindowSize} frames`,
|
|
597
626
|
STYLES.headerIdentity,
|
|
598
|
-
`color: ${THEME.muted}; font-weight: normal
|
|
627
|
+
`color: ${THEME.muted}; font-weight: normal;`
|
|
599
628
|
);
|
|
600
629
|
console.log(
|
|
601
|
-
`%cparent \u2192 child =
|
|
630
|
+
`%cparent \u2192 child = what we saw cause an update. (\xD7N) = times in this window. Repeat clicks with the same targets are grouped.`,
|
|
602
631
|
STYLES.subText
|
|
603
632
|
);
|
|
604
633
|
groups.forEach((group) => {
|
|
@@ -606,12 +635,12 @@ var displayGraphReport = (graph) => {
|
|
|
606
635
|
const isCtx = group.sourceNode?.role === "context" /* CONTEXT */;
|
|
607
636
|
const isFx = group.sourceNode?.role === "effect";
|
|
608
637
|
const isUnknown = group.sourceNode?.role === "unknown";
|
|
609
|
-
const icon = isEvent ? "\
|
|
638
|
+
const icon = isEvent ? "\u2022" : isCtx ? "ctx" : isFx ? "fx" : isUnknown ? "?" : "\u2022";
|
|
610
639
|
const color = isEvent ? THEME.solution : isCtx ? THEME.context : THEME.identity;
|
|
611
640
|
const fanout = group.edges.length;
|
|
612
641
|
const hits = group.occurrences;
|
|
613
642
|
const hitLabel = hits > 1 ? ` \xB7 \xD7${hits}` : "";
|
|
614
|
-
const title = isEvent ? `
|
|
643
|
+
const title = isEvent ? `click / event \xB7 ${fanout} update${fanout === 1 ? "" : "s"}${hitLabel}` : formatNode(group.sourceNode, group.sourceIds[0]);
|
|
615
644
|
console.groupCollapsed(
|
|
616
645
|
`%c${icon} %c${title}`,
|
|
617
646
|
`color: ${color};`,
|
|
@@ -623,16 +652,16 @@ var displayGraphReport = (graph) => {
|
|
|
623
652
|
const weight = edge.weight > 1 ? ` (\xD7${edge.weight})` : "";
|
|
624
653
|
if (target?.redundant) {
|
|
625
654
|
console.log(
|
|
626
|
-
`%c ${label}%c${weight} %
|
|
655
|
+
`%c ${label}%c${weight} %cmoving with another hook`,
|
|
627
656
|
`color: ${THEME.muted}; font-family: monospace;`,
|
|
628
|
-
`color: ${THEME.muted}
|
|
657
|
+
`color: ${THEME.muted};`,
|
|
629
658
|
`color: ${THEME.problem}; font-weight: bold;`
|
|
630
659
|
);
|
|
631
660
|
} else {
|
|
632
661
|
console.log(
|
|
633
662
|
`%c ${label}%c${weight}`,
|
|
634
663
|
`color: ${THEME.muted}; font-family: monospace;`,
|
|
635
|
-
`color: ${THEME.muted}
|
|
664
|
+
`color: ${THEME.muted};`
|
|
636
665
|
);
|
|
637
666
|
}
|
|
638
667
|
});
|
|
@@ -640,19 +669,26 @@ var displayGraphReport = (graph) => {
|
|
|
640
669
|
});
|
|
641
670
|
console.groupEnd();
|
|
642
671
|
};
|
|
643
|
-
var displayViolentBreaker = (label, count,
|
|
672
|
+
var displayViolentBreaker = (label, count, _threshold) => {
|
|
644
673
|
if (!isWeb) return;
|
|
645
|
-
const
|
|
646
|
-
console.group(`%c
|
|
647
|
-
console.error(
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
console.log(
|
|
674
|
+
const name = displayName(label);
|
|
675
|
+
console.group(`%c BASIS | loop guard `, STYLES.headerProblem);
|
|
676
|
+
console.error(
|
|
677
|
+
`${name} updated ${count} times in one second. Basis stopped recording this path so the tab stays usable.`
|
|
678
|
+
);
|
|
679
|
+
console.log(
|
|
680
|
+
`%cReact may still error on its own. Fix the effect that writes a value it also lists as a dependency.`,
|
|
681
|
+
`color: ${THEME.muted};`
|
|
682
|
+
);
|
|
651
683
|
console.groupEnd();
|
|
652
684
|
};
|
|
653
685
|
var displayBootLog = (windowSize) => {
|
|
654
686
|
if (!isWeb) return;
|
|
655
|
-
console.log(
|
|
687
|
+
console.log(
|
|
688
|
+
`%cBasis%c watching updates (${windowSize}-frame window)`,
|
|
689
|
+
STYLES.basis,
|
|
690
|
+
`color: ${THEME.muted}; margin-left: 8px;`
|
|
691
|
+
);
|
|
656
692
|
};
|
|
657
693
|
|
|
658
694
|
// src/core/analysis.ts
|
|
@@ -693,6 +729,12 @@ var calculateAllSimilarities = (entryA, entryB) => {
|
|
|
693
729
|
significantLead
|
|
694
730
|
};
|
|
695
731
|
};
|
|
732
|
+
var overlapFrom = (s) => ({
|
|
733
|
+
kSync: s.kSync,
|
|
734
|
+
densityA: s.densityA,
|
|
735
|
+
densityB: s.densityB,
|
|
736
|
+
cosine: s.sync
|
|
737
|
+
});
|
|
696
738
|
var shouldSkipComparison = (entryA, entryB, dirtyLabels2) => {
|
|
697
739
|
if (entryA.label === entryB.label) return true;
|
|
698
740
|
if (isSameField(entryA.label, entryB.label)) return true;
|
|
@@ -717,21 +759,21 @@ var detectRedundancy = (entryA, entryB, similarities, redundantSet, violationMap
|
|
|
717
759
|
const roleB = entryB.meta.role;
|
|
718
760
|
if (isGlobalSource(roleA) && isGlobalSource(roleB)) return;
|
|
719
761
|
if (similarities.densityA < 2 || similarities.densityB < 2) return;
|
|
720
|
-
const
|
|
762
|
+
const overlap = overlapFrom(similarities);
|
|
721
763
|
if (roleA === "local" /* LOCAL */ && isGlobalSource(roleB)) {
|
|
722
764
|
redundantSet.add(entryA.label);
|
|
723
|
-
pushViolation(violationMap, entryB.label, { type: "context_mirror", target: entryA.label,
|
|
724
|
-
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta,
|
|
765
|
+
pushViolation(violationMap, entryB.label, { type: "context_mirror", target: entryA.label, overlap });
|
|
766
|
+
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, overlap);
|
|
725
767
|
} else if (isGlobalSource(roleA) && roleB === "local" /* LOCAL */) {
|
|
726
768
|
redundantSet.add(entryB.label);
|
|
727
|
-
pushViolation(violationMap, entryA.label, { type: "context_mirror", target: entryB.label,
|
|
728
|
-
displayRedundancyAlert(entryB.label, entryB.meta, entryA.label, entryA.meta,
|
|
769
|
+
pushViolation(violationMap, entryA.label, { type: "context_mirror", target: entryB.label, overlap });
|
|
770
|
+
displayRedundancyAlert(entryB.label, entryB.meta, entryA.label, entryA.meta, overlap);
|
|
729
771
|
} else if (roleA === "local" /* LOCAL */ && roleB === "local" /* LOCAL */) {
|
|
730
772
|
redundantSet.add(entryA.label);
|
|
731
773
|
redundantSet.add(entryB.label);
|
|
732
|
-
pushViolation(violationMap, entryA.label, { type: "duplicate_state", target: entryB.label,
|
|
733
|
-
pushViolation(violationMap, entryB.label, { type: "duplicate_state", target: entryA.label,
|
|
734
|
-
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta,
|
|
774
|
+
pushViolation(violationMap, entryA.label, { type: "duplicate_state", target: entryB.label, overlap });
|
|
775
|
+
pushViolation(violationMap, entryB.label, { type: "duplicate_state", target: entryA.label, overlap });
|
|
776
|
+
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, overlap);
|
|
735
777
|
}
|
|
736
778
|
};
|
|
737
779
|
var detectCausalLeak = (entryA, entryB, similarities, violationMap, graph) => {
|
|
@@ -1095,4 +1137,4 @@ export {
|
|
|
1095
1137
|
getBasisGraph,
|
|
1096
1138
|
printBasisGraph
|
|
1097
1139
|
};
|
|
1098
|
-
//# sourceMappingURL=chunk-
|
|
1140
|
+
//# sourceMappingURL=chunk-KIXM6YRX.mjs.map
|