• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hexojs / hexo-util / 16567228044

28 Jul 2025 10:59AM UTC coverage: 96.583% (-0.3%) from 96.875%
16567228044

Pull #429

github

web-flow
Merge 85a2a3df0 into d497bc760
Pull Request #429: feat(lib): implement custom deepMerge with recursion depth limit and circular reference support

498 of 519 branches covered (95.95%)

113 of 122 new or added lines in 1 file covered. (92.62%)

1 existing line in 1 file now uncovered.

1894 of 1961 relevant lines covered (96.58%)

14.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.91
/lib/deep_merge.ts
1
/**
1✔
2
 * Recursively creates a deep clone of the given value, handling circular references.
1✔
3
 *
1✔
4
 * This function supports:
1✔
5
 * - Primitives (`string`, `number`, `boolean`, `null`, `undefined`, `symbol`, `bigint`)
1✔
6
 * - Arrays (including nested arrays)
1✔
7
 * - Plain objects (including nested objects)
1✔
8
 * - Circular references (using WeakMap)
1✔
9
 *
1✔
10
 * Note: This does **not** handle special types like `Date`, `Map`, `Set`,
1✔
11
 * `RegExp`, or `Function`.
1✔
12
 *
1✔
13
 * @param param - The value to deep clone.
1✔
14
 * @param seen - Internal WeakMap to track circular references.
1✔
15
 * @returns A deep clone of the input value.
1✔
16
 */
1✔
17
function deepClone<T>(param: T, seen = new WeakMap()): T {
22✔
18
  if (param === null || typeof param !== 'object') {
22✔
19
    return param;
17✔
20
  }
17✔
21

5✔
22
  // Only objects (not arrays) can be WeakMap keys
5✔
23
  if (seen.has(param as object)) {
22!
NEW
24
    return seen.get(param as object);
×
NEW
25
  }
×
26

5✔
27
  if (Array.isArray(param)) {
22✔
28
    const arr: unknown[] = [];
3✔
29
    seen.set(param as unknown as object, arr);
3✔
30
    for (const item of param as unknown as unknown[]) {
3✔
31
      arr.push(deepClone(item, seen));
6✔
32
    }
6✔
33
    return arr as unknown as T;
3✔
34
  }
3✔
35

2✔
36
  const newObj: Record<string, unknown> = {};
2✔
37
  seen.set(param as object, newObj);
2✔
38
  for (const key in param) {
22✔
39
    if (Object.prototype.hasOwnProperty.call(param, key)) {
3✔
40
      newObj[key] = deepClone((param as Record<string, unknown>)[key], seen);
3✔
41
    }
3✔
42
  }
3✔
43
  return newObj as T;
2✔
44
}
2✔
45

1✔
46
function isObject(item: unknown): item is Record<string, unknown> {
33✔
47
  return !!item && typeof item === 'object' && !Array.isArray(item);
33✔
48
}
33✔
49

1✔
50
function deepMerge<T>(target: Partial<T>, source: Partial<T>, seen = new WeakMap()): T {
22✔
51
  // If source is a primitive, return source (replace target)
22✔
52
  if (source === null || typeof source !== 'object') {
22✔
53
    return deepClone(source) as T;
5✔
54
  }
5✔
55
  // If target is a primitive, but source is object/array, clone source
17✔
56
  if (target === null || typeof target !== 'object') {
22✔
57
    return deepClone(source) as T;
1✔
58
  }
1✔
59

16✔
60
  // Handle circular references
16✔
61
  if (seen.has(target as object)) {
22!
NEW
62
    return seen.get(target as object);
×
NEW
UNCOV
63
  }
×
64

16✔
65
  // Array merge: merge objects at same index, otherwise use source value
16✔
66
  if (Array.isArray(target) && Array.isArray(source)) {
22✔
67
    const maxLength = Math.max(target.length, source.length);
2✔
68
    const resultArr: unknown[] = [];
2✔
69
    seen.set(target as object, resultArr);
2✔
70
    for (let i = 0; i < maxLength; i++) {
2✔
71
      const tVal = (target as unknown[])[i];
6✔
72
      const sVal = (source as unknown[])[i];
6✔
73
      if (i in target && i in source) {
6✔
74
        if (Array.isArray(tVal) && Array.isArray(sVal)) {
5✔
75
          // Union: all from target, then any from source not already present
1✔
76
          const union = [...(tVal as unknown[])];
1✔
77
          for (const v of sVal as unknown[]) {
1✔
78
            if (!union.some(u => u === v)) {
2✔
79
              union.push(v);
1✔
80
            }
1✔
81
          }
2✔
82
          resultArr[i] = union;
1✔
83
        } else if (Array.isArray(sVal)) {
5✔
84
          resultArr[i] = deepClone(sVal, seen);
1✔
85
        } else if (Array.isArray(tVal)) {
4!
NEW
86
          resultArr[i] = deepClone(tVal, seen);
×
87
        } else if (isObject(tVal) && isObject(sVal)) {
3✔
88
          resultArr[i] = deepMerge(tVal, sVal, seen);
2✔
89
        } else if (typeof sVal !== 'undefined') {
3✔
90
          resultArr[i] = deepClone(sVal, seen);
1✔
91
        } else {
1!
NEW
92
          resultArr[i] = deepClone(tVal, seen);
×
NEW
93
        }
×
94
      } else if (i in source) {
6✔
95
        resultArr[i] = deepClone(sVal, seen);
1✔
96
      } else if (i in target) {
1!
NEW
97
        resultArr[i] = deepClone(tVal, seen);
×
NEW
98
      }
×
99
    }
6✔
100
    return resultArr as T;
6✔
101
  }
6✔
102

14✔
103
  // Object merge
14✔
104
  if (isObject(target) && isObject(source)) {
22✔
105
    const resultObj: Record<string, unknown> = { ...target };
13✔
106
    seen.set(target as object, resultObj);
13✔
107
    for (const key in source) {
13✔
108
      if (Object.prototype.hasOwnProperty.call(source, key)) {
15✔
109
        if (key in target) {
15✔
110
          resultObj[key] = deepMerge(
12✔
111
            (target as Record<string, unknown>)[key],
12✔
112
            (source as Record<string, unknown>)[key],
12✔
113
            seen
12✔
114
          );
12✔
115
        } else {
15✔
116
          resultObj[key] = deepClone((source as Record<string, unknown>)[key], seen);
3✔
117
        }
3✔
118
      }
15✔
119
    }
15✔
120
    return resultObj as T;
13✔
121
  }
13✔
122

1✔
123
  // Fallback: clone source
1✔
124
  return deepClone(source) as T;
1✔
125
}
1✔
126

1✔
127
export = deepMerge;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc