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
|
@@ -311,6 +311,10 @@ var shouldLog = (key) => {
|
|
|
311
311
|
return false;
|
|
312
312
|
};
|
|
313
313
|
var isBooleanLike = (name) => /^(is|has|can|should|did|will|show|hide)(?=[A-Z_])/.test(name);
|
|
314
|
+
var displayName = (raw) => {
|
|
315
|
+
const { name } = parseLabel(raw);
|
|
316
|
+
return name.replace(/:\d+$/, "");
|
|
317
|
+
};
|
|
314
318
|
var areSyncSignificant = (metaA, metaB) => {
|
|
315
319
|
const { kSync, densityA, densityB } = countOverlapsCircular(
|
|
316
320
|
metaA.buffer,
|
|
@@ -322,95 +326,80 @@ var areSyncSignificant = (metaA, metaB) => {
|
|
|
322
326
|
};
|
|
323
327
|
var getSuggestedFix = (issue, info) => {
|
|
324
328
|
if (issue.label.includes("Global Event")) {
|
|
325
|
-
return `
|
|
329
|
+
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.`;
|
|
326
330
|
}
|
|
327
331
|
const violations = issue.violations || [];
|
|
328
332
|
const leaks = violations.filter((v) => v.type === "causal_leak");
|
|
329
333
|
const mirrors = violations.filter((v) => v.type === "context_mirror");
|
|
330
334
|
const duplicates = violations.filter((v) => v.type === "duplicate_state");
|
|
331
335
|
if (mirrors.length > 0) {
|
|
332
|
-
return `
|
|
336
|
+
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.`;
|
|
333
337
|
}
|
|
334
338
|
if (leaks.length > 0) {
|
|
335
|
-
const targetName =
|
|
339
|
+
const targetName = displayName(leaks[0].target);
|
|
336
340
|
if (issue.label.includes("effect")) {
|
|
337
|
-
return `
|
|
341
|
+
return `An effect is calling setState on ${targetName}, which paints again. If you can compute ${targetName} while rendering, drop the %ceffect%c.`;
|
|
338
342
|
}
|
|
339
|
-
return
|
|
343
|
+
return `${info.name} updates, then ${targetName} updates on the next frame. If they are one fact, write them in the same %csetState%c.`;
|
|
340
344
|
}
|
|
341
345
|
if (duplicates.length > 0) {
|
|
342
346
|
if (isBooleanLike(info.name)) {
|
|
343
|
-
return `
|
|
347
|
+
return `Several flags move together. One %cstatus%c value avoids impossible combinations.`;
|
|
344
348
|
}
|
|
345
|
-
return `
|
|
349
|
+
return `These hooks move together. If one is just the other in another shape, compute it while %crendering%c.`;
|
|
346
350
|
}
|
|
347
351
|
if (issue.metric === "density") {
|
|
348
|
-
return `
|
|
352
|
+
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.`;
|
|
349
353
|
}
|
|
350
|
-
return `
|
|
354
|
+
return `Inspect ${info.name} and what updates with it.`;
|
|
351
355
|
};
|
|
352
356
|
var displayHealthReport = (history2, violationMap) => {
|
|
353
357
|
if (!isWeb) return;
|
|
354
358
|
const entries = Array.from(history2.entries());
|
|
355
359
|
if (entries.length === 0) return;
|
|
356
360
|
const topIssues = identifyTopIssues(instance.graph, history2, instance.redundantLabels, violationMap);
|
|
357
|
-
console.group(`%c
|
|
361
|
+
console.group(`%c BASIS | report `, STYLES.headerIdentity);
|
|
358
362
|
if (topIssues.length > 0) {
|
|
359
|
-
console.log(
|
|
360
|
-
`%c\u{1F3AF} REFACTOR PRIORITIES %c(PRIME MOVERS)`,
|
|
361
|
-
`font-weight: bold; color: ${THEME.identity}; margin-top: 10px;`,
|
|
362
|
-
`font-weight: normal; color: ${THEME.muted}; font-style: italic;`
|
|
363
|
-
);
|
|
363
|
+
console.log(`%cStart here`, `font-weight: bold; color: ${THEME.identity}; margin-top: 10px;`);
|
|
364
364
|
topIssues.forEach((issue, idx) => {
|
|
365
365
|
const info = parseLabel(issue.label);
|
|
366
|
-
const icon = issue.metric === "influence" ? "\
|
|
366
|
+
const icon = issue.metric === "influence" ? "\u2192" : "\u2022";
|
|
367
367
|
const pColor = idx === 0 ? THEME.problem : idx === 1 ? THEME.solution : THEME.identity;
|
|
368
|
-
let displayName = info.name;
|
|
369
|
-
let displayFile = info.file;
|
|
370
|
-
if (issue.label.includes("Global Event")) {
|
|
371
|
-
displayName = info.name;
|
|
372
|
-
displayFile = info.file;
|
|
373
|
-
}
|
|
374
368
|
console.group(
|
|
375
|
-
` %c${idx + 1}%c ${icon} ${displayName} %c(${
|
|
369
|
+
` %c${idx + 1}%c ${icon} ${displayName(issue.label)} %c(${info.file})`,
|
|
376
370
|
`background: ${pColor}; color: ${idx === 1 ? "black" : "white"}; border-radius: 50%; padding: 0 5px;`,
|
|
377
371
|
"font-family: monospace; font-weight: 700;",
|
|
378
|
-
`color: ${THEME.muted}; font-size: 10px; font-weight: normal
|
|
372
|
+
`color: ${THEME.muted}; font-size: 10px; font-weight: normal;`
|
|
379
373
|
);
|
|
380
|
-
console.log(`%c${issue.reason}`, `color: ${THEME.muted}
|
|
374
|
+
console.log(`%c${issue.reason}`, `color: ${THEME.muted};`);
|
|
381
375
|
if (issue.violations.length > 0) {
|
|
382
376
|
const byFile = /* @__PURE__ */ new Map();
|
|
383
377
|
issue.violations.forEach((v) => {
|
|
384
378
|
if (issue.label.includes("Global Event") && v.type === "context_mirror") return;
|
|
385
379
|
const { file, name } = parseLabel(v.target);
|
|
386
380
|
if (!byFile.has(file)) byFile.set(file, []);
|
|
387
|
-
byFile.get(file).push(name);
|
|
381
|
+
byFile.get(file).push(name.replace(/:\d+$/, ""));
|
|
388
382
|
});
|
|
389
383
|
const impactParts = [];
|
|
390
384
|
byFile.forEach((vars, file) => {
|
|
391
|
-
|
|
392
|
-
impactParts.push(`${file} (${varList})`);
|
|
385
|
+
impactParts.push(`${file} (${vars.join(", ")})`);
|
|
393
386
|
});
|
|
394
387
|
if (impactParts.length > 0) {
|
|
395
|
-
console.log(`%
|
|
388
|
+
console.log(`%cAlso updates: %c${impactParts.join(" \xB7 ")}`, STYLES.impactLabel, "");
|
|
396
389
|
}
|
|
397
390
|
}
|
|
398
391
|
const fix = getSuggestedFix(issue, info);
|
|
399
392
|
const fixParts = fix.split("%c");
|
|
400
393
|
if (fixParts.length === 3) {
|
|
401
394
|
console.log(
|
|
402
|
-
`%
|
|
395
|
+
`%cTry: %c${fixParts[0]}%c${fixParts[1]}%c${fixParts[2]}`,
|
|
403
396
|
STYLES.actionLabel,
|
|
404
397
|
"",
|
|
405
398
|
STYLES.actionPill,
|
|
406
399
|
""
|
|
407
400
|
);
|
|
408
401
|
} else {
|
|
409
|
-
console.log(
|
|
410
|
-
`%cSolution: %c${fix}`,
|
|
411
|
-
STYLES.actionLabel,
|
|
412
|
-
""
|
|
413
|
-
);
|
|
402
|
+
console.log(`%cTry: %c${fix}`, STYLES.actionLabel, "");
|
|
414
403
|
}
|
|
415
404
|
console.groupEnd();
|
|
416
405
|
});
|
|
@@ -434,135 +423,170 @@ var displayHealthReport = (history2, violationMap) => {
|
|
|
434
423
|
else independentCount++;
|
|
435
424
|
});
|
|
436
425
|
const totalVars = entries.length;
|
|
437
|
-
const redundancyScore = (independentCount + clusters.length) / totalVars * 100;
|
|
438
|
-
let internalEdges = 0;
|
|
439
|
-
instance.graph.forEach((targets, source) => {
|
|
440
|
-
if (source.startsWith("Event_Tick_")) return;
|
|
441
|
-
internalEdges += targets.size;
|
|
442
|
-
});
|
|
443
|
-
const causalPenalty = internalEdges / totalVars * 100;
|
|
444
|
-
let healthScore = redundancyScore - causalPenalty;
|
|
445
|
-
if (healthScore < 0) healthScore = 0;
|
|
446
|
-
const scoreColor = healthScore > 85 ? THEME.success : THEME.problem;
|
|
447
426
|
console.log(
|
|
448
|
-
`%
|
|
449
|
-
STYLES.
|
|
450
|
-
`color: ${scoreColor}; font-weight: bold;`
|
|
427
|
+
`%c${independentCount + clusters.length} of ${totalVars} instrumented hooks look independent in this window.`,
|
|
428
|
+
STYLES.subText
|
|
451
429
|
);
|
|
452
|
-
console.log(`%cSources of Truth: ${independentCount + clusters.length}/${totalVars} | Causal Leaks: ${internalEdges}`, STYLES.subText);
|
|
453
430
|
if (clusters.length > 0) {
|
|
454
|
-
console.log(
|
|
431
|
+
console.log(
|
|
432
|
+
`%c${clusters.length} group${clusters.length === 1 ? "" : "s"} that keep updating together:`,
|
|
433
|
+
`font-weight: bold; color: ${THEME.problem}; margin-top: 10px;`
|
|
434
|
+
);
|
|
455
435
|
clusters.forEach((cluster, idx) => {
|
|
456
436
|
const clusterMetas = cluster.map((l) => ({
|
|
457
437
|
label: l,
|
|
458
438
|
meta: history2.get(l),
|
|
459
|
-
name:
|
|
439
|
+
name: displayName(l)
|
|
460
440
|
}));
|
|
461
441
|
const hasCtx = clusterMetas.some(
|
|
462
442
|
(c) => c.meta.role === "context" /* CONTEXT */ || c.meta.role === "store" /* STORE */
|
|
463
443
|
);
|
|
464
|
-
const names = clusterMetas.map((c) =>
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
444
|
+
const names = clusterMetas.map((c) => c.name).join(", ");
|
|
445
|
+
console.group(
|
|
446
|
+
` %c${idx + 1}%c ${names}`,
|
|
447
|
+
`background: ${THEME.problem}; color: white; border-radius: 50%; padding: 0 5px;`,
|
|
448
|
+
"font-family: monospace; font-weight: bold;"
|
|
449
|
+
);
|
|
469
450
|
if (hasCtx) {
|
|
470
451
|
const hasStore = clusterMetas.some((c) => c.meta.role === "store" /* STORE */);
|
|
471
|
-
const sourceType = hasStore ? "
|
|
472
|
-
console.log(
|
|
473
|
-
console.log(
|
|
452
|
+
const sourceType = hasStore ? "a store" : "context";
|
|
453
|
+
console.log(`A local hook is only following ${sourceType}.`);
|
|
454
|
+
console.log(
|
|
455
|
+
`%cTry:%c Read ${sourceType} in render if the local value is not a draft.`,
|
|
456
|
+
STYLES.actionLabel,
|
|
457
|
+
""
|
|
458
|
+
);
|
|
474
459
|
} else {
|
|
475
|
-
const boolKeywords = [
|
|
460
|
+
const boolKeywords = [
|
|
461
|
+
"is",
|
|
462
|
+
"has",
|
|
463
|
+
"can",
|
|
464
|
+
"should",
|
|
465
|
+
"loading",
|
|
466
|
+
"success",
|
|
467
|
+
"error",
|
|
468
|
+
"active",
|
|
469
|
+
"enabled",
|
|
470
|
+
"open",
|
|
471
|
+
"visible"
|
|
472
|
+
];
|
|
476
473
|
const boolCount = clusterMetas.filter(
|
|
477
474
|
(c) => boolKeywords.some((kw) => c.name.toLowerCase().startsWith(kw))
|
|
478
475
|
).length;
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
console.log(
|
|
482
|
-
|
|
476
|
+
if (cluster.length > 2 && boolCount / cluster.length > 0.5) {
|
|
477
|
+
console.log(`These flags move together.`);
|
|
478
|
+
console.log(
|
|
479
|
+
`%cTry:%c One %cstatus%c instead of several booleans.`,
|
|
480
|
+
STYLES.actionLabel,
|
|
481
|
+
"",
|
|
482
|
+
STYLES.actionPill,
|
|
483
|
+
""
|
|
484
|
+
);
|
|
483
485
|
} else if (cluster.length > 2) {
|
|
484
|
-
console.log(
|
|
485
|
-
console.log(
|
|
486
|
+
console.log(`These hooks move on the same frames. Often the same click or fetch.`);
|
|
487
|
+
console.log(
|
|
488
|
+
`%cTry:%c Leave it if that is intentional. Otherwise one %creducer%c.`,
|
|
489
|
+
STYLES.actionLabel,
|
|
490
|
+
"",
|
|
491
|
+
STYLES.actionPill,
|
|
492
|
+
""
|
|
493
|
+
);
|
|
486
494
|
} else {
|
|
487
|
-
console.log(
|
|
488
|
-
console.log(
|
|
495
|
+
console.log(`These two hooks keep updating in the same frame.`);
|
|
496
|
+
console.log(
|
|
497
|
+
`%cTry:%c If one is derived, compute it while %crendering%c.`,
|
|
498
|
+
STYLES.actionLabel,
|
|
499
|
+
"",
|
|
500
|
+
STYLES.actionPill,
|
|
501
|
+
""
|
|
502
|
+
);
|
|
489
503
|
}
|
|
490
504
|
}
|
|
491
505
|
console.groupEnd();
|
|
492
506
|
});
|
|
493
507
|
} else {
|
|
494
|
-
console.log(
|
|
508
|
+
console.log(
|
|
509
|
+
"%cNo hooks were updating in lockstep in this window.",
|
|
510
|
+
`color: ${THEME.success}; font-weight: bold;`
|
|
511
|
+
);
|
|
495
512
|
}
|
|
496
513
|
console.groupEnd();
|
|
497
514
|
};
|
|
498
|
-
var displayRedundancyAlert = (labelA, metaA, labelB, metaB,
|
|
515
|
+
var displayRedundancyAlert = (labelA, metaA, labelB, metaB, overlap) => {
|
|
499
516
|
if (!isWeb || !shouldLog(`redundant-${labelA}-${labelB}`)) return;
|
|
500
517
|
const infoA = parseLabel(labelA);
|
|
501
|
-
const
|
|
518
|
+
const nameA = displayName(labelA);
|
|
519
|
+
const nameB = displayName(labelB);
|
|
502
520
|
const isContextMirror = metaA.role === "local" /* LOCAL */ && metaB.role === "context" /* CONTEXT */ || metaB.role === "local" /* LOCAL */ && metaA.role === "context" /* CONTEXT */;
|
|
503
521
|
const isStoreMirror = metaA.role === "local" /* LOCAL */ && metaB.role === "store" /* STORE */ || metaB.role === "local" /* LOCAL */ && metaA.role === "store" /* STORE */;
|
|
504
|
-
const alertType = isContextMirror ? "
|
|
505
|
-
|
|
506
|
-
console.
|
|
507
|
-
console.log(`%
|
|
522
|
+
const alertType = isContextMirror ? "local state follows context" : isStoreMirror ? "local state follows a store" : "hooks moving together";
|
|
523
|
+
const times = overlap.kSync === 1 ? "time" : "times";
|
|
524
|
+
console.group(`%c BASIS | ${alertType} `, STYLES.headerProblem);
|
|
525
|
+
console.log(`%c${infoA.file}`, STYLES.location);
|
|
526
|
+
console.log(
|
|
527
|
+
`%c${nameA}%c and %c${nameB}%c updated in the same frame ${overlap.kSync} ${times}.`,
|
|
528
|
+
STYLES.label,
|
|
529
|
+
"",
|
|
530
|
+
STYLES.label,
|
|
531
|
+
""
|
|
532
|
+
);
|
|
508
533
|
if (isContextMirror || isStoreMirror) {
|
|
509
|
-
const sourceType = isStoreMirror ? "
|
|
534
|
+
const sourceType = isStoreMirror ? "store" : "context";
|
|
510
535
|
console.log(
|
|
511
|
-
`%
|
|
536
|
+
`%cTry:%c If this is not a draft, delete the local hook and read the %c${sourceType}%c in render.`,
|
|
537
|
+
STYLES.bold,
|
|
538
|
+
"",
|
|
539
|
+
STYLES.actionPill,
|
|
540
|
+
""
|
|
541
|
+
);
|
|
542
|
+
} else if (isBooleanLike(nameA) || isBooleanLike(nameB)) {
|
|
543
|
+
console.log(
|
|
544
|
+
`%cTry:%c One %cstatus%c instead of several flags.`,
|
|
512
545
|
STYLES.bold,
|
|
513
546
|
"",
|
|
514
547
|
STYLES.actionPill,
|
|
515
548
|
""
|
|
516
549
|
);
|
|
517
550
|
} else {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
);
|
|
528
|
-
} else {
|
|
529
|
-
console.log(
|
|
530
|
-
`%cFix:%c Redundant State detected. Derive %c${infoB.name}%c from %c${infoA.name}%c during render, or use %cuseMemo%c.`,
|
|
531
|
-
STYLES.bold,
|
|
532
|
-
"",
|
|
533
|
-
STYLES.label,
|
|
534
|
-
"",
|
|
535
|
-
STYLES.label,
|
|
536
|
-
"",
|
|
537
|
-
STYLES.actionPill,
|
|
538
|
-
""
|
|
539
|
-
);
|
|
540
|
-
}
|
|
551
|
+
console.log(
|
|
552
|
+
`%cTry:%c If %c${nameB}%c is just %c${nameA}%c in another shape, compute it while rendering.`,
|
|
553
|
+
STYLES.bold,
|
|
554
|
+
"",
|
|
555
|
+
STYLES.label,
|
|
556
|
+
"",
|
|
557
|
+
STYLES.label,
|
|
558
|
+
""
|
|
559
|
+
);
|
|
541
560
|
}
|
|
542
561
|
console.groupEnd();
|
|
543
562
|
};
|
|
544
|
-
var displayCausalHint = (targetLabel,
|
|
563
|
+
var displayCausalHint = (targetLabel, _targetMeta, sourceLabel, sourceMeta) => {
|
|
545
564
|
if (!isWeb || !shouldLog(`causal-${sourceLabel}-${targetLabel}`)) return;
|
|
546
565
|
const target = parseLabel(targetLabel);
|
|
547
|
-
const
|
|
548
|
-
const
|
|
566
|
+
const sourceName = displayName(sourceLabel);
|
|
567
|
+
const targetName = displayName(targetLabel);
|
|
568
|
+
const headerType = sourceMeta.role === "context" /* CONTEXT */ ? "extra render from context" : sourceMeta.role === "store" /* STORE */ ? "extra render from a store" : "extra render";
|
|
549
569
|
const isEffect = sourceLabel.includes("effect") || sourceLabel.includes("useLayoutEffect");
|
|
550
|
-
console.groupCollapsed(`%c
|
|
551
|
-
console.log(`%c
|
|
552
|
-
console.log(
|
|
570
|
+
console.groupCollapsed(`%c BASIS | ${headerType} `, STYLES.headerProblem);
|
|
571
|
+
console.log(`%c${target.file}`, STYLES.location);
|
|
572
|
+
console.log(
|
|
573
|
+
`%c${sourceName}%c updates %c${targetName}%c on the next frame.`,
|
|
574
|
+
STYLES.label,
|
|
575
|
+
"",
|
|
576
|
+
STYLES.label,
|
|
577
|
+
""
|
|
578
|
+
);
|
|
553
579
|
if (isEffect) {
|
|
554
580
|
console.log(
|
|
555
|
-
`%
|
|
581
|
+
`%cTry:%c If %c${targetName}%c can be computed while rendering, drop the extra setState.`,
|
|
556
582
|
STYLES.bold,
|
|
557
583
|
"",
|
|
558
584
|
STYLES.label,
|
|
559
|
-
"",
|
|
560
|
-
STYLES.actionPill,
|
|
561
585
|
""
|
|
562
586
|
);
|
|
563
587
|
} else {
|
|
564
588
|
console.log(
|
|
565
|
-
`%
|
|
589
|
+
`%cTry:%c Write %c${targetName}%c in the same update as %c${sourceName}%c if they are one fact.`,
|
|
566
590
|
STYLES.bold,
|
|
567
591
|
"",
|
|
568
592
|
STYLES.label,
|
|
@@ -595,7 +619,7 @@ var displayGraphReport = (graph) => {
|
|
|
595
619
|
if (!isWeb) return;
|
|
596
620
|
if (graph.nodes.length === 0) {
|
|
597
621
|
console.log(
|
|
598
|
-
`%c
|
|
622
|
+
`%c BASIS | update graph %c(nothing recorded yet)`,
|
|
599
623
|
STYLES.headerIdentity,
|
|
600
624
|
`color: ${THEME.muted}; font-style: italic;`
|
|
601
625
|
);
|
|
@@ -614,17 +638,22 @@ var displayGraphReport = (graph) => {
|
|
|
614
638
|
occurrences: g.occurrences
|
|
615
639
|
}));
|
|
616
640
|
const groupedSourceIds = new Set(graph.eventGroups.flatMap((g) => g.sourceIds));
|
|
617
|
-
const nonEventGroups = Array.from(outgoing.keys()).filter((id) => !groupedSourceIds.has(id)).map((id) => ({
|
|
641
|
+
const nonEventGroups = Array.from(outgoing.keys()).filter((id) => !groupedSourceIds.has(id)).map((id) => ({
|
|
642
|
+
sourceIds: [id],
|
|
643
|
+
sourceNode: nodeById.get(id),
|
|
644
|
+
edges: outgoing.get(id),
|
|
645
|
+
occurrences: 1
|
|
646
|
+
}));
|
|
618
647
|
const groups = [...eventGroups, ...nonEventGroups].sort(
|
|
619
648
|
(a, b) => b.edges.length - a.edges.length || b.occurrences - a.occurrences
|
|
620
649
|
);
|
|
621
650
|
console.group(
|
|
622
|
-
`%c
|
|
651
|
+
`%c BASIS | update graph %c${graph.nodes.length} nodes \xB7 ${graph.edges.length} edges \xB7 ${groups.length} sources \xB7 last ${graph.bufferWindowSize} frames`,
|
|
623
652
|
STYLES.headerIdentity,
|
|
624
|
-
`color: ${THEME.muted}; font-weight: normal
|
|
653
|
+
`color: ${THEME.muted}; font-weight: normal;`
|
|
625
654
|
);
|
|
626
655
|
console.log(
|
|
627
|
-
`%cparent \u2192 child =
|
|
656
|
+
`%cparent \u2192 child = what we saw cause an update. (\xD7N) = times in this window. Repeat clicks with the same targets are grouped.`,
|
|
628
657
|
STYLES.subText
|
|
629
658
|
);
|
|
630
659
|
groups.forEach((group) => {
|
|
@@ -632,12 +661,12 @@ var displayGraphReport = (graph) => {
|
|
|
632
661
|
const isCtx = group.sourceNode?.role === "context" /* CONTEXT */;
|
|
633
662
|
const isFx = group.sourceNode?.role === "effect";
|
|
634
663
|
const isUnknown = group.sourceNode?.role === "unknown";
|
|
635
|
-
const icon = isEvent ? "\
|
|
664
|
+
const icon = isEvent ? "\u2022" : isCtx ? "ctx" : isFx ? "fx" : isUnknown ? "?" : "\u2022";
|
|
636
665
|
const color = isEvent ? THEME.solution : isCtx ? THEME.context : THEME.identity;
|
|
637
666
|
const fanout = group.edges.length;
|
|
638
667
|
const hits = group.occurrences;
|
|
639
668
|
const hitLabel = hits > 1 ? ` \xB7 \xD7${hits}` : "";
|
|
640
|
-
const title = isEvent ? `
|
|
669
|
+
const title = isEvent ? `click / event \xB7 ${fanout} update${fanout === 1 ? "" : "s"}${hitLabel}` : formatNode(group.sourceNode, group.sourceIds[0]);
|
|
641
670
|
console.groupCollapsed(
|
|
642
671
|
`%c${icon} %c${title}`,
|
|
643
672
|
`color: ${color};`,
|
|
@@ -649,16 +678,16 @@ var displayGraphReport = (graph) => {
|
|
|
649
678
|
const weight = edge.weight > 1 ? ` (\xD7${edge.weight})` : "";
|
|
650
679
|
if (target?.redundant) {
|
|
651
680
|
console.log(
|
|
652
|
-
`%c ${label}%c${weight} %
|
|
681
|
+
`%c ${label}%c${weight} %cmoving with another hook`,
|
|
653
682
|
`color: ${THEME.muted}; font-family: monospace;`,
|
|
654
|
-
`color: ${THEME.muted}
|
|
683
|
+
`color: ${THEME.muted};`,
|
|
655
684
|
`color: ${THEME.problem}; font-weight: bold;`
|
|
656
685
|
);
|
|
657
686
|
} else {
|
|
658
687
|
console.log(
|
|
659
688
|
`%c ${label}%c${weight}`,
|
|
660
689
|
`color: ${THEME.muted}; font-family: monospace;`,
|
|
661
|
-
`color: ${THEME.muted}
|
|
690
|
+
`color: ${THEME.muted};`
|
|
662
691
|
);
|
|
663
692
|
}
|
|
664
693
|
});
|
|
@@ -666,14 +695,17 @@ var displayGraphReport = (graph) => {
|
|
|
666
695
|
});
|
|
667
696
|
console.groupEnd();
|
|
668
697
|
};
|
|
669
|
-
var displayViolentBreaker = (label, count,
|
|
698
|
+
var displayViolentBreaker = (label, count, _threshold) => {
|
|
670
699
|
if (!isWeb) return;
|
|
671
|
-
const
|
|
672
|
-
console.group(`%c
|
|
673
|
-
console.error(
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
console.log(
|
|
700
|
+
const name = displayName(label);
|
|
701
|
+
console.group(`%c BASIS | loop guard `, STYLES.headerProblem);
|
|
702
|
+
console.error(
|
|
703
|
+
`${name} updated ${count} times in one second. Basis stopped recording this path so the tab stays usable.`
|
|
704
|
+
);
|
|
705
|
+
console.log(
|
|
706
|
+
`%cReact may still error on its own. Fix the effect that writes a value it also lists as a dependency.`,
|
|
707
|
+
`color: ${THEME.muted};`
|
|
708
|
+
);
|
|
677
709
|
console.groupEnd();
|
|
678
710
|
};
|
|
679
711
|
|
|
@@ -715,6 +747,12 @@ var calculateAllSimilarities = (entryA, entryB) => {
|
|
|
715
747
|
significantLead
|
|
716
748
|
};
|
|
717
749
|
};
|
|
750
|
+
var overlapFrom = (s) => ({
|
|
751
|
+
kSync: s.kSync,
|
|
752
|
+
densityA: s.densityA,
|
|
753
|
+
densityB: s.densityB,
|
|
754
|
+
cosine: s.sync
|
|
755
|
+
});
|
|
718
756
|
var shouldSkipComparison = (entryA, entryB, dirtyLabels2) => {
|
|
719
757
|
if (entryA.label === entryB.label) return true;
|
|
720
758
|
if (isSameField(entryA.label, entryB.label)) return true;
|
|
@@ -739,21 +777,21 @@ var detectRedundancy = (entryA, entryB, similarities, redundantSet, violationMap
|
|
|
739
777
|
const roleB = entryB.meta.role;
|
|
740
778
|
if (isGlobalSource(roleA) && isGlobalSource(roleB)) return;
|
|
741
779
|
if (similarities.densityA < 2 || similarities.densityB < 2) return;
|
|
742
|
-
const
|
|
780
|
+
const overlap = overlapFrom(similarities);
|
|
743
781
|
if (roleA === "local" /* LOCAL */ && isGlobalSource(roleB)) {
|
|
744
782
|
redundantSet.add(entryA.label);
|
|
745
|
-
pushViolation(violationMap, entryB.label, { type: "context_mirror", target: entryA.label,
|
|
746
|
-
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta,
|
|
783
|
+
pushViolation(violationMap, entryB.label, { type: "context_mirror", target: entryA.label, overlap });
|
|
784
|
+
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, overlap);
|
|
747
785
|
} else if (isGlobalSource(roleA) && roleB === "local" /* LOCAL */) {
|
|
748
786
|
redundantSet.add(entryB.label);
|
|
749
|
-
pushViolation(violationMap, entryA.label, { type: "context_mirror", target: entryB.label,
|
|
750
|
-
displayRedundancyAlert(entryB.label, entryB.meta, entryA.label, entryA.meta,
|
|
787
|
+
pushViolation(violationMap, entryA.label, { type: "context_mirror", target: entryB.label, overlap });
|
|
788
|
+
displayRedundancyAlert(entryB.label, entryB.meta, entryA.label, entryA.meta, overlap);
|
|
751
789
|
} else if (roleA === "local" /* LOCAL */ && roleB === "local" /* LOCAL */) {
|
|
752
790
|
redundantSet.add(entryA.label);
|
|
753
791
|
redundantSet.add(entryB.label);
|
|
754
|
-
pushViolation(violationMap, entryA.label, { type: "duplicate_state", target: entryB.label,
|
|
755
|
-
pushViolation(violationMap, entryB.label, { type: "duplicate_state", target: entryA.label,
|
|
756
|
-
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta,
|
|
792
|
+
pushViolation(violationMap, entryA.label, { type: "duplicate_state", target: entryB.label, overlap });
|
|
793
|
+
pushViolation(violationMap, entryB.label, { type: "duplicate_state", target: entryA.label, overlap });
|
|
794
|
+
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, overlap);
|
|
757
795
|
}
|
|
758
796
|
};
|
|
759
797
|
var detectCausalLeak = (entryA, entryB, similarities, violationMap, graph) => {
|