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 338x 334x 334x 334x 334x 334x 334x 330x     330x 330x 330x 330x 330x 330x 330x 330x 330x 330x 330x 330x 330x 330x 330x 330x 330x 766x 766x 718x 762x 751x 751x 751x 751x 751x 751x 98x 98x 98x 751x 751x 751x 751x 341x 751x 260x 260x 491x 491x 491x 491x 718x 330x 330x 330x 330x 330x 247x 330x 209x 209x 12x 12x 12x 12x 12x 209x 209x 211x 211x 151x 151x 151x 211x 209x 334x 4x 4x 334x 338x 338x 338x  
/** @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();
}