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

93.08% Statements 175/188
90.14% Branches 64/71
100% Functions 1/1
92.93% Lines 171/184

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 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 1852x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 5192x 5192x 5192x 5192x 5192x 5192x 3478x 1155x 1155x 3478x 3478x 5192x 5192x 57x 1x 1x 56x 56x 56x 57x 57x 56x 56x 56x 56x 57x 2x 2x 54x 54x 5192x 5192x 2x   2x 2x 2x     5192x 5192x 263x 1x 1x 262x 262x 262x 262x 262x 263x 261x 263x 2x 2x 260x 263x 1x 1x 259x 259x 5192x 5192x 5192x 5192x 5192x 1166x 1166x 122x 1166x 5x 5x 1161x 1166x 2x 1166x 2x 2x 1157x 1157x 5192x 5192x 5192x 155x 2x 2x 153x 153x 2x 2x 151x 151x 151x 151x 151x 151x 5192x 5192x 10x     10x 10x 5192x 5192x 15x     15x 15x 5192x 5192x 27x     27x 27x 5192x 5192x 4x     4x 4x 5192x 5192x 11x 1x 1x 10x 10x 5192x 5192x 4x     4x 4x 5192x 5169x 5192x 758x 758x 758x 5169x 5192x 159x 159x 159x 159x 159x 159x 159x 2x 2x 159x 5169x 5192x 3218x 3218x 3218x 1579x 1579x 3218x 5169x 5169x 5192x 169x 5192x 5000x 5000x 5192x  
/** @import { CallExpression, VariableDeclarator } from 'estree' */
/** @import { SvelteNode } from '#compiler' */
/** @import { Context } from '../types' */
import { get_rune } from '../../scope.js';
import * as e from '../../../errors.js';
import { get_parent, unwrap_optional } from '../../../utils/ast.js';
import { is_pure, is_safe_identifier } from './shared/utils.js';
 
/**
 * @param {CallExpression} node
 * @param {Context} context
 */
export function CallExpression(node, context) {
	const parent = /** @type {SvelteNode} */ (get_parent(context.path, -1));
 
	const rune = get_rune(node, context.state.scope);
 
	switch (rune) {
		case null:
			if (!is_safe_identifier(node.callee, context.state.scope)) {
				context.state.analysis.needs_context = true;
			}
 
			break;
 
		case '$bindable':
			if (node.arguments.length > 1) {
				e.rune_invalid_arguments_length(node, '$bindable', 'zero or one arguments');
			}
 
			if (
				parent.type !== 'AssignmentPattern' ||
				context.path.at(-3)?.type !== 'ObjectPattern' ||
				context.path.at(-4)?.type !== 'VariableDeclarator' ||
				get_rune(
					/** @type {VariableDeclarator} */ (context.path.at(-4)).init,
					context.state.scope
				) !== '$props'
			) {
				e.bindable_invalid_location(node);
			}
 
			break;
 
		case '$host':
			if (node.arguments.length > 0) {
				e.rune_invalid_arguments(node, '$host');
			} else if (context.state.ast_type === 'module' || !context.state.analysis.custom_element) {
				e.host_invalid_placement(node);
			}

			break;
 
		case '$props':
			if (context.state.has_props_rune) {
				e.props_duplicate(node);
			}
 
			context.state.has_props_rune = true;
 
			if (
				parent.type !== 'VariableDeclarator' ||
				context.state.ast_type !== 'instance' ||
				context.state.scope !== context.state.analysis.instance.scope
			) {
				e.props_invalid_placement(node);
			}
 
			if (node.arguments.length > 0) {
				e.rune_invalid_arguments(node, rune);
			}
 
			break;
 
		case '$state':
		case '$state.raw':
		case '$derived':
		case '$derived.by':
			if (
				parent.type !== 'VariableDeclarator' &&
				!(parent.type === 'PropertyDefinition' && !parent.static && !parent.computed)
			) {
				e.state_invalid_placement(node, rune);
			}
 
			if ((rune === '$derived' || rune === '$derived.by') && node.arguments.length !== 1) {
				e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
			} else if (rune === '$state' && node.arguments.length > 1) {
				e.rune_invalid_arguments_length(node, rune, 'zero or one arguments');
			}
 
			break;
 
		case '$effect':
		case '$effect.pre':
			if (parent.type !== 'ExpressionStatement') {
				e.effect_invalid_placement(node);
			}
 
			if (node.arguments.length !== 1) {
				e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
			}
 
			// `$effect` needs context because Svelte needs to know whether it should re-run
			// effects that invalidate themselves, and that's determined by whether we're in runes mode
			context.state.analysis.needs_context = true;
 
			break;
 
		case '$effect.tracking':
			if (node.arguments.length !== 0) {
				e.rune_invalid_arguments(node, rune);
			}
 
			break;
 
		case '$effect.root':
			if (node.arguments.length !== 1) {
				e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
			}
 
			break;
 
		case '$inspect':
			if (node.arguments.length < 1) {
				e.rune_invalid_arguments_length(node, rune, 'one or more arguments');
			}
 
			break;
 
		case '$inspect().with':
			if (node.arguments.length !== 1) {
				e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
			}
 
			break;
 
		case '$state.snapshot':
			if (node.arguments.length !== 1) {
				e.rune_invalid_arguments_length(node, rune, 'exactly one argument');
			}
 
			break;
 
		case '$state.is':
			if (node.arguments.length !== 2) {
				e.rune_invalid_arguments_length(node, rune, 'exactly two arguments');
			}
 
			break;
	}
 
	if (context.state.expression && !is_pure(node.callee, context)) {
		context.state.expression.has_call = true;
		context.state.expression.has_state = true;
	}
 
	if (context.state.render_tag) {
		// Find out which of the render tag arguments contains this call expression
		const arg_idx = unwrap_optional(context.state.render_tag.expression).arguments.findIndex(
			(arg) => arg === node || context.path.includes(arg)
		);
 
		// -1 if this is the call expression of the render tag itself
		if (arg_idx !== -1) {
			context.state.render_tag.metadata.args_with_call_expression.add(arg_idx);
		}
	}
 
	if (node.callee.type === 'Identifier') {
		const binding = context.state.scope.get(node.callee.name);
 
		if (binding !== null) {
			binding.is_called = true;
		}
	}
 
	// `$inspect(foo)` or `$derived(foo) should not trigger the `static-state-reference` warning
	if (rune === '$inspect' || rune === '$derived') {
		context.next({ ...context.state, function_depth: context.state.function_depth + 1 });
	} else {
		context.next();
	}
}