[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 { 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. */
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. */
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.
* @example
* 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`. */
export type NormalEnum<T extends EnumOrObject> = Exclude<T, TSNumericEnum<T>>;

View File

@ -6,8 +6,6 @@
import type { AbAttr } from "#abilities/ability";
// 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.
*
@ -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 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;
}[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;
@ -65,6 +72,7 @@ export type NonFunctionProperties<T> = {
/**
* 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> = {
[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 { ModifierConstructorMap } from "#modifiers/modifier";
import type { ModifierType, WeightedModifierType } from "#modifiers/modifier-type";
import type { ObjectValues } from "#types/type-helpers";
export type ModifierTypeFunc = () => ModifierType;
export type WeightedModifierTypeWeightFunc = (party: Pokemon[], rerollCount?: number) => number;
@ -19,7 +20,7 @@ export type ModifierInstanceMap = {
/**
* 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.

View File

@ -1,4 +1,5 @@
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
// the centralized place for type definitions for the phase system.
@ -17,7 +18,7 @@ export type PhaseMap = {
/**
* 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.

View File

@ -1,3 +1,5 @@
import type { ObjectValues } from "#types/type-helpers";
/**
* Not to be confused with an Ability Attribute.
* 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,
});
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({
NON_SHINY: 1n,
SHINY: 2n,
@ -8,4 +10,4 @@ export const DexAttr = Object.freeze({
VARIANT_3: 64n,
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({
MOVE: 0,
LEGENDARY: 1,
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 */
export const HitCheckResult = {
/** Hit checks haven't been evaluated yet in this pass */
@ -20,4 +22,4 @@ export const HitCheckResult = {
ERROR: 8,
} 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 { InferKeys } from "#app/@types/type-helpers";
import type { EnumOrObject, NormalEnum, TSNumericEnum } from "#types/enum-types";
import type { InferKeys, ObjectValues } from "#types/type-helpers";
/**
* 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,
* 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>,
val: 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 { EnumOrObject, NormalEnum, TSNumericEnum } from "#types/enum-types";
import type { ObjectValues } from "#types/type-helpers";
import { describe, expectTypeOf, it } from "vitest";
enum testEnumNum {
@ -16,21 +17,33 @@ const testObjNum = { testON1: 1, testON2: 2 } as const;
const testObjString = { testOS1: "apple", testOS2: "banana" } as const;
describe("Enum Type Helpers", () => {
describe("EnumValues", () => {
it("should go from enum object type to value type", () => {
expectTypeOf<EnumValues<typeof testEnumNum>>().toEqualTypeOf<testEnumNum>();
expectTypeOf<EnumValues<typeof testEnumNum>>().branded.toEqualTypeOf<1 | 2>();
interface testObject {
key_1: "1";
key_2: "2";
key_3: "3";
}
expectTypeOf<EnumValues<typeof testEnumString>>().toEqualTypeOf<testEnumString>();
expectTypeOf<EnumValues<typeof testEnumString>>().toEqualTypeOf<testEnumString.testS1 | testEnumString.testS2>();
expectTypeOf<EnumValues<typeof testEnumString>>().toMatchTypeOf<"apple" | "banana">();
describe("Enum Type Helpers", () => {
describe("ObjectValues", () => {
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", () => {
expectTypeOf<EnumValues<typeof testObjNum>>().toEqualTypeOf<1 | 2>();
expectTypeOf<EnumValues<typeof testObjString>>().toEqualTypeOf<"apple" | "banana">();
expectTypeOf<ObjectValues<typeof testObjNum>>().toEqualTypeOf<1 | 2>();
expectTypeOf<ObjectValues<typeof testObjString>>().toEqualTypeOf<"apple" | "banana">();
});
});
@ -38,7 +51,6 @@ describe("Enum Type Helpers", () => {
it("should match numeric enums", () => {
expectTypeOf<TSNumericEnum<typeof testEnumNum>>().toEqualTypeOf<typeof testEnumNum>();
});
it("should not match string enums or const objects", () => {
expectTypeOf<TSNumericEnum<typeof testEnumString>>().toBeNever();
expectTypeOf<TSNumericEnum<typeof testObjNum>>().toBeNever();
@ -59,19 +71,19 @@ describe("Enum Type Helpers", () => {
describe("EnumOrObject", () => {
it("should match any enum or const object", () => {
expectTypeOf<typeof testEnumNum>().toMatchTypeOf<EnumOrObject>();
expectTypeOf<typeof testEnumString>().toMatchTypeOf<EnumOrObject>();
expectTypeOf<typeof testObjNum>().toMatchTypeOf<EnumOrObject>();
expectTypeOf<typeof testObjString>().toMatchTypeOf<EnumOrObject>();
expectTypeOf<typeof testEnumNum>().toExtend<EnumOrObject>();
expectTypeOf<typeof testEnumString>().toExtend<EnumOrObject>();
expectTypeOf<typeof testObjNum>().toExtend<EnumOrObject>();
expectTypeOf<typeof testObjString>().toExtend<EnumOrObject>();
});
it("should not match an enum value union w/o typeof", () => {
expectTypeOf<testEnumNum>().not.toMatchTypeOf<EnumOrObject>();
expectTypeOf<testEnumString>().not.toMatchTypeOf<EnumOrObject>();
expectTypeOf<testEnumNum>().not.toExtend<EnumOrObject>();
expectTypeOf<testEnumString>().not.toExtend<EnumOrObject>();
});
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", () => {
it("should retrieve keys of numeric enum", () => {
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"
],
"#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"],
"#utils/*": ["./utils/*.ts"],
"#data/*": ["./data/pokemon-forms/*.ts", "./data/pokemon/*.ts", "./data/*.ts"],