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

94.11% Statements 176/187
91.42% Branches 64/70
100% Functions 1/1
93.98% Lines 172/183

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 1842x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 5656x 5656x 5656x 5656x 5656x 5656x 3769x 1240x 1240x 3769x 3769x 5656x 5656x 63x 1x 1x 62x 62x 62x 63x 63x 62x 62x 62x 62x 63x 2x 2x 60x 60x 5656x 5656x 2x   2x 2x 2x     5656x 5656x 288x 1x 1x 287x 287x 287x 287x 287x 288x 286x 288x 2x 2x 285x 288x 1x 1x 284x 284x 5656x 5656x 5656x 5656x 5656x 1271x 1271x 136x 1271x 5x 5x 1266x 1271x 2x 1271x 2x 2x 1262x 1262x 5656x 5656x 5656x 182x 2x 2x 180x 180x 2x 2x 178x 178x 178x 178x 178x 178x 5656x 5656x 10x     10x 10x 5656x 5656x 15x     15x 15x 5656x 5656x 39x     39x 39x 5656x 5656x 4x     4x 4x 5656x 5656x 13x 1x 1x 12x 12x 5656x 5633x 5656x 171x 171x 171x 171x 171x 171x 171x 2x 2x 171x 5633x 5656x 3501x 3501x 3501x 1699x 1699x 3501x 5633x 5633x 5656x 202x 5656x 5431x 5431x 5629x 5656x 1132x 1132x 1132x 1132x 1132x 1031x 1031x 1031x 1132x 5656x  
/** @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.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();
	}
 
	if (context.state.expression) {
		// TODO We assume that any dependencies are stateful, which isn't necessarily the case — see
		// https://github.com/sveltejs/svelte/issues/13266. This check also includes dependencies
		// outside the call expression itself (e.g. `{blah && pure()}`) resulting in additional
		// false positives, but for now we accept that trade-off
		if (!is_pure(node.callee, context) || context.state.expression.dependencies.size > 0) {
			context.state.expression.has_call = true;
			context.state.expression.has_state = true;
		}
	}
}