123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231 |
- "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;
- class Node {
-
- constructor(item) {
- this.item = item;
-
- this.dependencies = new Set();
- this.marker = NO_MARKER;
-
- this.cycle = undefined;
- this.incoming = 0;
- }
- }
- class Cycle {
- constructor() {
-
- this.nodes = new Set();
- }
- }
- module.exports = (items, getDependencies) => {
-
- const itemToNode = new Map();
- for (const item of items) {
- const node = new Node(item);
- itemToNode.set(item, node);
- }
-
- if (itemToNode.size <= 1) return items;
-
- 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);
- }
- }
- }
-
-
-
- const roots = new Set();
-
-
-
-
- const rootCycles = new Set();
-
- for (const selectedNode of itemToNode.values()) {
- if (selectedNode.marker === NO_MARKER) {
-
-
-
- selectedNode.marker = IN_PROGRESS_MARKER;
-
-
- const stack = [
- {
- node: selectedNode,
- openEdges: Array.from(selectedNode.dependencies)
- }
- ];
-
- while (stack.length > 0) {
- const topOfStack = stack[stack.length - 1];
-
- if (topOfStack.openEdges.length > 0) {
-
- const dependency =
-
- (topOfStack.openEdges.pop());
- switch (dependency.marker) {
- case NO_MARKER:
-
-
- stack.push({
- node: dependency,
- openEdges: Array.from(dependency.dependencies)
- });
- dependency.marker = IN_PROGRESS_MARKER;
- break;
- case IN_PROGRESS_MARKER: {
-
- let cycle = dependency.cycle;
- if (!cycle) {
- cycle = new Cycle();
- cycle.nodes.add(dependency);
- dependency.cycle = cycle;
- }
-
-
-
- for (
- let i = stack.length - 1;
- stack[i].node !== dependency;
- i--
- ) {
- const node = stack[i].node;
- if (node.cycle) {
- if (node.cycle !== cycle) {
-
- for (const cycleNode of node.cycle.nodes) {
- cycleNode.cycle = cycle;
- cycle.nodes.add(cycleNode);
- }
- }
- } else {
- node.cycle = cycle;
- cycle.nodes.add(node);
- }
- }
-
-
- break;
- }
- case DONE_AND_ROOT_MARKER:
-
-
-
-
- dependency.marker = DONE_MARKER;
- roots.delete(dependency);
- break;
- case DONE_MAYBE_ROOT_CYCLE_MARKER:
-
-
-
-
-
-
- rootCycles.delete( (dependency.cycle));
- dependency.marker = DONE_MARKER;
- break;
-
- }
- } else {
-
-
- stack.pop();
- topOfStack.node.marker = DONE_MARKER;
- }
- }
- const cycle = selectedNode.cycle;
- if (cycle) {
- for (const node of cycle.nodes) {
- node.marker = DONE_MAYBE_ROOT_CYCLE_MARKER;
- }
- rootCycles.add(cycle);
- } else {
- selectedNode.marker = DONE_AND_ROOT_MARKER;
- roots.add(selectedNode);
- }
- }
- }
-
-
-
- for (const cycle of rootCycles) {
- let max = 0;
-
- 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);
- }
- }
-
- if (roots.size > 0) {
- return Array.from(roots, r => r.item);
- } else {
- throw new Error("Implementation of findGraphRoots is broken");
- }
- };
|