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

97.36% Statements 74/76
95.65% Branches 22/23
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 2842x 2842x 2842x 2728x 2842x 114x 114x 114x 114x 114x 114x 114x 215x 215x 215x 215x 215x 215x 215x 111x 111x 111x 111x 111x 215x 114x 114x 114x 53x 53x 53x 61x 61x 61x 61x 114x 13x 13x 13x 61x 114x 51x 51x 51x 51x 51x 51x 51x 51x 51x 10x 10x 10x 2728x 2842x     2728x 2728x 2728x  
/** @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);
}