diff --git a/types/ember/index.d.ts b/types/ember/index.d.ts index fe784dae8b..3ff82919dc 100755 --- a/types/ember/index.d.ts +++ b/types/ember/index.d.ts @@ -28,6 +28,20 @@ declare module 'ember' { // Get an alias to the global Array type to use in inner scope below. type GlobalArray = T[]; + // TODO: TypeScript 3.0 + // type FunctionArgs any> = F extends (...args: infer ARGS) => any ? ARGS : never; + type FunctionArgs = + F extends (a: infer A) => any + ? [A] + : F extends (a: infer A, b: infer B) => any + ? [A, B] + : F extends (a: infer A, b: infer B, c: infer C) => any + ? [A, B, C] + : F extends (a: infer A, b: infer B, c: infer C, d: infer D) => any + ? [A, B, C, D] + : F extends (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E) => any + ? [A, B, C, D, E] + : never; /** * Deconstructs computed properties into the types which would be returned by `.get()`. */ @@ -3320,7 +3334,14 @@ declare module 'ember' { * Checks to see if the `methodName` exists on the `obj`, * and if it does, invokes it with the arguments passed. */ - function tryInvoke(obj: any, methodName: string, args?: any[]): any; + function tryInvoke( + obj: T, + methodName: FNAME, + args: FunctionArgs): T[FNAME] extends ((...args: any[]) => any) + ? ReturnType + : undefined; + function tryInvoke(obj: T, methodName: FNAME): T[FNAME] extends (() => any) ? ReturnType : undefined; + function tryInvoke(obj: object, methodName: string, args?: any[]): undefined; /** * Forces the passed object to be part of an array. If the object is already * an array, it will return the object. Otherwise, it will add the object to diff --git a/types/ember/test/utils.ts b/types/ember/test/utils.ts index 9ddc2b361e..2a769dc2dc 100755 --- a/types/ember/test/utils.ts +++ b/types/ember/test/utils.ts @@ -99,3 +99,31 @@ function testDefineProperty() { return `${this.firstName} ${this.lastName}`; })); } + +function testTryInvoke() { + class Foo { + hello() { return ['world']; } + add(x: number, y: string) { return x + parseInt(y, 10); } + apples(n: number) { return `${n} apples`; } + } + // zero-argument function + Ember.tryInvoke(new Foo(), 'hello'); // $ExpectType string[] + // one-argument function + Ember.tryInvoke(new Foo(), 'apples', [4]); // $ExpectType string + // multi-argument function with different types (reversed types negative test case below) + Ember.tryInvoke(new Foo(), 'add', [4, '3']); // $ExpectType number + + // Cases that should return undefined + // No args provided + Ember.tryInvoke(new Foo(), 'apples'); // $ExpectType undefined + // Function does not exist + Ember.tryInvoke(new Foo(), 'doesNotExist'); // $ExpectType undefined + // Empty args provided + Ember.tryInvoke(new Foo(), 'apples', []); // $ExpectType undefined + // Wrong args provided + Ember.tryInvoke(new Foo(), 'apples', ['']); // $ExpectType undefined + // Wrong arg types + Ember.tryInvoke(new Foo(), 'add', [4, 3]); // $ExpectType undefined + // Reversed arg types + Ember.tryInvoke(new Foo(), 'add', ['4', 3]); // $ExpectType undefined +}