Skip to content

Commit

Permalink
chore: markdown runtime errors/warnings (#11304)
Browse files Browse the repository at this point in the history
* chore: markdown runtime warnings

* on second thoughts

* start adding errors too

* lint

* centralise
  • Loading branch information
Rich-Harris authored Apr 24, 2024
1 parent 8808860 commit 94b4268
Show file tree
Hide file tree
Showing 27 changed files with 243 additions and 45 deletions.
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ packages/**/config/*.js
packages/svelte/messages/**/*.md
packages/svelte/src/compiler/errors.js
packages/svelte/src/compiler/warnings.js
packages/svelte/src/internal/client/errors.js
packages/svelte/src/internal/client/warnings.js
packages/svelte/src/internal/shared/warnings.js
packages/svelte/tests/**/*.svelte
packages/svelte/tests/**/_expected*
packages/svelte/tests/**/_actual*
Expand Down
3 changes: 3 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export default [
'**/tests',
'packages/svelte/scripts/process-messages/templates/*.js',
'packages/svelte/src/compiler/errors.js',
'packages/svelte/src/internal/client/errors.js',
'packages/svelte/src/internal/client/warnings.js',
'packages/svelte/src/internal/shared/warnings.js',
'packages/svelte/compiler/index.js',
// documentation can contain invalid examples
'documentation',
Expand Down
3 changes: 3 additions & 0 deletions packages/svelte/messages/client-errors/effects.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## effect_update_depth_exceeded

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 changes: 7 additions & 0 deletions packages/svelte/messages/client-errors/lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## lifecycle_outside_component

`%name%(...)` can only be used during component initialisation

## lifecycle_legacy_only

`%name%(...)` cannot be used in runes mode
7 changes: 7 additions & 0 deletions packages/svelte/messages/client-warnings/warnings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## lifecycle_double_unmount

Tried to unmount a component that was not mounted

## ownership_invalid_binding

%parent% passed a value to %child% with `bind:`, but the value is owned by %owner%. Consider creating a binding between %owner% and %parent%
3 changes: 3 additions & 0 deletions packages/svelte/messages/shared-warnings/warnings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## dynamic_void_element_content

`<svelte:element this="%tag%">` is a void element — it cannot have content
4 changes: 4 additions & 0 deletions packages/svelte/scripts/process-messages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,7 @@ function transform(name, dest) {

transform('compile-errors', 'src/compiler/errors.js');
transform('compile-warnings', 'src/compiler/warnings.js');

transform('client-warnings', 'src/internal/client/warnings.js');
transform('client-errors', 'src/internal/client/errors.js');
transform('shared-warnings', 'src/internal/shared/warnings.js');
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { DEV } from 'esm-env';

/**
* MESSAGE
* @param {string} PARAMETER
* @returns {never}
*/
export function CODE(PARAMETER) {
if (DEV) {
const error = new Error(`${'CODE'}\n${MESSAGE}`);
error.name = 'Svelte error';
throw error;
} else {
// TODO print a link to the documentation
throw new Error('CODE');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { DEV } from 'esm-env';

var bold = 'font-weight: bold';
var normal = 'font-weight: normal';

/**
* MESSAGE
* @param {string} PARAMETER
*/
export function CODE(PARAMETER) {
if (DEV) {
console.warn(`%c[svelte] ${'CODE'}\n%c${MESSAGE}`, bold, normal);
} else {
// TODO print a link to the documentation
console.warn('CODE');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DEV } from 'esm-env';

var bold = 'font-weight: bold';
var normal = 'font-weight: normal';

/**
* MESSAGE
* @param {boolean} trace
* @param {string} PARAMETER
*/
export function CODE(trace, PARAMETER) {
if (DEV) {
console.warn(`%c[svelte] ${'CODE'}\n%c${MESSAGE}`, bold, normal);
if (trace) console.trace('stack trace');
} else {
// TODO print a link to the documentation
console.warn('CODE');
}
}
13 changes: 7 additions & 6 deletions packages/svelte/src/index-client.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { current_component_context, flush_sync, untrack } from './internal/client/runtime.js';
import { is_array } from './internal/client/utils.js';
import { user_effect } from './internal/client/index.js';
import * as e from './internal/client/errors.js';

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

if (current_component_context.l !== null) {
Expand All @@ -43,7 +44,7 @@ export function onMount(fn) {
*/
export function onDestroy(fn) {
if (current_component_context === null) {
throw new Error('onDestroy can only be used during component initialisation.');
e.lifecycle_outside_component('onDestroy');
}

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

return (type, detail, options) => {
Expand Down Expand Up @@ -126,7 +127,7 @@ export function createEventDispatcher() {
*/
export function beforeUpdate(fn) {
if (current_component_context === null) {
throw new Error('beforeUpdate can only be used during component initialisation');
e.lifecycle_outside_component('beforeUpdate');
}

if (current_component_context.l === null) {
Expand All @@ -150,11 +151,11 @@ export function beforeUpdate(fn) {
*/
export function afterUpdate(fn) {
if (current_component_context === null) {
throw new Error('afterUpdate can only be used during component initialisation.');
e.lifecycle_outside_component('afterUpdate');
}

if (current_component_context.l === null) {
throw new Error('afterUpdate cannot be used in runes mode');
e.lifecycle_legacy_only('afterUpdate');
}

init_update_callbacks(current_component_context).a.push(fn);
Expand Down
7 changes: 3 additions & 4 deletions packages/svelte/src/internal/client/dev/ownership.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { STATE_SYMBOL } from '../constants.js';
import { render_effect } from '../reactivity/effects.js';
import { current_component_context, untrack } from '../runtime.js';
import { get_prototype_of } from '../utils.js';
import * as w from '../warnings.js';

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

if (owner.filename !== component.filename) {
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}`;

// eslint-disable-next-line no-console
console.warn(message);
w.ownership_invalid_binding(component.filename, owner.filename, original.filename);
}
}
}
Expand Down Expand Up @@ -234,6 +232,7 @@ export function check_ownership(metadata) {
`${component.filename} mutated a value owned by ${original.filename}. This is strongly discouraged`
: 'Mutating a value outside the component that created it is strongly discouraged';

// TODO get rid of this, but implement message overloads first
// eslint-disable-next-line no-console
console.warn(
`${message}. Consider passing values to child components with \`bind:\`, or use a callback instead.`
Expand Down
53 changes: 53 additions & 0 deletions packages/svelte/src/internal/client/errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/* This file is generated by scripts/process-messages.js. Do not edit! */

import { DEV } from 'esm-env';

/**
* 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
* @returns {never}
*/
export function effect_update_depth_exceeded() {
if (DEV) {
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"}`);

error.name = 'Svelte error';
throw error;
} else {
// TODO print a link to the documentation
throw new Error("effect_update_depth_exceeded");
}
}

/**
* `%name%(...)` can only be used during component initialisation
* @param {string} name
* @returns {never}
*/
export function lifecycle_outside_component(name) {
if (DEV) {
const error = new Error(`${"lifecycle_outside_component"}\n${`\`${name}(...)\` can only be used during component initialisation`}`);

error.name = 'Svelte error';
throw error;
} else {
// TODO print a link to the documentation
throw new Error("lifecycle_outside_component");
}
}

/**
* `%name%(...)` cannot be used in runes mode
* @param {string} name
* @returns {never}
*/
export function lifecycle_legacy_only(name) {
if (DEV) {
const error = new Error(`${"lifecycle_legacy_only"}\n${`\`${name}(...)\` cannot be used in runes mode`}`);

error.name = 'Svelte error';
throw error;
} else {
// TODO print a link to the documentation
throw new Error("lifecycle_legacy_only");
}
}
5 changes: 4 additions & 1 deletion packages/svelte/src/internal/client/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { array_from } from './utils.js';
import { handle_event_propagation } from './dom/elements/events.js';
import { reset_head_anchor } from './dom/blocks/svelte-head.js';
import * as w from './warnings.js';

/** @type {Set<string>} */
export const all_registered_events = new Set();
Expand Down Expand Up @@ -269,6 +270,7 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
target.removeEventListener(event_name, bound_event_listener);
}
root_event_handles.delete(event_handle);
mounted_components.delete(component);
};
});

Expand All @@ -289,8 +291,9 @@ let mounted_components = new WeakMap();
export function unmount(component) {
const fn = mounted_components.get(component);
if (DEV && !fn) {
w.lifecycle_double_unmount();
// eslint-disable-next-line no-console
console.warn('Tried to unmount a component that was not mounted.');
console.trace('stack trace');
}
fn?.();
}
Expand Down
39 changes: 18 additions & 21 deletions packages/svelte/src/internal/client/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { add_owner } from './dev/ownership.js';
import { mutate, set, source } from './reactivity/sources.js';
import { update_derived } from './reactivity/deriveds.js';
import { inspect_captured_signals, inspect_fn, set_inspect_fn } from './dev/inspect.js';
import * as e from './errors.js';

const FLUSH_MICROTASK = 0;
const FLUSH_SYNC = 1;
Expand Down Expand Up @@ -412,13 +413,7 @@ export function execute_effect(effect) {
function infinite_loop_guard() {
if (flush_count > 1000) {
flush_count = 0;
throw new Error(
'ERR_SVELTE_TOO_MANY_UPDATES' +
(DEV
? ': 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.'
: '')
);
e.effect_update_depth_exceeded();
}
flush_count++;
}
Expand Down Expand Up @@ -880,12 +875,12 @@ export function is_signal(val) {
* @returns {T}
*/
export function getContext(key) {
const context_map = get_or_init_context_map();
const context_map = get_or_init_context_map('getContext');
const result = /** @type {T} */ (context_map.get(key));

if (DEV) {
// @ts-expect-error
const fn = current_component_context?.function;
const fn = current_component_context.function;
if (fn) {
add_owner(result, fn, true);
}
Expand All @@ -908,7 +903,7 @@ export function getContext(key) {
* @returns {T}
*/
export function setContext(key, context) {
const context_map = get_or_init_context_map();
const context_map = get_or_init_context_map('setContext');
context_map.set(key, context);
return context;
}
Expand All @@ -922,7 +917,7 @@ export function setContext(key, context) {
* @returns {boolean}
*/
export function hasContext(key) {
const context_map = get_or_init_context_map();
const context_map = get_or_init_context_map('hasContext');
return context_map.has(key);
}

Expand All @@ -936,7 +931,7 @@ export function hasContext(key) {
* @returns {T}
*/
export function getAllContexts() {
const context_map = get_or_init_context_map();
const context_map = get_or_init_context_map('getAllContexts');

if (DEV) {
// @ts-expect-error
Expand All @@ -951,16 +946,18 @@ export function getAllContexts() {
return /** @type {T} */ (context_map);
}

/** @returns {Map<unknown, unknown>} */
function get_or_init_context_map() {
const component_context = current_component_context;
if (component_context === null) {
throw new Error(
'ERR_SVELTE_ORPHAN_CONTEXT' +
(DEV ? 'Context can only be used during component initialisation.' : '')
);
/**
* @param {string} name
* @returns {Map<unknown, unknown>}
*/
function get_or_init_context_map(name) {
if (current_component_context === null) {
e.lifecycle_outside_component(name);
}
return (component_context.c ??= new Map(get_parent_context(component_context) || undefined));

return (current_component_context.c ??= new Map(
get_parent_context(current_component_context) || undefined
));
}

/**
Expand Down
Loading

0 comments on commit 94b4268

Please sign in to comment.