isaacscript-common 27.5.2 → 27.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isaacscript-common",
3
- "version": "27.5.2",
3
+ "version": "27.6.0",
4
4
  "description": "Helper functions and features for IsaacScript mods.",
5
5
  "keywords": [
6
6
  "isaac",
@@ -1,7 +1,7 @@
1
1
  import { CallbackPriority } from "isaac-typescript-definitions/dist/src/enums/CallbackPriority";
2
2
  import { ModCallbackCustom } from "../../enums/ModCallbackCustom";
3
3
  import { log } from "../../functions/log";
4
- import { sortObjectArrayByKey } from "../../functions/sort";
4
+ import { sortObjectArrayByKey, stableSort } from "../../functions/sort";
5
5
  import { getTSTLClassName } from "../../functions/tstlClass";
6
6
  import { AddCallbackParametersCustom } from "../../interfaces/private/AddCallbackParametersCustom";
7
7
  import { AllButFirst } from "../../types/AllButFirst";
@@ -42,7 +42,10 @@ export abstract class CustomCallback<
42
42
  optionalArgs,
43
43
  };
44
44
  this.subscriptions.push(subscription);
45
- this.subscriptions.sort(sortObjectArrayByKey("priority"));
45
+ this.subscriptions = stableSort(
46
+ this.subscriptions,
47
+ sortObjectArrayByKey("priority"),
48
+ );
46
49
  }
47
50
 
48
51
  /**
@@ -173,7 +173,7 @@ export function arrayRemoveIndexInPlace<T>(
173
173
  return true;
174
174
  }
175
175
 
176
- export function arrayToString<T>(array: T[] | readonly T[]): string {
176
+ export function arrayToString(array: unknown[]): string {
177
177
  if (array.length === 0) {
178
178
  return "[]";
179
179
  }
@@ -2,11 +2,19 @@ import { isNumber, isString, isTable } from "./types";
2
2
 
3
3
  function sortNormal(a: unknown, b: unknown): -1 | 0 | 1 {
4
4
  if (!isNumber(a) && !isString(a)) {
5
- error("Failed to sort since the first value was not a number or string.");
5
+ error(
6
+ `Failed to normal sort since the first value was not a number or string and was instead: ${type(
7
+ a,
8
+ )}`,
9
+ );
6
10
  }
7
11
 
8
12
  if (!isNumber(b) && !isString(b)) {
9
- error("Failed to sort since the second value was not a number or string.");
13
+ error(
14
+ `Failed to normal sort since the second value was not a number or string and was instead: ${type(
15
+ b,
16
+ )}`,
17
+ );
10
18
  }
11
19
 
12
20
  if (a < b) {
@@ -43,13 +51,17 @@ export function sortObjectArrayByKey(key: string) {
43
51
  return (a: unknown, b: unknown): -1 | 0 | 1 => {
44
52
  if (!isTable(a)) {
45
53
  error(
46
- `Failed to sort an object by the key of "${key}" since the first element was not a table.`,
54
+ `Failed to sort an object array by the key of "${key}" since the first element was not a table and was instead: ${type(
55
+ a,
56
+ )}`,
47
57
  );
48
58
  }
49
59
 
50
60
  if (!isTable(b)) {
51
61
  error(
52
- `Failed to sort an object by the key of "${key}" since the second element was not a table.`,
62
+ `Failed to sort an object array by the key of "${key}" since the second element was not a table and was instead: ${type(
63
+ b,
64
+ )}.`,
53
65
  );
54
66
  }
55
67
 
@@ -126,3 +138,68 @@ export function sortTwoDimensionalArray<T>(a: T[], b: T[]): -1 | 0 | 1 {
126
138
 
127
139
  return sortNormal(firstElement1, firstElement2);
128
140
  }
141
+
142
+ /**
143
+ * Helper function to sort an array in a stable way.
144
+ *
145
+ * This is useful because by default, the transpiled `Array.sort` method from TSTL is not stable.
146
+ */
147
+ export function stableSort<T>(
148
+ array: T[],
149
+ sortFunc: (a: T, b: T) => -1 | 0 | 1 = sortNormal,
150
+ ): T[] {
151
+ // Base case: an array of zero or one elements is already sorted
152
+ if (array.length <= 1) {
153
+ return array;
154
+ }
155
+
156
+ // Split the array into two halves.
157
+ const middleIndex = Math.floor(array.length / 2);
158
+ const leftArray = array.slice(0, middleIndex);
159
+ const rightArray = array.slice(middleIndex);
160
+
161
+ // Recursively sort each half.
162
+ const sortedLeftArray = stableSort(leftArray, sortFunc);
163
+ const sortedRightArray = stableSort(rightArray, sortFunc);
164
+
165
+ // Merge the two sorted halves.
166
+ const mergedArray: T[] = [];
167
+ let leftIndex = 0;
168
+ let rightIndex = 0;
169
+ while (
170
+ leftIndex < sortedLeftArray.length &&
171
+ rightIndex < sortedRightArray.length
172
+ ) {
173
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
174
+ const left = sortedLeftArray[leftIndex]!;
175
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
176
+ const right = sortedRightArray[rightIndex]!;
177
+
178
+ const sortResult = sortFunc(left, right);
179
+ if (sortResult === -1 || sortResult === 0) {
180
+ mergedArray.push(left);
181
+ leftIndex++;
182
+ } else {
183
+ mergedArray.push(right);
184
+ rightIndex++;
185
+ }
186
+ }
187
+
188
+ // Add any remaining elements from the left array.
189
+ while (leftIndex < sortedLeftArray.length) {
190
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
191
+ const left = sortedLeftArray[leftIndex]!;
192
+ mergedArray.push(left);
193
+ leftIndex++;
194
+ }
195
+
196
+ // Add any remaining elements from the right array.
197
+ while (rightIndex < sortedRightArray.length) {
198
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
199
+ const right = sortedRightArray[rightIndex]!;
200
+ mergedArray.push(right);
201
+ rightIndex++;
202
+ }
203
+
204
+ return mergedArray;
205
+ }
@@ -1,4 +1,8 @@
1
1
  export function capitalizeFirstLetter(string: string): string {
2
+ if (string === "") {
3
+ return string;
4
+ }
5
+
2
6
  const firstCharacter = string.charAt(0);
3
7
  const capitalizedFirstLetter = firstCharacter.toUpperCase();
4
8
  const restOfString = string.slice(1);