| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | /* | 
					
						
							|  |  |  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
					
						
							|  |  |  | 	Author Tobias Koppers @sokra | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const NO_MARKER = 0; | 
					
						
							|  |  |  | const IN_PROGRESS_MARKER = 1; | 
					
						
							|  |  |  | const DONE_MARKER = 2; | 
					
						
							|  |  |  | const DONE_MAYBE_ROOT_CYCLE_MARKER = 3; | 
					
						
							|  |  |  | const DONE_AND_ROOT_MARKER = 4; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-18 05:35:05 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @template T | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | class Node { | 
					
						
							| 
									
										
										
										
											2019-07-18 05:35:05 +08:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {T} item the value of the node | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | 	constructor(item) { | 
					
						
							|  |  |  | 		this.item = item; | 
					
						
							| 
									
										
										
										
											2019-07-18 05:35:05 +08:00
										 |  |  | 		/** @type {Set<Node<T>>} */ | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | 		this.dependencies = new Set(); | 
					
						
							|  |  |  | 		this.marker = NO_MARKER; | 
					
						
							| 
									
										
										
										
											2019-07-18 05:35:05 +08:00
										 |  |  | 		/** @type {Cycle<T> | undefined} */ | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | 		this.cycle = undefined; | 
					
						
							|  |  |  | 		this.incoming = 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-18 05:35:05 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @template T | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | class Cycle { | 
					
						
							|  |  |  | 	constructor() { | 
					
						
							| 
									
										
										
										
											2019-07-18 05:35:05 +08:00
										 |  |  | 		/** @type {Set<Node<T>>} */ | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | 		this.nodes = new Set(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2019-07-18 05:35:05 +08:00
										 |  |  |  * @template T | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  |  * @typedef {Object} StackEntry | 
					
						
							| 
									
										
										
										
											2019-07-18 05:35:05 +08:00
										 |  |  |  * @property {Node<T>} node | 
					
						
							|  |  |  |  * @property {Node<T>[]} openEdges | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @template T | 
					
						
							|  |  |  |  * @param {Iterable<T>} items list of items | 
					
						
							|  |  |  |  * @param {function(T): Iterable<T>} getDependencies function to get dependencies of an item (items that are not in list are ignored) | 
					
						
							|  |  |  |  * @returns {Iterable<T>} graph roots of the items | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | module.exports = (items, getDependencies) => { | 
					
						
							| 
									
										
										
										
											2019-07-18 05:35:05 +08:00
										 |  |  | 	/** @type {Map<T, Node<T>>} */ | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | 	const itemToNode = new Map(); | 
					
						
							|  |  |  | 	for (const item of items) { | 
					
						
							|  |  |  | 		const node = new Node(item); | 
					
						
							|  |  |  | 		itemToNode.set(item, node); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// early exit when there is only a single item
 | 
					
						
							|  |  |  | 	if (itemToNode.size <= 1) return items; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// grab all the dependencies
 | 
					
						
							|  |  |  | 	for (const node of itemToNode.values()) { | 
					
						
							|  |  |  | 		for (const dep of getDependencies(node.item)) { | 
					
						
							|  |  |  | 			const depNode = itemToNode.get(dep); | 
					
						
							|  |  |  | 			if (depNode !== undefined) { | 
					
						
							|  |  |  | 				node.dependencies.add(depNode); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Set of current root modules
 | 
					
						
							|  |  |  | 	// items will be removed if a new reference to it has been found
 | 
					
						
							| 
									
										
										
										
											2019-07-18 05:35:05 +08:00
										 |  |  | 	/** @type {Set<Node<T>>} */ | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | 	const roots = new Set(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Set of current cycles without references to it
 | 
					
						
							|  |  |  | 	// cycles will be removed if a new reference to it has been found
 | 
					
						
							|  |  |  | 	// that is not part of the cycle
 | 
					
						
							| 
									
										
										
										
											2019-07-18 05:35:05 +08:00
										 |  |  | 	/** @type {Set<Cycle<T>>} */ | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | 	const rootCycles = new Set(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// For all non-marked nodes
 | 
					
						
							|  |  |  | 	for (const selectedNode of itemToNode.values()) { | 
					
						
							|  |  |  | 		if (selectedNode.marker === NO_MARKER) { | 
					
						
							|  |  |  | 			// deep-walk all referenced modules
 | 
					
						
							|  |  |  | 			// in a non-recursive way
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// start by entering the selected node
 | 
					
						
							|  |  |  | 			selectedNode.marker = IN_PROGRESS_MARKER; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// keep a stack to avoid recursive walk
 | 
					
						
							| 
									
										
										
										
											2019-07-18 05:35:05 +08:00
										 |  |  | 			/** @type {StackEntry<T>[]} */ | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | 			const stack = [ | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					node: selectedNode, | 
					
						
							|  |  |  | 					openEdges: Array.from(selectedNode.dependencies) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// process the top item until stack is empty
 | 
					
						
							|  |  |  | 			while (stack.length > 0) { | 
					
						
							|  |  |  | 				const topOfStack = stack[stack.length - 1]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				// Are there still edges unprocessed in the current node?
 | 
					
						
							|  |  |  | 				if (topOfStack.openEdges.length > 0) { | 
					
						
							|  |  |  | 					// Process one dependency
 | 
					
						
							| 
									
										
										
										
											2023-06-12 22:21:21 +08:00
										 |  |  | 					const dependency = | 
					
						
							|  |  |  | 						/** @type {Node<T>} */ | 
					
						
							|  |  |  | 						(topOfStack.openEdges.pop()); | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | 					switch (dependency.marker) { | 
					
						
							|  |  |  | 						case NO_MARKER: | 
					
						
							|  |  |  | 							// dependency has not be visited yet
 | 
					
						
							|  |  |  | 							// mark it as in-progress and recurse
 | 
					
						
							|  |  |  | 							stack.push({ | 
					
						
							|  |  |  | 								node: dependency, | 
					
						
							|  |  |  | 								openEdges: Array.from(dependency.dependencies) | 
					
						
							|  |  |  | 							}); | 
					
						
							|  |  |  | 							dependency.marker = IN_PROGRESS_MARKER; | 
					
						
							|  |  |  | 							break; | 
					
						
							|  |  |  | 						case IN_PROGRESS_MARKER: { | 
					
						
							|  |  |  | 							// It's a in-progress cycle
 | 
					
						
							|  |  |  | 							let cycle = dependency.cycle; | 
					
						
							|  |  |  | 							if (!cycle) { | 
					
						
							|  |  |  | 								cycle = new Cycle(); | 
					
						
							|  |  |  | 								cycle.nodes.add(dependency); | 
					
						
							|  |  |  | 								dependency.cycle = cycle; | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 							// set cycle property for each node in the cycle
 | 
					
						
							|  |  |  | 							// if nodes are already part of a cycle
 | 
					
						
							|  |  |  | 							// we merge the cycles to a shared cycle
 | 
					
						
							|  |  |  | 							for ( | 
					
						
							|  |  |  | 								let i = stack.length - 1; | 
					
						
							|  |  |  | 								stack[i].node !== dependency; | 
					
						
							|  |  |  | 								i-- | 
					
						
							|  |  |  | 							) { | 
					
						
							|  |  |  | 								const node = stack[i].node; | 
					
						
							|  |  |  | 								if (node.cycle) { | 
					
						
							|  |  |  | 									if (node.cycle !== cycle) { | 
					
						
							|  |  |  | 										// merge cycles
 | 
					
						
							|  |  |  | 										for (const cycleNode of node.cycle.nodes) { | 
					
						
							|  |  |  | 											cycleNode.cycle = cycle; | 
					
						
							|  |  |  | 											cycle.nodes.add(cycleNode); | 
					
						
							|  |  |  | 										} | 
					
						
							|  |  |  | 									} | 
					
						
							|  |  |  | 								} else { | 
					
						
							|  |  |  | 									node.cycle = cycle; | 
					
						
							|  |  |  | 									cycle.nodes.add(node); | 
					
						
							|  |  |  | 								} | 
					
						
							|  |  |  | 							} | 
					
						
							|  |  |  | 							// don't recurse into dependencies
 | 
					
						
							|  |  |  | 							// these are already on the stack
 | 
					
						
							|  |  |  | 							break; | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 						case DONE_AND_ROOT_MARKER: | 
					
						
							|  |  |  | 							// This node has be visited yet and is currently a root node
 | 
					
						
							|  |  |  | 							// But as this is a new reference to the node
 | 
					
						
							|  |  |  | 							// it's not really a root
 | 
					
						
							|  |  |  | 							// so we have to convert it to a normal node
 | 
					
						
							|  |  |  | 							dependency.marker = DONE_MARKER; | 
					
						
							|  |  |  | 							roots.delete(dependency); | 
					
						
							|  |  |  | 							break; | 
					
						
							|  |  |  | 						case DONE_MAYBE_ROOT_CYCLE_MARKER: | 
					
						
							| 
									
										
										
										
											2018-12-07 03:19:39 +08:00
										 |  |  | 							// This node has be visited yet and
 | 
					
						
							|  |  |  | 							// is maybe currently part of a completed root cycle
 | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | 							// we found a new reference to the cycle
 | 
					
						
							|  |  |  | 							// so it's not really a root cycle
 | 
					
						
							|  |  |  | 							// remove the cycle from the root cycles
 | 
					
						
							|  |  |  | 							// and convert it to a normal node
 | 
					
						
							| 
									
										
										
										
											2023-06-12 22:21:21 +08:00
										 |  |  | 							rootCycles.delete(/** @type {Cycle<T>} */ (dependency.cycle)); | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | 							dependency.marker = DONE_MARKER; | 
					
						
							|  |  |  | 							break; | 
					
						
							|  |  |  | 						// DONE_MARKER: nothing to do, don't recurse into dependencies
 | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					// All dependencies of the current node has been visited
 | 
					
						
							|  |  |  | 					// we leave the node
 | 
					
						
							|  |  |  | 					stack.pop(); | 
					
						
							| 
									
										
										
										
											2018-12-07 03:19:39 +08:00
										 |  |  | 					topOfStack.node.marker = DONE_MARKER; | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2018-12-07 03:19:39 +08:00
										 |  |  | 			const cycle = selectedNode.cycle; | 
					
						
							|  |  |  | 			if (cycle) { | 
					
						
							|  |  |  | 				for (const node of cycle.nodes) { | 
					
						
							|  |  |  | 					node.marker = DONE_MAYBE_ROOT_CYCLE_MARKER; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				rootCycles.add(cycle); | 
					
						
							|  |  |  | 			} else { | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | 				selectedNode.marker = DONE_AND_ROOT_MARKER; | 
					
						
							|  |  |  | 				roots.add(selectedNode); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Extract roots from root cycles
 | 
					
						
							|  |  |  | 	// We take the nodes with most incoming edges
 | 
					
						
							|  |  |  | 	// inside of the cycle
 | 
					
						
							|  |  |  | 	for (const cycle of rootCycles) { | 
					
						
							|  |  |  | 		let max = 0; | 
					
						
							| 
									
										
										
										
											2019-07-18 05:35:05 +08:00
										 |  |  | 		/** @type {Set<Node<T>>} */ | 
					
						
							| 
									
										
										
										
											2018-12-06 19:30:22 +08:00
										 |  |  | 		const cycleRoots = new Set(); | 
					
						
							|  |  |  | 		const nodes = cycle.nodes; | 
					
						
							|  |  |  | 		for (const node of nodes) { | 
					
						
							|  |  |  | 			for (const dep of node.dependencies) { | 
					
						
							|  |  |  | 				if (nodes.has(dep)) { | 
					
						
							|  |  |  | 					dep.incoming++; | 
					
						
							|  |  |  | 					if (dep.incoming < max) continue; | 
					
						
							|  |  |  | 					if (dep.incoming > max) { | 
					
						
							|  |  |  | 						cycleRoots.clear(); | 
					
						
							|  |  |  | 						max = dep.incoming; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					cycleRoots.add(dep); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		for (const cycleRoot of cycleRoots) { | 
					
						
							|  |  |  | 			roots.add(cycleRoot); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// When roots were found, return them
 | 
					
						
							|  |  |  | 	if (roots.size > 0) { | 
					
						
							|  |  |  | 		return Array.from(roots, r => r.item); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		throw new Error("Implementation of findGraphRoots is broken"); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | }; |