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

96.94% Statements 127/131
93.61% Branches 44/47
100% Functions 1/1
96.8% Lines 121/125

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 121 122 123 124 125 1262x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 45205x 45205x 45205x 45205x 9503x 9503x 35702x 35702x 35702x 35702x 35702x 45205x 3x 45205x 1x 1x 35701x 35701x 45205x 12x 12x 35701x 45205x 10788x 10788x 10788x 1723x 10788x 1671x 1671x 1671x 1671x 1671x 124x 124x 124x 124x 124x 124x 124x 2x 1x 1x 1x 2x     1x 2x     1x 1x 1x 124x 1669x 1671x 2x 2x 1671x 10788x 35697x 35697x 35697x 39596x 24913x 68x 68x 24913x 24913x 59x 59x 24913x 35697x 45205x 32733x 10262x 10262x 10262x 32733x 32733x 32733x 32733x 32733x 640x 640x 640x 54x 35x 35x 35x 640x 640x 32733x 42x 32733x 29x 32733x 24x 24x 32733x 32733x 32733x 32733x 34x 32733x 3x 3x 32733x 45205x  
/** @import { Expression, Identifier } from 'estree' */
/** @import { EachBlock } from '#compiler' */
/** @import { Context } from '../types' */
import is_reference from 'is-reference';
import { should_proxy } from '../../3-transform/client/utils.js';
import * as e from '../../../errors.js';
import * as w from '../../../warnings.js';
import { is_rune } from '../../../../utils.js';
import { mark_subtree_dynamic } from './shared/fragment.js';
 
/**
 * @param {Identifier} node
 * @param {Context} context
 */
export function Identifier(node, context) {
	let i = context.path.length;
	let parent = /** @type {Expression} */ (context.path[--i]);
 
	if (!is_reference(node, parent)) {
		return;
	}
 
	mark_subtree_dynamic(context.path);
 
	// If we are using arguments outside of a function, then throw an error
	if (
		node.name === 'arguments' &&
		!context.path.some((n) => n.type === 'FunctionDeclaration' || n.type === 'FunctionExpression')
	) {
		e.invalid_arguments_usage(node);
	}
 
	// `$$slots` exists even in runes mode
	if (node.name === '$$slots') {
		context.state.analysis.uses_slots = true;
	}
 
	if (context.state.analysis.runes) {
		if (
			is_rune(node.name) &&
			context.state.scope.get(node.name) === null &&
			context.state.scope.get(node.name.slice(1)) === null
		) {
			/** @type {Expression} */
			let current = node;
			let name = node.name;
 
			while (parent.type === 'MemberExpression') {
				if (parent.computed) e.rune_invalid_computed_property(parent);
				name += `.${/** @type {Identifier} */ (parent.property).name}`;
 
				current = parent;
				parent = /** @type {Expression} */ (context.path[--i]);
 
				if (!is_rune(name)) {
					if (name === '$effect.active') {
						e.rune_renamed(parent, '$effect.active', '$effect.tracking');
					}
 
					if (name === '$state.frozen') {
						e.rune_renamed(parent, '$state.frozen', '$state.raw');
					}
 
					if (name === '$state.is') {
						e.rune_removed(parent, '$state.is');
					}
 
					e.rune_invalid_name(parent, name);
				}
			}
 
			if (parent.type !== 'CallExpression') {
				e.rune_missing_parentheses(current);
			}
		}
	}
 
	let binding = context.state.scope.get(node.name);
 
	if (!context.state.analysis.runes) {
		if (node.name === '$$props') {
			context.state.analysis.uses_props = true;
		}
 
		if (node.name === '$$restProps') {
			context.state.analysis.uses_rest_props = true;
		}
	}
 
	if (binding) {
		if (context.state.expression) {
			context.state.expression.dependencies.add(binding);
			context.state.expression.has_state ||= binding.kind !== 'normal';
		}
 
		if (
			context.state.analysis.runes &&
			node !== binding.node &&
			context.state.function_depth === binding.scope.function_depth &&
			// If we have $state that can be proxied or frozen and isn't re-assigned, then that means
			// it's likely not using a primitive value and thus this warning isn't that helpful.
			((binding.kind === 'state' &&
				(binding.reassigned ||
					(binding.initial?.type === 'CallExpression' &&
						binding.initial.arguments.length === 1 &&
						binding.initial.arguments[0].type !== 'SpreadElement' &&
						!should_proxy(binding.initial.arguments[0], context.state.scope)))) ||
				binding.kind === 'raw_state' ||
				binding.kind === 'derived') &&
			// We're only concerned with reads here
			(parent.type !== 'AssignmentExpression' || parent.left !== node) &&
			parent.type !== 'UpdateExpression'
		) {
			w.state_referenced_locally(node);
		}
 
		if (
			context.state.reactive_statement &&
			binding.scope === context.state.analysis.module.scope &&
			binding.reassigned
		) {
			w.reactive_declaration_module_script_dependency(node);
		}
	}
}