fix: ensure keys are validated

fix-15339
7nik 5 days ago
parent 492b3018fd
commit e541296da4

@ -337,10 +337,6 @@ export function EachBlock(node, context) {
const statements = [add_svelte_meta(b.call('$.each', ...args), node, 'each')]; const statements = [add_svelte_meta(b.call('$.each', ...args), node, 'each')];
if (dev && node.metadata.keyed) {
statements.unshift(b.stmt(b.call('$.validate_each_keys', thunk, key_function)));
}
if (has_await) { if (has_await) {
context.state.init.push( context.state.init.push(
b.stmt( b.stmt(

@ -43,6 +43,7 @@ import { DEV } from 'esm-env';
import { derived_safe_equal } from '../../reactivity/deriveds.js'; import { derived_safe_equal } from '../../reactivity/deriveds.js';
import { current_batch } from '../../reactivity/batch.js'; import { current_batch } from '../../reactivity/batch.js';
import { each_key_duplicate } from '../../errors.js'; import { each_key_duplicate } from '../../errors.js';
import { validate_each_keys } from '../../validate.js';
/** /**
* The row of a keyed each block that is currently updating. We track this * The row of a keyed each block that is currently updating. We track this
@ -202,6 +203,11 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
} }
was_empty = length === 0; was_empty = length === 0;
// skip if #each block isn't keyed
if (DEV && get_key !== index) {
validate_each_keys(array, get_key);
}
/** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */ /** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false; let mismatch = false;

@ -7,35 +7,27 @@ import * as w from './warnings.js';
import { capture_store_binding } from './reactivity/store.js'; import { capture_store_binding } from './reactivity/store.js';
/** /**
* @param {() => any} collection * @param {Array<any>} array
* @param {(item: any, index: number) => string} key_fn * @param {(item: any, index: number) => string} key_fn
* @returns {void} * @returns {void}
*/ */
export function validate_each_keys(collection, key_fn) { export function validate_each_keys(array, key_fn) {
render_effect(() => { const keys = new Map();
const keys = new Map(); const length = array.length;
const maybe_array = collection(); for (let i = 0; i < length; i++) {
const array = is_array(maybe_array) const key = key_fn(array[i], i);
? maybe_array if (keys.has(key)) {
: maybe_array == null const a = String(keys.get(key));
? [] const b = String(i);
: Array.from(maybe_array);
const length = array.length;
for (let i = 0; i < length; i++) {
const key = key_fn(array[i], i);
if (keys.has(key)) {
const a = String(keys.get(key));
const b = String(i);
/** @type {string | null} */ /** @type {string | null} */
let k = String(key); let k = String(key);
if (k.startsWith('[object ')) k = null; if (k.startsWith('[object ')) k = null;
e.each_key_duplicate(a, b, k); e.each_key_duplicate(a, b, k);
}
keys.set(key, i);
} }
}); keys.set(key, i);
}
} }
/** /**

Loading…
Cancel
Save