[Dev] Moved type helpers to separate directory; (#6123)

* [Dev] Moved type helpers to separate directory; renamed `EnumValues` to `ObjectValues` and enforced usage

* Update tsconfig.json

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Fixed import issue

* Updated documentation slightly

---------

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
Co-authored-by: Dean <69436131+emdeann@users.noreply.github.com>
This commit is contained in:
Bertie690 2025-07-30 21:17:17 -04:00 committed by GitHub
parent 17eeceb4f3
commit 1ae69a4183
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 70 additions and 43 deletions

View File

@ -1,6 +1,6 @@
import type { ArenaTagTypeMap } from "#data/arena-tag"; import type { ArenaTagTypeMap } from "#data/arena-tag";
import type { ArenaTagType } from "#enums/arena-tag-type"; import type { ArenaTagType } from "#enums/arena-tag-type";
import type { NonFunctionProperties } from "./type-helpers"; import type { NonFunctionProperties } from "#types/type-helpers";
/** Subset of {@linkcode ArenaTagType}s that apply some negative effect to pokemon that switch in ({@link https://bulbapedia.bulbagarden.net/wiki/List_of_moves_that_cause_entry_hazards#List_of_traps | entry hazards} and Imprison. */ /** Subset of {@linkcode ArenaTagType}s that apply some negative effect to pokemon that switch in ({@link https://bulbapedia.bulbagarden.net/wiki/List_of_moves_that_cause_entry_hazards#List_of_traps | entry hazards} and Imprison. */
export type ArenaTrapTagType = export type ArenaTrapTagType =

View File

@ -1,18 +1,14 @@
import type { ObjectValues } from "#types/type-helpers";
/** Union type accepting any TS Enum or `const object`, with or without reverse mapping. */ /** Union type accepting any TS Enum or `const object`, with or without reverse mapping. */
export type EnumOrObject = Record<string | number, string | number>; export type EnumOrObject = Record<string | number, string | number>;
/**
* Utility type to extract the enum values from a `const object`,
* or convert an `enum` interface produced by `typeof Enum` into the union type representing its values.
*/
export type EnumValues<E> = E[keyof E];
/** /**
* Generic type constraint representing a TS numeric enum with reverse mappings. * Generic type constraint representing a TS numeric enum with reverse mappings.
* @example * @example
* TSNumericEnum<typeof WeatherType> * TSNumericEnum<typeof WeatherType>
*/ */
export type TSNumericEnum<T extends EnumOrObject> = number extends EnumValues<T> ? T : never; export type TSNumericEnum<T extends EnumOrObject> = number extends ObjectValues<T> ? T : never;
/** Generic type constraint representing a non reverse-mapped TS enum or `const object`. */ /** Generic type constraint representing a non reverse-mapped TS enum or `const object`. */
export type NormalEnum<T extends EnumOrObject> = Exclude<T, TSNumericEnum<T>>; export type NormalEnum<T extends EnumOrObject> = Exclude<T, TSNumericEnum<T>>;

View File

@ -6,8 +6,6 @@
import type { AbAttr } from "#abilities/ability"; import type { AbAttr } from "#abilities/ability";
// biome-ignore-end lint/correctness/noUnusedImports: Used in a tsdoc comment // biome-ignore-end lint/correctness/noUnusedImports: Used in a tsdoc comment
import type { EnumValues } from "#types/enum-types";
/** /**
* Exactly matches the type of the argument, preventing adding additional properties. * Exactly matches the type of the argument, preventing adding additional properties.
* *
@ -37,16 +35,25 @@ export type Mutable<T> = {
}; };
/** /**
* Type helper to obtain the keys associated with a given value inside a `const object`. * Type helper to obtain the keys associated with a given value inside an object.
* @typeParam O - The type of the object * @typeParam O - The type of the object
* @typeParam V - The type of one of O's values * @typeParam V - The type of one of O's values
*/ */
export type InferKeys<O extends Record<keyof any, unknown>, V extends EnumValues<O>> = { export type InferKeys<O extends object, V extends ObjectValues<O>> = {
[K in keyof O]: O[K] extends V ? K : never; [K in keyof O]: O[K] extends V ? K : never;
}[keyof O]; }[keyof O];
/** /**
* Type helper that matches any `Function` type. Equivalent to `Function`, but will not raise a warning from Biome. * Utility type to obtain the values of a given object. \
* Functions similar to `keyof E`, except producing the values instead of the keys.
* @remarks
* This can be used to convert an `enum` interface produced by `typeof Enum` into the union type representing its members.
*/
export type ObjectValues<E extends object> = E[keyof E];
/**
* Type helper that matches any `Function` type.
* Equivalent to `Function`, but will not raise a warning from Biome.
*/ */
export type AnyFn = (...args: any[]) => any; export type AnyFn = (...args: any[]) => any;
@ -65,6 +72,7 @@ export type NonFunctionProperties<T> = {
/** /**
* Type helper to extract out non-function properties from a type, recursively applying to nested properties. * Type helper to extract out non-function properties from a type, recursively applying to nested properties.
* This can be used to mimic the effects of JSON serialization and de-serialization on a given type.
*/ */
export type NonFunctionPropertiesRecursive<Class> = { export type NonFunctionPropertiesRecursive<Class> = {
[K in keyof Class as Class[K] extends AnyFn ? never : K]: Class[K] extends Array<infer U> [K in keyof Class as Class[K] extends AnyFn ? never : K]: Class[K] extends Array<infer U>

View File

@ -3,6 +3,7 @@
import type { Pokemon } from "#field/pokemon"; import type { Pokemon } from "#field/pokemon";
import type { ModifierConstructorMap } from "#modifiers/modifier"; import type { ModifierConstructorMap } from "#modifiers/modifier";
import type { ModifierType, WeightedModifierType } from "#modifiers/modifier-type"; import type { ModifierType, WeightedModifierType } from "#modifiers/modifier-type";
import type { ObjectValues } from "#types/type-helpers";
export type ModifierTypeFunc = () => ModifierType; export type ModifierTypeFunc = () => ModifierType;
export type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: number) => number; export type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: number) => number;
@ -19,7 +20,7 @@ export type ModifierInstanceMap = {
/** /**
* Union type of all modifier constructors. * Union type of all modifier constructors.
*/ */
export type ModifierClass = ModifierConstructorMap[keyof ModifierConstructorMap]; export type ModifierClass = ObjectValues<ModifierConstructorMap>;
/** /**
* Union type of all modifier names as strings. * Union type of all modifier names as strings.

View File

@ -1,4 +1,5 @@
import type { PhaseConstructorMap } from "#app/phase-manager"; import type { PhaseConstructorMap } from "#app/phase-manager";
import type { ObjectValues } from "#types/type-helpers";
// Intentionally export the types of everything in phase-manager, as this file is meant to be // Intentionally export the types of everything in phase-manager, as this file is meant to be
// the centralized place for type definitions for the phase system. // the centralized place for type definitions for the phase system.
@ -17,7 +18,7 @@ export type PhaseMap = {
/** /**
* Union type of all phase constructors. * Union type of all phase constructors.
*/ */
export type PhaseClass = PhaseConstructorMap[keyof PhaseConstructorMap]; export type PhaseClass = ObjectValues<PhaseConstructorMap>;
/** /**
* Union type of all phase names as strings. * Union type of all phase names as strings.

View File

@ -1,3 +1,5 @@
import type { ObjectValues } from "#types/type-helpers";
/** /**
* Not to be confused with an Ability Attribute. * Not to be confused with an Ability Attribute.
* This is an object literal storing the slot that an ability can occupy. * This is an object literal storing the slot that an ability can occupy.
@ -8,4 +10,4 @@ export const AbilityAttr = Object.freeze({
ABILITY_HIDDEN: 4, ABILITY_HIDDEN: 4,
}); });
export type AbilityAttr = typeof AbilityAttr[keyof typeof AbilityAttr]; export type AbilityAttr = ObjectValues<typeof AbilityAttr>;

View File

@ -1,3 +1,5 @@
import type { ObjectValues } from "#types/type-helpers";
export const DexAttr = Object.freeze({ export const DexAttr = Object.freeze({
NON_SHINY: 1n, NON_SHINY: 1n,
SHINY: 2n, SHINY: 2n,
@ -8,4 +10,4 @@ export const DexAttr = Object.freeze({
VARIANT_3: 64n, VARIANT_3: 64n,
DEFAULT_FORM: 128n, DEFAULT_FORM: 128n,
}); });
export type DexAttr = typeof DexAttr[keyof typeof DexAttr]; export type DexAttr = ObjectValues<typeof DexAttr>;

View File

@ -1,7 +1,9 @@
import type { ObjectValues } from "#types/type-helpers";
export const GachaType = Object.freeze({ export const GachaType = Object.freeze({
MOVE: 0, MOVE: 0,
LEGENDARY: 1, LEGENDARY: 1,
SHINY: 2 SHINY: 2
}); });
export type GachaType = typeof GachaType[keyof typeof GachaType]; export type GachaType = ObjectValues<typeof GachaType>;

View File

@ -1,3 +1,5 @@
import type { ObjectValues } from "#types/type-helpers";
/** The result of a hit check calculation */ /** The result of a hit check calculation */
export const HitCheckResult = { export const HitCheckResult = {
/** Hit checks haven't been evaluated yet in this pass */ /** Hit checks haven't been evaluated yet in this pass */
@ -20,4 +22,4 @@ export const HitCheckResult = {
ERROR: 8, ERROR: 8,
} as const; } as const;
export type HitCheckResult = typeof HitCheckResult[keyof typeof HitCheckResult]; export type HitCheckResult = ObjectValues<typeof HitCheckResult>;

View File

@ -1,5 +1,5 @@
import type { EnumOrObject, EnumValues, NormalEnum, TSNumericEnum } from "#app/@types/enum-types"; import type { EnumOrObject, NormalEnum, TSNumericEnum } from "#types/enum-types";
import type { InferKeys } from "#app/@types/type-helpers"; import type { InferKeys, ObjectValues } from "#types/type-helpers";
/** /**
* Return the string keys of an Enum object, excluding reverse-mapped numbers. * Return the string keys of an Enum object, excluding reverse-mapped numbers.
@ -61,7 +61,7 @@ export function getEnumValues<E extends EnumOrObject>(enumType: TSNumericEnum<E>
* If multiple keys map to the same value, the first one (in insertion order) will be retrieved, * If multiple keys map to the same value, the first one (in insertion order) will be retrieved,
* but the return type will be the union of ALL their corresponding keys. * but the return type will be the union of ALL their corresponding keys.
*/ */
export function enumValueToKey<T extends EnumOrObject, V extends EnumValues<T>>( export function enumValueToKey<T extends EnumOrObject, V extends ObjectValues<T>>(
object: NormalEnum<T>, object: NormalEnum<T>,
val: V, val: V,
): InferKeys<T, V> { ): InferKeys<T, V> {

View File

@ -1,5 +1,6 @@
import type { EnumOrObject, EnumValues, NormalEnum, TSNumericEnum } from "#app/@types/enum-types";
import type { enumValueToKey, getEnumKeys, getEnumValues } from "#app/utils/enums"; import type { enumValueToKey, getEnumKeys, getEnumValues } from "#app/utils/enums";
import type { EnumOrObject, NormalEnum, TSNumericEnum } from "#types/enum-types";
import type { ObjectValues } from "#types/type-helpers";
import { describe, expectTypeOf, it } from "vitest"; import { describe, expectTypeOf, it } from "vitest";
enum testEnumNum { enum testEnumNum {
@ -16,21 +17,33 @@ const testObjNum = { testON1: 1, testON2: 2 } as const;
const testObjString = { testOS1: "apple", testOS2: "banana" } as const; const testObjString = { testOS1: "apple", testOS2: "banana" } as const;
describe("Enum Type Helpers", () => { interface testObject {
describe("EnumValues", () => { key_1: "1";
it("should go from enum object type to value type", () => { key_2: "2";
expectTypeOf<EnumValues<typeof testEnumNum>>().toEqualTypeOf<testEnumNum>(); key_3: "3";
expectTypeOf<EnumValues<typeof testEnumNum>>().branded.toEqualTypeOf<1 | 2>(); }
expectTypeOf<EnumValues<typeof testEnumString>>().toEqualTypeOf<testEnumString>(); describe("Enum Type Helpers", () => {
expectTypeOf<EnumValues<typeof testEnumString>>().toEqualTypeOf<testEnumString.testS1 | testEnumString.testS2>(); describe("ObjectValues", () => {
expectTypeOf<EnumValues<typeof testEnumString>>().toMatchTypeOf<"apple" | "banana">(); it("should produce a union of an object's values", () => {
expectTypeOf<ObjectValues<testObject>>().toEqualTypeOf<"1" | "2" | "3">();
});
it("should go from enum object type to value type", () => {
expectTypeOf<ObjectValues<typeof testEnumNum>>().toEqualTypeOf<testEnumNum>();
expectTypeOf<ObjectValues<typeof testEnumNum>>().branded.toEqualTypeOf<1 | 2>();
expectTypeOf<ObjectValues<typeof testEnumString>>().toEqualTypeOf<testEnumString>();
expectTypeOf<ObjectValues<typeof testEnumString>>().toEqualTypeOf<
testEnumString.testS1 | testEnumString.testS2
>();
expectTypeOf<ObjectValues<typeof testEnumString>>().toExtend<"apple" | "banana">();
}); });
it("should produce union of const object values as type", () => { it("should produce union of const object values as type", () => {
expectTypeOf<EnumValues<typeof testObjNum>>().toEqualTypeOf<1 | 2>(); expectTypeOf<ObjectValues<typeof testObjNum>>().toEqualTypeOf<1 | 2>();
expectTypeOf<ObjectValues<typeof testObjString>>().toEqualTypeOf<"apple" | "banana">();
expectTypeOf<EnumValues<typeof testObjString>>().toEqualTypeOf<"apple" | "banana">();
}); });
}); });
@ -38,7 +51,6 @@ describe("Enum Type Helpers", () => {
it("should match numeric enums", () => { it("should match numeric enums", () => {
expectTypeOf<TSNumericEnum<typeof testEnumNum>>().toEqualTypeOf<typeof testEnumNum>(); expectTypeOf<TSNumericEnum<typeof testEnumNum>>().toEqualTypeOf<typeof testEnumNum>();
}); });
it("should not match string enums or const objects", () => { it("should not match string enums or const objects", () => {
expectTypeOf<TSNumericEnum<typeof testEnumString>>().toBeNever(); expectTypeOf<TSNumericEnum<typeof testEnumString>>().toBeNever();
expectTypeOf<TSNumericEnum<typeof testObjNum>>().toBeNever(); expectTypeOf<TSNumericEnum<typeof testObjNum>>().toBeNever();
@ -59,19 +71,19 @@ describe("Enum Type Helpers", () => {
describe("EnumOrObject", () => { describe("EnumOrObject", () => {
it("should match any enum or const object", () => { it("should match any enum or const object", () => {
expectTypeOf<typeof testEnumNum>().toMatchTypeOf<EnumOrObject>(); expectTypeOf<typeof testEnumNum>().toExtend<EnumOrObject>();
expectTypeOf<typeof testEnumString>().toMatchTypeOf<EnumOrObject>(); expectTypeOf<typeof testEnumString>().toExtend<EnumOrObject>();
expectTypeOf<typeof testObjNum>().toMatchTypeOf<EnumOrObject>(); expectTypeOf<typeof testObjNum>().toExtend<EnumOrObject>();
expectTypeOf<typeof testObjString>().toMatchTypeOf<EnumOrObject>(); expectTypeOf<typeof testObjString>().toExtend<EnumOrObject>();
}); });
it("should not match an enum value union w/o typeof", () => { it("should not match an enum value union w/o typeof", () => {
expectTypeOf<testEnumNum>().not.toMatchTypeOf<EnumOrObject>(); expectTypeOf<testEnumNum>().not.toExtend<EnumOrObject>();
expectTypeOf<testEnumString>().not.toMatchTypeOf<EnumOrObject>(); expectTypeOf<testEnumString>().not.toExtend<EnumOrObject>();
}); });
it("should be equivalent to `TSNumericEnum | NormalEnum`", () => { it("should be equivalent to `TSNumericEnum | NormalEnum`", () => {
expectTypeOf<EnumOrObject>().branded.toEqualTypeOf<TSNumericEnum<EnumOrObject> | NormalEnum<EnumOrObject>>(); expectTypeOf<EnumOrObject>().toEqualTypeOf<TSNumericEnum<EnumOrObject> | NormalEnum<EnumOrObject>>();
}); });
}); });
}); });
@ -80,6 +92,7 @@ describe("Enum Functions", () => {
describe("getEnumKeys", () => { describe("getEnumKeys", () => {
it("should retrieve keys of numeric enum", () => { it("should retrieve keys of numeric enum", () => {
expectTypeOf<typeof getEnumKeys<typeof testEnumNum>>().returns.toEqualTypeOf<("testN1" | "testN2")[]>(); expectTypeOf<typeof getEnumKeys<typeof testEnumNum>>().returns.toEqualTypeOf<("testN1" | "testN2")[]>();
expectTypeOf<typeof getEnumKeys<typeof testObjNum>>().returns.toEqualTypeOf<("testON1" | "testON2")[]>();
}); });
}); });

View File

@ -49,7 +49,7 @@
"./system/*.ts" "./system/*.ts"
], ],
"#trainers/*": ["./data/trainers/*.ts"], "#trainers/*": ["./data/trainers/*.ts"],
"#types/*": ["./@types/*.ts", "./typings/phaser/*.ts"], "#types/*": ["./@types/helpers/*.ts", "./@types/*.ts", "./typings/phaser/*.ts"],
"#ui/*": ["./ui/battle-info/*.ts", "./ui/settings/*.ts", "./ui/*.ts"], "#ui/*": ["./ui/battle-info/*.ts", "./ui/settings/*.ts", "./ui/*.ts"],
"#utils/*": ["./utils/*.ts"], "#utils/*": ["./utils/*.ts"],
"#data/*": ["./data/pokemon-forms/*.ts", "./data/pokemon/*.ts", "./data/*.ts"], "#data/*": ["./data/pokemon-forms/*.ts", "./data/pokemon/*.ts", "./data/*.ts"],