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 323x 319x 319x 319x 319x 319x 319x 315x     315x 315x 315x 315x 315x 315x 315x 315x 315x 315x 315x 315x 315x 315x 315x 315x 315x 734x 734x 691x 725x 723x 723x 723x 723x 723x 723x 98x 98x 98x 723x 723x 723x 723x 330x 723x 249x 249x 474x 474x 474x 474x 691x 315x 315x 315x 315x 315x 241x 315x 204x 204x 12x 12x 12x 12x 12x 204x 204x 206x 206x 147x 147x 147x 206x 204x 319x 4x 4x 319x 323x 323x 323x  
/** @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();
}