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 5724x 5724x 5724x 5724x 5724x 5724x 3799x 1242x 1242x 3799x 3799x 5724x 5724x 67x 1x 1x 66x 66x 66x 67x 67x 66x 66x 66x 66x 67x 2x 2x 64x 64x 5724x 5724x 2x   2x 2x 2x     5724x 5724x 294x 1x 1x 293x 293x 293x 293x 293x 294x 292x 294x 2x 2x 291x 294x 1x 1x 290x 290x 5724x 5724x 5724x 5724x 5724x 1293x 1293x 136x 1293x 5x 5x 1288x 1293x 2x 1293x 2x 2x 1284x 1284x 5724x 5724x 5724x 184x 2x 2x 182x 182x 2x 2x 180x 180x 180x 180x 180x 180x 5724x 5724x 10x     10x 10x 5724x 5724x 15x     15x 15x 5724x 5724x 41x     41x 41x 5724x 5724x 6x     6x 6x 5724x 5724x 13x 1x 1x 12x 12x 5724x 5701x 5724x 171x 171x 171x 171x 171x 171x 171x 2x 2x 171x 5701x 5724x 3541x 3541x 3541x 1703x 1703x 3541x 5701x 5701x 5724x 208x 5724x 5493x 5493x 5697x 5724x 1144x 1144x 1144x 1144x 1144x 1043x 1043x 1043x 1144x 5724x  
/** @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;
		}
	}
}