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

93.49% Statements 115/123
93.1% Branches 54/58
100% Functions 1/1
93.22% Lines 110/118

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 116 117 118 1192x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 8039x 8039x 8039x 1870x 1870x 1870x 1870x 1870x 1870x 1870x 1870x 1870x 1870x 810x 1870x 1326x 1460x 1460x 1460x 1460x 1460x 1460x 558x 558x 550x 550x 536x 536x 380x 1460x 1460x 1326x 1870x 1870x 266x     266x 266x 266x 266x 3x 3x 3x 266x 263x 263x 263x 377x 349x 377x     349x 377x 1x 1x 348x 348x 377x 377x 377x     348x 348x 348x 376x 377x 377x 377x 377x 377x 377x 377x 377x 377x 377x 377x 63x 377x 55x 55x 377x 293x 293x 377x 262x 266x 8039x 6168x 360x 360x 360x 360x 1x 360x     360x 6168x 8037x 8037x 8037x  
/** @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.link' ||
			rune === '$state.raw' ||
			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.link'
							? 'linked_state'
							: rune === '$state.raw'
								? 'raw_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();
}