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

93.92% Statements 170/181
91.3% Branches 63/69
100% Functions 1/1
93.78% Lines 166/177

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 1782x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 5398x 5398x 5398x 5398x 5398x 5398x 3580x 1187x 1187x 3580x 3580x 5398x 5398x 63x 1x 1x 62x 62x 62x 63x 63x 62x 62x 62x 62x 63x 2x 2x 60x 60x 5398x 5398x 2x   2x 2x 2x     5398x 5398x 275x 1x 1x 274x 274x 274x 274x 274x 275x 273x 275x 2x 2x 272x 275x 1x 1x 271x 271x 5398x 5398x 5398x 5398x 5398x 1228x 1228x 136x 1228x 5x 5x 1223x 1228x 2x 1228x 2x 2x 1219x 1219x 5398x 5398x 5398x 173x 2x 2x 171x 171x 2x 2x 169x 169x 169x 169x 169x 169x 5398x 5398x 10x     10x 10x 5398x 5398x 15x     15x 15x 5398x 5398x 37x     37x 37x 5398x 5398x 4x     4x 4x 5398x 5398x 11x 1x 1x 10x 10x 5398x 5375x 5398x 770x 770x 770x 5375x 5398x 165x 165x 165x 165x 165x 165x 165x 2x 2x 165x 5375x 5398x 3342x 3342x 3342x 1605x 1605x 3342x 5375x 5375x 5398x 198x 5398x 5177x 5177x 5398x  
/** @import { CallExpression, VariableDeclarator } from 'estree' */
/** @import { AST, 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;
	}
 
	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();
	}
}