Skip to content

chore: markdown runtime errors/warnings #11304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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