function build_element_attributes(node, context) {
	/** @type {Array<AST.Attribute | AST.SpreadAttribute>} */
	const attributes = [];

	/** @type {AST.ClassDirective[]} */
	const class_directives = [];

	/** @type {AST.StyleDirective[]} */
	const style_directives = [];

	/** @type {Expression | null} */
	let content = null;

	let has_spread = false;
	let events_to_capture = new Set();

	for (const attribute of node.attributes) {
		if (attribute.type === 'Attribute') {
			if (attribute.name === 'value') {
				if (node.name === 'textarea') {
					if (
						attribute.value !== true &&
						Array.isArray(attribute.value) &&
						attribute.value[0].type === 'Text' &&
						regex_starts_with_newline.test(attribute.value[0].data)
					) {
						// Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`.
						// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
						// also see related code in analysis phase
						attribute.value[0].data = '\n' + attribute.value[0].data;
					}
					content = b.call('$.escape', build_attribute_value(attribute.value, context));
				} else if (node.name !== 'select') {
					// omit value attribute for select elements, it's irrelevant for the initially selected value and has no
					// effect on the selected value after the user interacts with the select element (the value _property_ does, but not the attribute)
					attributes.push(attribute);
				}

				// omit event handlers except for special cases
			} else if (is_event_attribute(attribute)) {
				if (
					(attribute.name === 'onload' || attribute.name === 'onerror') &&
					is_load_error_element(node.name)
				) {
					events_to_capture.add(attribute.name);
				}
				// the defaultValue/defaultChecked properties don't exist as attributes
			} else if (attribute.name !== 'defaultValue' && attribute.name !== 'defaultChecked') {
				if (attribute.name === 'class') {
					if (attribute.metadata.needs_clsx) {
						attributes.push({
							...attribute,
							value: {
								.../** @type {AST.ExpressionTag} */ (attribute.value),
								expression: b.call(
									'$.clsx',
									/** @type {AST.ExpressionTag} */ (attribute.value).expression
								)
							}
						});
					} else {
						attributes.push(attribute);
					}
				} else {
					attributes.push(attribute);
				}
			}
		} else if (attribute.type === 'BindDirective') {
			if (attribute.name === 'value' && node.name === 'select') continue;
			if (
				attribute.name === 'value' &&
				attributes.some(
					(attr) =>
						attr.type === 'Attribute' &&
						attr.name === 'type' &&
						is_text_attribute(attr) &&
						attr.value[0].data === 'file'
				)
			) {
				continue;
			}
			if (attribute.name === 'this') continue;

			const binding = binding_properties[attribute.name];
			if (binding?.omit_in_ssr) continue;

			let expression = /** @type {Expression} */ (context.visit(attribute.expression));

			if (expression.type === 'SequenceExpression') {
				expression = b.call(expression.expressions[0]);
			}

			if (is_content_editable_binding(attribute.name)) {
				content = expression;
			} else if (attribute.name === 'value' && node.name === 'textarea') {
				content = b.call('$.escape', expression);
			} else if (attribute.name === 'group' && attribute.expression.type !== 'SequenceExpression') {
				const value_attribute = /** @type {AST.Attribute | undefined} */ (
					node.attributes.find((attr) => attr.type === 'Attribute' && attr.name === 'value')
				);
				if (!value_attribute) continue;

				const is_checkbox = node.attributes.some(
					(attr) =>
						attr.type === 'Attribute' &&
						attr.name === 'type' &&
						is_text_attribute(attr) &&
						attr.value[0].data === 'checkbox'
				);

				attributes.push(
					create_attribute('checked', -1, -1, [
						{
							type: 'ExpressionTag',
							start: -1,
							end: -1,
							expression: is_checkbox
								? b.call(
										b.member(attribute.expression, 'includes'),
										build_attribute_value(value_attribute.value, context)
									)
								: b.binary(
										'===',
										attribute.expression,
										build_attribute_value(value_attribute.value, context)
									),
							metadata: {
								expression: create_expression_metadata()
							}
						}
					])
				);
			} else {
				attributes.push(
					create_attribute(attribute.name, -1, -1, [
						{
							type: 'ExpressionTag',
							start: -1,
							end: -1,
							expression,
							metadata: {
								expression: create_expression_metadata()
							}
						}
					])
				);
			}
		} else if (attribute.type === 'SpreadAttribute') {
			attributes.push(attribute);
			has_spread = true;
			if (is_load_error_element(node.name)) {
				events_to_capture.add('onload');
				events_to_capture.add('onerror');
			}
		} else if (attribute.type === 'UseDirective') {
			if (is_load_error_element(node.name)) {
				events_to_capture.add('onload');
				events_to_capture.add('onerror');
			}
		} else if (attribute.type === 'ClassDirective') {
			class_directives.push(attribute);
		} else if (attribute.type === 'StyleDirective') {
			style_directives.push(attribute);
		} else if (attribute.type === 'LetDirective') {
			// do nothing, these are handled inside `build_inline_component`
		} else {
			context.visit(attribute);
		}
	}

	if (has_spread) {
		build_element_spread_attributes(node, attributes, style_directives, class_directives, context);
		if (node.name === 'option') {
			context.state.template.push(
				b.call(
					'$.maybe_selected',
					b.id('$$payload'),
					b.member(
						build_spread_object(
							node,
							node.attributes.filter(
								(attribute) =>
									attribute.type === 'Attribute' ||
									attribute.type === 'BindDirective' ||
									attribute.type === 'SpreadAttribute'
							),
							context
						),
						'value',
						false,
						true
					)
				)
			);
		}
	} else {
		const css_hash = node.metadata.scoped ? context.state.analysis.css.hash : null;

		for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
			const name = get_attribute_name(node, attribute);
			const can_use_literal =
				(name !== 'class' || class_directives.length === 0) &&
				(name !== 'style' || style_directives.length === 0);

			if (can_use_literal && (attribute.value === true || is_text_attribute(attribute))) {
				let literal_value = /** @type {Literal} */ (
					build_attribute_value(
						attribute.value,
						context,
						WHITESPACE_INSENSITIVE_ATTRIBUTES.includes(name)
					)
				).value;

				if (name === 'class' && css_hash) {
					literal_value = (String(literal_value) + ' ' + css_hash).trim();
				}

				if (name !== 'class' || literal_value) {
					context.state.template.push(
						b.literal(
							` ${attribute.name}${
								is_boolean_attribute(name) && literal_value === true
									? ''
									: `="${literal_value === true ? '' : String(literal_value)}"`
							}`
						)
					);
				}

				if (node.name === 'option' && name === 'value') {
					context.state.template.push(
						b.call(
							'$.maybe_selected',
							b.id('$$payload'),
							literal_value != null ? b.literal(/** @type {any} */ (literal_value)) : b.void0
						)
					);
				}

				continue;
			}

			const value = build_attribute_value(
				attribute.value,
				context,
				WHITESPACE_INSENSITIVE_ATTRIBUTES.includes(name)
			);

			// pre-escape and inline literal attributes :
			if (can_use_literal && value.type === 'Literal' && typeof value.value === 'string') {
				if (name === 'class' && css_hash) {
					value.value = (value.value + ' ' + css_hash).trim();
				}
				context.state.template.push(b.literal(` ${name}="${escape_html(value.value, true)}"`));
			} else if (name === 'class') {
				context.state.template.push(build_attr_class(class_directives, value, context, css_hash));
			} else if (name === 'style') {
				context.state.template.push(build_attr_style(style_directives, value, context));
			} else {
				context.state.template.push(
					b.call('$.attr', b.literal(name), value, is_boolean_attribute(name) && b.true)
				);
			}

			if (name === 'value' && node.name === 'option') {
				context.state.template.push(b.call('$.maybe_selected', b.id('$$payload'), value));
			}
		}
	}

	if (events_to_capture.size !== 0) {
		for (const event of events_to_capture) {
			context.state.template.push(b.literal(` ${event}="this.__e=event"`));
		}
	}

	return content;
}
Writes the output to the template output. Some elements may have attributes on them that require the
their output to be the child content instead. In this case, an object is returned.
@param {AST.RegularElement | AST.SvelteElement} node
@param {import('zimmerframe').Context<AST.SvelteNode, ComponentServerTransformState>} context