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

98.34% Statements 119/121
96.77% Branches 30/31
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 1089x 1089x 1089x 1089x 1089x 1089x 1031x 1089x 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 1031x 1089x 1089x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1139x 1139x 1139x 1139x 333x 333x 333x 1139x 1139x 1093x 1093x 46x 46x 46x 1113x     46x 1113x 38x 38x 38x 5x 5x 5x 5x 5x 5x 5x 38x 38x 38x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 2x 2x 2x 2x 1090x 1090x 1090x  
/** @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 { serialize_get_binding } 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 = serialize_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 serialize_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 serialize_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)),
				serialize_get_binding(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]);
}