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

93.33% Statements 112/120
92.85% Branches 52/56
100% Functions 1/1
93.04% Lines 107/115

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 109 110 111 112 113 114 115 1162x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 7987x 7987x 7987x 1829x 1829x 1829x 1829x 1829x 1829x 1829x 1829x 1829x 795x 1829x 1293x 1423x 1423x 1423x 1423x 1423x 1423x 532x 532x 520x 520x 369x 1423x 1423x 1293x 1829x 1829x 259x     259x 259x 259x 259x 3x 3x 3x 257x 256x 256x 256x 366x 338x 366x     338x 366x 1x 1x 337x 337x 366x 366x 366x     337x 337x 337x 365x 366x 366x 366x 366x 366x 366x 366x 366x 366x 366x 366x 61x 366x 53x 53x 366x 284x 284x 366x 255x 259x 7987x 6157x 354x 354x 354x 354x 1x 354x     354x 6157x 7985x 7985x 7985x  
/** @import { Expression, Identifier, Literal, VariableDeclarator } from 'estree' */
/** @import { Binding } from '#compiler' */
/** @import { Context } from '../types' */
import { get_rune } from '../../scope.js';
import { ensure_no_module_import_conflict } from './shared/utils.js';
import * as e from '../../../errors.js';
import { extract_paths } from '../../../utils/ast.js';
import { equal } from '../../../utils/assert.js';
 
/**
 * @param {VariableDeclarator} node
 * @param {Context} context
 */
export function VariableDeclarator(node, context) {
	ensure_no_module_import_conflict(node, context.state);
 
	if (context.state.analysis.runes) {
		const init = node.init;
		const rune = get_rune(init, context.state.scope);
 
		// TODO feels like this should happen during scope creation?
		if (
			rune === '$state' ||
			rune === '$state.frozen' ||
			rune === '$derived' ||
			rune === '$derived.by' ||
			rune === '$props'
		) {
			for (const path of extract_paths(node.id)) {
				// @ts-ignore this fails in CI for some insane reason
				const binding = /** @type {Binding} */ (context.state.scope.get(path.node.name));
				binding.kind =
					rune === '$state'
						? 'state'
						: rune === '$state.frozen'
							? 'frozen_state'
							: rune === '$derived' || rune === '$derived.by'
								? 'derived'
								: path.is_rest
									? 'rest_prop'
									: 'prop';
			}
		}
 
		if (rune === '$props') {
			if (node.id.type !== 'ObjectPattern' && node.id.type !== 'Identifier') {
				e.props_invalid_identifier(node);
			}
 
			context.state.analysis.needs_props = true;
 
			if (node.id.type === 'Identifier') {
				const binding = /** @type {Binding} */ (context.state.scope.get(node.id.name));
				binding.initial = null; // else would be $props()
				binding.kind = 'rest_prop';
			} else {
				equal(node.id.type, 'ObjectPattern');
 
				for (const property of node.id.properties) {
					if (property.type !== 'Property') continue;
 
					if (property.computed) {
						e.props_invalid_pattern(property);
					}
 
					if (property.key.type === 'Identifier' && property.key.name.startsWith('$$')) {
						e.props_illegal_name(property);
					}
 
					const value =
						property.value.type === 'AssignmentPattern' ? property.value.left : property.value;
 
					if (value.type !== 'Identifier') {
						e.props_invalid_pattern(property);
					}
 
					const alias =
						property.key.type === 'Identifier'
							? property.key.name
							: String(/** @type {Literal} */ (property.key).value);
 
					let initial = property.value.type === 'AssignmentPattern' ? property.value.right : null;
 
					const binding = /** @type {Binding} */ (context.state.scope.get(value.name));
					binding.prop_alias = alias;
 
					// rewire initial from $props() to the actual initial value, stripping $bindable() if necessary
					if (
						initial?.type === 'CallExpression' &&
						initial.callee.type === 'Identifier' &&
						initial.callee.name === '$bindable'
					) {
						binding.initial = /** @type {Expression | null} */ (initial.arguments[0] ?? null);
						binding.kind = 'bindable_prop';
					} else {
						binding.initial = initial;
					}
				}
			}
		}
	} else {
		if (node.init?.type === 'CallExpression') {
			const callee = node.init.callee;
			if (
				callee.type === 'Identifier' &&
				(callee.name === '$state' || callee.name === '$derived' || callee.name === '$props') &&
				context.state.scope.get(callee.name)?.kind !== 'store_sub'
			) {
				e.rune_invalid_usage(node.init, callee.name);
			}
		}
	}
 
	context.next();
}