I don't think the "identify an enum" thing is possible right now. Even if you could, you can't programmatically convert from the Features type (which is an element of the Features enumeration) to the typeof Features type (the mapping from keys to Features elements) without knowing about Features in the first place. Again: the type Features.B for example, doesn't know anything about the string literal "B". Only typeof Features has a property like {B: Features.B}. If you want a type function to convert from Features to Features | keyof typeof Features, you need to mention typeof Features explicitly. So even if you had your dream extends enum notation, you'd still need to write the replacement code with a list of mappings you care about. Sorry.
Addressing just the recursion part, in case it matters, here's how I'd recursively process a type to replace a known enum value with the union of the enum values and the relevant keys:
enum Features {
"A" = 1,
"B" = 2,
"C" = 2
}
type ValueOf<T> = T[keyof T]
type FeatureKey<T extends Features> =
Extract<ValueOf<{
[K in keyof typeof Features]: [K, typeof Features[K]]
}>, [any, T]>[0]
type DeepFeaturesOrKey<T> =
T extends Features ? (T | FeatureKey<T>) :
T extends Array<infer L> ? DeepFeaturesOrKeyArray<L> :
T extends object ? { [K in keyof T]: DeepFeaturesOrKey<T[K]> } : T
interface DeepFeaturesOrKeyArray<L> extends Array<DeepFeaturesOrKey<L>> { }
The tricky bits are extracting a subset of the enum if you don't specify the whole thing (e.g., you're using a discriminated union keyed off a specific enum value), and of course, the whole deep-whatever Array trickery to avoid the dreaded "circular reference" error message mentioned here:
Similar to union and intersection types, conditional types are not permitted to reference themselves recursively (however, indirect references through interface types or object literal types are allowed)
Let's test it:
interface Foo {
bar: string,
baz: Features,
qux: {
a: Features[],
b: boolean
},
quux: Features.A,
quuux: Features.B
}
type DeepFeaturesOrKeyFoo = DeepFeaturesOrKey<Foo>
declare const deepFeaturesOrKeyFoo: DeepFeaturesOrKeyFoo
deepFeaturesOrKeyFoo.bar; // string
deepFeaturesOrKeyFoo.baz; // Features | "A" | "B" | "C"
deepFeaturesOrKeyFoo.qux.a[1]; // Features | "A" | "B" | "C"
deepFeaturesOrKeyFoo.qux.b; // boolean
deepFeaturesOrKeyFoo.quux; // Features.A | "A"
deepFeaturesOrKeyFoo.quuux; // Features.B | "B" | "C"
// note that the compiler considers Features.B and Features.C to be the
// same value, so this becomes Features.B | "B" | "C"
Looks good. Hope that helps.
enumprogrammatically. The closest I've ever gotten is something liketype IsEnum<T> = T extends Record<keyof T, string | number> ? true : false, but in any case you'd need to doEnumOrString<typeof Features>.FeaturestoFeatures | keyof typeof Features?Features).