Skip to content

Commit 57a4b5d

Browse files
authored
feat: better compiler warnings for non-reactive dependencies of reactive statements (#12824)
* feat: better compiler warnings for non-reactive dependencies of reactive statements * fix * regenerate
1 parent 0a06a3f commit 57a4b5d

File tree

11 files changed

+78
-28
lines changed

11 files changed

+78
-28
lines changed

.changeset/strange-pillows-greet.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+
feat: better compiler warnings for non-reactive dependencies of reactive statements

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,13 @@
2626

2727
> Reactive declarations only exist at the top level of the instance script
2828
29-
## reactive_declaration_module_script
29+
## reactive_declaration_module_script_dependency
3030

31-
> All dependencies of the reactive declaration are declared in a module script and will not be reactive
31+
> Reassignments of module-level declarations will not cause reactive statements to update
32+
33+
## reactive_declaration_non_reactive_property
34+
35+
> Properties of objects and arrays are not reactive unless in runes mode. Changes to this property will not cause the reactive statement to update
3236
3337
## state_referenced_locally
3438

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,13 @@ export function Identifier(node, context) {
109109
) {
110110
w.state_referenced_locally(node);
111111
}
112+
113+
if (
114+
context.state.reactive_statement &&
115+
binding.scope === context.state.analysis.module.scope &&
116+
binding.reassigned
117+
) {
118+
w.reactive_declaration_module_script_dependency(node);
119+
}
112120
}
113121
}

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

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import * as e from '../../../errors.js';
55
import { extract_identifiers, object } from '../../../utils/ast.js';
66
import * as w from '../../../warnings.js';
7+
import { is_state_source } from '../../3-transform/client/utils.js';
78

89
/**
910
* @param {LabeledStatement} node
@@ -66,15 +67,6 @@ export function LabeledStatement(node, context) {
6667

6768
context.state.reactive_statements.set(node, reactive_statement);
6869

69-
if (
70-
reactive_statement.dependencies.length &&
71-
reactive_statement.dependencies.every(
72-
(d) => d.scope === context.state.analysis.module.scope && d.declaration_kind !== 'const'
73-
)
74-
) {
75-
w.reactive_declaration_module_script(node);
76-
}
77-
7870
if (
7971
node.body.type === 'ExpressionStatement' &&
8072
node.body.expression.type === 'AssignmentExpression'

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
/** @import { MemberExpression } from 'estree' */
1+
/** @import { MemberExpression, Node } from 'estree' */
22
/** @import { Context } from '../types' */
33
import * as e from '../../../errors.js';
4+
import * as w from '../../../warnings.js';
5+
import { object } from '../../../utils/ast.js';
46
import { is_pure, is_safe_identifier } from './shared/utils.js';
57

68
/**
@@ -23,5 +25,29 @@ export function MemberExpression(node, context) {
2325
context.state.analysis.needs_context = true;
2426
}
2527

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+
) {
44+
if (parent.type !== 'MemberExpression' && parent.type !== 'CallExpression') {
45+
w.reactive_declaration_non_reactive_property(node);
46+
}
47+
}
48+
}
49+
}
50+
}
51+
2652
context.next();
2753
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/** @import { ArrowFunctionExpression, Expression, FunctionDeclaration, FunctionExpression, Identifier, Pattern, PrivateIdentifier, Statement } from 'estree' */
22
/** @import { Binding, SvelteNode } from '#compiler' */
33
/** @import { ClientTransformState, ComponentClientTransformState, ComponentContext } from './types.js' */
4+
/** @import { Analysis } from '../../types.js' */
45
/** @import { Scope } from '../../scope.js' */
56
import * as b from '../../../utils/builders.js';
67
import { extract_identifiers, is_simple_expression } from '../../../utils/ast.js';
@@ -16,13 +17,13 @@ import { get_value } from './visitors/shared/declarations.js';
1617

1718
/**
1819
* @param {Binding} binding
19-
* @param {ClientTransformState} state
20+
* @param {Analysis} analysis
2021
* @returns {boolean}
2122
*/
22-
export function is_state_source(binding, state) {
23+
export function is_state_source(binding, analysis) {
2324
return (
2425
(binding.kind === 'state' || binding.kind === 'raw_state') &&
25-
(!state.analysis.immutable || binding.reassigned || state.analysis.accessors)
26+
(!analysis.immutable || binding.reassigned || analysis.accessors)
2627
);
2728
}
2829

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export function VariableDeclaration(node, context) {
126126
if (rune === '$state' && should_proxy(value, context.state.scope)) {
127127
value = b.call('$.proxy', value);
128128
}
129-
if (is_state_source(binding, context.state)) {
129+
if (is_state_source(binding, context.state.analysis)) {
130130
value = b.call('$.source', value);
131131
}
132132
return value;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function get_value(node) {
1818
export function add_state_transformers(context) {
1919
for (const [name, binding] of context.state.scope.declarations) {
2020
if (
21-
is_state_source(binding, context.state) ||
21+
is_state_source(binding, context.state.analysis) ||
2222
binding.kind === 'derived' ||
2323
binding.kind === 'legacy_reactive'
2424
) {

packages/svelte/src/compiler/warnings.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ export const codes = [
101101
"perf_avoid_inline_class",
102102
"perf_avoid_nested_class",
103103
"reactive_declaration_invalid_placement",
104-
"reactive_declaration_module_script",
104+
"reactive_declaration_module_script_dependency",
105+
"reactive_declaration_non_reactive_property",
105106
"state_referenced_locally",
106107
"store_rune_conflict",
107108
"css_unused_selector",
@@ -630,11 +631,19 @@ export function reactive_declaration_invalid_placement(node) {
630631
}
631632

632633
/**
633-
* All dependencies of the reactive declaration are declared in a module script and will not be reactive
634+
* Reassignments of module-level declarations will not cause reactive statements to update
634635
* @param {null | NodeLike} node
635636
*/
636-
export function reactive_declaration_module_script(node) {
637-
w(node, "reactive_declaration_module_script", "All dependencies of the reactive declaration are declared in a module script and will not be reactive");
637+
export function reactive_declaration_module_script_dependency(node) {
638+
w(node, "reactive_declaration_module_script_dependency", "Reassignments of module-level declarations will not cause reactive statements to update");
639+
}
640+
641+
/**
642+
* Properties of objects and arrays are not reactive unless in runes mode. Changes to this property will not cause the reactive statement to update
643+
* @param {null | NodeLike} node
644+
*/
645+
export function reactive_declaration_non_reactive_property(node) {
646+
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");
638647
}
639648

640649
/**
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
<script context="module">
2-
let foo;
2+
let foo = 0;
3+
4+
export function update() {
5+
foo += 1;
6+
}
37
</script>
8+
49
<script>
510
$: bar = foo;
611
</script>

0 commit comments

Comments
 (0)