All files / src/internal/client/dev inspect.js

94.54% Statements 104/110
83.87% Branches 26/31
100% Functions 4/4
94.28% Lines 99/105

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 1062x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 27956x 27956x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 22x 22x 22x 22x 22x 22x 22x 22x 44x 44x 22x 22x 22x 42x 42x 42x 42x 42x 42x 42x 42x 22x 22x 22x 42x 42x 42x 103x 103x 42x 22x 22x 2x 2x 2x 2x 2x 2x 2x 116x 116x 78x 78x 78x 78x     78x     78x 78x 78x 78x 18x 18x 18x 60x 60x 60x 60x 78x 60x 60x 60x 60x 60x 72x 72x 72x 34x 34x 72x 60x 60x 78x     78x 98x 116x 116x  
import { DEV } from 'esm-env';
import { snapshot } from '../proxy.js';
import { render_effect, validate_effect } from '../reactivity/effects.js';
import { deep_read, untrack } from '../runtime.js';
import { array_prototype, get_prototype_of, object_prototype } from '../utils.js';
 
/** @type {Function | null} */
export let inspect_fn = null;
 
/** @param {Function | null} fn */
export function set_inspect_fn(fn) {
	inspect_fn = fn;
}
 
/** @type {Array<import('#client').ValueDebug>} */
export let inspect_captured_signals = [];
 
/**
 * @param {() => any[]} get_value
 * @param {Function} [inspector]
 */
// eslint-disable-next-line no-console
export function inspect(get_value, inspector = console.log) {
	validate_effect('$inspect');
 
	let initial = true;
 
	// we assign the function directly to signals, rather than just
	// calling `inspector` directly inside the effect, so that
	// we get useful stack traces
	var fn = () => {
		const value = untrack(() => deep_snapshot(get_value()));
		inspector(initial ? 'init' : 'update', ...value);
	};
 
	render_effect(() => {
		inspect_fn = fn;
		deep_read(get_value());
		inspect_fn = null;
 
		const signals = inspect_captured_signals.slice();
		inspect_captured_signals = [];
 
		if (initial) {
			fn();
			initial = false;
		}
 
		return () => {
			for (const s of signals) {
				s.inspect.delete(fn);
			}
		};
	});
}
 
/**
 * Like `snapshot`, but recursively traverses into normal arrays/objects to find potential states in them.
 * @param {any} value
 * @param {Map<any, any>} visited
 * @returns {any}
 */
function deep_snapshot(value, visited = new Map()) {
	if (typeof value === 'object' && value !== null && !visited.has(value)) {
		if (DEV) {
			// When dealing with ReactiveMap or ReactiveSet, return normal versions
			// so that console.log provides better output versions
			if (value instanceof Map && value.constructor !== Map) {
				return new Map(value);
			}
			if (value instanceof Set && value.constructor !== Set) {
				return new Set(value);
			}
		}
		const unstated = snapshot(value);
 
		if (unstated !== value) {
			visited.set(value, unstated);
			return unstated;
		}
 
		const prototype = get_prototype_of(value);
 
		// Only deeply snapshot plain objects and arrays
		if (prototype === object_prototype || prototype === array_prototype) {
			let contains_unstated = false;
			/** @type {any} */
			const nested_unstated = Array.isArray(value) ? [] : {};
 
			for (let key in value) {
				const result = deep_snapshot(value[key], visited);
				nested_unstated[key] = result;
				if (result !== value[key]) {
					contains_unstated = true;
				}
			}
 
			visited.set(value, contains_unstated ? nested_unstated : value);
		} else {
			visited.set(value, value);
		}
	}
 
	return visited.get(value) ?? value;
}