function validate_assignment(node, argument, context) {
	validate_no_const_assignment(node, argument, context.state.scope, node.type === 'BindDirective');

	if (argument.type === 'Identifier') {
		const binding = context.state.scope.get(argument.name);

		if (context.state.analysis.runes) {
			if (binding?.node === context.state.analysis.props_id) {
				e.constant_assignment(node, '$props.id()');
			}

			if (binding?.kind === 'each') {
				e.each_item_invalid_assignment(node);
			}
		}

		if (binding?.kind === 'snippet') {
			e.snippet_parameter_assignment(node);
		}
	}

	if (argument.type === 'MemberExpression' && argument.object.type === 'ThisExpression') {
		const name =
			argument.computed && argument.property.type !== 'Literal'
				? null
				: get_name(argument.property);

		const field = name !== null && context.state.state_fields?.get(name);

		// check we're not assigning to a state field before its declaration in the constructor
		if (field && field.node.type === 'AssignmentExpression' && node !== field.node) {
			let i = context.path.length;
			while (i--) {
				const parent = context.path[i];

				if (
					parent.type === 'FunctionDeclaration' ||
					parent.type === 'FunctionExpression' ||
					parent.type === 'ArrowFunctionExpression'
				) {
					const grandparent = get_parent(context.path, i - 1);

					if (
						grandparent.type === 'MethodDefinition' &&
						grandparent.kind === 'constructor' &&
						/** @type {number} */ (node.start) < /** @type {number} */ (field.node.start)
					) {
						e.state_field_invalid_assignment(node);
					}

					break;
				}
			}
		}
	}
}
@param {AssignmentExpression | UpdateExpression | AST.BindDirective} node
@param {Pattern | Expression} argument
@param {Context} context