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

93.6% Statements 117/125
93.1% Branches 54/58
100% Functions 1/1
93.33% Lines 112/120

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 119 120 1212x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 8341x 8341x 8341x 2083x 2083x 2083x 2083x 2083x 2223x 2223x 2082x 2082x 2082x 2083x 2083x 2083x 2083x 913x 2083x 1470x 1610x 1610x 1610x 1610x 1610x 1610x 620x 620x 606x 606x 421x 1610x 1610x 1470x 2082x 2083x 301x     301x 301x 301x 301x 5x 5x 5x 301x 296x 296x 296x 416x 386x 416x     386x 416x 1x 1x 385x 385x 416x 416x 416x     385x 385x 385x 415x 416x 416x 416x 416x 416x 416x 416x 416x 416x 416x 416x 73x 416x 65x 65x 416x 320x 320x 416x 295x 301x 8341x 6257x 365x 365x 365x 365x 1x 365x     365x 6257x 8338x 8338x 8338x  
/** @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, validate_identifier_name } 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);
		const paths = extract_paths(node.id);
 
		for (const path of paths) {
			validate_identifier_name(context.state.scope.get(/** @type {Identifier} */ (path.node).name));
		}
 
		// TODO feels like this should happen during scope creation?
		if (
			rune === '$state' ||
			rune === '$state.raw' ||
			rune === '$derived' ||
			rune === '$derived.by' ||
			rune === '$props'
		) {
			for (const path of paths) {
				// @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.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();
}