From 4d05ed1139fe3438da9a1c5c1d2fa46107bf02ea Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 31 May 2025 16:11:21 -0400 Subject: [PATCH] disallow late setContext calls --- .../98-reference/.generated/client-errors.md | 6 ++++++ .../svelte/messages/client-errors/errors.md | 4 ++++ .../svelte/src/internal/client/context.js | 10 +++++++++- packages/svelte/src/internal/client/errors.js | 15 +++++++++++++++ .../set-context-after-mount/_config.js | 11 +++++++++++ .../set-context-after-mount/main.svelte | 19 +++++++++++++++++++ 6 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 packages/svelte/tests/runtime-runes/samples/set-context-after-mount/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/set-context-after-mount/main.svelte diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md index 32348bb781..b9268636b2 100644 --- a/documentation/docs/98-reference/.generated/client-errors.md +++ b/documentation/docs/98-reference/.generated/client-errors.md @@ -110,6 +110,12 @@ Rest element properties of `$props()` such as `%property%` are readonly The `%rune%` rune is only available inside `.svelte` and `.svelte.js/ts` files ``` +### set_context_after_init + +``` +`setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression +``` + ### state_descriptors_fixed ``` diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md index c4e68f8fee..8748bf8978 100644 --- a/packages/svelte/messages/client-errors/errors.md +++ b/packages/svelte/messages/client-errors/errors.md @@ -72,6 +72,10 @@ See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-long > The `%rune%` rune is only available inside `.svelte` and `.svelte.js/ts` files +## set_context_after_init + +> `setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression + ## state_descriptors_fixed > Property descriptors defined on `$state` objects must contain `value` and always be `enumerable`, `configurable` and `writable`. diff --git a/packages/svelte/src/internal/client/context.js b/packages/svelte/src/internal/client/context.js index c0c4f5fda9..f326f3a0b7 100644 --- a/packages/svelte/src/internal/client/context.js +++ b/packages/svelte/src/internal/client/context.js @@ -2,6 +2,7 @@ import { DEV } from 'esm-env'; import { lifecycle_outside_component } from '../shared/errors.js'; +import * as e from './errors.js'; import { source } from './reactivity/sources.js'; import { active_effect, @@ -10,7 +11,7 @@ import { set_active_reaction } from './runtime.js'; import { effect, teardown } from './reactivity/effects.js'; -import { legacy_mode_flag } from '../flags/index.js'; +import { async_mode_flag, legacy_mode_flag } from '../flags/index.js'; /** @type {ComponentContext | null} */ export let component_context = null; @@ -65,6 +66,13 @@ export function getContext(key) { */ export function setContext(key, context) { const context_map = get_or_init_context_map('setContext'); + + if (async_mode_flag) { + if (/** @type {ComponentContext} */ (component_context).m) { + e.set_context_after_init(); + } + } + context_map.set(key, context); return context; } diff --git a/packages/svelte/src/internal/client/errors.js b/packages/svelte/src/internal/client/errors.js index 429dd99da9..0209976b11 100644 --- a/packages/svelte/src/internal/client/errors.js +++ b/packages/svelte/src/internal/client/errors.js @@ -197,6 +197,21 @@ export function hydration_failed() { } } +/** + * `setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression + * @returns {never} + */ +export function set_context_after_init() { + if (DEV) { + const error = new Error(`set_context_after_init\n\`setContext\` must be called when a component first initializes, not in a subsequent effect or after an \`await\` expression\nhttps://svelte.dev/e/set_context_after_init`); + + error.name = 'Svelte error'; + throw error; + } else { + throw new Error(`https://svelte.dev/e/set_context_after_init`); + } +} + /** * Could not `{@render}` snippet due to the expression being `null` or `undefined`. Consider using optional chaining `{@render snippet?.()}` * @returns {never} diff --git a/packages/svelte/tests/runtime-runes/samples/set-context-after-mount/_config.js b/packages/svelte/tests/runtime-runes/samples/set-context-after-mount/_config.js new file mode 100644 index 0000000000..cc7c483667 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/set-context-after-mount/_config.js @@ -0,0 +1,11 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + async test({ target, assert, logs }) { + const button = target.querySelector('button'); + + flushSync(() => button?.click()); + assert.ok(logs[0].startsWith('set_context_after_init')); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/set-context-after-mount/main.svelte b/packages/svelte/tests/runtime-runes/samples/set-context-after-mount/main.svelte new file mode 100644 index 0000000000..40145c28da --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/set-context-after-mount/main.svelte @@ -0,0 +1,19 @@ + + +