UNPKG

isaacscript-common

Version:

Helper functions and features for IsaacScript mods.

242 lines (241 loc) • 10.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getEnumEntries = getEnumEntries; exports.getEnumKeys = getEnumKeys; exports.getEnumLength = getEnumLength; exports.getEnumNames = getEnumNames; exports.getEnumValues = getEnumValues; exports.getHighestEnumValue = getHighestEnumValue; exports.getLowestEnumValue = getLowestEnumValue; exports.getRandomEnumValue = getRandomEnumValue; exports.interfaceSatisfiesEnum = interfaceSatisfiesEnum; exports.isEnumValue = isEnumValue; exports.validateCustomEnum = validateCustomEnum; exports.validateEnumContiguous = validateEnumContiguous; const ReadonlySet_1 = require("../types/ReadonlySet"); const array_1 = require("./array"); const sort_1 = require("./sort"); const types_1 = require("./types"); const utils_1 = require("./utils"); /** * TypeScriptToLua will transpile TypeScript number enums to Lua tables that have a double mapping. * Thus, when you iterate over them, you will get both the names of the enums and the values of the * enums, in a random order. Use this helper function to get the entries of the enum with the * reverse mappings filtered out. * * This function will return the enum values in a sorted order, which may not necessarily be the * same order as which they were declared in. (It is impossible to get the declaration order at * run-time.) * * This function will work properly for both number enums and string enums. (Reverse mappings are * not created for string enums.) * * Also see the `getEnumKeys` and `getEnumValues` helper functions. * * For a more in depth explanation, see: * https://isaacscript.github.io/main/gotchas#iterating-over-enums */ function getEnumEntries(transpiledEnum) { const entries = Object.entries(transpiledEnum); const numberEntries = entries.filter(([_key, value]) => typeof value === "number"); // If there are no number values, then this must be a string enum, and no filtration is required. const entriesToReturn = numberEntries.length > 0 ? numberEntries : entries; // The enums will be in a random order (because of "pairs"), so sort them based on the values. // https://stackoverflow.com/questions/5199901/how-to-sort-an-associative-array-by-its-values-in-javascript entriesToReturn.sort(([_key1, value1], [_key2, value2]) => value1 < value2 ? -1 : value1 > value2 ? 1 : 0); return entriesToReturn; } /** * TypeScriptToLua will transpile TypeScript number enums to Lua tables that have a double mapping. * Thus, when you iterate over them, you will get both the names of the enums and the values of the * enums, in a random order. If all you need are the keys of an enum, use this helper function. * * This function will return the enum keys in a sorted order, which may not necessarily be the same * order as which they were declared in. (It is impossible to get the declaration order at * run-time.) * * This function will work properly for both number enums and string enums. (Reverse mappings are * not created for string enums.) * * Also see the `getEnumEntries` and `getEnumValues` helper functions. * * For a more in depth explanation, see: * https://isaacscript.github.io/main/gotchas#iterating-over-enums */ function getEnumKeys(transpiledEnum) { const enumEntries = getEnumEntries(transpiledEnum); return enumEntries.map(([key, _value]) => key); } /** Helper function to get the amount of entries inside of an enum. */ function getEnumLength(transpiledEnum) { const enumEntries = getEnumEntries(transpiledEnum); return enumEntries.length; } /** * TypeScriptToLua will transpile TypeScript number enums to Lua tables that have a double mapping. * Thus, when you iterate over them, you will get both the names of the enums and the values of the * enums, in a random order. If all you need are the names of an enum from the reverse mapping, use * this helper function. * * This function will return the enum names in a sorted order, which may not necessarily be the same * order as which they were declared in. (It is impossible to get the declaration order at * run-time.) * * This function will work properly for both number enums and string enums. (Reverse mappings are * not created for string enums, so their names would be equivalent to what would be returned by the * `getEnumKeys` function.) * * For a more in depth explanation, see: * https://isaacscript.github.io/main/gotchas#iterating-over-enums */ function getEnumNames(transpiledEnum) { const enumNames = []; for (const [key, _value] of pairs(transpiledEnum)) { if ((0, types_1.isString)(key)) { enumNames.push(key); } } // The enum names will be in a random order (because of "pairs"), so sort them. enumNames.sort(); return enumNames; } /** * TypeScriptToLua will transpile TypeScript number enums to Lua tables that have a double mapping. * Thus, when you iterate over them, you will get both the names of the enums and the values of the * enums, in a random order. If all you need are the values of an enum, use this helper function. * * This function will return the enum values in a sorted order, which may not necessarily be the * same order as which they were declared in. (It is impossible to get the declaration order at * run-time.) * * This function will work properly for both number enums and string enums. (Reverse mappings are * not created for string enums.) * * Also see the `getEnumEntries` and `getEnumKeys` helper functions. * * For a more in depth explanation, see: * https://isaacscript.github.io/main/gotchas#iterating-over-enums */ function getEnumValues(transpiledEnum) { const enumEntries = getEnumEntries(transpiledEnum); return enumEntries.map(([_key, value]) => value); } /** * Helper function to get the enum value with the highest value. * * Note that this is not necessarily the enum value that is declared last in the code, since there * is no way to infer that at run-time. * * Throws an error if the provided enum is empty. */ function getHighestEnumValue(transpiledEnum) { const enumValues = getEnumValues(transpiledEnum); const sortedValues = enumValues.toSorted(sort_1.sortNormal); const lastElement = sortedValues.at(-1); (0, utils_1.assertDefined)(lastElement, "Failed to get the highest value from an enum since the enum was empty."); return lastElement; } /** * Helper function to get the enum value with the lowest value. * * Note that this is not necessarily the enum value that is declared first in the code, since there * is no way to infer that at run-time. * * Throws an error if the provided enum is empty. */ function getLowestEnumValue(transpiledEnum) { const enumValues = getEnumValues(transpiledEnum); const sortedValues = enumValues.toSorted(sort_1.sortNormal); const firstElement = sortedValues[0]; (0, utils_1.assertDefined)(firstElement, "Failed to get the lowest value from an enum since the enum was empty."); return firstElement; } /** * Helper function to get a random value from the provided enum. * * If you want an unseeded value, you must explicitly pass `undefined` to the `seedOrRNG` parameter. * * @param transpiledEnum The enum to get the value from. * @param seedOrRNG The `Seed` or `RNG` object to use. If an `RNG` object is provided, the * `RNG.Next` method will be called. If `undefined` is provided, it will default to * a random seed. * @param exceptions Optional. An array of elements to skip over if selected. */ function getRandomEnumValue(transpiledEnum, seedOrRNG, exceptions = []) { const enumValues = getEnumValues(transpiledEnum); return (0, array_1.getRandomArrayElement)(enumValues, seedOrRNG, exceptions); } /** * Helper function to validate that an interface contains all of the keys of an enum. You must * specify both generic parameters in order for this to work properly (i.e. the interface and then * the enum). * * For example: * * ```ts * enum MyEnum { * Value1, * Value2, * Value3, * } * * interface MyEnumToType { * [MyEnum.Value1]: boolean; * [MyEnum.Value2]: number; * [MyEnum.Value3]: string; * } * * interfaceSatisfiesEnum<MyEnumToType, MyEnum>(); * ``` * * This function is only meant to be used with interfaces (i.e. types that will not exist at * run-time). If you are generating an object that will contain all of the keys of an enum, use the * `satisfies` operator with the `Record` type instead. */ function interfaceSatisfiesEnum() { } // eslint-disable-line @typescript-eslint/no-empty-function /** Helper function to validate that a particular value exists inside of an enum. */ function isEnumValue(value, transpiledEnum) { const enumValues = getEnumValues(transpiledEnum); return enumValues.includes(value); } /** * Helper function to check every value of a custom enum for -1. Will throw an run-time error if any * -1 values are found. This is helpful because many methods of the Isaac class return -1 if they * fail. * * For example: * * ```ts * enum EntityTypeCustom { * FOO = Isaac.GetEntityTypeByName("Foo"), * } * * validateCustomEnum("EntityTypeCustom", EntityTypeCustom); * ``` */ function validateCustomEnum(transpiledEnumName, transpiledEnum) { for (const [key, value] of getEnumEntries(transpiledEnum)) { if (value === -1) { error(`Failed to find the custom enum value: ${transpiledEnumName}.${key}`); } } } /** * Helper function to validate if every value in a number enum is contiguous, starting at 0. * * This is useful to automate checking large enums for typos. */ function validateEnumContiguous(transpiledEnumName, transpiledEnum) { const values = getEnumValues(transpiledEnum); const lastValue = values.at(-1); (0, utils_1.assertDefined)(lastValue, "Failed to validate that an enum was contiguous, since the last value was undefined."); if (!(0, types_1.isNumber)(lastValue)) { error("Failed to validate that an enum was contiguous, since the last value was not a number."); } const valuesSet = new ReadonlySet_1.ReadonlySet(values); for (const value of (0, utils_1.iRange)(lastValue)) { if (!valuesSet.has(value)) { error(`Failed to find a custom enum value of ${value} for: ${transpiledEnumName}`); } } }