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

97.36% Statements 74/76
90.47% Branches 19/21
100% Functions 1/1
97.29% Lines 72/74

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 752x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1986x 1986x 1986x 1924x 1986x 62x 62x 62x 62x 62x 62x 62x 136x 136x 136x 136x 136x 136x 136x 63x 63x 63x 63x 63x 136x 62x 62x 62x 24x 24x 24x 38x 38x 38x 38x 62x 8x 8x 8x 38x 62x 29x 29x 29x 29x 29x 29x 29x 29x 29x 9x 9x 9x 1924x 1986x     1924x 1924x 1924x  
/** @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 null;
		}
 
		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);
}