Skip to content

Commit 94b4268

Browse files
authored
chore: markdown runtime errors/warnings (#11304)
* chore: markdown runtime warnings * on second thoughts * start adding errors too * lint * centralise
1 parent 8808860 commit 94b4268

File tree

27 files changed

+243
-45
lines changed

27 files changed

+243
-45
lines changed

.prettierignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ packages/**/config/*.js
55
packages/svelte/messages/**/*.md
66
packages/svelte/src/compiler/errors.js
77
packages/svelte/src/compiler/warnings.js
8+
packages/svelte/src/internal/client/errors.js
9+
packages/svelte/src/internal/client/warnings.js
10+
packages/svelte/src/internal/shared/warnings.js
811
packages/svelte/tests/**/*.svelte
912
packages/svelte/tests/**/_expected*
1013
packages/svelte/tests/**/_actual*

eslint.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export default [
3535
'**/tests',
3636
'packages/svelte/scripts/process-messages/templates/*.js',
3737
'packages/svelte/src/compiler/errors.js',
38+
'packages/svelte/src/internal/client/errors.js',
39+
'packages/svelte/src/internal/client/warnings.js',
40+
'packages/svelte/src/internal/shared/warnings.js',
3841
'packages/svelte/compiler/index.js',
3942
// documentation can contain invalid examples
4043
'documentation',
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## effect_update_depth_exceeded
2+
3+
Maximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
## lifecycle_outside_component
2+
3+
`%name%(...)` can only be used during component initialisation
4+
5+
## lifecycle_legacy_only
6+
7+
`%name%(...)` cannot be used in runes mode
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
## lifecycle_double_unmount
2+
3+
Tried to unmount a component that was not mounted
4+
5+
## ownership_invalid_binding
6+
7+
%parent% passed a value to %child% with `bind:`, but the value is owned by %owner%. Consider creating a binding between %owner% and %parent%
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## dynamic_void_element_content
2+
3+
`<svelte:element this="%tag%">` is a void element — it cannot have content

packages/svelte/scripts/process-messages/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,3 +220,7 @@ function transform(name, dest) {
220220

221221
transform('compile-errors', 'src/compiler/errors.js');
222222
transform('compile-warnings', 'src/compiler/warnings.js');
223+
224+
transform('client-warnings', 'src/internal/client/warnings.js');
225+
transform('client-errors', 'src/internal/client/errors.js');
226+
transform('shared-warnings', 'src/internal/shared/warnings.js');
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { DEV } from 'esm-env';
2+
3+
/**
4+
* MESSAGE
5+
* @param {string} PARAMETER
6+
* @returns {never}
7+
*/
8+
export function CODE(PARAMETER) {
9+
if (DEV) {
10+
const error = new Error(`${'CODE'}\n${MESSAGE}`);
11+
error.name = 'Svelte error';
12+
throw error;
13+
} else {
14+
// TODO print a link to the documentation
15+
throw new Error('CODE');
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { DEV } from 'esm-env';
2+
3+
var bold = 'font-weight: bold';
4+
var normal = 'font-weight: normal';
5+
6+
/**
7+
* MESSAGE
8+
* @param {string} PARAMETER
9+
*/
10+
export function CODE(PARAMETER) {
11+
if (DEV) {
12+
console.warn(`%c[svelte] ${'CODE'}\n%c${MESSAGE}`, bold, normal);
13+
} else {
14+
// TODO print a link to the documentation
15+
console.warn('CODE');
16+
}
17+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { DEV } from 'esm-env';
2+
3+
var bold = 'font-weight: bold';
4+
var normal = 'font-weight: normal';
5+
6+
/**
7+
* MESSAGE
8+
* @param {boolean} trace
9+
* @param {string} PARAMETER
10+
*/
11+
export function CODE(trace, PARAMETER) {
12+
if (DEV) {
13+
console.warn(`%c[svelte] ${'CODE'}\n%c${MESSAGE}`, bold, normal);
14+
if (trace) console.trace('stack trace');
15+
} else {
16+
// TODO print a link to the documentation
17+
console.warn('CODE');
18+
}
19+
}

packages/svelte/src/index-client.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { current_component_context, flush_sync, untrack } from './internal/client/runtime.js';
22
import { is_array } from './internal/client/utils.js';
33
import { user_effect } from './internal/client/index.js';
4+
import * as e from './internal/client/errors.js';
45

56
/**
67
* The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM.
@@ -18,7 +19,7 @@ import { user_effect } from './internal/client/index.js';
1819
*/
1920
export function onMount(fn) {
2021
if (current_component_context === null) {
21-
throw new Error('onMount can only be used during component initialisation.');
22+
e.lifecycle_outside_component('onMount');
2223
}
2324

2425
if (current_component_context.l !== null) {
@@ -43,7 +44,7 @@ export function onMount(fn) {
4344
*/
4445
export function onDestroy(fn) {
4546
if (current_component_context === null) {
46-
throw new Error('onDestroy can only be used during component initialisation.');
47+
e.lifecycle_outside_component('onDestroy');
4748
}
4849

4950
onMount(() => () => untrack(fn));
@@ -87,7 +88,7 @@ function create_custom_event(type, detail, { bubbles = false, cancelable = false
8788
export function createEventDispatcher() {
8889
const component_context = current_component_context;
8990
if (component_context === null) {
90-
throw new Error('createEventDispatcher can only be used during component initialisation.');
91+
e.lifecycle_outside_component('createEventDispatcher');
9192
}
9293

9394
return (type, detail, options) => {
@@ -126,7 +127,7 @@ export function createEventDispatcher() {
126127
*/
127128
export function beforeUpdate(fn) {
128129
if (current_component_context === null) {
129-
throw new Error('beforeUpdate can only be used during component initialisation');
130+
e.lifecycle_outside_component('beforeUpdate');
130131
}
131132

132133
if (current_component_context.l === null) {
@@ -150,11 +151,11 @@ export function beforeUpdate(fn) {
150151
*/
151152
export function afterUpdate(fn) {
152153
if (current_component_context === null) {
153-
throw new Error('afterUpdate can only be used during component initialisation.');
154+
e.lifecycle_outside_component('afterUpdate');
154155
}
155156

156157
if (current_component_context.l === null) {
157-
throw new Error('afterUpdate cannot be used in runes mode');
158+
e.lifecycle_legacy_only('afterUpdate');
158159
}
159160

160161
init_update_callbacks(current_component_context).a.push(fn);

packages/svelte/src/internal/client/dev/ownership.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { STATE_SYMBOL } from '../constants.js';
44
import { render_effect } from '../reactivity/effects.js';
55
import { current_component_context, untrack } from '../runtime.js';
66
import { get_prototype_of } from '../utils.js';
7+
import * as w from '../warnings.js';
78

89
/** @type {Record<string, Array<{ start: Location, end: Location, component: Function }>>} */
910
const boundaries = {};
@@ -115,10 +116,7 @@ export function add_owner(object, owner, global = false) {
115116
let original = get_owner(metadata);
116117

117118
if (owner.filename !== component.filename) {
118-
let message = `${component.filename} passed a value to ${owner.filename} with \`bind:\`, but the value is owned by ${original.filename}. Consider creating a binding between ${original.filename} and ${component.filename}`;
119-
120-
// eslint-disable-next-line no-console
121-
console.warn(message);
119+
w.ownership_invalid_binding(component.filename, owner.filename, original.filename);
122120
}
123121
}
124122
}
@@ -234,6 +232,7 @@ export function check_ownership(metadata) {
234232
`${component.filename} mutated a value owned by ${original.filename}. This is strongly discouraged`
235233
: 'Mutating a value outside the component that created it is strongly discouraged';
236234

235+
// TODO get rid of this, but implement message overloads first
237236
// eslint-disable-next-line no-console
238237
console.warn(
239238
`${message}. Consider passing values to child components with \`bind:\`, or use a callback instead.`
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/* This file is generated by scripts/process-messages.js. Do not edit! */
2+
3+
import { DEV } from 'esm-env';
4+
5+
/**
6+
* Maximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops
7+
* @returns {never}
8+
*/
9+
export function effect_update_depth_exceeded() {
10+
if (DEV) {
11+
const error = new Error(`${"effect_update_depth_exceeded"}\n${"Maximum update depth exceeded. This can happen when a reactive block or effect repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops"}`);
12+
13+
error.name = 'Svelte error';
14+
throw error;
15+
} else {
16+
// TODO print a link to the documentation
17+
throw new Error("effect_update_depth_exceeded");
18+
}
19+
}
20+
21+
/**
22+
* `%name%(...)` can only be used during component initialisation
23+
* @param {string} name
24+
* @returns {never}
25+
*/
26+
export function lifecycle_outside_component(name) {
27+
if (DEV) {
28+
const error = new Error(`${"lifecycle_outside_component"}\n${`\`${name}(...)\` can only be used during component initialisation`}`);
29+
30+
error.name = 'Svelte error';
31+
throw error;
32+
} else {
33+
// TODO print a link to the documentation
34+
throw new Error("lifecycle_outside_component");
35+
}
36+
}
37+
38+
/**
39+
* `%name%(...)` cannot be used in runes mode
40+
* @param {string} name
41+
* @returns {never}
42+
*/
43+
export function lifecycle_legacy_only(name) {
44+
if (DEV) {
45+
const error = new Error(`${"lifecycle_legacy_only"}\n${`\`${name}(...)\` cannot be used in runes mode`}`);
46+
47+
error.name = 'Svelte error';
48+
throw error;
49+
} else {
50+
// TODO print a link to the documentation
51+
throw new Error("lifecycle_legacy_only");
52+
}
53+
}

packages/svelte/src/internal/client/render.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { array_from } from './utils.js';
2020
import { handle_event_propagation } from './dom/elements/events.js';
2121
import { reset_head_anchor } from './dom/blocks/svelte-head.js';
22+
import * as w from './warnings.js';
2223

2324
/** @type {Set<string>} */
2425
export const all_registered_events = new Set();
@@ -269,6 +270,7 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
269270
target.removeEventListener(event_name, bound_event_listener);
270271
}
271272
root_event_handles.delete(event_handle);
273+
mounted_components.delete(component);
272274
};
273275
});
274276

@@ -289,8 +291,9 @@ let mounted_components = new WeakMap();
289291
export function unmount(component) {
290292
const fn = mounted_components.get(component);
291293
if (DEV && !fn) {
294+
w.lifecycle_double_unmount();
292295
// eslint-disable-next-line no-console
293-
console.warn('Tried to unmount a component that was not mounted.');
296+
console.trace('stack trace');
294297
}
295298
fn?.();
296299
}

packages/svelte/src/internal/client/runtime.js

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { add_owner } from './dev/ownership.js';
2222
import { mutate, set, source } from './reactivity/sources.js';
2323
import { update_derived } from './reactivity/deriveds.js';
2424
import { inspect_captured_signals, inspect_fn, set_inspect_fn } from './dev/inspect.js';
25+
import * as e from './errors.js';
2526

2627
const FLUSH_MICROTASK = 0;
2728
const FLUSH_SYNC = 1;
@@ -412,13 +413,7 @@ export function execute_effect(effect) {
412413
function infinite_loop_guard() {
413414
if (flush_count > 1000) {
414415
flush_count = 0;
415-
throw new Error(
416-
'ERR_SVELTE_TOO_MANY_UPDATES' +
417-
(DEV
418-
? ': Maximum update depth exceeded. This can happen when a reactive block or effect ' +
419-
'repeatedly sets a new value. Svelte limits the number of nested updates to prevent infinite loops.'
420-
: '')
421-
);
416+
e.effect_update_depth_exceeded();
422417
}
423418
flush_count++;
424419
}
@@ -880,12 +875,12 @@ export function is_signal(val) {
880875
* @returns {T}
881876
*/
882877
export function getContext(key) {
883-
const context_map = get_or_init_context_map();
878+
const context_map = get_or_init_context_map('getContext');
884879
const result = /** @type {T} */ (context_map.get(key));
885880

886881
if (DEV) {
887882
// @ts-expect-error
888-
const fn = current_component_context?.function;
883+
const fn = current_component_context.function;
889884
if (fn) {
890885
add_owner(result, fn, true);
891886
}
@@ -908,7 +903,7 @@ export function getContext(key) {
908903
* @returns {T}
909904
*/
910905
export function setContext(key, context) {
911-
const context_map = get_or_init_context_map();
906+
const context_map = get_or_init_context_map('setContext');
912907
context_map.set(key, context);
913908
return context;
914909
}
@@ -922,7 +917,7 @@ export function setContext(key, context) {
922917
* @returns {boolean}
923918
*/
924919
export function hasContext(key) {
925-
const context_map = get_or_init_context_map();
920+
const context_map = get_or_init_context_map('hasContext');
926921
return context_map.has(key);
927922
}
928923

@@ -936,7 +931,7 @@ export function hasContext(key) {
936931
* @returns {T}
937932
*/
938933
export function getAllContexts() {
939-
const context_map = get_or_init_context_map();
934+
const context_map = get_or_init_context_map('getAllContexts');
940935

941936
if (DEV) {
942937
// @ts-expect-error
@@ -951,16 +946,18 @@ export function getAllContexts() {
951946
return /** @type {T} */ (context_map);
952947
}
953948

954-
/** @returns {Map<unknown, unknown>} */
955-
function get_or_init_context_map() {
956-
const component_context = current_component_context;
957-
if (component_context === null) {
958-
throw new Error(
959-
'ERR_SVELTE_ORPHAN_CONTEXT' +
960-
(DEV ? 'Context can only be used during component initialisation.' : '')
961-
);
949+
/**
950+
* @param {string} name
951+
* @returns {Map<unknown, unknown>}
952+
*/
953+
function get_or_init_context_map(name) {
954+
if (current_component_context === null) {
955+
e.lifecycle_outside_component(name);
962956
}
963-
return (component_context.c ??= new Map(get_parent_context(component_context) || undefined));
957+
958+
return (current_component_context.c ??= new Map(
959+
get_parent_context(current_component_context) || undefined
960+
));
964961
}
965962

966963
/**

0 commit comments

Comments
 (0)