Skip to content

Commit c27510f

Browse files
committed
turn reactive_declaration_non_reactive_property into a runtime warning
1 parent f1aeaf1 commit c27510f

File tree

12 files changed

+100
-56
lines changed

12 files changed

+100
-56
lines changed

documentation/docs/98-reference/.generated/client-warnings.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ Mutating a value outside the component that created it is strongly discouraged.
8686
%component% mutated a value owned by %owner%. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead
8787
```
8888

89+
### reactive_declaration_non_reactive_property
90+
91+
```
92+
A `$:` statement (%location%) read reactive state that was not visible to the compiler. Updates to this state will not cause the statement to re-run. The behaviour of this code will change if you migrate it to runes mode
93+
```
94+
8995
### state_proxy_equality_mismatch
9096

9197
```

documentation/docs/98-reference/.generated/compile-warnings.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -726,12 +726,6 @@ Reactive declarations only exist at the top level of the instance script
726726
Reassignments of module-level declarations will not cause reactive statements to update
727727
```
728728

729-
### reactive_declaration_non_reactive_property
730-
731-
```
732-
Properties of objects and arrays are not reactive unless in runes mode. Changes to this property will not cause the reactive statement to update
733-
```
734-
735729
### script_context_deprecated
736730

737731
```

packages/svelte/messages/client-warnings/warnings.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ The easiest way to log a value as it changes over time is to use the [`$inspect`
5454
5555
> %component% mutated a value owned by %owner%. This is strongly discouraged. Consider passing values to child components with `bind:`, or use a callback instead
5656
57+
## reactive_declaration_non_reactive_property
58+
59+
> A `$:` statement (%location%) read reactive state that was not visible to the compiler. Updates to this state will not cause the statement to re-run. The behaviour of this code will change if you migrate it to runes mode
60+
5761
## state_proxy_equality_mismatch
5862

5963
> Reactive `$state(...)` proxies and the values they proxy have different identities. Because of this, comparisons with `%operator%` will produce unexpected results

packages/svelte/messages/compile-warnings/script.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,6 @@
2626

2727
> Reassignments of module-level declarations will not cause reactive statements to update
2828
29-
## reactive_declaration_non_reactive_property
30-
31-
> Properties of objects and arrays are not reactive unless in runes mode. Changes to this property will not cause the reactive statement to update
32-
3329
## state_referenced_locally
3430

3531
> State referenced in its own scope will never update. Did you mean to reference it inside a closure?

packages/svelte/src/compiler/phases/2-analyze/visitors/MemberExpression.js

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,5 @@ export function MemberExpression(node, context) {
2525
context.state.analysis.needs_context = true;
2626
}
2727

28-
if (context.state.reactive_statement) {
29-
const left = object(node);
30-
31-
if (left !== null) {
32-
const binding = context.state.scope.get(left.name);
33-
34-
if (binding && binding.kind === 'normal') {
35-
const parent = /** @type {Node} */ (context.path.at(-1));
36-
37-
if (
38-
binding.scope === context.state.analysis.module.scope ||
39-
binding.declaration_kind === 'import' ||
40-
(binding.initial &&
41-
binding.initial.type !== 'ArrayExpression' &&
42-
binding.initial.type !== 'ObjectExpression' &&
43-
binding.scope.function_depth <= 1)
44-
) {
45-
if (parent.type !== 'MemberExpression' && parent.type !== 'CallExpression') {
46-
w.reactive_declaration_non_reactive_property(node);
47-
}
48-
}
49-
}
50-
}
51-
}
52-
5328
context.next();
5429
}

packages/svelte/src/compiler/phases/3-transform/client/visitors/LabeledStatement.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
/** @import { Location } from 'locate-character' */
12
/** @import { Expression, LabeledStatement, Statement } from 'estree' */
23
/** @import { ReactiveStatement } from '#compiler' */
34
/** @import { ComponentContext } from '../types' */
5+
import { dev, locator } from '../../../../state.js';
46
import * as b from '../../../../utils/builders.js';
57
import { build_getter } from '../utils.js';
68

@@ -48,14 +50,18 @@ export function LabeledStatement(node, context) {
4850
sequence.push(serialized);
4951
}
5052

53+
const location = dev ? locator(/** @type {number} */ (node.start)) : undefined;
54+
5155
// these statements will be topologically ordered later
5256
context.state.legacy_reactive_statements.set(
5357
node,
5458
b.stmt(
5559
b.call(
5660
'$.legacy_pre_effect',
5761
sequence.length > 0 ? b.thunk(b.sequence(sequence)) : b.thunk(b.block([])),
58-
b.thunk(b.block(body))
62+
b.thunk(b.block(body)),
63+
location && b.literal(location.line),
64+
location && b.literal(location.column)
5965
)
6066
)
6167
);

packages/svelte/src/compiler/warnings.js

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ export const codes = [
102102
"perf_avoid_nested_class",
103103
"reactive_declaration_invalid_placement",
104104
"reactive_declaration_module_script_dependency",
105-
"reactive_declaration_non_reactive_property",
106105
"state_referenced_locally",
107106
"store_rune_conflict",
108107
"css_unused_selector",
@@ -641,14 +640,6 @@ export function reactive_declaration_module_script_dependency(node) {
641640
w(node, "reactive_declaration_module_script_dependency", "Reassignments of module-level declarations will not cause reactive statements to update");
642641
}
643642

644-
/**
645-
* Properties of objects and arrays are not reactive unless in runes mode. Changes to this property will not cause the reactive statement to update
646-
* @param {null | NodeLike} node
647-
*/
648-
export function reactive_declaration_non_reactive_property(node) {
649-
w(node, "reactive_declaration_non_reactive_property", "Properties of objects and arrays are not reactive unless in runes mode. Changes to this property will not cause the reactive statement to update");
650-
}
651-
652643
/**
653644
* State referenced in its own scope will never update. Did you mean to reference it inside a closure?
654645
* @param {null | NodeLike} node
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { DEV } from 'esm-env';
2+
import { FILENAME } from '../../../constants.js';
3+
import { dev_current_component_function } from '../runtime.js';
4+
5+
/**
6+
*
7+
* @param {number} [line]
8+
* @param {number} [column]
9+
*/
10+
export function get_location(line, column) {
11+
if (!DEV) return undefined;
12+
13+
var filename = dev_current_component_function?.[FILENAME];
14+
var location = filename && `${filename}:${line}:${column}`;
15+
16+
return sanitize_location(location);
17+
}
18+
19+
/**
20+
* Prevent devtools trying to make `location` a clickable link by inserting a zero-width space
21+
* @param {string | undefined} location
22+
*/
23+
export function sanitize_location(location) {
24+
return location?.replace(/\//g, '/\u200b');
25+
}

packages/svelte/src/internal/client/dom/blocks/html.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { hash } from '../../../../utils.js';
99
import { DEV } from 'esm-env';
1010
import { dev_current_component_function } from '../../runtime.js';
1111
import { get_first_child, get_next_sibling } from '../operations.js';
12+
import { sanitize_location } from '../../dev/location.js';
1213

1314
/**
1415
* @param {Element} element
@@ -28,9 +29,7 @@ function check_hash(element, server_hash, value) {
2829
location = `in ${dev_current_component_function[FILENAME]}`;
2930
}
3031

31-
w.hydration_html_changed(
32-
location?.replace(/\//g, '/\u200b') // prevent devtools trying to make it a clickable link by inserting a zero-width space
33-
);
32+
w.hydration_html_changed(sanitize_location(location));
3433
}
3534

3635
/**

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import {
1616
set_is_flushing_effect,
1717
set_signal_status,
1818
untrack,
19-
skip_reaction
19+
skip_reaction,
20+
capture_signals
2021
} from '../runtime.js';
2122
import {
2223
DIRTY,
@@ -39,10 +40,13 @@ import {
3940
} from '../constants.js';
4041
import { set } from './sources.js';
4142
import * as e from '../errors.js';
43+
import * as w from '../warnings.js';
4244
import { DEV } from 'esm-env';
4345
import { define_property } from '../../shared/utils.js';
4446
import { get_next_sibling } from '../dom/operations.js';
4547
import { destroy_derived } from './deriveds.js';
48+
import { FILENAME } from '../../../constants.js';
49+
import { get_location } from '../dev/location.js';
4650

4751
/**
4852
* @param {'$effect' | '$effect.pre' | '$inspect'} rune
@@ -261,14 +265,21 @@ export function effect(fn) {
261265
* Internal representation of `$: ..`
262266
* @param {() => any} deps
263267
* @param {() => void | (() => void)} fn
268+
* @param {number} [line]
269+
* @param {number} [column]
264270
*/
265-
export function legacy_pre_effect(deps, fn) {
271+
export function legacy_pre_effect(deps, fn, line, column) {
266272
var context = /** @type {ComponentContextLegacy} */ (component_context);
267273

268274
/** @type {{ effect: null | Effect, ran: boolean }} */
269275
var token = { effect: null, ran: false };
270276
context.l.r1.push(token);
271277

278+
if (DEV) {
279+
var location = get_location(line, column);
280+
var explicit_deps = capture_signals(deps);
281+
}
282+
272283
token.effect = render_effect(() => {
273284
deps();
274285

@@ -278,7 +289,16 @@ export function legacy_pre_effect(deps, fn) {
278289

279290
token.ran = true;
280291
set(context.l.r2, true);
281-
untrack(fn);
292+
293+
if (DEV) {
294+
var implicit_deps = capture_signals(() => untrack(fn));
295+
296+
for (const signal of implicit_deps) {
297+
if (!explicit_deps.has(signal)) {
298+
w.reactive_declaration_non_reactive_property(/** @type {string} */ (location));
299+
}
300+
}
301+
}
282302
});
283303
}
284304

0 commit comments

Comments
 (0)