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

hexojs / hexo-util / 16651986209

31 Jul 2025 02:35PM UTC coverage: 73.036% (-23.8%) from 96.875%
16651986209

Pull #426

github

web-flow
Merge e24598988 into d497bc760
Pull Request #426: build: restructure for dual ESM/CJS output, update tooling, and enhance utilities

811 of 1128 branches covered (71.9%)

1594 of 2028 new or added lines in 40 files covered. (78.6%)

6 existing lines in 1 file now uncovered.

5290 of 7243 relevant lines covered (73.04%)

69.13 hits per line

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

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

8✔
22
  // Handle special types
8✔
23
  if (param instanceof Date) {
4,047!
NEW
24
    return new Date(param.getTime()) as unknown as T;
×
NEW
25
  }
×
26
  if (param instanceof RegExp) {
4,047!
NEW
27
    return new RegExp(param.source, param.flags) as unknown as T;
×
NEW
28
  }
×
29
  if (param instanceof Map) {
4,047!
NEW
30
    const result = new Map();
×
NEW
31
    seen.set(param as object, result);
×
NEW
32
    for (const [k, v] of param as unknown as Map<unknown, unknown>) {
×
NEW
33
      result.set(deepClone(k, seen), deepClone(v, seen));
×
NEW
34
    }
×
NEW
35
    return result as unknown as T;
×
NEW
36
  }
×
37
  if (param instanceof Set) {
4,047!
NEW
38
    const result = new Set();
×
NEW
39
    seen.set(param as object, result);
×
NEW
40
    for (const v of param as unknown as Set<unknown>) {
×
NEW
41
      result.add(deepClone(v, seen));
×
NEW
42
    }
×
NEW
43
    return result as unknown as T;
×
NEW
44
  }
×
45
  if (typeof param === 'function') {
4,047!
NEW
46
    // Functions are not clonable, return as-is
×
NEW
47
    return param;
×
NEW
48
  }
×
49

8✔
50
  // Only objects (not arrays) can be WeakMap keys
8✔
51
  if (seen.has(param as object)) {
4,047!
NEW
52
    return seen.get(param as object);
×
NEW
53
  }
×
54

8✔
55
  if (Array.isArray(param)) {
4,047✔
56
    const arr: unknown[] = [];
3✔
57
    seen.set(param as unknown as object, arr);
3✔
58
    for (const item of param as unknown as unknown[]) {
3✔
59
      arr.push(deepClone(item, seen));
6✔
60
    }
6✔
61
    return arr as unknown as T;
3✔
62
  }
3✔
63

5✔
64
  const newObj: Record<PropertyKey, unknown> = {};
5✔
65
  seen.set(param as object, newObj);
5✔
66
  // Merge string keys
5✔
67
  for (const key in param) {
4,047✔
68
    if (Object.prototype.hasOwnProperty.call(param, key)) {
6✔
69
      newObj[key] = deepClone((param as Record<string, unknown>)[key], seen);
6✔
70
    }
6✔
71
  }
6✔
72
  // Merge symbol keys
5✔
73
  const symbols = Object.getOwnPropertySymbols(param as object);
5✔
74
  for (const sym of symbols) {
4,047!
NEW
75
    newObj[sym] = deepClone((param as Record<symbol, unknown>)[sym], seen);
×
NEW
76
  }
×
77
  return newObj as T;
5✔
78
}
5✔
79

2✔
80
/**
2✔
81
 * Checks if a value is a plain object (not an array, null, or special type).
2✔
82
 *
2✔
83
 * @param item - The value to check.
2✔
84
 * @returns True if the value is a plain object, false otherwise.
2✔
85
 */
2✔
86
function isObject(item: unknown): item is Record<string, unknown> {
6,077✔
87
  return !!item && typeof item === 'object' && !Array.isArray(item);
6,077✔
88
}
6,077✔
89

2✔
90
/**
2✔
91
 * Deeply merges two values, handling arrays, objects, and special types.
2✔
92
 *
2✔
93
 * - If both values are arrays, merges by index, unions nested arrays, and merges objects/maps/sets/functions at the same index.
2✔
94
 * - If both values are objects, merges properties recursively, handling special types (`Date`, `Map`, `Set`, `RegExp`, `Function`).
2✔
95
 * - If types differ, source replaces target.
2✔
96
 * - Handles circular references.
2✔
97
 *
2✔
98
 * @param target - The target value to merge into.
2✔
99
 * @param source - The source value to merge from.
2✔
100
 * @param seen - Internal WeakMap to track circular references.
2✔
101
 * @returns The deeply merged value.
2✔
102
 */
2✔
103
function deepMerge<T>(target: Partial<T>, source: Partial<T>, seen = new WeakMap()): T {
6,057✔
104
  // If source is a primitive, return source (replace target)
6,057✔
105
  if (source === null || typeof source !== 'object') {
6,057✔
106
    return deepClone(source) as T;
2,014✔
107
  }
2,014✔
108
  // If target is a primitive, but source is object/array, clone source
4,043✔
109
  if (target === null || typeof target !== 'object') {
6,057✔
110
    return deepClone(source) as T;
3✔
111
  }
3✔
112

4,040✔
113
  // Handle circular references
4,040✔
114
  if (seen.has(target as object)) {
6,057✔
115
    return seen.get(target as object);
1,999✔
116
  }
1,999✔
117

2,041✔
118
  // Array merge: merge objects at same index, otherwise use source value
2,041✔
119
  if (Array.isArray(target) && Array.isArray(source)) {
6,057✔
120
    const maxLength = Math.max(target.length, source.length);
8✔
121
    const resultArr: unknown[] = [];
8✔
122
    seen.set(target as object, resultArr);
8✔
123
    for (let i = 0; i < maxLength; i++) {
8✔
124
      const tVal = (target as unknown[])[i];
1,013✔
125
      const sVal = (source as unknown[])[i];
1,013✔
126
      if (i in target && i in source) {
1,013✔
127
        if (Array.isArray(tVal) && Array.isArray(sVal)) {
1,009✔
128
          // Union: all from target, then any from source not already present
1✔
129
          const union = [...(tVal as unknown[])];
1✔
130
          for (const v of sVal as unknown[]) {
1✔
131
            if (!union.some(u => u === v)) {
2✔
132
              union.push(v);
1✔
133
            }
1✔
134
          }
2✔
135
          resultArr[i] = union;
1✔
136
        } else if (Array.isArray(sVal)) {
1,009✔
137
          resultArr[i] = deepClone(sVal, seen);
1✔
138
        } else if (Array.isArray(tVal)) {
1,008!
NEW
139
          resultArr[i] = deepClone(tVal, seen);
×
140
        } else if (isObject(tVal) && isObject(sVal)) {
1,007✔
141
          resultArr[i] = deepMerge(tVal, sVal, seen);
1,004✔
142
        } else if (tVal instanceof Date && sVal instanceof Date) {
1,007!
NEW
143
          resultArr[i] = new Date(Math.max(tVal.getTime(), sVal.getTime()));
×
144
        } else if (tVal instanceof RegExp && sVal instanceof RegExp) {
3!
NEW
145
          // Prefer source's RegExp
×
NEW
146
          resultArr[i] = new RegExp(sVal.source, sVal.flags);
×
147
        } else if (tVal instanceof Map && sVal instanceof Map) {
3!
NEW
148
          // Merge maps: keys from both, values deep merged
×
NEW
149
          const merged = new Map(tVal);
×
NEW
150
          for (const [k, v] of sVal) {
×
NEW
151
            if (merged.has(k)) {
×
NEW
152
              merged.set(k, deepMerge(merged.get(k), v, seen));
×
NEW
153
            } else {
×
NEW
154
              merged.set(deepClone(k, seen), deepClone(v, seen));
×
NEW
155
            }
×
NEW
156
          }
×
NEW
157
          resultArr[i] = merged;
×
158
        } else if (tVal instanceof Set && sVal instanceof Set) {
3!
NEW
159
          // Union of sets
×
NEW
160
          const merged = new Set(tVal);
×
NEW
161
          for (const v of sVal) {
×
NEW
162
            merged.add(deepClone(v, seen));
×
NEW
163
          }
×
NEW
164
          resultArr[i] = merged;
×
165
        } else if (typeof tVal === 'function' && typeof sVal === 'function') {
3!
NEW
166
          // Prefer source's function
×
NEW
167
          resultArr[i] = sVal;
×
168
        } else if (typeof sVal !== 'undefined') {
3✔
169
          resultArr[i] = deepClone(sVal, seen);
3✔
170
        } else {
3!
NEW
171
          resultArr[i] = deepClone(tVal, seen);
×
NEW
172
        }
×
173
      } else if (i in source) {
1,013✔
174
        resultArr[i] = deepClone(sVal, seen);
2✔
175
      } else if (i in target) {
2✔
176
        resultArr[i] = deepClone(tVal, seen);
1✔
177
      }
1✔
178
    }
1,013✔
179
    return resultArr as T;
1,013✔
180
  }
1,013✔
181

2,033✔
182
  // Object merge
2,033✔
183
  if (isObject(target) && isObject(source)) {
6,057✔
184
    const resultObj: Record<PropertyKey, unknown> = { ...target };
2,032✔
185
    seen.set(target as object, resultObj);
2,032✔
186
    // Merge string keys
2,032✔
187
    for (const key in source) {
2,032✔
188
      if (Object.prototype.hasOwnProperty.call(source, key)) {
9,031✔
189
        const tVal = (target as Record<string, unknown>)[key];
9,031✔
190
        const sVal = (source as Record<string, unknown>)[key];
9,031✔
191
        if (key in target) {
9,031✔
192
          // Handle special types
9,026✔
193
          if (tVal instanceof Date && sVal instanceof Date) {
9,026✔
194
            resultObj[key] = new Date(Math.max(tVal.getTime(), sVal.getTime()));
1,001✔
195
          } else if (tVal instanceof RegExp && sVal instanceof RegExp) {
9,026✔
196
            resultObj[key] = new RegExp(sVal.source, sVal.flags);
1,001✔
197
          } else if (tVal instanceof Map && sVal instanceof Map) {
8,025✔
198
            const merged = new Map(tVal);
1,001✔
199
            for (const [k, v] of sVal) {
1,001✔
200
              if (merged.has(k)) {
1,002✔
201
                merged.set(k, deepMerge(merged.get(k), v, seen));
1,001✔
202
              } else {
1,002✔
203
                merged.set(deepClone(k, seen), deepClone(v, seen));
1✔
204
              }
1✔
205
            }
1,002✔
206
            resultObj[key] = merged;
1,001✔
207
          } else if (tVal instanceof Set && sVal instanceof Set) {
7,024✔
208
            const merged = new Set(tVal);
1,001✔
209
            for (const v of sVal) {
1,001✔
210
              merged.add(deepClone(v, seen));
2,003✔
211
            }
2,003✔
212
            resultObj[key] = merged;
1,001✔
213
          } else if (typeof tVal === 'function' && typeof sVal === 'function') {
6,023✔
214
            resultObj[key] = sVal;
1,001✔
215
          } else {
5,022✔
216
            // Use 'as unknown as Partial<T>' to satisfy TS type system for deepMerge recursion
4,021✔
217
            resultObj[key] = deepMerge(tVal as unknown as Partial<T>, sVal as unknown as Partial<T>, seen);
4,021✔
218
          }
4,021✔
219
        } else {
9,031✔
220
          resultObj[key] = deepClone(sVal, seen);
5✔
221
        }
5✔
222
      }
9,031✔
223
    }
9,031✔
224
    // Merge symbol keys
2,032✔
225
    const sourceSymbols = Object.getOwnPropertySymbols(source);
2,032✔
226
    for (const sym of sourceSymbols) {
2,032✔
227
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
1✔
228
      const tVal = (target as any)[sym];
1✔
229
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
1✔
230
      const sVal = (source as any)[sym];
1✔
231
      if (Object.prototype.hasOwnProperty.call(target, sym)) {
1✔
232
        // Handle special types for symbols
1✔
233
        if (tVal instanceof Date && sVal instanceof Date) {
1!
NEW
234
          resultObj[sym] = new Date(Math.max(tVal.getTime(), sVal.getTime()));
×
235
        } else if (tVal instanceof RegExp && sVal instanceof RegExp) {
1!
NEW
236
          resultObj[sym] = new RegExp(sVal.source, sVal.flags);
×
237
        } else if (tVal instanceof Map && sVal instanceof Map) {
1!
NEW
238
          const merged = new Map(tVal);
×
NEW
239
          for (const [k, v] of sVal) {
×
NEW
240
            if (merged.has(k)) {
×
NEW
241
              merged.set(k, deepMerge(merged.get(k), v, seen));
×
NEW
242
            } else {
×
NEW
243
              merged.set(deepClone(k, seen), deepClone(v, seen));
×
NEW
244
            }
×
NEW
245
          }
×
NEW
246
          resultObj[sym] = merged;
×
247
        } else if (tVal instanceof Set && sVal instanceof Set) {
1!
NEW
248
          const merged = new Set(tVal);
×
NEW
249
          for (const v of sVal) {
×
NEW
250
            merged.add(deepClone(v, seen));
×
NEW
251
          }
×
NEW
252
          resultObj[sym] = merged;
×
253
        } else if (typeof tVal === 'function' && typeof sVal === 'function') {
1!
NEW
254
          resultObj[sym] = sVal;
×
255
        } else {
1✔
256
          resultObj[sym] = deepMerge(tVal, sVal, seen);
1✔
257
        }
1✔
258
      } else {
1!
NEW
259
        resultObj[sym] = deepClone(sVal, seen);
×
NEW
260
      }
×
261
    }
1✔
262
    return resultObj as T;
2,032✔
263
  }
2,032✔
264

1✔
265
  // Fallback: clone source
1✔
266
  return deepClone(source) as T;
1✔
267
}
1✔
268

2✔
269

2✔
270
// For ESM compatibility
2✔
271
export default deepMerge;
2✔
272
// For CommonJS compatibility
2✔
273
if (typeof module !== 'undefined' && typeof module.exports === 'object' && module.exports !== null) {
2!
274
  module.exports = deepMerge;
1✔
275
  // For ESM compatibility
1✔
276
  module.exports.default = deepMerge;
1✔
277
}
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