Passed
Push — main ( fa525e...035c88 )
by Yuri
16:20 queued 03:04
created

index.ts ➔ sortWithCache   A

Complexity

Conditions 5

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 13
dl 0
loc 26
ccs 8
cts 8
cp 1
crap 5
rs 9.2833
c 0
b 0
f 0
1
// Types that represent the structure of our data
2
type ObjectType = Record<string | symbol, unknown>;
3
type SortedEntry = [string | symbol, unknown];
4
type NonSortableType = Date | RegExp | Function | Error | Map<unknown, unknown> | Set<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<unknown>;
5
6
/**
7
 * The Grand Sorting Function
8
 * 
9
 * This function is the entry point for our sorting adventure.
10
 * It takes any data and returns it sorted, like a well-organized closet.
11
 * 
12
 * @template T - The type of data we're dealing with. Could be anything!
13
 * @param {T} data - The messy data that needs sorting
14
 * @param {boolean} [ascending=true] - Do we sort A-Z (true) or Z-A (false)?
15
 * @returns {T} - The same data, but neatly organized
16
 */
17 1
export function sort<T>(data: T, ascending: boolean = true): T {
18 22
  return sortWithCache(data, ascending, new WeakMap());
19
}
20
21
/**
22
 * The Recursive Sorting Wizard
23
 * 
24
 * This function does the heavy lifting. It sorts the data
25
 * and uses a cache to avoid getting stuck in circular references.
26
 * 
27
 * @template T - The type of our data (could be anything!)
28
 * @param {T} data - The data we're currently sorting
29
 * @param {boolean} ascending - The direction of our sort
30
 * @param {WeakMap<object, T>} cache - Our circular reference safety net
31
 * @returns {T} - The sorted version of our input
32
 */
33
function sortWithCache<T>(data: T, ascending: boolean, cache: WeakMap<object, T>): T {
34 143
  if (!isObject(data)) return data;
35 64
  if (Array.isArray(data)) return data.map(item => sortWithCache(item, ascending, cache)) as T;
36 51
  if (isNonSortableObject(data)) return data;
37
38 34
  if (cache.has(data as object)) {
39 1
    return cache.get(data as object) as T;
40
  }
41
42 33
  const result = {} as T;
43 33
  cache.set(data as object, result);
44
45 33
  return sortObject(data as ObjectType, ascending, cache) as T;
46
}
47
48
/**
49
 * The Object Detector
50
 * 
51
 * This function checks if something is an object.
52
 * It's like a metal detector, but for objects!
53
 * 
54
 * @param {unknown} data - The mysterious data we're investigating
55
 * @returns {boolean} - True if it's an object, false otherwise
56
 */
57
function isObject(data: unknown): data is ObjectType {
58 143
  return typeof data === 'object' && data !== null;
59
}
60
61
/**
62
 * The Object Sorter
63
 * 
64
 * This function takes an object and sorts its keys.
65
 * It's like alphabetizing a bookshelf, but for object properties.
66
 * 
67
 * @param {ObjectType} obj - The object we're sorting
68
 * @param {boolean} ascending - The direction of our sort
69
 * @param {WeakMap<object, unknown>} cache - Our circular reference safety net
70
 * @returns {ObjectType} - A new object with sorted keys
71
 */
72
function sortObject(obj: ObjectType, ascending: boolean, cache: WeakMap<object, unknown>): ObjectType {
73 33
  const entries = [...Object.entries(obj), ...Object.getOwnPropertySymbols(obj).map(sym => [sym, obj[sym]] as SortedEntry)];
74
  
75 33
  const sortedEntries = entries.sort(([keyA], [keyB]) => {
76 73
    if (typeof keyA === 'symbol' && typeof keyB === 'symbol') return 0;
77 72
    if (typeof keyA === 'symbol') return 1;
78 69
    if (typeof keyB === 'symbol') return -1;
79 69
    return ascending ? (keyA as string).localeCompare(keyB as string) : (keyB as string).localeCompare(keyA as string);
80
  });
81
82 85
  return Object.fromEntries(sortedEntries.map(([key, value]) => [key, sortWithCache(value, ascending, cache)]));
83
}
84
85
/**
86
 * The Unsortable Object Identifier
87
 * 
88
 * This function checks if an object is one of those special snowflakes
89
 * that we can't (or shouldn't) sort. It's like a bouncer for a "No Sorting Allowed" club.
90
 * 
91
 * @param {unknown} obj - The object we're checking
92
 * @returns {boolean} - True if it's a special unsortable object, false otherwise
93
 */
94
function isNonSortableObject(obj: unknown): obj is NonSortableType {
95 51
  const nonSortableTypes: Array<new (...args: any[]) => NonSortableType> = [
96
    Date, RegExp, Function, Error, Map, Set, WeakMap, WeakSet, Promise
97
  ];
98 369
  return nonSortableTypes.some(type => obj instanceof type) || Symbol.iterator in Object(obj);
99
}
100