function analyze_module(ast, options) {
	const { scope, scopes } = create_scopes(ast, new ScopeRoot(), false, null);

	for (const [name, references] of scope.references) {
		if (name[0] !== '$' || RESERVED.includes(name)) continue;
		if (name === '$' || name[1] === '$') {
			e.global_reference_invalid(references[0].node, name);
		}

		const binding = scope.get(name.slice(1));

		if (binding !== null && !is_rune(name)) {
			e.store_invalid_subscription_module(references[0].node);
		}
	}

	/** @type {Analysis} */
	const analysis = {
		module: { ast, scope, scopes },
		name: options.filename,
		accessors: false,
		runes: true,
		immutable: true,
		tracing: false,
		classes: new Map()
	};

	walk(
		/** @type {Node} */ (ast),
		{
			scope,
			scopes,
			analysis: /** @type {ComponentAnalysis} */ (analysis),
			state_fields: new Map(),
			// TODO the following are not needed for modules, but we have to pass them in order to avoid type error,
			// and reducing the type would result in a lot of tedious type casts elsewhere - find a good solution one day
			ast_type: /** @type {any} */ (null),
			component_slots: new Set(),
			expression: null,
			function_depth: 0,
			has_props_rune: false,
			options: /** @type {ValidatedCompileOptions} */ (options),
			parent_element: null,
			reactive_statement: null
		},
		visitors
	);

	return analysis;
}
@param {Program} ast
@param {ValidatedModuleCompileOptions} options
@returns {Analysis}