Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SetParameterType: compatible with break change about in typescript 5.4 #835

Merged
merged 4 commits into from Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
74 changes: 60 additions & 14 deletions source/set-parameter-type.d.ts
@@ -1,17 +1,53 @@
import type {IsUnknown} from './is-unknown';
import type {StaticPartOfArray} from './internal';
import type {UnknownArray} from './unknown-array';

/**
Create a tuple that replaces the given `Tuple`'s elements with the given `Record_`'s values at the given indices.
Create an array that replaces the given `TArray`'s elements with the given `TObject`'s values at the given indices.

`TArray` and `TObject` supports tailing spread array like `[string, ...boolean[]]`, but does not support `[string, ...boolean[], number]`.

@example:
```ts
// object
type A = MergeObjectToArray<[string, number], {0: boolean}>;
//=> [boolean, number]

// array
type B = MergeObjectToArray<[string, number], [boolean]>;
//=> [boolean, number]

// tailing spread array
type C = MergeObjectToArray<[string, ...boolean[]], {1: number}>;
//=> [string, ...number[]]

type D = MergeObjectToArray<[string, ...boolean[]], [number, ...string[]]>;
//=> [number, ...string[]]
```
*/
type MergeObjectToTuple<Tuple extends unknown[], Record_ extends Record<number, unknown>> = {
[K in keyof Tuple]: Record_ extends Record<string, unknown>
// Handle object case like `{0: string, 1: number}`
? K extends `${infer NumberK extends number}`
? NumberK extends keyof Record_ ? Record_[K] : Tuple[K]
: never // Should not happen
// Handle tuple case like `[string, number]`
: K extends keyof Record_ ? Record_[K] : Tuple[K]
};
type MergeObjectToArray<TArray extends UnknownArray, TObject, TArrayCopy extends UnknownArray = TArray> =
// If `TObject` is an array like `[0, 1, 2]`
TObject extends UnknownArray
// If `TObject` is a variable length array, we should use `TObject`'s type as the result type.
? number extends TObject['length']
? TObject
: {
[K in keyof TArray]: K extends keyof TObject ? TObject[K] : TArray[K]
}
: TObject extends object
// If `TObject` is a object witch key is number like `{0: string, 1: number}`
? {
[K in keyof TArray]:
K extends `${infer NumberK extends number}`
? NumberK extends keyof TObject ? TObject[NumberK] : TArray[K]
: number extends K
// If array key `K` is `number`, means it's a rest parameter, we should set the rest parameter type to corresponding type in `TObject`.
// example: `MergeObjectToParamterArray<[string, ...boolean[]], {1: number}>` => `[string, ...number[]]`
? StaticPartOfArray<TArrayCopy>['length'] extends keyof TObject
? TObject[StaticPartOfArray<TArrayCopy>['length']]
: TArray[K]
: never
} : never;

/**
Create a function that replaces some parameters with the given parameters.
Expand Down Expand Up @@ -39,7 +75,7 @@ Use-case:
```
import type {SetParameterType} from 'type-fest';

type HandleMessage = (data: Data, message: string) => void;
type HandleMessage = (data: Data, message: string, ...arguments: any[]) => void;

type HandleOk = SetParameterType<HandleMessage, {0: SuccessData, 1: 'ok'}>;
//=> type HandleOk = (data: SuccessData, message: 'ok') => void;
Expand All @@ -48,9 +84,19 @@ type HandleOk = SetParameterType<HandleMessage, {0: SuccessData, 1: 'ok'}>;
type HandleError = SetParameterType<HandleMessage, [data: ErrorData, message: 'error']>;
//=> type HandleError = (data: ErrorData, message: 'error') => void;

// Could change single parameter type.
// Change single parameter type.
type HandleWarn = SetParameterType<HandleMessage, {1: 'warn'}>;
//=> type HandleWarn = (data: Data, message: 'warn') => void;

// Change rest parameter type.

// Way 1: Input full parameter type.
type HandleLog = SetParameterType<HandleMessage, [data: Data, message: 'log', ...arguments: string[]]>;
//=> type HandleLog = (data: Data, message: 'log', ...arguments: string[]) => void;

// Way 2: Input rest parameter type by Object index.
type HandleLog2 = SetParameterType<HandleMessage, {2: string}>;
//=> type HandleLog2 = (data: Data, message: string, ...arguments: string[]) => void;
```

@category Function
Expand All @@ -62,7 +108,7 @@ export type SetParameterType<Fn extends (...arguments_: any[]) => unknown, P ext
// If a function did not specify the `this` fake parameter, it will be inferred to `unknown`.
// We want to detect this situation just to display a friendlier type upon hovering on an IntelliSense-powered IDE.
IsUnknown<ThisArg> extends true
? (...arguments_: MergeObjectToTuple<Arguments, P>) => ReturnType<Fn>
: (this: ThisArg, ...arguments_: MergeObjectToTuple<Arguments, P>) => ReturnType<Fn>
? (...arguments_: MergeObjectToArray<Arguments, P>) => ReturnType<Fn>
: (this: ThisArg, ...arguments_: MergeObjectToArray<Arguments, P>) => ReturnType<Fn>
)
: Fn; // This part should be unreachable
12 changes: 8 additions & 4 deletions test-d/set-parameter-type.ts
@@ -1,5 +1,5 @@
import {expectType} from 'tsd';
import {type SetParameterType} from '../index';
import type {SetParameterType} from '../index';

function fn(_a: number, _b: string, _c: Object, ..._arguments: boolean[]) {
return null;
Expand Down Expand Up @@ -27,9 +27,13 @@ expectType<(a: string, b: boolean, c: Object, ...args: boolean[]) => null>(test3
test3('1', true, {}, true);

// Test `...args` parameter
declare const test4: SetParameterType<typeof fn, {3: string}>;
expectType<(a: number, b: string, c: Object, ...args: string[]) => null>(test4);
test4(1, '1', {}, '1');
declare const testargs: SetParameterType<typeof fn, {3: string}>;
expectType<(a: number, b: string, c: Object, ...args: string[]) => null>(testargs);
testargs(1, '1', {}, '1');

declare const testargs2: SetParameterType<typeof fn, [string, number, number, ...boolean[]]>;
expectType<(a: string, b: number, c: number, ...args: boolean[]) => null>(testargs2);
testargs2('1', 1, 1, true);

// Test arrow function
declare const test5: SetParameterType<typeof arrowFunction, {0: string}>;
Expand Down