"use strict"; const findCycle = require("find-cycle/directed"); module.exports = function findCycleSets(nodes) { let queue = nodes.slice(); let cycleSets = []; let innocentNodes = []; let lastQueueLength = queue.length; while (queue.length > 0) { let queueSet = new Set(queue); let cycle = findCycle(queue, (node) => { return node.children.filter((child) => { // We want to prevent findCycle from discovering and traversing any nodes that are not in our "conflict set"; otherwise a second pass will fail, because it will rediscover already-handled nodes (that have been removed from the queue) indirectly, and endlessly loop on discovering that cycle. return queueSet.has(child); }); }); if (cycle != null) { cycleSets.push(cycle); queue = queue.filter((node) => !cycle.includes(node)); if (queue.length === lastQueueLength) { throw new Error(`Failed to reduce queue length; this is a bug, please report it!`); } else { lastQueueLength = queue.length; } } else { innocentNodes = queue; break; } } return { cycleSets: cycleSets, innocentNodes: innocentNodes }; };