isaacscript-common 14.1.0 → 14.1.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"TSTLClassMetatable.d.ts","sourceRoot":"","sources":["../../../src/interfaces/TSTLClassMetatable.ts"],"names":[],"mappings":";;;;AAAA,MAAM,WAAW,kBAAkB;IACjC,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,YAAY,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;KACrD,CAAC;CACH"}
1
+ {"version":3,"file":"TSTLClassMetatable.d.ts","sourceRoot":"","sources":["../../../src/interfaces/TSTLClassMetatable.ts"],"names":[],"mappings":";;;;AAAA,MAAM,WAAW,kBAAkB;IACjC,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE;QACX,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,YAAY,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;KAErD,CAAC;CACH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isaacscript-common",
3
- "version": "14.1.0",
3
+ "version": "14.1.2",
4
4
  "description": "Helper functions and features for IsaacScript mods.",
5
5
  "keywords": [
6
6
  "isaac",
package/src/callbacks.ts CHANGED
@@ -48,6 +48,7 @@ import { PostSpikesRender } from "./classes/callbacks/PostSpikesRender";
48
48
  import { PreBerserkDeath } from "./classes/callbacks/PreBerserkDeath";
49
49
  import { PreCustomRevive } from "./classes/callbacks/PreCustomRevive";
50
50
  import { ModCallbackCustom2 } from "./enums/ModCallbackCustom2";
51
+ import { getEnumValues } from "./functions/enums";
51
52
  import { newObjectWithEnumKeys } from "./functions/utils";
52
53
 
53
54
  const MOD_CALLBACK_CUSTOM_TO_CLASS = newObjectWithEnumKeys(ModCallbackCustom2, {
@@ -116,19 +117,18 @@ const MOD_CALLBACK_CUSTOM_TO_CLASS = newObjectWithEnumKeys(ModCallbackCustom2, {
116
117
  } as const);
117
118
 
118
119
  export type ModCallbackCustomToClass = {
119
- [Property in keyof typeof MOD_CALLBACK_CUSTOM_TO_CLASS]: InstanceType<
120
- typeof MOD_CALLBACK_CUSTOM_TO_CLASS[Property]
120
+ readonly [key in keyof typeof MOD_CALLBACK_CUSTOM_TO_CLASS]: InstanceType<
121
+ typeof MOD_CALLBACK_CUSTOM_TO_CLASS[key]
121
122
  >;
122
123
  };
123
124
 
124
125
  export function getCallbacks(): ModCallbackCustomToClass {
125
- const entries = Object.entries(MOD_CALLBACK_CUSTOM_TO_CLASS);
126
- const instantiatedClasses = entries.map(
127
- ([modCallbackCustom, constructor]) => [
128
- modCallbackCustom,
129
- new constructor(),
130
- ],
131
- );
126
+ const instantiatedClasses: Record<number, unknown> = {};
127
+
128
+ for (const modCallbackCustom of getEnumValues(ModCallbackCustom2)) {
129
+ const constructor = MOD_CALLBACK_CUSTOM_TO_CLASS[modCallbackCustom];
130
+ instantiatedClasses[modCallbackCustom] = new constructor();
131
+ }
132
132
 
133
133
  return instantiatedClasses as unknown as ModCallbackCustomToClass;
134
134
  }
@@ -1,5 +1,6 @@
1
1
  import { ModCallback } from "isaac-typescript-definitions";
2
2
  import { getCallbacks } from "../callbacks";
3
+ import { EXPORTED_METHOD_NAMES_KEY } from "../decorators";
3
4
  import { ISCFeature } from "../enums/ISCFeature";
4
5
  import { ModCallbackCustom } from "../enums/ModCallbackCustom";
5
6
  import { ModCallbackCustom2 } from "../enums/ModCallbackCustom2";
@@ -10,7 +11,10 @@ import {
10
11
  } from "../features/saveDataManager/exports";
11
12
  import { getTime } from "../functions/debugFunctions";
12
13
  import { getParentFunctionDescription } from "../functions/log";
13
- import { getTSTLClassMethods, getTSTLClassName } from "../functions/tstlClass";
14
+ import {
15
+ getTSTLClassConstructor,
16
+ getTSTLClassName,
17
+ } from "../functions/tstlClass";
14
18
  import { AddCallbackParametersCustom } from "../interfaces/private/AddCallbackParametersCustom";
15
19
  import { AddCallbackParametersCustom2 } from "../interfaces/private/AddCallbackParametersCustom2";
16
20
  import { CALLBACK_REGISTER_FUNCTIONS } from "../objects/callbackRegisterFunctions";
@@ -213,14 +217,14 @@ export class ModUpgraded implements Mod {
213
217
  }
214
218
 
215
219
  /**
216
- * This method should only be used by the `upgradeMod` function. Returns the class methods from
217
- * the features that were added.
220
+ * This method should only be used by the `upgradeMod` function. Returns the names of the exported
221
+ * class methods from the features that were added.
218
222
  */
219
223
  public initOptionalFeature(feature: ISCFeature): FunctionTuple[] {
220
224
  const featureClass = this.features[feature];
221
225
  this.initFeature(featureClass);
222
226
 
223
- return getTSTLClassMethods(featureClass);
227
+ return getExportedMethodsFromFeature(featureClass);
224
228
  }
225
229
 
226
230
  // ----------------------
@@ -302,3 +306,41 @@ export class ModUpgraded implements Mod {
302
306
  }
303
307
  }
304
308
  }
309
+
310
+ /**
311
+ * In this context, "exported" methods are methods that are annotated with the "@Exported"
312
+ * decorator, which signify that the method should be attached to the `ModUpgraded` class.
313
+ *
314
+ * Exported methods are stored in an internal static array on the class that is created by the
315
+ * decorator.
316
+ */
317
+ function getExportedMethodsFromFeature(featureClass: unknown): FunctionTuple[] {
318
+ const constructor = getTSTLClassConstructor(featureClass) as Record<
319
+ string,
320
+ unknown
321
+ >;
322
+ const exportedMethodNames = constructor[
323
+ EXPORTED_METHOD_NAMES_KEY
324
+ ] as string[];
325
+
326
+ return exportedMethodNames.map((name) => {
327
+ const featureClassRecord = featureClass as Record<
328
+ string,
329
+ (...args: unknown[]) => unknown
330
+ >;
331
+ const method = featureClassRecord[name];
332
+ if (method === undefined) {
333
+ error(`Failed to find a decorated exported method: ${name}`);
334
+ }
335
+
336
+ // In order for "this" to work properly in the method, we have to wrap the method invocation in
337
+ // an arrow function.
338
+ const wrappedMethod = (...args: unknown[]) =>
339
+ // We cannot split out the method to a separate variable or else the "self" parameter will not
340
+ // be properly passed to the method.
341
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
342
+ featureClassRecord[name]!(...args);
343
+
344
+ return [name, wrappedMethod];
345
+ });
346
+ }
@@ -19,6 +19,4 @@ export class Feature {
19
19
  public featuresUsed?: ISCFeature[];
20
20
  public callbacksUsed?: CallbackTuple[];
21
21
  public customCallbacksUsed?: CustomCallbackTuple[];
22
-
23
- public exportedMethods: string[] = [];
24
22
  }
@@ -83,7 +83,7 @@ export function upgradeMod<T extends ISCFeature = never>(
83
83
  legacyInit(mod); // TODO: remove
84
84
 
85
85
  for (const feature of features) {
86
- const tstlClassMethods = mod.initOptionalFeature(feature);
86
+ const exportedMethodTuples = mod.initOptionalFeature(feature);
87
87
 
88
88
  // If the optional feature provides helper functions, attach them to the base mod object. (This
89
89
  // provides a convenient API for end-users.)
@@ -91,7 +91,7 @@ export function upgradeMod<T extends ISCFeature = never>(
91
91
  string,
92
92
  (...args: unknown[]) => unknown
93
93
  >;
94
- for (const [funcName, func] of tstlClassMethods) {
94
+ for (const [funcName, func] of exportedMethodTuples) {
95
95
  modRecord[funcName] = func;
96
96
  }
97
97
  }
package/src/decorators.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { Feature } from "./classes/private/Feature";
2
2
 
3
+ export const EXPORTED_METHOD_NAMES_KEY = "__exportedMethodNames";
4
+
3
5
  /**
4
6
  * A decorator function that signifies that the decorated class method should be added to the
5
7
  * `ModUpgraded` object.
@@ -8,6 +10,19 @@ import { Feature } from "./classes/private/Feature";
8
10
  */
9
11
  export function Exported() {
10
12
  return <T extends Feature>(target: T, propertyKey: keyof T): void => {
11
- target.exportedMethods.push(propertyKey as string);
13
+ // Since the decorator runs prior to instantiation, we only have access to get and set static
14
+ // properties, which are located on the "constructor" table.
15
+ const constructor = target.constructor as unknown as LuaTable<
16
+ AnyNotNil,
17
+ unknown
18
+ >;
19
+ if (!constructor.has(EXPORTED_METHOD_NAMES_KEY)) {
20
+ constructor.set(EXPORTED_METHOD_NAMES_KEY, []);
21
+ }
22
+
23
+ const exportedMethodNames = constructor.get(
24
+ EXPORTED_METHOD_NAMES_KEY,
25
+ ) as string[];
26
+ exportedMethodNames.push(propertyKey as string);
12
27
  };
13
28
  }
@@ -1,6 +1,5 @@
1
1
  import { DefaultMap } from "../classes/DefaultMap";
2
2
  import { TSTLClassMetatable } from "../interfaces/TSTLClassMetatable";
3
- import { FunctionTuple } from "../types/FunctionTuple";
4
3
  import { TSTLClass } from "../types/TSTLClass";
5
4
  import { isString, isTable } from "./types";
6
5
 
@@ -33,20 +32,6 @@ export function getTSTLClassConstructor(
33
32
  return metatable.constructor;
34
33
  }
35
34
 
36
- export function getTSTLClassMethods(object: unknown): FunctionTuple[] {
37
- const constructor = getTSTLClassConstructor(object);
38
- if (constructor === undefined) {
39
- return [];
40
- }
41
-
42
- const classEntries = Object.entries(constructor.prototype);
43
- return classEntries.filter(
44
- ([key, value]) =>
45
- // Ignore the stock TSTL keys that are baked into every class.
46
- !TSTL_CLASS_METATABLE_KEYS.has(key) && type(value) === "function",
47
- );
48
- }
49
-
50
35
  /**
51
36
  * Helper function to get the name of a TypeScriptToLua class from the instantiated class object.
52
37
  *
@@ -4,5 +4,6 @@ export interface TSTLClassMetatable {
4
4
  constructor: {
5
5
  name: string;
6
6
  prototype: LuaMetatable<LuaMap<AnyNotNil, unknown>>;
7
+ // Other static things on the class will be here, if any.
7
8
  };
8
9
  }