All files / src/compiler/phases/2-analyze/visitors/shared component.js

100% Statements 109/109
100% Branches 37/37
100% Functions 1/1
100% Lines 104/104

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 106 107 108 1092x 2x 2x 2x 2x         2x 2x 2x 2x 2x 2x 2x 2x 1455x 1455x 1455x 1549x 1549x 1549x 1549x 1549x 319x 1549x 4x 4x 1545x 1545x 1549x 62x 1549x 1x 1x 1544x 1549x 967x 216x 216x 216x 203x 203x 2x 2x 2x 2x 1x 2x 2x 203x 216x 965x 965x 965x 967x 23x 23x 967x 1540x 1549x 239x 239x 1549x 1446x 1446x 1446x 1446x 1446x 1455x 1455x 1455x 1455x 1539x 1539x 1445x 1445x 1445x 1445x 1445x 1445x 1445x 1455x 1887x 7x 7x 7x 1880x 1887x 1887x 1887x 1887x 1887x 1445x 1445x 1445x 1455x 1626x 1626x 1626x 1626x 1626x 1626x 1626x 1626x 1626x 1428x  
/** @import { AST } from '#compiler' */
/** @import { Context } from '../../types' */
import * as e from '../../../../errors.js';
import { get_attribute_expression, is_expression_attribute } from '../../../../utils/ast.js';
import { determine_slot } from '../../../../utils/slot.js';
import {
	validate_attribute,
	validate_attribute_name,
	validate_slot_attribute
} from './attribute.js';
import { mark_subtree_dynamic } from './fragment.js';
 
/**
 * @param {AST.Component | AST.SvelteComponent | AST.SvelteSelf} node
 * @param {Context} context
 */
export function visit_component(node, context) {
	mark_subtree_dynamic(context.path);
 
	for (const attribute of node.attributes) {
		if (
			attribute.type !== 'Attribute' &&
			attribute.type !== 'SpreadAttribute' &&
			attribute.type !== 'LetDirective' &&
			attribute.type !== 'OnDirective' &&
			attribute.type !== 'BindDirective'
		) {
			e.component_invalid_directive(attribute);
		}
 
		if (
			attribute.type === 'OnDirective' &&
			(attribute.modifiers.length > 1 || attribute.modifiers.some((m) => m !== 'once'))
		) {
			e.event_handler_invalid_component_modifier(attribute);
		}
 
		if (attribute.type === 'Attribute') {
			if (context.state.analysis.runes) {
				validate_attribute(attribute, node);
 
				if (is_expression_attribute(attribute)) {
					const expression = get_attribute_expression(attribute);
					if (expression.type === 'SequenceExpression') {
						let i = /** @type {number} */ (expression.start);
						while (--i > 0) {
							const char = context.state.analysis.source[i];
							if (char === '(') break; // parenthesized sequence expressions are ok
							if (char === '{') e.attribute_invalid_sequence_expression(expression);
						}
					}
				}
			}
 
			validate_attribute_name(attribute);
 
			if (attribute.name === 'slot') {
				validate_slot_attribute(context, attribute, true);
			}
		}
 
		if (attribute.type === 'BindDirective' && attribute.name !== 'this') {
			context.state.analysis.uses_component_bindings = true;
		}
	}
 
	// If the component has a slot attribute — `<Foo slot="whatever" .../>` —
	// then `let:` directives apply to other attributes, instead of just the
	// top-level contents of the component. Yes, this is very weird.
	const default_state = determine_slot(node)
		? context.state
		: { ...context.state, scope: node.metadata.scopes.default };
 
	for (const attribute of node.attributes) {
		context.visit(attribute, attribute.type === 'LetDirective' ? default_state : context.state);
	}
 
	/** @type {AST.Comment[]} */
	let comments = [];
 
	/** @type {Record<string, AST.Fragment['nodes']>} */
	const nodes = { default: [] };
 
	for (const child of node.fragment.nodes) {
		if (child.type === 'Comment') {
			comments.push(child);
			continue;
		}
 
		const slot_name = determine_slot(child) ?? 'default';
		(nodes[slot_name] ??= []).push(...comments, child);
 
		if (slot_name !== 'default') comments = [];
	}
 
	const component_slots = new Set();
 
	for (const slot_name in nodes) {
		const state = {
			...context.state,
			scope: node.metadata.scopes[slot_name],
			parent_element: null,
			component_slots
		};
 
		context.visit({ ...node.fragment, nodes: nodes[slot_name] }, state);
	}
}