Repository: babel/babel
File: packages/babel-core/src/transformation/util/clone-deep.ts (TypeScript)

Code before:
const circleSet = new Set();
// https://github.com/babel/babel/pull/14583#discussion_r882828856
function deepClone(
  value: any,
  cache: Map<any, any>,
  allowCircle: boolean,
): any {
  if (value !== null) {
    if (allowCircle) {
      if (cache.has(value)) return cache.get(value);
    } else {
      if (circleSet.has(value)) {
        circleSet.clear();
        throw new Error("Babel-deepClone: Cycles are not allowed in AST");
      }
      circleSet.add(value);
    }
    let cloned: any;
    if (Array.isArray(value)) {
      cloned = new Array(value.length);
      if (allowCircle) cache.set(value, cloned);
      for (let i = 0; i < value.length; i++) {
        cloned[i] =
          typeof value[i] !== "object"
            ? value[i]
            : deepClone(value[i], cache, allowCircle);
      }
    } else {
      cloned = {};
      if (allowCircle) cache.set(value, cloned);
      const keys = Object.keys(value);
      for (let i = 0; i < keys.length; i++) {
        const key = keys[i];
        cloned[key] =
          typeof value[key] !== "object"
            ? value[key]
            : deepClone(
                value[key],
                cache,
                allowCircle ||
                  key === "leadingComments" ||
                  key === "innerComments" ||
                  key === "trailingComments" ||

Review comment (bug):
How big is the performance impact of using this set compared to:
- the old behavior with the map
- the behavior with no protection against infinite loops

If it's significantly expensive, we could keep track of the depth we are at in the cloning and only start using the cycle detection logic if it's, for example, >100. It means that we'll be slower in error cases, but faster in the (common) successful case.

Code after:
const circleSet = new Set();
let depth = 0;
// https://github.com/babel/babel/pull/14583#discussion_r882828856
function deepClone(
  value: any,
  cache: Map<any, any>,
  allowCircle: boolean,
): any {
  if (value !== null) {
    if (allowCircle) {
      if (cache.has(value)) return cache.get(value);
    } else if (++depth > 250) {
      if (circleSet.has(value)) {
        depth = 0;
        circleSet.clear();
        throw new Error("Babel-deepClone: Cycles are not allowed in AST");
      }
      circleSet.add(value);
    }
    let cloned: any;
    if (Array.isArray(value)) {
      cloned = new Array(value.length);
      if (allowCircle) cache.set(value, cloned);
      for (let i = 0; i < value.length; i++) {
        cloned[i] =
          typeof value[i] !== "object"
            ? value[i]
            : deepClone(value[i], cache, allowCircle);
      }
    } else {
      cloned = {};
      if (allowCircle) cache.set(value, cloned);
      const keys = Object.keys(value);
      for (let i = 0; i < keys.length; i++) {
        const key = keys[i];
        cloned[key] =
          typeof value[key] !== "object"
            ? value[key]
            : deepClone(
                value[key],
                cache,
                allowCircle ||
                  key === "leadingComments" ||