validate() {
    let index = 0;

    // Map that assigns each dependency an index in visit-order.
    const indexMap = new Map();
    // Stack of dependencies that are potentially part of the current strongly
    // connected component. If any dependency has a back pointer it is kept
    // on the stack. Thus after recursion anything left on the stack above the
    // current dependency is part of the SCC. If the dependency has no back
    // pointer the SCC is created from all dependencies on the stack until the
    // current dependency is popped off and added to the SCC.
    const depStack = new Set();

    const validate = (dep) => {
      const thisIndex = index++;
      indexMap.set(dep, thisIndex);
      let lowIndex = thisIndex;

      depStack.add(dep);

      // We might modify the imports when iterating (drop unrecognized ES6
      // imports), so iterate over a copy.
      for (const i of [...dep.imports]) {
        const to = this.resolve_(i);

        if (!to) {
          if (i.isGoogRequire()) {
            throw new SourceError(`Could not find "${i.symOrPath}".`, dep.path);
          } else {
            // Just drop unrecognized ES6 imports. Assume their entire branch is
            // non-Closure, like a built-in Node module.
            dep.imports.splice(dep.imports.indexOf(i), 1);
            console.warn(
                `Could not find "${i.symOrPath}".` +
                    'Assuming it (and its transitive dependencies) are ' +
                    'non-Closure managed.',
                dep.path);
            continue;
          }
        }

        i.validate(to);

        if (!indexMap.has(to)) {
          // "to" has not been visited, recurse.
          lowIndex = Math.min(lowIndex, validate(to));
        } else if (depStack.has(to)) {
          // "to" is on the stack and thus a part of the SCC.
          lowIndex = Math.min(lowIndex, indexMap.get(to));
        }
        // else visited but not on the stack, so it is not part of the SCC. It
        // is a common dependency of some other tree, but does not reach this
        // dependency and thus is not strongly connected.
      }

      if (lowIndex === thisIndex) {
        let anyClosure = false;
        const deps = [...depStack];
        const scc = [];
        for (let i = deps.length - 1; i > -1; i--) {
          scc.push(deps[i]);
          depStack.delete(deps[i]);
          anyClosure = anyClosure ||
              deps[i].type === DependencyType.CLOSURE_PROVIDE ||
              deps[i].type === DependencyType.CLOSURE_MODULE;
          if (deps[i] === dep) {
            break;
          }
        }
        if (anyClosure && scc.length > 1) {
          throw new InvalidCycleError(scc);
        }
      }

      return lowIndex;
    };

    for (const dep of this.depsByPath.values()) {
      if (!indexMap.has(dep)) {
        validate(dep);
      }
    }
  }
Validates the dependency graph. Throws an error if the graph is invalid.

This method uses Tarjan's algorithm to ensure Closure files are not part
of any cycle. Check it out:
https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm