chore: add error for derived self referencing (#12746)

* chore: add warning for derived self referencin

* update build

* address feedback

* address feedback

* build

* messages shouldn't end with a period

* simplify test

* regenerate

* newlines are free

* no need to export this, we can move it closer to where it's used

* fix double negative

---------

Co-authored-by: Rich Harris <rich.harris@vercel.com>
pull/12752/head
Dominic Gannaway 5 months ago committed by GitHub
parent bd9a2d2077
commit d06174e461
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: add error for derived self referencing

@ -18,6 +18,10 @@
> Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `compatibility.componentApi` compiler option to `4` to keep it working. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information
## derived_references_self
> A derived value cannot reference itself recursively
## each_key_duplicate
> Keyed each block has duplicate key at indexes %a% and %b%

@ -93,6 +93,22 @@ export function component_api_invalid_new(component, name) {
}
}
/**
* A derived value cannot reference itself recursively
* @returns {never}
*/
export function derived_references_self() {
if (DEV) {
const error = new Error(`derived_references_self\nA derived value cannot reference itself recursively`);
error.name = 'Svelte error';
throw error;
} else {
// TODO print a link to the documentation
throw new Error("derived_references_self");
}
}
/**
* Keyed each block has duplicate key `%value%` at indexes %a% and %b%
* @param {string} a

@ -1,4 +1,5 @@
/** @import { Derived } from '#client' */
import { DEV } from 'esm-env';
import { CLEAN, DERIVED, DESTROYED, DIRTY, MAYBE_DIRTY, UNOWNED } from '../constants.js';
import {
current_reaction,
@ -11,6 +12,7 @@ import {
increment_version
} from '../runtime.js';
import { equals, safe_equals } from './equality.js';
import * as e from '../errors.js';
export let updating_derived = false;
@ -79,17 +81,36 @@ function destroy_derived_children(derived) {
}
}
/**
* The currently updating deriveds, used to detect infinite recursion
* in dev mode and provide a nicer error than 'too much recursion'
* @type {Derived[]}
*/
let stack = [];
/**
* @param {Derived} derived
* @returns {void}
*/
export function update_derived(derived) {
if (DEV) {
if (stack.includes(derived)) {
e.derived_references_self();
}
stack.push(derived);
}
var previous_updating_derived = updating_derived;
updating_derived = true;
destroy_derived_children(derived);
var value = update_reaction(derived);
updating_derived = previous_updating_derived;
if (DEV) {
stack.pop();
}
var status =
(current_skip_reaction || (derived.f & UNOWNED) !== 0) && derived.deps !== null
? MAYBE_DIRTY

@ -0,0 +1,21 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
html: `<button>increment</button>\n0`,
mode: ['client'],
test({ assert, target }) {
const btn = target.querySelector('button');
btn?.click();
assert.throws(
flushSync,
'derived_references_self\nA derived value cannot reference itself recursively'
);
assert.htmlEqual(target.innerHTML, `<button>increment</button>\n0`);
}
});

@ -0,0 +1,11 @@
<script>
let count = $state(0);
let even = $derived.by(() => {
return count > 0 ? even : 0;
})
</script>
<button onclick={() => count++}>increment</button>
{even}
Loading…
Cancel
Save