function migrate(source, { filename, use_ts } = {}) {
	let og_source = source;
	try {
		has_migration_task = false;
		// Blank CSS, could contain SCSS or similar that needs a preprocessor.
		// Since we don't care about CSS in this migration, we'll just ignore it.
		/** @type {Array<[number, string]>} */
		const style_contents = [];
		source = source.replace(regex_style_tags, (_, start, content, end, idx) => {
			style_contents.push([idx + start.length, content]);
			return start + style_placeholder + end;
		});

		reset_warning_filter(() => false);
		reset(source, { filename: filename ?? '(unknown)' });

		let parsed = parse(source);

		const { customElement: customElementOptions, ...parsed_options } = parsed.options || {};

		/** @type {ValidatedCompileOptions} */
		const combined_options = {
			...validate_component_options({}, ''),
			...parsed_options,
			customElementOptions,
			filename: filename ?? '(unknown)'
		};

		const str = new MagicString(source);
		const analysis = analyze_component(parsed, source, combined_options);
		const indent = guess_indent(source);

		str.replaceAll(/(<svelte:options\s.*?\s?)accessors\s?/g, (_, $1) => $1);

		for (const content of style_contents) {
			str.overwrite(content[0], content[0] + style_placeholder.length, content[1]);
		}

		/** @type {State} */
		let state = {
			scope: analysis.instance.scope,
			analysis,
			filename,
			str,
			indent,
			props: [],
			props_insertion_point: parsed.instance?.content.start ?? 0,
			has_props_rune: false,
			has_type_or_fallback: false,
			end: source.length,
			names: {
				props: analysis.root.unique('props').name,
				rest: analysis.root.unique('rest').name,

				// event stuff
				run: analysis.root.unique('run').name,
				handlers: analysis.root.unique('handlers').name,
				stopImmediatePropagation: analysis.root.unique('stopImmediatePropagation').name,
				preventDefault: analysis.root.unique('preventDefault').name,
				stopPropagation: analysis.root.unique('stopPropagation').name,
				once: analysis.root.unique('once').name,
				self: analysis.root.unique('self').name,
				trusted: analysis.root.unique('trusted').name,
				createBubbler: analysis.root.unique('createBubbler').name,
				bubble: analysis.root.unique('bubble').name,
				passive: analysis.root.unique('passive').name,
				nonpassive: analysis.root.unique('nonpassive').name
			},
			legacy_imports: new Set(),
			script_insertions: new Set(),
			derived_components: new Map(),
			derived_conflicting_slots: new Map(),
			derived_labeled_statements: new Set(),
			has_svelte_self: false,
			uses_ts:
				// Some people could use jsdoc but have a tsconfig.json, so double-check file for jsdoc indicators
				(use_ts && !source.includes('@type {')) ||
				!!parsed.instance?.attributes.some(
					(attr) => attr.name === 'lang' && /** @type {any} */ (attr).value[0].data === 'ts'
				)
		};

		if (parsed.module) {
			const context = parsed.module.attributes.find((attr) => attr.name === 'context');
			if (context) {
				state.str.update(context.start, context.end, 'module');
			}
		}

		if (parsed.instance) {
			walk(parsed.instance.content, state, instance_script);
		}

		state = { ...state, scope: analysis.template.scope };
		walk(parsed.fragment, state, template);

		let insertion_point = parsed.instance
			? /** @type {number} */ (parsed.instance.content.start)
			: 0;

		const need_script =
			state.legacy_imports.size > 0 ||
			state.derived_components.size > 0 ||
			state.derived_conflicting_slots.size > 0 ||
			state.script_insertions.size > 0 ||
			state.props.length > 0 ||
			analysis.uses_rest_props ||
			analysis.uses_props ||
			state.has_svelte_self;

		const need_ts_tag =
			state.uses_ts &&
			(!parsed.instance || !parsed.instance.attributes.some((attr) => attr.name === 'lang'));

		if (!parsed.instance && need_script) {
			str.appendRight(0, need_ts_tag ? '<script lang="ts">' : '<script>');
		}

		if (state.has_svelte_self && filename) {
			const file = filename.split('/').pop();
			str.appendRight(
				insertion_point,
				`\n${indent}import ${state.analysis.name} from './${file}';`
			);
		}

		const specifiers = [...state.legacy_imports].map((imported) => {
			const local = state.names[imported];
			return imported === local ? imported : `${imported} as ${local}`;
		});

		const legacy_import = `import { ${specifiers.join(', ')} } from 'svelte/legacy';\n`;

		if (state.legacy_imports.size > 0) {
			str.appendRight(insertion_point, `\n${indent}${legacy_import}`);
		}

		if (state.script_insertions.size > 0) {
			str.appendRight(
				insertion_point,
				`\n${indent}${[...state.script_insertions].join(`\n${indent}`)}`
			);
		}

		insertion_point = state.props_insertion_point;

		/**
		 * @param {"derived"|"props"|"bindable"} rune
		 */
		function check_rune_binding(rune) {
			const has_rune_binding = !!state.scope.get(rune);
			if (has_rune_binding) {
				throw new MigrationError(
					`migrating this component would require adding a \`$${rune}\` rune but there's already a variable named ${rune}.\n     Rename the variable and try again or migrate by hand.`
				);
			}
		}

		if (state.props.length > 0 || analysis.uses_rest_props || analysis.uses_props) {
			const has_many_props = state.props.length > 3;
			const newline_separator = `\n${indent}${indent}`;
			const props_separator = has_many_props ? newline_separator : ' ';
			let props = '';
			if (analysis.uses_props) {
				props = `...${state.names.props}`;
			} else {
				props = state.props
					.filter((prop) => !prop.type_only)
					.map((prop) => {
						let prop_str =
							prop.local === prop.exported ? prop.local : `${prop.exported}: ${prop.local}`;
						if (prop.bindable) {
							check_rune_binding('bindable');
							prop_str += ` = $bindable(${prop.init})`;
						} else if (prop.init) {
							prop_str += ` = ${prop.init}`;
						}
						return prop_str;
					})
					.join(`,${props_separator}`);

				if (analysis.uses_rest_props) {
					props += `${state.props.length > 0 ? `,${props_separator}` : ''}...${state.names.rest}`;
				}
			}

			if (state.has_props_rune) {
				// some render tags or forwarded event attributes to add
				str.appendRight(insertion_point, ` ${props},`);
			} else {
				const type_name = state.scope.root.unique('Props').name;
				let type = '';

				// Try to infer when we don't want to add types (e.g. user doesn't use types, or this is a zero-types +page.svelte)
				if (state.has_type_or_fallback || state.props.every((prop) => prop.slot_name)) {
					if (state.uses_ts) {
						type = `interface ${type_name} {${newline_separator}${state.props
							.map((prop) => {
								const comment = prop.comment ? `${prop.comment}${newline_separator}` : '';
								return `${comment}${prop.exported}${prop.optional ? '?' : ''}: ${prop.type};${prop.trailing_comment ? ' ' + prop.trailing_comment : ''}`;
							})
							.join(newline_separator)}`;
						if (analysis.uses_props || analysis.uses_rest_props) {
							type += `${state.props.length > 0 ? newline_separator : ''}[key: string]: any`;
						}
						type += `\n${indent}}`;
					} else {
						type = `/**\n${indent} * @typedef {Object} ${type_name}${state.props
							.map((prop) => {
								return `\n${indent} * @property {${prop.type}} ${prop.optional ? `[${prop.exported}]` : prop.exported}${prop.comment ? ` - ${prop.comment}` : ''}${prop.trailing_comment ? ` - ${prop.trailing_comment.trim()}` : ''}`;
							})
							.join(``)}\n${indent} */`;
					}
				}

				let props_declaration = `let {${props_separator}${props}${has_many_props ? `\n${indent}` : ' '}}`;
				if (state.uses_ts) {
					if (type) {
						props_declaration = `${type}\n\n${indent}${props_declaration}`;
					}
					check_rune_binding('props');
					props_declaration = `${props_declaration}${type ? `: ${type_name}` : ''} = $props();`;
				} else {
					if (type) {
						props_declaration = `${state.props.length > 0 ? `${type}\n\n${indent}` : ''}/** @type {${state.props.length > 0 ? type_name : ''}${analysis.uses_props || analysis.uses_rest_props ? `${state.props.length > 0 ? ' & ' : ''}{ [key: string]: any }` : ''}} */\n${indent}${props_declaration}`;
					}
					check_rune_binding('props');
					props_declaration = `${props_declaration} = $props();`;
				}

				props_declaration = `\n${indent}${props_declaration}`;
				str.appendRight(insertion_point, props_declaration);
			}

			if (parsed.instance && need_ts_tag) {
				str.appendRight(parsed.instance.start + '<script'.length, ' lang="ts"');
			}
		}

		/**
		 * If true, then we need to move all reactive statements to the end of the script block,
		 * in their correct order. Svelte 4 reordered reactive statements, $derived/$effect.pre
		 * don't have this behavior.
		 */
		let needs_reordering = false;

		for (const [node, { dependencies }] of state.analysis.reactive_statements) {
			/** @type {Binding[]} */
			let ids = [];
			if (
				node.body.type === 'ExpressionStatement' &&
				node.body.expression.type === 'AssignmentExpression'
			) {
				ids = extract_identifiers(node.body.expression.left)
					.map((id) => state.scope.get(id.name))
					.filter((id) => !!id);
			}

			if (
				dependencies.some(
					(dep) =>
						!ids.includes(dep) &&
						(dep.kind === 'prop' || dep.kind === 'bindable_prop'
							? state.props_insertion_point
							: /** @type {number} */ (dep.node.start)) > /** @type {number} */ (node.start)
				)
			) {
				needs_reordering = true;
				break;
			}
		}

		if (needs_reordering) {
			const nodes = Array.from(state.analysis.reactive_statements.keys());
			for (const node of nodes) {
				const { start, end } = get_node_range(source, node);
				str.appendLeft(end, '\n');
				str.move(start, end, /** @type {number} */ (parsed.instance?.content.end));
				str.update(start - (source[start - 2] === '\r' ? 2 : 1), start, '');
			}
		}

		insertion_point = parsed.instance
			? /** @type {number} */ (parsed.instance.content.end)
			: insertion_point;

		if (state.derived_components.size > 0) {
			check_rune_binding('derived');
			str.appendRight(
				insertion_point,
				`\n${indent}${[...state.derived_components.entries()].map(([init, name]) => `const ${name} = $derived(${init});`).join(`\n${indent}`)}\n`
			);
		}

		if (state.derived_conflicting_slots.size > 0) {
			check_rune_binding('derived');
			str.appendRight(
				insertion_point,
				`\n${indent}${[...state.derived_conflicting_slots.entries()].map(([name, init]) => `const ${name} = $derived(${init});`).join(`\n${indent}`)}\n`
			);
		}

		if (state.props.length > 0 && state.analysis.accessors) {
			str.appendRight(
				insertion_point,
				`\n${indent}export {${state.props.reduce((acc, prop) => (prop.slot_name || prop.type_only ? acc : `${acc}\n${indent}\t${prop.local},`), '')}\n${indent}}\n`
			);
		}

		if (!parsed.instance && need_script) {
			str.appendRight(insertion_point, '\n</script>\n\n');
		}
		migrate_css(state);
		return {
			code: str.toString()
		};
	} catch (e) {
		if (!(e instanceof MigrationError)) {
			// eslint-disable-next-line no-console
			console.error('Error while migrating Svelte code', e);
		}
		has_migration_task = true;
		return {
			code: `<!-- @migration-task Error while migrating Svelte code: ${/** @type {any} */ (e).message} -->\n${og_source}`
		};
	} finally {
		if (has_migration_task) {
			// eslint-disable-next-line no-console
			console.log(
				`One or more \`@migration-task\` comments were added to ${filename ? `\`${filename}\`` : "a file (unfortunately we don't know the name)"}, please check them and complete the migration manually.`
			);
		}
	}
}
Does a best-effort migration of Svelte code towards using runes, event attributes and render tags.
May throw an error if the code is too complex to migrate automatically.

@param {string} source
@param {{ filename?: string, use_ts?: boolean }} [options]
@returns {{ code: string; }}