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

97.46% Statements 77/79
95.83% Branches 23/24
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 2830x 2830x 2830x 2720x 2830x 110x 110x 110x 110x 110x 110x 110x 207x 207x 207x 207x 207x 207x 207x 87x 87x 87x 87x 87x 207x 110x 110x 110x 47x 47x 47x 63x 63x 63x 63x 110x 13x 13x 13x 63x 110x 53x 53x 53x 53x 53x 53x 53x 53x 53x 10x 10x 10x 2720x 2830x     2720x 2720x 2830x 1046x 2830x 2830x  
/** @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())
	);
}