I want to generate TypeScript definitions for JavaScript code in Magento 2 or RequireJS to be more precise.
Example:
// Generated
const modules = {
a: 1,
b: 2,
c: 3,
test: { nesttest: 'Nice' }
} as const;
// Module
define(['test', 'a', 'b', 'unknown'], function (test, a, b, UnKnown) {
// test = { nesttest: 'Nice' }
// a = 1
// b = 2
// UnKnown = any | unknown
});
This is what I have so far. It is only for TypeScript, but I know how to continue after this problem:
- The
trueCallback
should return the same values as thepseudoCallback
//---------------//
//--- Modules ---//
//---------------//
const modules = {
a: 1,
b: 2,
c: 3,
test: { nestedtest: 'Nice' }
} as const
//-------------------//
//--- Definitions ---//
//-------------------//
function define<
Mods extends typeof modules,
Keys extends keyof Mods,
Input extends ReadonlyArray<InputVal>,
InputVal extends string,
InputUnion extends Input[number],
InputArr extends TuplifyUnion<InputUnion>,
Values extends Mods[Keys],
ValuesArr extends TuplifyUnion<Values>,
>(
keys: Readonly<Input>,
pseudoCallback: (...args: [Mods['test'], Mods['b'], Mods['a']]) => void,
trueCallback: (...args: unknown[]) => void, // WHAT COMES HERE?
) {
const obj = modules as Mods;
const values = keys.map(key => {
if (key in obj) {
return obj[key as keyof Mods];
}
return undefined;
});
trueCallback(...values);
}
//-------------//
//--- Usage ---//
//-------------//
define(['test', 'b', 'a'], function (test, b, a) {
test
// ^?
const nested = test.nestedtest
nested
// ^?
b
// ^?
a
// ^?
// SHOULD BE LIKE THIS
}, function (test, b, a) {
test
// ^?
b
// ^?
a
// ^?
// DOES NOT WORK
})
//-------------//
//--- Types ---//
//-------------//
// Converts Union To Array
type TuplifyUnion<
T,
L = LastOf<T>,
N = [T] extends [never] ? true : false
> = true extends N
? []
: Push<TuplifyUnion<Exclude<T, L>>, L>;
type LastOf<T> = UnionToIntersection<T extends any ? () => T : never> extends () => (infer R) ? R : never;
type Push<T extends any[], V> = [...T, V];
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type ObjValueTuple<
T extends any,
KS extends Array<any> = TuplifyUnion<keyof T>,
R extends Array<any> = []
> = KS extends [infer K, ...infer KT]
? ObjValueTuple<T, KT, [...R, T[K & keyof T]]>
: R;
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
}
type DeepMutable<T> = {
-readonly [K in keyof T]: T[K] extends object ? DeepMutable<T[K]> : T[K];
}
CodePudding user response:
For simplicity, I created a type Mods
that just holds typeof modules
.
const modules = {
a: 1,
b: 2,
c: 3,
test: { nestedtest: 'Nice' }
} as const
type Mods = typeof modules
Let's just use a single generic type K
to represent the keys
as a tuple type.
function define<
K extends (keyof Mods)[]
>(
keys: [...K],
/* ... */
) {
/* ... */
}
For trueCallback
we can map over the tuple K
with a mapped type. For each index I
we take K[I]
and use it to index Mods
.
function define<
K extends (keyof Mods)[]
>(
keys: [...K],
trueCallback: (...args: {
[I in keyof K]: Mods[K[I] & keyof Mods]
}) => void
) {
/* ... */
}
For the implementation of the function, we can use an any
assertion when we call trueCallback
. TypeScript can't really understand that values
has the correct type to call trueCallback
.
This leads to the following end result:
function define<
K extends (keyof Mods)[]
>(
keys: [...K],
trueCallback: (...args: {
[I in keyof K]: Mods[K[I] & keyof Mods]
}) => void
) {
const obj = modules as Mods;
const values = keys.map(key => {
if (key in obj) {
return obj[key as keyof Mods];
}
return undefined;
});
trueCallback(...values as any);
}
define(['test', 'b', 'a'], function (test, b, a) {
test
// ^?{ readonly nestedtest: "Nice"; }
b
// ^? 2
a
// ^? 1
})