Repository: rollup/rollup
File: src/utils/PluginDriver.ts (TypeScript)

Code before:
		replaceContext?: ReplaceContext
	): Promise<void> {
		let promise: Promise<unknown> = Promise.resolve();
		for (const plugin of this.getSortedPlugins(hookName)) {
			promise = promise.then(() => this.runHook(hookName, parameters, plugin, replaceContext));
		}
		return promise.then(noReturn);
	}

	private getSortedPlugins(
		hookName: keyof FunctionPluginHooks | AddonHooks,
		validateHandler?: (handler: unknown, hookName: string, plugin: Plugin) => void
	): Plugin[] {
		return getOrCreate(this.sortedPlugins, hookName, () =>
			getSortedValidatedPlugins(hookName, this.plugins, validateHandler)
		);
	}

	private getCompiledTransformFilter(filter: HookFilter): TransformHookFilter | undefined {
		if (!this.compiledPluginFilters.transformFilter.has(filter)) {
			this.compiledPluginFilters.transformFilter.set(
				filter,
				createFilterForTransform(filter.id, filter.code)
			);
		}
		return this.compiledPluginFilters.transformFilter.get(filter);
	}

	private getCompiledIdOnlyFilter(filter: HookFilter): PluginFilter | undefined {
		if (!this.compiledPluginFilters.idOnlyFilter.has(filter)) {
			this.compiledPluginFilters.idOnlyFilter.set(filter, createFilterForId(filter.id));
		}
		return this.compiledPluginFilters.idOnlyFilter.get(filter);
	}

	/**
	 * Run an async plugin hook and return the result.
	 * @param hookName Name of the plugin hook. Must be either in `PluginHooks`
	 *   or `OutputPluginValueHooks`.
	 * @param args Arguments passed to the plugin hook.
	 * @param plugin The actual pluginObject to run.
	 * @param replaceContext When passed, the plugin context can be overridden.
	 */
	private runHook<H extends AddonHooks>(
		hookName: H,
		parameters: Parameters<AddonHookFunction>,
		plugin: Plugin
	): EnsurePromise<ReturnType<AddonHookFunction>>;
	private runHook<H extends AsyncPluginHooks>(
		hookName: H,
		parameters: Parameters<FunctionPluginHooks[H]>,

Review comment (refactor):
There is a common helper function `getOrCreate` that does this in a generic way. Then you can inline the two functions.

Code after:
		replaceContext?: ReplaceContext
	): Promise<void> {
		let promise: Promise<unknown> = Promise.resolve();
		for (const plugin of this.getSortedPlugins(hookName)) {
			promise = promise.then(() => this.runHook(hookName, parameters, plugin, replaceContext));
		}
		return promise.then(noReturn);
	}

	private getSortedPlugins(
		hookName: keyof FunctionPluginHooks | AddonHooks,
		validateHandler?: (handler: unknown, hookName: string, plugin: Plugin) => void
	): Plugin[] {
		return getOrCreate(this.sortedPlugins, hookName, () =>
			getSortedValidatedPlugins(hookName, this.plugins, validateHandler)
		);
	}

	/**
	 * Run an async plugin hook and return the result.
	 * @param hookName Name of the plugin hook. Must be either in `PluginHooks`
	 *   or `OutputPluginValueHooks`.
	 * @param args Arguments passed to the plugin hook.
	 * @param plugin The actual pluginObject to run.
	 * @param replaceContext When passed, the plugin context can be overridden.
	 */
	private runHook<H extends AddonHooks>(
		hookName: H,
		parameters: Parameters<AddonHookFunction>,
		plugin: Plugin
	): EnsurePromise<ReturnType<AddonHookFunction>>;
	private runHook<H extends AsyncPluginHooks>(
		hookName: H,
		parameters: Parameters<FunctionPluginHooks[H]>,
		plugin: Plugin,
		replaceContext?: ReplaceContext | null
	): Promise<ReturnType<FunctionPluginHooks[H]>>;
	// Implementation signature
	private runHook<H extends AsyncPluginHooks | AddonHooks>(
		hookName: H,
		parameters: unknown[],
		plugin: Plugin,
		replaceContext?: ReplaceContext | null
	): Promise<unknown> {
		// We always filter for plugins that support the hook before running it
		const hook = plugin[hookName];
		const handler = typeof hook === 'object' ? hook.handler : hook;

		if (typeof hook === 'object' && 'filter' in hook && hook.filter) {
			if (hookName === 'transform') {
				const filter = hook.filter as HookFilter;