diff --git a/.changeset/stale-gorillas-judge.md b/.changeset/stale-gorillas-judge.md new file mode 100644 index 000000000000..3d91f401ddf4 --- /dev/null +++ b/.changeset/stale-gorillas-judge.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: update `state_referenced_locally` message diff --git a/documentation/docs/98-reference/.generated/compile-warnings.md b/documentation/docs/98-reference/.generated/compile-warnings.md index 57396bd7fd52..0e94cbadb2e8 100644 --- a/documentation/docs/98-reference/.generated/compile-warnings.md +++ b/documentation/docs/98-reference/.generated/compile-warnings.md @@ -823,15 +823,16 @@ See [the migration guide](v5-migration-guide#Snippets-instead-of-slots) for more ### state_referenced_locally ``` -State referenced in its own scope will never update. Did you mean to reference it inside a closure? +This reference only captures the initial value of `%name%`. Did you mean to reference it inside a %type% instead? ``` This warning is thrown when the compiler detects the following: + - A reactive variable is declared -- the variable is reassigned -- the variable is referenced inside the same scope it is declared and it is a non-reactive context +- ...and later reassigned... +- ...and referenced in the same scope -In this case, the state reassignment will not be noticed by whatever you passed it to. For example, if you pass the state to a function, that function will not notice the updates: +This 'breaks the link' to the original state declaration. For example, if you pass the state to a function, the function loses access to the state once it is reassigned: ```svelte diff --git a/packages/svelte/messages/compile-warnings/script.md b/packages/svelte/messages/compile-warnings/script.md index 8c32fb708266..66037591562b 100644 --- a/packages/svelte/messages/compile-warnings/script.md +++ b/packages/svelte/messages/compile-warnings/script.md @@ -54,14 +54,15 @@ To fix this, wrap your variable declaration with `$state`. ## state_referenced_locally -> State referenced in its own scope will never update. Did you mean to reference it inside a closure? +> This reference only captures the initial value of `%name%`. Did you mean to reference it inside a %type% instead? This warning is thrown when the compiler detects the following: + - A reactive variable is declared -- the variable is reassigned -- the variable is referenced inside the same scope it is declared and it is a non-reactive context +- ...and later reassigned... +- ...and referenced in the same scope -In this case, the state reassignment will not be noticed by whatever you passed it to. For example, if you pass the state to a function, that function will not notice the updates: +This 'breaks the link' to the original state declaration. For example, if you pass the state to a function, the function loses access to the state once it is reassigned: ```svelte diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js index 79dccd5a7cf5..dcbe564543bf 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/Identifier.js @@ -7,6 +7,7 @@ import * as e from '../../../errors.js'; import * as w from '../../../warnings.js'; import { is_rune } from '../../../../utils.js'; import { mark_subtree_dynamic } from './shared/fragment.js'; +import { get_rune } from '../../scope.js'; /** * @param {Identifier} node @@ -111,7 +112,34 @@ export function Identifier(node, context) { (parent.type !== 'AssignmentExpression' || parent.left !== node) && parent.type !== 'UpdateExpression' ) { - w.state_referenced_locally(node); + let type = 'closure'; + + let i = context.path.length; + while (i--) { + const parent = context.path[i]; + + if ( + parent.type === 'ArrowFunctionExpression' || + parent.type === 'FunctionDeclaration' || + parent.type === 'FunctionExpression' + ) { + break; + } + + if ( + parent.type === 'CallExpression' && + parent.arguments.includes(/** @type {any} */ (context.path[i + 1])) + ) { + const rune = get_rune(parent, context.state.scope); + + if (rune === '$state' || rune === '$state.raw') { + type = 'derived'; + break; + } + } + } + + w.state_referenced_locally(node, node.name, type); } if ( diff --git a/packages/svelte/src/compiler/warnings.js b/packages/svelte/src/compiler/warnings.js index a9ea617d3f01..e6fc8caba54f 100644 --- a/packages/svelte/src/compiler/warnings.js +++ b/packages/svelte/src/compiler/warnings.js @@ -641,11 +641,13 @@ export function reactive_declaration_module_script_dependency(node) { } /** - * State referenced in its own scope will never update. Did you mean to reference it inside a closure? + * This reference only captures the initial value of `%name%`. Did you mean to reference it inside a %type% instead? * @param {null | NodeLike} node + * @param {string} name + * @param {string} type */ -export function state_referenced_locally(node) { - w(node, 'state_referenced_locally', `State referenced in its own scope will never update. Did you mean to reference it inside a closure?\nhttps://svelte.dev/e/state_referenced_locally`); +export function state_referenced_locally(node, name, type) { + w(node, 'state_referenced_locally', `This reference only captures the initial value of \`${name}\`. Did you mean to reference it inside a ${type} instead?\nhttps://svelte.dev/e/state_referenced_locally`); } /** diff --git a/packages/svelte/tests/validator/samples/static-state-reference/input.svelte b/packages/svelte/tests/validator/samples/static-state-reference/input.svelte index cd0c7b734925..577527ee60c7 100644 --- a/packages/svelte/tests/validator/samples/static-state-reference/input.svelte +++ b/packages/svelte/tests/validator/samples/static-state-reference/input.svelte @@ -2,6 +2,7 @@ let obj = $state({ a: 0 }); let count = $state(0); let doubled = $derived(count * 2); + let tripled = $state(count * 3); console.log(obj); console.log(count); diff --git a/packages/svelte/tests/validator/samples/static-state-reference/warnings.json b/packages/svelte/tests/validator/samples/static-state-reference/warnings.json index 9ba09415190b..a118d5e4a0d7 100644 --- a/packages/svelte/tests/validator/samples/static-state-reference/warnings.json +++ b/packages/svelte/tests/validator/samples/static-state-reference/warnings.json @@ -1,26 +1,38 @@ [ { "code": "state_referenced_locally", - "message": "State referenced in its own scope will never update. Did you mean to reference it inside a closure?", + "message": "This reference only captures the initial value of `count`. Did you mean to reference it inside a derived instead?", + "start": { + "column": 22, + "line": 5 + }, + "end": { + "column": 27, + "line": 5 + } + }, + { + "code": "state_referenced_locally", + "message": "This reference only captures the initial value of `count`. Did you mean to reference it inside a closure instead?", "start": { "column": 13, - "line": 7 + "line": 8 }, "end": { "column": 18, - "line": 7 + "line": 8 } }, { "code": "state_referenced_locally", - "message": "State referenced in its own scope will never update. Did you mean to reference it inside a closure?", + "message": "This reference only captures the initial value of `doubled`. Did you mean to reference it inside a closure instead?", "start": { "column": 13, - "line": 8 + "line": 9 }, "end": { "column": 20, - "line": 8 + "line": 9 } } ]