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 1521x 1521x 1521x 1584x 1584x 1584x 1584x 1584x 321x 1584x 4x 4x 1580x 1580x 1584x 72x 1584x 1x 1x 1579x 1584x 984x 225x 225x 225x 204x 204x 2x 2x 2x 2x 1x 2x 2x 204x 225x 982x 982x 982x 984x 23x 23x 984x 1575x 1584x 241x 241x 1584x 1512x 1512x 1512x 1512x 1512x 1521x 1521x 1521x 1521x 1574x 1574x 1511x 1511x 1511x 1511x 1511x 1511x 1511x 1521x 1950x 7x 7x 7x 1943x 1950x 1950x 1950x 1950x 1950x 1511x 1511x 1511x 1521x 1698x 1698x 1698x 1698x 1698x 1698x 1698x 1698x 1698x 1494x  
/** @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);
	}
}