mirror of https://github.com/sveltejs/svelte
Warn on non-destructured `$props()` reads in runes mode (#17708)
Non-destructured `$props()` access in runes mode silently skipped the
`state_referenced_locally` warning, leading to missed guidance when
users read `props` via identifiers or member expressions.
- **Analyzer behavior**
- Include `rest_prop` bindings in `state_referenced_locally` detection
so reads of `$props()` identifiers warn consistently with destructured
props.
- **Validation coverage**
- Add a validator fixture for `$props()` identifiers and update the
`props-identifier` snapshot expectations to capture the new warnings.
Example:
```svelte
<script>
const props = $props();
const { model } = props; // now warns
const value = props.model.value; // now warns
</script>
```
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
>
> ----
>
> *This section details on the original issue you should resolve*
>
> <issue_title>False negative for `state_referenced_locally` warning on
not destructured `$props` access?</issue_title>
> <issue_description>### Describe the bug
>
> I was looking for a workaround for sveltejs/svelte#17669 and thought
of not destructuring the `$props` directly; to my surprise there were no
warnings at all.
>
>
> ### Reproduction
>
> ```js
> const props = $props();
> const { model } = props; // missing warning
>
> const value = props.model.value; // missing warning
> ```
>
>
[Playground](https://svelte.dev/playground/untitled?version=5.50.2#H4sIAAAAAAAACn2QT4vCQAzFv0oIe1CQ9l51YY97lj1tPYxtXAam6TAT_1H63U0HUax1j3nvJeT3OmTTEBb4w2LFUY0L3FtHEYvfDuXiB28QVL8lv7zP4pGcDNrORJrSq5aFWPQMrmIVrJfPkktROQp00LQ1OehhDR8-tD7O5ku174GjcQdSM8WyNC0hz4HOniqhGk4msOW_klf54zrPNkTwzVUbgsZuz8z1G6GzYCHhQP3iDdV47Zltwv2XMEGN6Cbgk5vIGhujAj3AXstI4WxcycvivZFn7q1OxrqT5RqLvXGR-itXywVk_AEAAA)
>
> ### Logs
>
> ```shell
>
> ```
>
> ### System Info
>
> ```shell
> REPL - Svelte v.5.50.2
> ```
>
> ### Severity
>
> annoyance</issue_description>
>
> ## Comments on the Issue (you are @copilot in this section)
>
> <comments>
> </comments>
>
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
- Fixes sveltejs/svelte#17685
<!-- START COPILOT CODING AGENT TIPS -->
---
💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Rich-Harris <1162160+Rich-Harris@users.noreply.github.com>
Co-authored-by: Rich Harris <rich.harris@vercel.com>
Co-authored-by: Paolo Ricciuti <ricciutipaolo@gmail.com>
pull/17711/head
parent
01f1937a98
commit
dd9fc0d1ad
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: emit state_referenced_locally warning for non-destructured props
|
||||
@ -0,0 +1,142 @@
|
||||
[
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally",
|
||||
"filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte",
|
||||
"start": {
|
||||
"line": 3,
|
||||
"column": 1,
|
||||
"character": 33
|
||||
},
|
||||
"end": {
|
||||
"line": 3,
|
||||
"column": 6,
|
||||
"character": 38
|
||||
},
|
||||
"position": [
|
||||
33,
|
||||
38
|
||||
],
|
||||
"frame": "1: <script>\n2: let props = $props();\n3: props.a;\n ^\n4: props[a];\n5: props.a.b;"
|
||||
},
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally",
|
||||
"filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte",
|
||||
"start": {
|
||||
"line": 4,
|
||||
"column": 1,
|
||||
"character": 43
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"column": 6,
|
||||
"character": 48
|
||||
},
|
||||
"position": [
|
||||
43,
|
||||
48
|
||||
],
|
||||
"frame": "2: let props = $props();\n3: props.a;\n4: props[a];\n ^\n5: props.a.b;\n6: props.a.b = true;"
|
||||
},
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally",
|
||||
"filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte",
|
||||
"start": {
|
||||
"line": 5,
|
||||
"column": 1,
|
||||
"character": 54
|
||||
},
|
||||
"end": {
|
||||
"line": 5,
|
||||
"column": 6,
|
||||
"character": 59
|
||||
},
|
||||
"position": [
|
||||
54,
|
||||
59
|
||||
],
|
||||
"frame": "3: props.a;\n4: props[a];\n5: props.a.b;\n ^\n6: props.a.b = true;\n7: props.a = true;"
|
||||
},
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally",
|
||||
"filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte",
|
||||
"start": {
|
||||
"line": 6,
|
||||
"column": 1,
|
||||
"character": 66
|
||||
},
|
||||
"end": {
|
||||
"line": 6,
|
||||
"column": 6,
|
||||
"character": 71
|
||||
},
|
||||
"position": [
|
||||
66,
|
||||
71
|
||||
],
|
||||
"frame": "4: props[a];\n5: props.a.b;\n6: props.a.b = true;\n ^\n7: props.a = true;\n8: props[a] = true;"
|
||||
},
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally",
|
||||
"filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte",
|
||||
"start": {
|
||||
"line": 7,
|
||||
"column": 1,
|
||||
"character": 85
|
||||
},
|
||||
"end": {
|
||||
"line": 7,
|
||||
"column": 6,
|
||||
"character": 90
|
||||
},
|
||||
"position": [
|
||||
85,
|
||||
90
|
||||
],
|
||||
"frame": " 5: props.a.b;\n 6: props.a.b = true;\n 7: props.a = true;\n ^\n 8: props[a] = true;\n 9: props;"
|
||||
},
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally",
|
||||
"filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte",
|
||||
"start": {
|
||||
"line": 8,
|
||||
"column": 1,
|
||||
"character": 102
|
||||
},
|
||||
"end": {
|
||||
"line": 8,
|
||||
"column": 6,
|
||||
"character": 107
|
||||
},
|
||||
"position": [
|
||||
102,
|
||||
107
|
||||
],
|
||||
"frame": " 6: props.a.b = true;\n 7: props.a = true;\n 8: props[a] = true;\n ^\n 9: props;\n10: </script>"
|
||||
},
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally",
|
||||
"filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte",
|
||||
"start": {
|
||||
"line": 9,
|
||||
"column": 1,
|
||||
"character": 120
|
||||
},
|
||||
"end": {
|
||||
"line": 9,
|
||||
"column": 6,
|
||||
"character": 125
|
||||
},
|
||||
"position": [
|
||||
120,
|
||||
125
|
||||
],
|
||||
"frame": " 7: props.a = true;\n 8: props[a] = true;\n 9: props;\n ^\n10: </script>\n11: "
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,142 @@
|
||||
[
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally",
|
||||
"filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte",
|
||||
"start": {
|
||||
"line": 3,
|
||||
"column": 1,
|
||||
"character": 33
|
||||
},
|
||||
"end": {
|
||||
"line": 3,
|
||||
"column": 6,
|
||||
"character": 38
|
||||
},
|
||||
"position": [
|
||||
33,
|
||||
38
|
||||
],
|
||||
"frame": "1: <script>\n2: let props = $props();\n3: props.a;\n ^\n4: props[a];\n5: props.a.b;"
|
||||
},
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally",
|
||||
"filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte",
|
||||
"start": {
|
||||
"line": 4,
|
||||
"column": 1,
|
||||
"character": 43
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"column": 6,
|
||||
"character": 48
|
||||
},
|
||||
"position": [
|
||||
43,
|
||||
48
|
||||
],
|
||||
"frame": "2: let props = $props();\n3: props.a;\n4: props[a];\n ^\n5: props.a.b;\n6: props.a.b = true;"
|
||||
},
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally",
|
||||
"filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte",
|
||||
"start": {
|
||||
"line": 5,
|
||||
"column": 1,
|
||||
"character": 54
|
||||
},
|
||||
"end": {
|
||||
"line": 5,
|
||||
"column": 6,
|
||||
"character": 59
|
||||
},
|
||||
"position": [
|
||||
54,
|
||||
59
|
||||
],
|
||||
"frame": "3: props.a;\n4: props[a];\n5: props.a.b;\n ^\n6: props.a.b = true;\n7: props.a = true;"
|
||||
},
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally",
|
||||
"filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte",
|
||||
"start": {
|
||||
"line": 6,
|
||||
"column": 1,
|
||||
"character": 66
|
||||
},
|
||||
"end": {
|
||||
"line": 6,
|
||||
"column": 6,
|
||||
"character": 71
|
||||
},
|
||||
"position": [
|
||||
66,
|
||||
71
|
||||
],
|
||||
"frame": "4: props[a];\n5: props.a.b;\n6: props.a.b = true;\n ^\n7: props.a = true;\n8: props[a] = true;"
|
||||
},
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally",
|
||||
"filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte",
|
||||
"start": {
|
||||
"line": 7,
|
||||
"column": 1,
|
||||
"character": 85
|
||||
},
|
||||
"end": {
|
||||
"line": 7,
|
||||
"column": 6,
|
||||
"character": 90
|
||||
},
|
||||
"position": [
|
||||
85,
|
||||
90
|
||||
],
|
||||
"frame": " 5: props.a.b;\n 6: props.a.b = true;\n 7: props.a = true;\n ^\n 8: props[a] = true;\n 9: props;"
|
||||
},
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally",
|
||||
"filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte",
|
||||
"start": {
|
||||
"line": 8,
|
||||
"column": 1,
|
||||
"character": 102
|
||||
},
|
||||
"end": {
|
||||
"line": 8,
|
||||
"column": 6,
|
||||
"character": 107
|
||||
},
|
||||
"position": [
|
||||
102,
|
||||
107
|
||||
],
|
||||
"frame": " 6: props.a.b = true;\n 7: props.a = true;\n 8: props[a] = true;\n ^\n 9: props;\n10: </script>"
|
||||
},
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?\nhttps://svelte.dev/e/state_referenced_locally",
|
||||
"filename": "packages/svelte/tests/snapshot/samples/props-identifier/index.svelte",
|
||||
"start": {
|
||||
"line": 9,
|
||||
"column": 1,
|
||||
"character": 120
|
||||
},
|
||||
"end": {
|
||||
"line": 9,
|
||||
"column": 6,
|
||||
"character": 125
|
||||
},
|
||||
"position": [
|
||||
120,
|
||||
125
|
||||
],
|
||||
"frame": " 7: props.a = true;\n 8: props[a] = true;\n 9: props;\n ^\n10: </script>\n11: "
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,6 @@
|
||||
<script>
|
||||
const props = $props();
|
||||
const { model } = props;
|
||||
const value = props.model.value;
|
||||
console.log(model, value);
|
||||
</script>
|
||||
@ -0,0 +1,26 @@
|
||||
[
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?",
|
||||
"start": {
|
||||
"line": 3,
|
||||
"column": 19
|
||||
},
|
||||
"end": {
|
||||
"line": 3,
|
||||
"column": 24
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "state_referenced_locally",
|
||||
"message": "This reference only captures the initial value of `props`. Did you mean to reference it inside a closure instead?",
|
||||
"start": {
|
||||
"line": 4,
|
||||
"column": 15
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"column": 20
|
||||
}
|
||||
}
|
||||
]
|
||||
Loading…
Reference in new issue