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 2 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
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Expand Up @@ -37,4 +37,4 @@ jobs:
node-version: 18
- run: npm install
- run: npm install typescript@${{ matrix.typescript-version }}
- run: npx tsc
- run: npm test
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes it test more than just typescript and may fail for unrelated reasons

Copy link
Collaborator Author

@Emiyaaaaa Emiyaaaaa Mar 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using npm test in ts-canary.yml?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As was mentioned in the issue, tsd ships with its own copy of typescript and as such isn't affected by any changes to the typescript dependency: #831 (comment)

One would instead have to do what I believe @shicks and @trevorade are doing: Use eg. expect-type instead: #499

And since expect-type is validated as part of running tsc itself, no changes to the GitHub Actions would be done then either.

That said: Replacing tsd with expect-type is a much broader topic, as highlighted by eg #499

Though: Since expect-type is already a dev dependency of type-fest as far as I remember it could be good to add a test using that and ensure that breaks the canary tests correctly, at least that way we won't see a regression again.

Copy link
Collaborator

@voxpelli voxpelli Mar 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See

import {expectTypeOf} from 'expect-type';
and
import {expectTypeOf} from 'expect-type';

Though I'm a bit uncertain if those are actually run using tsc rather than tsd

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it

Copy link
Collaborator Author

@Emiyaaaaa Emiyaaaaa Mar 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though I'm a bit uncertain if those are actually run using tsc rather than tsd

Unfortunately it's using tsd.

Directory test-d/ was excluded in tsconfig. https://github.com/sindresorhus/type-fest/blob/4f14bff7321b9a9876dca0719945423abd6b4da1/tsconfig.json#L14C2-L14C16

71 changes: 58 additions & 13 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 a 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 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 @@ -51,6 +87,15 @@ type HandleError = SetParameterType<HandleMessage, [data: ErrorData, message: 'e
// Could change single parameter type.
type HandleWarn = SetParameterType<HandleMessage, {1: 'warn'}>;
//=> type HandleWarn = (data: Data, message: 'warn') => void;

// Could 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 +107,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