| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 | /*	MIT License http://www.opensource.org/licenses/mit-license.php	Author Tobias Koppers @sokra*/"use strict";const path = require("path");/** * @template T * @typedef {Object} TreeNode * @property {string} filePath * @property {TreeNode} parent * @property {TreeNode[]} children * @property {number} entries * @property {boolean} active * @property {T[] | T | undefined} value *//** * @template T * @param {Map<string, T[] | T} plan * @param {number} limit * @returns {Map<string, Map<T, string>>} the new plan */module.exports = (plan, limit) => {	const treeMap = new Map();	// Convert to tree	for (const [filePath, value] of plan) {		treeMap.set(filePath, {			filePath,			parent: undefined,			children: undefined,			entries: 1,			active: true,			value		});	}	let currentCount = treeMap.size;	// Create parents and calculate sum of entries	for (const node of treeMap.values()) {		const parentPath = path.dirname(node.filePath);		if (parentPath !== node.filePath) {			let parent = treeMap.get(parentPath);			if (parent === undefined) {				parent = {					filePath: parentPath,					parent: undefined,					children: [node],					entries: node.entries,					active: false,					value: undefined				};				treeMap.set(parentPath, parent);				node.parent = parent;			} else {				node.parent = parent;				if (parent.children === undefined) {					parent.children = [node];				} else {					parent.children.push(node);				}				do {					parent.entries += node.entries;					parent = parent.parent;				} while (parent);			}		}	}	// Reduce until limit reached	while (currentCount > limit) {		// Select node that helps reaching the limit most effectively without overmerging		const overLimit = currentCount - limit;		let bestNode = undefined;		let bestCost = Infinity;		for (const node of treeMap.values()) {			if (node.entries <= 1 || !node.children || !node.parent) continue;			if (node.children.length === 0) continue;			if (node.children.length === 1 && !node.value) continue;			// Try to select the node with has just a bit more entries than we need to reduce			// When just a bit more is over 30% over the limit,			// also consider just a bit less entries then we need to reduce			const cost =				node.entries - 1 >= overLimit					? node.entries - 1 - overLimit					: overLimit - node.entries + 1 + limit * 0.3;			if (cost < bestCost) {				bestNode = node;				bestCost = cost;			}		}		if (!bestNode) break;		// Merge all children		const reduction = bestNode.entries - 1;		bestNode.active = true;		bestNode.entries = 1;		currentCount -= reduction;		let parent = bestNode.parent;		while (parent) {			parent.entries -= reduction;			parent = parent.parent;		}		const queue = new Set(bestNode.children);		for (const node of queue) {			node.active = false;			node.entries = 0;			if (node.children) {				for (const child of node.children) queue.add(child);			}		}	}	// Write down new plan	const newPlan = new Map();	for (const rootNode of treeMap.values()) {		if (!rootNode.active) continue;		const map = new Map();		const queue = new Set([rootNode]);		for (const node of queue) {			if (node.active && node !== rootNode) continue;			if (node.value) {				if (Array.isArray(node.value)) {					for (const item of node.value) {						map.set(item, node.filePath);					}				} else {					map.set(node.value, node.filePath);				}			}			if (node.children) {				for (const child of node.children) {					queue.add(child);				}			}		}		newPlan.set(rootNode.filePath, map);	}	return newPlan;};
 |