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

97.95% Statements 96/98
95.83% Branches 23/24
100% Functions 1/1
97.89% Lines 93/95

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 962x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 356x 352x 352x 352x 352x 352x 352x 348x     348x 348x 348x 348x 348x 348x 348x 348x 348x 348x 348x 348x 348x 348x 348x 348x 348x 805x 805x 750x 801x 783x 783x 783x 783x 783x 783x 100x 100x 100x 783x 783x 783x 783x 357x 783x 274x 274x 509x 509x 509x 509x 750x 348x 348x 348x 348x 348x 261x 348x 221x 221x 13x 13x 13x 13x 13x 221x 221x 223x 223x 158x 158x 158x 223x 221x 352x 4x 4x 352x 356x 356x 356x  
/** @import { Expression, LabeledStatement } from 'estree' */
/** @import { AST, ReactiveStatement, SvelteNode } from '#compiler' */
/** @import { Context } from '../types' */
import * as e from '../../../errors.js';
import { extract_identifiers, object } from '../../../utils/ast.js';
import * as w from '../../../warnings.js';
 
/**
 * @param {LabeledStatement} node
 * @param {Context} context
 */
export function LabeledStatement(node, context) {
	if (node.label.name === '$') {
		const parent = /** @type {SvelteNode} */ (context.path.at(-1));
 
		const is_reactive_statement =
			context.state.ast_type === 'instance' && parent.type === 'Program';
 
		if (is_reactive_statement) {
			if (context.state.analysis.runes) {
				e.legacy_reactive_statement_invalid(node);
			}
 
			// Find all dependencies of this `$: {...}` statement
			/** @type {ReactiveStatement} */
			const reactive_statement = {
				assignments: new Set(),
				dependencies: []
			};
 
			context.next({
				...context.state,
				reactive_statement,
				function_depth: context.state.scope.function_depth + 1
			});
 
			// Every referenced binding becomes a dependency, unless it's on
			// the left-hand side of an `=` assignment
			for (const [name, nodes] of context.state.scope.references) {
				const binding = context.state.scope.get(name);
				if (binding === null) continue;
 
				for (const { node, path } of nodes) {
					/** @type {Expression} */
					let left = node;
 
					let i = path.length - 1;
					let parent = /** @type {Expression} */ (path.at(i));
					while (parent.type === 'MemberExpression') {
						left = parent;
						parent = /** @type {Expression} */ (path.at(--i));
					}
 
					if (
						parent.type === 'AssignmentExpression' &&
						parent.operator === '=' &&
						parent.left === left
					) {
						continue;
					}
 
					reactive_statement.dependencies.push(binding);
					break;
				}
			}
 
			context.state.reactive_statements.set(node, reactive_statement);
 
			if (
				node.body.type === 'ExpressionStatement' &&
				node.body.expression.type === 'AssignmentExpression'
			) {
				let ids = extract_identifiers(node.body.expression.left);
				if (node.body.expression.left.type === 'MemberExpression') {
					const id = object(node.body.expression.left);
					if (id !== null) {
						ids = [id];
					}
				}
 
				for (const id of ids) {
					const binding = context.state.scope.get(id.name);
					if (binding?.kind === 'legacy_reactive') {
						// TODO does this include `let double; $: double = x * 2`?
						binding.legacy_dependencies = Array.from(reactive_statement.dependencies);
					}
				}
			}
		} else if (!context.state.analysis.runes) {
			w.reactive_declaration_invalid_placement(node);
		}
	}
 
	context.next();
}