Skip to content

Commit 16c7671

Browse files
committed
fix: correctly highlight sources reassigned inside trace
1 parent 1d773ef commit 16c7671

File tree

7 files changed

+118
-1
lines changed

7 files changed

+118
-1
lines changed

.changeset/silver-shoes-rule.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: correctly highlight sources reassigned inside `trace`

packages/svelte/src/internal/client/dev/tracing.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export let tracing_expressions = null;
1515
*/
1616
function log_entry(signal, entry) {
1717
const debug = signal.debug;
18-
const value = signal.v;
18+
const value = signal.trace_need_increase ? signal.trace_v : signal.v;
1919

2020
if (value === UNINITIALIZED) {
2121
return;

packages/svelte/src/internal/client/reactivity/sources.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,16 @@ export function set(source, value) {
167167
*/
168168
export function internal_set(source, value) {
169169
if (!source.equals(value)) {
170+
var old_value = source.v;
170171
source.v = value;
171172
source.version = increment_version();
172173

173174
if (DEV && tracing_mode_flag) {
174175
source.updated = get_stack('UpdatedAt');
176+
if (active_effect != null) {
177+
source.trace_need_increase = true;
178+
source.trace_v ??= old_value;
179+
}
175180
}
176181

177182
mark_reactions(source, DIRTY);

packages/svelte/src/internal/client/reactivity/types.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export interface Value<V = unknown> extends Signal {
1717
/** Dev only */
1818
created?: Error | null;
1919
updated?: Error | null;
20+
trace_need_increase?: boolean;
21+
trace_v?: V;
2022
debug?: null | (() => void);
2123
}
2224

packages/svelte/src/internal/client/runtime.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,23 @@ export function update_effect(effect) {
528528
effect.teardown = typeof teardown === 'function' ? teardown : null;
529529
effect.version = current_version;
530530

531+
var deps = effect.deps;
532+
533+
// In DEV, we need to handle a case where $inspect.trace() might
534+
// incorrectly state a source dependency has not changed when it has.
535+
// That's beacuse that source was changed by the same effect, causing
536+
// the versions to match. We can avoid this by incrementing the version
537+
if (DEV && tracing_mode_flag && (effect.f & DIRTY) !== 0 && deps !== null) {
538+
for (let i = 0; i < deps.length; i++) {
539+
var dep = deps[i];
540+
if (dep.trace_need_increase) {
541+
dep.version = increment_version();
542+
dep.trace_need_increase = undefined;
543+
dep.trace_v = undefined;
544+
}
545+
}
546+
}
547+
531548
if (DEV) {
532549
dev_effect_stack.push(effect);
533550
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
/**
5+
* @param {any[]} logs
6+
*/
7+
function normalise_trace_logs(logs) {
8+
let normalised = [];
9+
for (let i = 0; i < logs.length; i++) {
10+
const log = logs[i];
11+
12+
if (typeof log === 'string' && log.includes('%c')) {
13+
const split = log.split('%c');
14+
normalised.push({
15+
log: (split[0].length !== 0 ? split[0] : split[1]).trim(),
16+
highlighted: logs[i + 1] === 'color: CornflowerBlue; font-weight: bold'
17+
});
18+
i++;
19+
} else if (log instanceof Error) {
20+
continue;
21+
} else {
22+
normalised.push({ log });
23+
}
24+
}
25+
return normalised;
26+
}
27+
28+
export default test({
29+
compileOptions: {
30+
dev: true
31+
},
32+
33+
test({ assert, target, logs }) {
34+
// initial log, everything is highlighted
35+
36+
assert.deepEqual(normalise_trace_logs(logs), [
37+
{ log: 'effect', highlighted: false },
38+
{ log: '$state', highlighted: true },
39+
{ log: false }
40+
]);
41+
42+
logs.length = 0;
43+
44+
const button = target.querySelector('button');
45+
button?.click();
46+
flushSync();
47+
48+
const input = target.querySelector('input');
49+
input?.click();
50+
flushSync();
51+
52+
// checked changed, effect reassign state, values should be correct and be correctly highlighted
53+
54+
assert.deepEqual(normalise_trace_logs(logs), [
55+
{ log: 'effect', highlighted: false },
56+
{ log: '$state', highlighted: true },
57+
{ log: true },
58+
{ log: '$state', highlighted: true },
59+
{ log: 1 },
60+
{ log: '$state', highlighted: false },
61+
{ log: true },
62+
{ log: '$state', highlighted: true },
63+
{ log: 2 },
64+
{ log: '$state', highlighted: false },
65+
{ log: true },
66+
{ log: '$state', highlighted: true },
67+
{ log: 3 }
68+
]);
69+
}
70+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script>
2+
let count = $state(0);
3+
let checked = $state(false);
4+
5+
$effect(() => {
6+
$inspect.trace("effect");
7+
if(checked && count > 0 && count < 3){
8+
let old = count;
9+
// this should not show up in the logs
10+
count = 1000;
11+
count = old + 1;
12+
}
13+
});
14+
</script>
15+
<input type="checkbox" bind:checked />
16+
<button onclick={()=>{
17+
count++;
18+
}}>{count}</button>

0 commit comments

Comments
 (0)