Passed
Pull Request — main (#3)
by Yuri
03:21 queued 01:21
created

index.ts ➔ sortObject   B

Complexity

Conditions 5

Size

Total Lines 27
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 22
dl 0
loc 27
ccs 9
cts 9
cp 1
crap 5
rs 8.8853
c 0
b 0
f 0
1
// Type definitions for object types and non-sortable types
2
type ObjectType = Record<string | symbol, unknown>;
3
type SortedEntry = [string | symbol, unknown];
4
type NonSortableType =
5
  | Date
6
  | RegExp
7
  | (() => unknown)
8
  | ((...args: unknown[]) => unknown)
9
  | Error
10
  | Map<unknown, unknown>
11
  | Set<unknown>
12
  | WeakMap<object, unknown>
13
  | WeakSet<object>
14
  | Promise<unknown>;
15
16
// Main sorting function that can handle various data types
17 1
export function sort<T>(
18
  data: T,
19
  ascending = true,
20
  sortPrimitiveArrays = false
21
): T {
22 38
  return sortRecursively(data, ascending, sortPrimitiveArrays);
23
}
24
25
// Recursive function to sort nested structures
26
function sortRecursively<T>(
27
  data: T,
28
  ascending: boolean,
29
  sortPrimitiveArrays: boolean
30
): T {
31 193
  if (!isObject(data) || Array.isArray(data) || isNonSortableObject(data)) {
32 149
    if (Array.isArray(data)) {
33
      // If sortPrimitiveArrays is true and all items are primitives, sort the array
34 37
      if (
35
        sortPrimitiveArrays &&
36
        data.length > 0 &&
37 61
        data.every((item) => !isObject(item))
38
      ) {
39 16
        return [...data].sort((a, b) => {
40 60
          if (typeof a === 'string' && typeof b === 'string') {
41 9
            return ascending ? a.localeCompare(b) : b.localeCompare(a);
42
          }
43 51
          if (typeof a === 'number' && typeof b === 'number') {
44 36
            return ascending ? a - b : b - a;
45
          }
46 15
          if (typeof a === 'boolean' && typeof b === 'boolean') {
47 10
            return ascending
48
              ? a === b
49
                ? 0
50
                : a
51
                  ? 1
52
                  : -1
53
              : a === b
54
                ? 0
55
                : a
56
                  ? -1
57
                  : 1;
58
          }
59
          // For mixed types or other primitives, maintain original order
60 5
          return 0;
61
        }) as T;
62
      }
63
      // Otherwise, recursively sort array items
64 21
      return data.map((item) =>
65 52
        sortRecursively(item, ascending, sortPrimitiveArrays)
66
      ) as T;
67
    }
68 112
    return data;
69
  }
70
71 44
  return sortObject(data as ObjectType, ascending, sortPrimitiveArrays) as T;
72
}
73
74
// Helper function to check if a value is an object
75
function isObject(data: unknown): data is ObjectType {
76 254
  return typeof data === 'object' && data !== null;
77
}
78
79
// Function to sort object properties
80
function sortObject(
81
  obj: ObjectType,
82
  ascending: boolean,
83
  sortPrimitiveArrays: boolean
84
): ObjectType {
85 44
  const entries = [
86
    ...Object.entries(obj),
87
    ...Object.getOwnPropertySymbols(obj).map(
88 2
      (sym) => [sym, obj[sym]] as SortedEntry
89
    ),
90
  ];
91
92 44
  const sortedEntries = entries.sort(([keyA], [keyB]) => {
93 81
    if (typeof keyA === 'symbol' && typeof keyB === 'symbol') return 0;
94 80
    if (typeof keyA === 'symbol') return 1;
95 77
    if (typeof keyB === 'symbol') return -1;
96 77
    return ascending
97
      ? (keyA as string).localeCompare(keyB as string)
98
      : (keyB as string).localeCompare(keyA as string);
99
  });
100
101 44
  return Object.fromEntries(
102 103
    sortedEntries.map(([key, value]) => [
103
      key,
104
      sortRecursively(value, ascending, sortPrimitiveArrays),
105
    ])
106
  );
107
}
108
109
// Helper function to check if an object is of a non-sortable type
110
function isNonSortableObject(obj: unknown): obj is NonSortableType {
111 61
  const nonSortableTypes = [
112
    Date,
113
    RegExp,
114
    Function,
115
    Error,
116
    Map,
117
    Set,
118
    WeakMap,
119
    WeakSet,
120
    Promise,
121
  ];
122 61
  return (
123 459
    nonSortableTypes.some((type) => obj instanceof type) ||
124
    Symbol.iterator in Object(obj)
125
  );
126
}
127