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 1808x 1808x 1808x 1808x 1808x 1808x 1808x 1808x 2x 2x 2x 2x 2x 2x 2x 2x 1857x 1857x 1857x 1857x 1857x 234x 1857x 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 1846x 1846x 1846x 1857x 600x 600x 600x 1846x 1857x 47x 47x 1799x 1799x 1857x 1794x 1794x 1857x 1857x 1857x 1857x 1857x 1192x 1192x 1192x 1192x 1192x 1192x 1192x 1192x 1192x 1192x 1192x 1192x 1192x 331x 1192x 149x 149x 1192x 1192x 1192x 602x 602x 1857x 247x 247x 247x 247x 247x 247x 247x 247x 247x 355x 355x 355x  
/** @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;
}