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

[@types/lodash] Infer lodash.throttle return type from 'leading' option #69504

Merged
merged 3 commits into from
May 16, 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
10 changes: 10 additions & 0 deletions types/lodash/common/function.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1355,6 +1355,7 @@ declare module "../index" {
*/
trailing?: boolean | undefined;
}
type ThrottleSettingsLeading = (ThrottleSettings & { leading: true }) | Omit<ThrottleSettings, 'leading'>
interface LoDashStatic {
/**
* Creates a throttled function that only invokes func at most once per every wait milliseconds. The throttled
Expand All @@ -1372,12 +1373,17 @@ declare module "../index" {
* @param options.trailing Specify invoking on the trailing edge of the timeout.
* @return Returns the new throttled function.
*/
throttle<T extends (...args: any) => any>(func: T, wait?: number, options?: ThrottleSettingsLeading): DebouncedFuncLeading<T>;
throttle<T extends (...args: any) => any>(func: T, wait?: number, options?: ThrottleSettings): DebouncedFunc<T>;
}
interface Function<T extends (...args: any) => any> {
/**
* @see _.throttle
*/
throttle(
wait?: number,
options?: ThrottleSettingsLeading
): T extends (...args: any[]) => any ? Function<DebouncedFuncLeading<T>> : never;
throttle(
wait?: number,
options?: ThrottleSettings
Expand All @@ -1387,6 +1393,10 @@ declare module "../index" {
/**
* @see _.throttle
*/
throttle(
wait?: number,
options?: ThrottleSettingsLeading
): T extends (...args: any[]) => any ? FunctionChain<DebouncedFuncLeading<T>> : never;
throttle(
wait?: number,
options?: ThrottleSettings
Expand Down
6 changes: 3 additions & 3 deletions types/lodash/fp.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4173,10 +4173,10 @@ declare namespace _ {
interface LodashThrottle {
(wait: number): LodashThrottle1x1;
<T extends (...args: any) => any>(wait: lodash.__, func: T): LodashThrottle1x2<T>;
<T extends (...args: any) => any>(wait: number, func: T): lodash.DebouncedFunc<T>;
<T extends (...args: any) => any>(wait: number, func: T): lodash.DebouncedFuncLeading<T>;
}
type LodashThrottle1x1 = <T extends (...args: any) => any>(func: T) => lodash.DebouncedFunc<T>;
type LodashThrottle1x2<T extends (...args: any) => any> = (wait: number) => lodash.DebouncedFunc<T>;
type LodashThrottle1x1 = <T extends (...args: any) => any>(func: T) => lodash.DebouncedFuncLeading<T>;
type LodashThrottle1x2<T extends (...args: any) => any> = (wait: number) => lodash.DebouncedFuncLeading<T>;
interface LodashThru {
<T, TResult>(interceptor: (value: T) => TResult): LodashThru1x1<T, TResult>;
<T>(interceptor: lodash.__, value: T): LodashThru1x2<T>;
Expand Down
57 changes: 44 additions & 13 deletions types/lodash/lodash-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3656,25 +3656,56 @@ fp.now(); // $ExpectType number

// _.throttle
{
const options: _.ThrottleSettings = {
const optionsLeading: _.ThrottleSettingsLeading = {
leading: true,
trailing: false,
}
const badOptionsLeading: _.ThrottleSettingsLeading = {
// @ts-expect-error lodash will treat an explicit undefined the same as `leading: false`
leading: undefined,
};
const optionsNoLeading: _.ThrottleSettings = {
leading: false,
};

const optionsAmbiguous = {
leading: true,
};
const boolean: boolean = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: you might need false as boolean, otherwise TS will automatically narrow the type to false which is not what we want.

const maybeUndefined = (function (): true | undefined { return true })();
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar to above: a simpler way to do this might be something like:

const maybeUndefined: true | undefined = true as any;


const func = (a: number, b: string): boolean => true;

_.throttle(func); // $ExpectType DebouncedFunc<(a: number, b: string) => boolean>
_.throttle(func, 42); // $ExpectType DebouncedFunc<(a: number, b: string) => boolean>
_.throttle(func, 42, options); // $ExpectType DebouncedFunc<(a: number, b: string) => boolean>
_(func).throttle(); // $ExpectType Function<DebouncedFunc<(a: number, b: string) => boolean>>
_(func).throttle(42); // $ExpectType Function<DebouncedFunc<(a: number, b: string) => boolean>>
_(func).throttle(42, options); // $ExpectType Function<DebouncedFunc<(a: number, b: string) => boolean>>
_.chain(func).throttle(); // $ExpectType FunctionChain<DebouncedFunc<(a: number, b: string) => boolean>>
_.chain(func).throttle(42); // $ExpectType FunctionChain<DebouncedFunc<(a: number, b: string) => boolean>>
_.chain(func).throttle(42, options); // $ExpectType FunctionChain<DebouncedFunc<(a: number, b: string) => boolean>>

fp.throttle(42, func); // $ExpectType DebouncedFunc<(a: number, b: string) => boolean>
fp.throttle(42)(func); // $ExpectType DebouncedFunc<(a: number, b: string) => boolean>
_.throttle(func); // $ExpectType DebouncedFuncLeading<(a: number, b: string) => boolean>
_.throttle(func, 42); // $ExpectType DebouncedFuncLeading<(a: number, b: string) => boolean>
_.throttle(func, 42, {}); // $ExpectType DebouncedFuncLeading<(a: number, b: string) => boolean>
_.throttle(func, 42, { leading: true, trailing: false }); // $ExpectType DebouncedFuncLeading<(a: number, b: string) => boolean>
_.throttle(func, 42, { leading: false }); // $ExpectType DebouncedFunc<(a: number, b: string) => boolean>
_.throttle(func, 42, { leading: undefined }); // $ExpectType DebouncedFunc<(a: number, b: string) => boolean>
_.throttle(func, 42, { leading: boolean }); // $ExpectType DebouncedFunc<(a: number, b: string) => boolean>
_.throttle(func, 42, { leading: maybeUndefined }); // $ExpectType DebouncedFunc<(a: number, b: string) => boolean>
Copy link
Contributor

Choose a reason for hiding this comment

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

Also add a comment explaining why we should NOT expect DebouncedFuncLeading here, since this is probably surprising to everyone.

_.throttle(func, 42, optionsAmbiguous); // $ExpectType DebouncedFunc<(a: number, b: string) => boolean>
_(func).throttle(); // $ExpectType Function<DebouncedFuncLeading<(a: number, b: string) => boolean>>
_(func).throttle(42); // $ExpectType Function<DebouncedFuncLeading<(a: number, b: string) => boolean>>
_(func).throttle(42, {}); // $ExpectType Function<DebouncedFuncLeading<(a: number, b: string) => boolean>>
_(func).throttle(42, { leading: true, trailing: false }); // $ExpectType Function<DebouncedFuncLeading<(a: number, b: string) => boolean>>
_(func).throttle(42, { leading: false }); // $ExpectType Function<DebouncedFunc<(a: number, b: string) => boolean>>
_(func).throttle(42, { leading: undefined }); // $ExpectType Function<DebouncedFunc<(a: number, b: string) => boolean>>
_(func).throttle(42, { leading: boolean }); // $ExpectType Function<DebouncedFunc<(a: number, b: string) => boolean>>
_(func).throttle(42, { leading: maybeUndefined }); // $ExpectType Function<DebouncedFunc<(a: number, b: string) => boolean>>
_(func).throttle(42, optionsAmbiguous); // $ExpectType Function<DebouncedFunc<(a: number, b: string) => boolean>>
_.chain(func).throttle(); // $ExpectType FunctionChain<DebouncedFuncLeading<(a: number, b: string) => boolean>>
_.chain(func).throttle(42); // $ExpectType FunctionChain<DebouncedFuncLeading<(a: number, b: string) => boolean>>
_.chain(func).throttle(42, {}); // $ExpectType FunctionChain<DebouncedFuncLeading<(a: number, b: string) => boolean>>
_.chain(func).throttle(42, { leading: true, trailing: false }); // $ExpectType FunctionChain<DebouncedFuncLeading<(a: number, b: string) => boolean>>
_.chain(func).throttle(42, { leading: false }); // $ExpectType FunctionChain<DebouncedFunc<(a: number, b: string) => boolean>>
_.chain(func).throttle(42, { leading: undefined }); // $ExpectType FunctionChain<DebouncedFunc<(a: number, b: string) => boolean>>
_.chain(func).throttle(42, { leading: boolean }); // $ExpectType FunctionChain<DebouncedFunc<(a: number, b: string) => boolean>>
_.chain(func).throttle(42, { leading: maybeUndefined }); // $ExpectType FunctionChain<DebouncedFunc<(a: number, b: string) => boolean>>
_.chain(func).throttle(42, optionsAmbiguous); // $ExpectType FunctionChain<DebouncedFunc<(a: number, b: string) => boolean>>

fp.throttle(42, func); // $ExpectType DebouncedFuncLeading<(a: number, b: string) => boolean>
fp.throttle(42)(func); // $ExpectType DebouncedFuncLeading<(a: number, b: string) => boolean>
}

// _.unary
Expand Down