All files / src/compiler/phases/3-transform/shared assignments.js

97.46% Statements 77/79
96% Branches 24/25
100% Functions 1/1
97.4% Lines 75/77

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 782x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2833x 2833x 2833x 2721x 2833x 112x 112x 112x 112x 112x 112x 112x 211x 211x 211x 211x 211x 211x 211x 89x 89x 89x 89x 89x 211x 112x 112x 112x 48x 48x 48x 64x 64x 64x 64x 112x 13x 13x 13x 64x 112x 53x 53x 53x 53x 53x 53x 53x 53x 53x 11x 11x 11x 2721x 2833x     2721x 2721x 2833x 1046x 2833x 2833x  
/** @import { AssignmentExpression, AssignmentOperator, Expression, Node, Pattern } from 'estree' */
/** @import { Context as ClientContext } from '../client/types.js' */
/** @import { Context as ServerContext } from '../server/types.js' */
import { extract_paths, is_expression_async } from '../../../utils/ast.js';
import * as b from '../../../utils/builders.js';
 
/**
 * @template {ClientContext | ServerContext} Context
 * @param {AssignmentExpression} node
 * @param {Context} context
 * @param {(operator: AssignmentOperator, left: Pattern, right: Expression, context: Context) => Expression | null} build_assignment
 * @returns
 */
export function visit_assignment_expression(node, context, build_assignment) {
	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(
					'=',
					/** @type {Pattern} */ (context.visit(path.node)),
					/** @type {Expression} */ (context.visit(value))
				)
			);
		});
 
		if (!changed) {
			// No change to output -> nothing to transform -> we can keep the original assignment
			return context.next();
		}
 
		const is_standalone = /** @type {Node} */ (context.path.at(-1)).type.endsWith('Statement');
		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
			const iife = b.arrow([rhs], sequence);
 
			const iife_is_async =
				is_expression_async(value) ||
				assignments.some((assignment) => is_expression_async(assignment));
 
			return iife_is_async ? b.await(b.call(b.async(iife), value)) : b.call(iife, value);
		}
 
		return sequence;
	}
 
	if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') {
		throw new Error(`Unexpected assignment type ${node.left.type}`);
	}
 
	return (
		build_assignment(node.operator, node.left, node.right, context) ??
		/** @type {Expression} */ (context.next())
	);
}