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 350x 346x 346x 346x 346x 346x 346x 342x     342x 342x 342x 342x 342x 342x 342x 342x 342x 342x 342x 342x 342x 342x 342x 342x 342x 790x 790x 738x 786x 771x 771x 771x 771x 771x 771x 100x 100x 100x 771x 771x 771x 771x 351x 771x 269x 269x 502x 502x 502x 502x 738x 342x 342x 342x 342x 342x 255x 342x 215x 215x 12x 12x 12x 12x 12x 215x 215x 217x 217x 155x 155x 155x 217x 215x 346x 4x 4x 346x 350x 350x 350x  
/** @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();
}