I am writing a helper for iterating, similar to the upcoming experimental Iterator
APIs, like map
. This is written in vanilla JS and I want all type hints to work for chain of calls.
To try and extract a type from a template, first thing I tried was this:
/**
* @template TItem
* @param {Iterable<TItem>} items
* @returns {TItem}
*/
function getIterableType(items) {}
This works correctly when tested like this:
const x = getIterableType([1,2,3]);
Here x
will be correctly hinted. But it is no good for my use, and also I don't want to type hint using actual code if possible.
This is my class example where you can see which type hints work and which do not. This was tested in VSCode.
/**
* @template TIteratorItem
* @param {IteratorExample<Iterable<TIteratorItem>>} thisArg
* @returns {IteratorExample<TIteratorItem>}
*/
function flatTypeWrap(thisArg) {
return thisArg;
}
/**
* @template TItem
*/
class IteratorExample {
/**
* @template TResult
* @param {(input: TItem)=>TResult} mapFn
* @returns {IteratorExample<TResult>}
*/
map(mapFn) {
// here would be a call to add a map step of the chain of steps to apply
// to the incoming values
return this;
}
/**
* TItem here is assumed to be an iterable
* if it is not the code will still work though, since we delegate the type hinting to the callback
* @template TResult
* @param {(items:TItem)=>Iterator<TResult>} mapper
* @returns {IteratorExample<TResult>}
*/
flatMap(mapper) {
// here would be code that adds step that converts all previous items
// to generator and yields the results individually using the mapper
return this;
}
/**
* Here I am stuck. If there is no callback I have nowhere to get the type
*/
flat() {
// I obviously don't want the solution to have runtime impact
// or be code at all. But this was my best attempt and it still does not work
/** @type {TItem} **/
const item = null;
const iteratorType = getIterableType(item);
/** @type {IteratorExample<typeof iteratorType>} **/
const res = this;
return res;
}
flatV2() {
return flatTypeWrap(this);
}
}
/** @type {IteratorExample<number>} **/
const start = new IteratorExample();
const flatMapResults = start.map(x=>[x,x]).flatMap(x=>x[Symbol.iterator]());
const flatResults = start.map(x=>[x,x]).flat();
const flatResultsV2 = start.map(x=>[x,x]).flatV2();
As you can see, I tried two versions for plain flat
function and neither works - the type shows as any
. Not that what is particularly annoying is that flatTypeWrap
actually works in its own, but the return type does not translate to flatV2
:
const flattened = flatTypeWrap(start.map(x=>[x,x]));
// On hovering flattened I get:
// const flattened: IteratorExample<number>
I also tried the ReturnType
template from VScode typescript lib, here it will show unknown
:
/** @type {ReturnType<getIterableType<Iterable<{a:number}>>>} **/
const test2 = null;
If I add typeof:
/** @type {ReturnType<typeof getIterableType<Iterable<{a:number}>>>} **/
const test2 = null;
Then it shows type as this:
const test2: Iterable<{
a: number;
}>
@type — {ReturnType<typeof getIterableType<Iterable<{a:number}>>>} *
Which is not even correct. When tested ReturnType
with plain functions it worked fine.
So I figured it out, but not with JSDoc. Instead, I found you can place XXX.d.ts
files in your project. In those, you can use any typescript definitions whic will become global for your project.
To extract the inner type of an iterable or an iterator, you can use this:
type IteratorEntryType<T extends Iterator> = T extends Iterator<infer TValue> ? TValue : any;
type IterableEntryType<T extends Iterable> = T extends Iterable<infer TValue> ? TValue : any;
Used as such:
/**
* @template TItem
*/
class IteratorExample {
/**
*
* @returns {IteratorExample<IterableEntryType<TItem>>}
*/
flat() {
return this;
}
}