All files / src/compiler/phases/3-transform/client/visitors SnippetBlock.js

97.87% Statements 92/94
85.71% Branches 12/14
100% Functions 1/1
97.77% Lines 88/90

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 912x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 33x 33x 33x 33x 33x 20x 20x 20x 20x 20x 20x 20x 20x 20x 20x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x     13x 13x 47x 47x 47x 47x 47x 47x 47x 47x 47x 47x 3x 3x 47x 47x 47x 47x 47x 38x 47x 9x 9x 47x  
/** @import { BlockStatement, Expression, Identifier, Pattern, Statement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { ComponentContext } from '../types' */
import { dev } from '../../../../state.js';
import { extract_paths } from '../../../../utils/ast.js';
import * as b from '../../../../utils/builders.js';
import { get_value } from './shared/declarations.js';
 
/**
 * @param {AST.SnippetBlock} node
 * @param {ComponentContext} context
 */
export function SnippetBlock(node, context) {
	// TODO hoist where possible
	/** @type {Pattern[]} */
	const args = [b.id('$$anchor')];
 
	/** @type {BlockStatement} */
	let body;
 
	/** @type {Statement[]} */
	const declarations = [];
 
	const transform = { ...context.state.transform };
	const child_state = { ...context.state, transform };
 
	for (let i = 0; i < node.parameters.length; i++) {
		const argument = node.parameters[i];
 
		if (!argument) continue;
 
		if (argument.type === 'Identifier') {
			args.push({
				type: 'AssignmentPattern',
				left: argument,
				right: b.id('$.noop')
			});
 
			transform[argument.name] = { read: b.call };
 
			continue;
		}
 
		let arg_alias = `$$arg${i}`;
		args.push(b.id(arg_alias));
 
		const paths = extract_paths(argument);
 
		for (const path of paths) {
			const name = /** @type {Identifier} */ (path.node).name;
			const needs_derived = path.has_default_value; // to ensure that default value is only called once
			const fn = b.thunk(
				/** @type {Expression} */ (context.visit(path.expression?.(b.maybe_call(b.id(arg_alias)))))
			);
 
			declarations.push(b.let(path.node, needs_derived ? b.call('$.derived_safe_equal', fn) : fn));
 
			transform[name] = {
				read: needs_derived ? get_value : b.call
			};
 
			// we need to eagerly evaluate the expression in order to hit any
			// 'Cannot access x before initialization' errors
			if (dev) {
				declarations.push(b.stmt(transform[name].read(b.id(name))));
			}
		}
	}
 
	body = b.block([
		...declarations,
		.../** @type {BlockStatement} */ (context.visit(node.body, child_state)).body
	]);
 
	/** @type {Expression} */
	let snippet = b.arrow(args, body);
 
	if (dev) {
		snippet = b.call('$.wrap_snippet', b.id(context.state.analysis.name), snippet);
	}
 
	const declaration = b.const(node.expression, snippet);
 
	// Top-level snippets are hoisted so they can be referenced in the `<script>`
	if (context.path.length === 1 && context.path[0].type === 'Fragment') {
		context.state.analysis.top_level_snippets.push(declaration);
	} else {
		context.state.init.push(declaration);
	}
}