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

100% Statements 122/122
100% Branches 43/43
100% Functions 2/2
100% Lines 117/117

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 1182x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1776x 1776x 1776x 1776x 1776x 1776x 1776x 1776x 2x 2x 2x 2x 2x 2x 2x 2x 1825x 1825x 1825x 1825x 1825x 227x 1825x 38x 14x 14x 14x 12x 12x 12x 12x 12x 12x 8x 8x 8x 8x 8x 8x 12x 12x 7x 12x 4x 4x 12x 14x 38x 1814x 1814x 1814x 1825x 591x 591x 591x 1814x 1825x 47x 47x 1767x 1767x 1825x 1762x 1762x 1825x 1825x 1825x 1825x 1825x 1174x 1174x 1174x 1174x 1174x 1174x 1174x 1174x 1174x 1174x 1174x 1174x 1174x 321x 1174x 145x 145x 1174x 1174x 1174x 588x 588x 1825x 245x 245x 245x 245x 245x 245x 245x 245x 245x 343x 343x 343x  
/** @import { AssignmentExpression, AssignmentOperator, Expression, Pattern } from 'estree' */
/** @import { Context } from '../types.js' */
import * as b from '../../../../utils/builders.js';
import { build_assignment_value } from '../../../../utils/ast.js';
import { is_ignored } from '../../../../state.js';
import { build_proxy_reassignment, should_proxy } from '../utils.js';
import { visit_assignment_expression } from '../../shared/assignments.js';
 
/**
 * @param {AssignmentExpression} node
 * @param {Context} context
 */
export function AssignmentExpression(node, context) {
	const expression = /** @type {Expression} */ (
		visit_assignment_expression(node, context, build_assignment) ?? context.next()
	);
 
	return is_ignored(node, 'ownership_invalid_mutation')
		? b.call('$.skip_ownership_validation', b.thunk(expression))
		: expression;
}
 
/**
 * @param {AssignmentOperator} operator
 * @param {Pattern} left
 * @param {Expression} right
 * @param {Context} context
 * @returns {Expression | null}
 */
function build_assignment(operator, left, right, context) {
	// Handle class private/public state assignment cases
	if (
		context.state.analysis.runes &&
		left.type === 'MemberExpression' &&
		left.object.type === 'ThisExpression'
	) {
		if (left.property.type === 'PrivateIdentifier') {
			const private_state = context.state.private_state.get(left.property.name);
 
			if (private_state !== undefined) {
				let transformed = false;
				let value = /** @type {Expression} */ (
					context.visit(build_assignment_value(operator, left, right))
				);
 
				if (should_proxy(value, context.state.scope)) {
					transformed = true;
					value =
						private_state.kind === 'raw_state'
							? value
							: build_proxy_reassignment(value, b.member(b.this, private_state.id));
				}
 
				if (!context.state.in_constructor) {
					return b.call('$.set', left, value);
				} else if (transformed) {
					return b.assignment(operator, /** @type {Pattern} */ (context.visit(left)), value);
				}
			}
		}
	}
 
	let object = left;
 
	while (object.type === 'MemberExpression') {
		// @ts-expect-error
		object = object.object;
	}
 
	if (object.type !== 'Identifier') {
		return null;
	}
 
	const binding = context.state.scope.get(object.name);
	if (!binding) return null;
 
	const transform = Object.hasOwn(context.state.transform, object.name)
		? context.state.transform[object.name]
		: null;
 
	// reassignment
	if (object === left && transform?.assign) {
		let value = /** @type {Expression} */ (
			context.visit(build_assignment_value(operator, left, right))
		);
 
		// special case — if an element binding, we know it's a primitive
		const path = context.path.map((node) => node.type);
		const is_primitive = path.at(-1) === 'BindDirective' && path.at(-2) === 'RegularElement';
 
		if (
			!is_primitive &&
			binding.kind !== 'prop' &&
			binding.kind !== 'bindable_prop' &&
			context.state.analysis.runes &&
			should_proxy(value, context.state.scope)
		) {
			value = binding.kind === 'raw_state' ? value : build_proxy_reassignment(value, object);
		}
 
		return transform.assign(object, value);
	}
 
	// mutation
	if (transform?.mutate) {
		return transform.mutate(
			object,
			b.assignment(
				operator,
				/** @type {Pattern} */ (context.visit(left)),
				/** @type {Expression} */ (context.visit(right))
			)
		);
	}
 
	return null;
}