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 7967x 7967x 7967x 1813x 1813x 1813x 1813x 1813x 1813x 1813x 1813x 1813x 789x 1813x 1281x 1407x 1407x 1407x 1407x 1407x 1407x 524x 524x 512x 512x 367x 1407x 1407x 1281x 1813x 1813x 257x     257x 257x 257x 257x 3x 3x 3x 255x 254x 254x 254x 364x 336x 364x     336x 364x 1x 1x 335x 335x 364x 364x 364x     335x 335x 335x 363x 364x 364x 364x 364x 364x 364x 364x 364x 364x 364x 364x 61x 364x 53x 53x 364x 282x 282x 364x 253x 257x 7967x 6153x 354x 354x 354x 354x 1x 354x     354x 6153x 7965x 7965x 7965x  
/** @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();
}