-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Description
🔎 Search Terms
"Intellisense template literal keys", "VSCode Typescript Intellisense dynamic type", "VSCode Typescript Intellisense dynamic interface", "Intellisense interface suggestion", "Intellisense dynamic type suggestion", "Intellisense template literal dynamic key type", "Intellisense template literal dynamic key interface"
🕗 Version & Regression Information
- This is the behavior in every version I tried (the latest two), and I reviewed the FAQ for entries about Intellisense, template, and literal.
⏯ Playground Link
💻 Code
type EventType = 'start' | 'foo' | 'bar' | 'fooBar' | 'end';
type EventDataType<T extends EventType> =
T extends 'fooBar' ? { foo: string, bar: number }
: T extends 'foo' ? { foo: string }
: T extends 'bar' ? { bar: number }
: T extends EventType ? Record<string, any>
: never;
type ListenerFunction<T extends EventType> = (context: any, data: EventDataType<T>) => void | Promise<void>;
type EvtListenerMethods = {
[K in EventType as `on${Capitalize<K>}`]?: ListenerFunction<K>;
};
/*
* Dynamic solution attempt A: directly implement the generated type.
* Type checking works, but Intellisense does not suggest the methods.
*
* Note A.onBar will raise a type error, as expected
*/
class A implements EvtListenerMethods {
onFoo(context: any, data: EventDataType<'foo'>) { /*...*/ };
onBar(context: any, data: { abcd: any }) { /*...*/ };
}
/*
* Dynamic solution attempt B: implement an interface that extends the generated type.
* Type checking works, but Intellisense does not suggest the methods.
*
* Note B.onBar will raise a type error, as expected
*/
interface EvtListener extends EvtListenerMethods {
}
class B implements EvtListener {
onFoo(context: any, data: EventDataType<'foo'>) { /*...*/ };
onBar(context: any, data: { abcd: any }) { /*...*/ };
}
/*
* Hardcoded solution C: write out every method and its signature.
* Works both with type checking and with Intellisense suggestions.
*
* Note C.onBar will raise a type error as expected
*/
interface EvtListenerHardcoded {
onStart?: ListenerFunction<"start">;
onFooBar?: ListenerFunction<"fooBar">;
onFoo?: ListenerFunction<"foo">;
onBar?: ListenerFunction<"bar">;
onEnd?: ListenerFunction<"end">;
}
class C implements EvtListenerHardcoded {
onFoo(context: any, data: EventDataType<'foo'>) { /*...*/ };
onBar(context: any, data: { abcd: any }) { /*...*/ };
}🙁 Actual behavior
VSCode IntelliSense does not recognize/suggest function names on the dynamically generated type EvtListenerMethods or the extending interface EvtListener (from template literals on the original EventType constant string union). It only does so with the hardcoded interface, which should not be functionally different.
For what it's worth, changing methods to required (removing optional specifier ?) does not change this behavior.
All expanded function names and signatures are knowable at compile time, i.e., type-checking time. Type checking works, raising errors on incorrect signatures of implementations of said methods, and when missing any required methods.
Screenshot of current IntelliSense suggestions on an implementer of the dynamic type/interface:
🙂 Expected behavior
VSCode IntelliSense should recognize/suggest the dynamically generated function names (from template literals on the original EventType constant string union) just as it does when all the functions are written out individually in a type/interface.
Screenshot of current IntelliSense suggestions on an implementer of the hardcoded interface, which is expected for dynamic type/interface as well:
Additional information about the issue
This issue was prompted to be created from this StackOverflow question (also written by me): VSCode Typescript Intellisense not suggesting methods from dynamic type or interface