move error handling code into separate module

pull/16091/head
Rich Harris 4 months ago
parent fd2e7a2813
commit c0cdc5a6a3

@ -2,14 +2,13 @@
import { BOUNDARY_EFFECT, EFFECT_TRANSPARENT } from '#client/constants';
import { component_context, set_component_context } from '../../context.js';
import { handle_error, reset_is_throwing_error } from '../../error-handling.js';
import { block, branch, destroy_effect, pause_effect } from '../../reactivity/effects.js';
import {
active_effect,
active_reaction,
handle_error,
set_active_effect,
set_active_reaction,
reset_is_throwing_error
set_active_reaction
} from '../../runtime.js';
import {
hydrate_next,

@ -0,0 +1,138 @@
/** @import { ComponentContext, Effect } from '#client' */
import { DEV } from 'esm-env';
import { FILENAME } from '../../constants.js';
import { is_firefox } from './dom/operations.js';
import { BOUNDARY_EFFECT, DESTROYED } from './constants.js';
import { define_property } from '../shared/utils.js';
// Used for DEV time error handling
/** @param {WeakSet<Error>} value */
const handled_errors = new WeakSet();
let is_throwing_error = false;
/**
* @param {unknown} error
* @param {Effect} effect
* @param {Effect | null} [previous_effect]
*/
export function handle_error(error, effect, previous_effect = null) {
var component_context = effect.ctx;
if (is_throwing_error) {
if (previous_effect === null) {
is_throwing_error = false;
}
if (should_rethrow_error(effect)) {
throw error;
}
return;
}
if (previous_effect !== null) {
is_throwing_error = true;
}
if (DEV && component_context !== null && error instanceof Error && !handled_errors.has(error)) {
handled_errors.add(error);
const component_stack = [];
const effect_name = effect.fn?.name;
if (effect_name) {
component_stack.push(effect_name);
}
/** @type {ComponentContext | null} */
let current_context = component_context;
while (current_context !== null) {
/** @type {string} */
var filename = current_context.function?.[FILENAME];
if (filename) {
const file = filename.split('/').pop();
component_stack.push(file);
}
current_context = current_context.p;
}
const indent = is_firefox ? ' ' : '\t';
define_property(error, 'message', {
value:
error.message + `\n${component_stack.map((name) => `\n${indent}in ${name}`).join('')}\n`
});
define_property(error, 'component_stack', {
value: component_stack
});
const stack = error.stack;
// Filter out internal files from callstack
if (stack) {
const lines = stack.split('\n');
const new_lines = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes('svelte/src/internal')) {
continue;
}
new_lines.push(line);
}
define_property(error, 'stack', {
value: new_lines.join('\n')
});
}
}
propagate_error(error, effect);
if (should_rethrow_error(effect)) {
throw error;
}
}
/**
* @param {unknown} error
* @param {Effect} effect
*/
function propagate_error(error, effect) {
/** @type {Effect | null} */
var current = effect;
while (current !== null) {
if ((current.f & BOUNDARY_EFFECT) !== 0) {
try {
// @ts-expect-error
current.fn(error);
return;
} catch {
// Remove boundary flag from effect
current.f ^= BOUNDARY_EFFECT;
}
}
current = current.parent;
}
is_throwing_error = false;
throw error;
}
/**
* @param {Effect} effect
*/
function should_rethrow_error(effect) {
return (
(effect.f & DESTROYED) === 0 &&
(effect.parent === null || (effect.parent.f & BOUNDARY_EFFECT) === 0)
);
}
export function reset_is_throwing_error() {
is_throwing_error = false;
}

@ -29,7 +29,7 @@ import { flush_tasks } from './dom/task.js';
import { internal_set, old_values } from './reactivity/sources.js';
import { destroy_derived_effects, update_derived } from './reactivity/deriveds.js';
import * as e from './errors.js';
import { FILENAME } from '../../constants.js';
import { tracing_mode_flag } from '../flags/index.js';
import { tracing_expressions, get_stack } from './dev/tracing.js';
import {
@ -39,12 +39,7 @@ import {
set_component_context,
set_dev_current_component_function
} from './context.js';
import { is_firefox } from './dom/operations.js';
// Used for DEV time error handling
/** @param {WeakSet<Error>} value */
const handled_errors = new WeakSet();
let is_throwing_error = false;
import { handle_error } from './error-handling.js';
let is_flushing = false;
@ -227,132 +222,6 @@ export function check_dirtiness(reaction) {
return false;
}
/**
* @param {unknown} error
* @param {Effect} effect
*/
function propagate_error(error, effect) {
/** @type {Effect | null} */
var current = effect;
while (current !== null) {
if ((current.f & BOUNDARY_EFFECT) !== 0) {
try {
// @ts-expect-error
current.fn(error);
return;
} catch {
// Remove boundary flag from effect
current.f ^= BOUNDARY_EFFECT;
}
}
current = current.parent;
}
is_throwing_error = false;
throw error;
}
/**
* @param {Effect} effect
*/
function should_rethrow_error(effect) {
return (
(effect.f & DESTROYED) === 0 &&
(effect.parent === null || (effect.parent.f & BOUNDARY_EFFECT) === 0)
);
}
export function reset_is_throwing_error() {
is_throwing_error = false;
}
/**
* @param {unknown} error
* @param {Effect} effect
* @param {Effect | null} [previous_effect]
*/
export function handle_error(error, effect, previous_effect = null) {
var component_context = effect.ctx;
if (is_throwing_error) {
if (previous_effect === null) {
is_throwing_error = false;
}
if (should_rethrow_error(effect)) {
throw error;
}
return;
}
if (previous_effect !== null) {
is_throwing_error = true;
}
if (DEV && component_context !== null && error instanceof Error && !handled_errors.has(error)) {
handled_errors.add(error);
const component_stack = [];
const effect_name = effect.fn?.name;
if (effect_name) {
component_stack.push(effect_name);
}
/** @type {ComponentContext | null} */
let current_context = component_context;
while (current_context !== null) {
/** @type {string} */
var filename = current_context.function?.[FILENAME];
if (filename) {
const file = filename.split('/').pop();
component_stack.push(file);
}
current_context = current_context.p;
}
const indent = is_firefox ? ' ' : '\t';
define_property(error, 'message', {
value:
error.message + `\n${component_stack.map((name) => `\n${indent}in ${name}`).join('')}\n`
});
define_property(error, 'component_stack', {
value: component_stack
});
const stack = error.stack;
// Filter out internal files from callstack
if (stack) {
const lines = stack.split('\n');
const new_lines = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes('svelte/src/internal')) {
continue;
}
new_lines.push(line);
}
define_property(error, 'stack', {
value: new_lines.join('\n')
});
}
}
propagate_error(error, effect);
if (should_rethrow_error(effect)) {
throw error;
}
}
/**
* @param {Value} signal
* @param {Effect} effect

Loading…
Cancel
Save