Tested on TypeScript 3.9.7
Solution
type EnumTypeString<TEnum extends string> =
{ [key in string]: TEnum | string; }
type EnumTypeNumber<TEnum extends number> =
{ [key in string]: TEnum | number; }
| { [key in number]: string; }
type EnumType<TEnum extends string | number> =
(TEnum extends string ? EnumTypeString<TEnum> : never)
| (TEnum extends number ? EnumTypeNumber<TEnum> : never)
type EnumOf<TEnumType> = TEnumType extends EnumType<infer U>
? U
: never
Usage
EnumType:
function forEachEnum<TEnum extends string | number>(
enumType: EnumType<TEnum>,
callback: (value: TEnum, key: string) => boolean|void,
) {
for (let key in enumType) {
if (Object.prototype.hasOwnProperty.call(enumType, key) && isNaN(Number(key))) {
const value = enumType[key] as any
if (callback(value, key)) {
return
}
}
}
}
EnumOf:
function forEachEnum2<TEnumType>(
enumType: TEnumType,
callback: (value: EnumOf<TEnumType>, key: string) => boolean|void,
) {
for (let key in enumType) {
if (Object.prototype.hasOwnProperty.call(enumType, key) && isNaN(Number(key))) {
const value = enumType[key] as any
if (callback(value, key)) {
return
}
}
}
}
Tests
enum EnumAsString {
Value1 = 'value 1',
Value2 = 'value 2',
}
enum EnumAsNumber {
Value1 = 1,
Value2 = 2,
}
// Error
let sn: EnumType<string> = EnumAsNumber
// Correct
let ns: EnumType<number> = EnumAsString // I have not found a solution for the error here
let nn: EnumType<number> = EnumAsNumber
let Nn: EnumType<EnumAsNumber> = EnumAsNumber
let ss: EnumType<string> = EnumAsString
let Ss: EnumType<EnumAsString> = EnumAsString
forEachEnum(EnumAsString, value => {
let e: EnumAsString = value
let s: string = value
let n: number = value // Error
})
forEachEnum(EnumAsNumber, value => {
let e: EnumAsNumber = value
let s: string = value // Error
let n: number = value
})
forEachEnum2(EnumAsString, value => {
let e: EnumAsString = value
let s: string = value
let n: number = value // Error
})
forEachEnum2(EnumAsNumber, value => {
let e: EnumAsNumber = value
let s: string = value // Error
let n: number = value
})