function hasLoopOrAddToSet(g, node, set, parent = null, grouping = new Map(), depth = 0) {
  if (set.has(node)) set.delete(node);
  set.add(node);
  grouping.set(node, depth);

  // console.log({node, adjs: g.nodes.get(node)});

  for (const adj of g.nodes.get(node)) {
    // if (adj === parent) continue; // only for indirected graph

    if (!grouping.has(adj)) {
      if (hasLoopOrAddToSet(g, adj, set, node, grouping, depth + 1)) return true;
    }

    const minGroup = Math.min(grouping.get(adj), grouping.get(node));
    grouping.set(node, minGroup);

    if (grouping.get(adj) === grouping.get(node)) return true;
  }
}
DFS + tarjan for loop detection

@param {Graph} g
@param {Map} node
@param {Set} set
@param {Map} [parent=null]
@param {Map} [grouping=new Map()]
@param {number} [depth=0]
@returns {boolean} true if has a loop, false otherwise