All files / src/compiler/phases/3-transform/server/visitors AssignmentExpression.js

98.34% Statements 119/121
96.87% Branches 31/32
100% Functions 3/3
98.3% Lines 116/118

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 1192x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1095x 1095x 1095x 1095x 1095x 1095x 1037x 1095x 58x 58x 58x 58x 58x 58x 58x 108x 108x 108x 108x 108x 108x 58x 58x 58x 47x 47x 47x 11x 11x 11x 29x 3x 3x 3x 11x 29x 7x 7x 7x 4x 4x 4x 1037x 1095x 1095x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1145x 1145x 1145x 1145x 336x 336x 336x 1145x 1145x 1097x 1097x 48x 48x 48x 1119x     48x 1119x 38x 38x 38x 5x 5x 5x 5x 5x 5x 5x 38x 38x 38x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 2x 2x 2x 2x 1096x 1096x 1096x  
/** @import { AssignmentExpression, AssignmentOperator, BinaryOperator, Expression, Node, Pattern } from 'estree' */
/** @import { SvelteNode } from '#compiler' */
/** @import { Context, ServerTransformState } from '../types.js' */
import * as b from '../../../../utils/builders.js';
import { extract_paths } from '../../../../utils/ast.js';
import { build_getter } from './shared/utils.js';
 
/**
 * @param {AssignmentExpression} node
 * @param {Context} context
 */
export function AssignmentExpression(node, context) {
	const parent = /** @type {Node} */ (context.path.at(-1));
	const is_standalone = parent.type.endsWith('Statement');
 
	if (
		node.left.type === 'ArrayPattern' ||
		node.left.type === 'ObjectPattern' ||
		node.left.type === 'RestElement'
	) {
		const value = /** @type {Expression} */ (context.visit(node.right));
		const should_cache = value.type !== 'Identifier';
		const rhs = should_cache ? b.id('$$value') : value;
 
		let changed = false;
 
		const assignments = extract_paths(node.left).map((path) => {
			const value = path.expression?.(rhs);
 
			let assignment = build_assignment('=', path.node, value, context);
			if (assignment !== null) changed = true;
 
			return assignment ?? b.assignment('=', path.node, value);
		});
 
		if (!changed) {
			// No change to output -> nothing to transform -> we can keep the original assignment
			return context.next();
		}
 
		const sequence = b.sequence(assignments);
 
		if (!is_standalone) {
			// this is part of an expression, we need the sequence to end with the value
			sequence.expressions.push(rhs);
		}
 
		if (should_cache) {
			// the right hand side is a complex expression, wrap in an IIFE to cache it
			return b.call(b.arrow([rhs], sequence), value);
		}
 
		return sequence;
	}
 
	return build_assignment(node.operator, node.left, node.right, context) || context.next();
}
 
/**
 * Only returns an expression if this is not a `$store` assignment, as others can be kept as-is
 * @param {AssignmentOperator} operator
 * @param {Pattern} left
 * @param {Expression} right
 * @param {import('zimmerframe').Context<SvelteNode, ServerTransformState>} context
 * @returns {Expression | null}
 */
function build_assignment(operator, left, right, context) {
	let object = left;
 
	while (object.type === 'MemberExpression') {
		// @ts-expect-error
		object = object.object;
	}
 
	if (object.type !== 'Identifier' || !is_store_name(object.name)) {
		return null;
	}
 
	const name = object.name.slice(1);
 
	if (!context.state.scope.get(name)) {
		return null;
	}
 
	if (object === left) {
		let value = /** @type {Expression} */ (context.visit(right));
 
		if (operator !== '=') {
			// turn `x += 1` into `x = x + 1`
			value = b.binary(
				/** @type {BinaryOperator} */ (operator.slice(0, -1)),
				build_getter(left, context.state),
				value
			);
		}
 
		return b.call('$.store_set', b.id(name), value);
	}
 
	return b.call(
		'$.store_mutate',
		b.assignment('??=', b.id('$$store_subs'), b.object([])),
		b.literal(object.name),
		b.id(name),
		b.assignment(
			operator,
			/** @type {Pattern} */ (context.visit(left)),
			/** @type {Expression} */ (context.visit(right))
		)
	);
}
 
/**
 * @param {string} name
 */
function is_store_name(name) {
	return name[0] === '$' && /[A-Za-z_]/.test(name[1]);
}