Skip to content

Commit a91308d

Browse files
fix: correctly highlight sources reassigned inside trace (#14811)
* fix: correctly highlight sources reassigned inside `trace` * chore: add missing effect logs * fix: prevent `null` access on `tracing_expressions` for nested tracing * chore: add test case for #14853 * fix: types for `$inpect.trace`
1 parent f3a7ded commit a91308d

File tree

13 files changed

+209
-4
lines changed

13 files changed

+209
-4
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/ambient.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ declare namespace $inspect {
433433
* });
434434
* </script>
435435
*/
436-
export function trace(name: string): void;
436+
export function trace(name?: string): void;
437437

438438
// prevent intellisense from being unhelpful
439439
/** @deprecated */

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

Lines changed: 2 additions & 2 deletions
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;
@@ -121,7 +121,7 @@ export function trace(label, fn) {
121121
console.groupEnd();
122122
}
123123

124-
if (previously_tracing_expressions !== null) {
124+
if (previously_tracing_expressions !== null && tracing_expressions !== null) {
125125
for (const [signal, entry] of tracing_expressions.entries) {
126126
var prev_entry = previously_tracing_expressions.get(signal);
127127

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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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: 'iife', highlighted: false },
38+
{ log: '$state', highlighted: true },
39+
{ log: 0 },
40+
{ log: 'effect', highlighted: false }
41+
]);
42+
43+
logs.length = 0;
44+
45+
const button = target.querySelector('button');
46+
button?.click();
47+
flushSync();
48+
49+
assert.deepEqual(normalise_trace_logs(logs), [
50+
{ log: 'iife', highlighted: false },
51+
{ log: '$state', highlighted: true },
52+
{ log: 1 },
53+
{ log: 'effect', highlighted: false }
54+
]);
55+
}
56+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
let count = $state(0);
3+
4+
$effect(() => {
5+
$inspect.trace('effect');
6+
(()=>{
7+
$inspect.trace("iife");
8+
count;
9+
})();
10+
});
11+
</script>
12+
13+
<button onclick={() => count++}>{count}</button>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
compileOptions: {
5+
dev: true
6+
},
7+
test() {}
8+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
let count = $state(null);
3+
$effect(() => {
4+
$inspect.trace();
5+
count;
6+
});
7+
</script>
8+
9+
<button onclick={()=>count++}>{count}</button>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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: 'effect', highlighted: false },
61+
{ log: '$state', highlighted: false },
62+
{ log: true },
63+
{ log: '$state', highlighted: true },
64+
{ log: 2 },
65+
{ log: 'effect', highlighted: false },
66+
{ log: '$state', highlighted: false },
67+
{ log: true },
68+
{ log: '$state', highlighted: true },
69+
{ log: 3 }
70+
]);
71+
}
72+
});
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>

packages/svelte/types/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3091,7 +3091,7 @@ declare namespace $inspect {
30913091
* });
30923092
* </script>
30933093
*/
3094-
export function trace(name: string): void;
3094+
export function trace(name?: string): void;
30953095

30963096
// prevent intellisense from being unhelpful
30973097
/** @deprecated */

0 commit comments

Comments
 (0)