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

100% Statements 136/136
100% Branches 47/47
100% Functions 2/2
100% Lines 131/131

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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 1322x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1776x 1776x 1776x 1776x 1776x 1776x 1776x 1776x 1776x 1776x 1776x 69x 69x 69x 69x 69x 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 { Location } from 'locate-character' */
/** @import { Context } from '../types.js' */
import * as b from '../../../../utils/builders.js';
import { build_assignment_value } from '../../../../utils/ast.js';
import { dev, filename, is_ignored, locator } 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) {
	let expression = /** @type {Expression} */ (
		visit_assignment_expression(node, context, build_assignment) ?? context.next()
	);
 
	const loc =
		dev &&
		node.left.type === 'MemberExpression' &&
		node.left.start !== undefined &&
		locator(node.left.start);
 
	if (loc) {
		expression = b.sequence([
			b.call('$.track_assignment', b.literal(`${filename}:${loc.line}:${loc.column}`)),
			expression
		]);
	}
 
	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;
}