[core-object] Add typings for core-object (#29344)

This commit is contained in:
Dan Freeman
2018-10-02 00:20:55 -04:00
committed by Wesley Wigham
parent b4529f27c1
commit 52fe47713e
5 changed files with 251 additions and 0 deletions

40
types/core-object/-private/utils.d.ts vendored Normal file
View File

@@ -0,0 +1,40 @@
export type Constructor<Instance> = new (...args: any[]) => Instance;
export type Mix<T, U> = U & Pick<T, Exclude<keyof T, keyof U>>;
export type Values<T> = T[keyof T];
/** Just the strings corresponding to method names on the given type */
export type MethodNames<T> = Values<{ [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never }>;
/**
* Given a `this` type, arg types tuple and return type, creates a function-ish
* type that can only be only be invoked via `.call` or `.apply`. Because of the
* way CoreObject's `_super` works, it's an error to write e.g. `this._super.init()`;
*/
export interface CallOrApply<This, Args, Return> {
apply: (thisArg: This, args: Args extends undefined ? any[] : Args | IArguments) => Return;
// TODO support this properly with `...args: Args` once we can restrict to 3.0+ on DT
call:
Args extends undefined ? (thisArg: This) => Return :
Args extends [infer A] ? (thisArg: This, a: A) => Return :
Args extends [infer A, infer B] ? (thisArg: This, a: A, b: B) => Return :
Args extends [infer A, infer B, infer C] ? (thisArg: This, a: A, b: B, c: C) => Return :
Args extends [infer A, infer B, infer C, infer D] ? (thisArg: This, a: A, b: B, c: C, d: D) => Return :
(thisArg: This, ...args: any[]) => Return;
}
/**
* The type of `this._super`, which has keys for all methods appearing in the given
* type, but forces the caller to use `.call` or `.apply` to invoke them.
*/
export type Super<T> = {
// TODO just do `infer Args` once we can restrict to 3.0+ on DT
[K in MethodNames<T>]:
T[K] extends () => infer Return ? CallOrApply<T, undefined, Return> :
T[K] extends (a: infer A) => infer Return ? CallOrApply<T, [A], Return> :
T[K] extends (a: infer A, b: infer B) => infer Return ? CallOrApply<T, [A, B], Return> :
T[K] extends (a: infer A, b: infer B, c: infer C) => infer Return ? CallOrApply<T, [A, B, C], Return> :
T[K] extends (a: infer A, b: infer B, c: infer C, d: infer D) => infer Return ? CallOrApply<T, [A, B, C, D], Return> :
T[K] extends (...args: any[]) => infer Return ? CallOrApply<T, any[], Return> :
never;
};

View File

@@ -0,0 +1,163 @@
import { Mix, MethodNames } from 'core-object/-private/utils';
import CoreObject, { ExtendOptions, ExtendThisType } from 'core-object';
//////////// Mix ////////////
declare const mix1: Mix<{ a: number }, { b: string }>;
mix1.a; // $ExpectType number
mix1.b; // $ExpectType string
declare const mix2: Mix<{ a: number }, { a: string }>;
mix2.a; // $ExpectType string
mix2.b; // $ExpectError
//////////// MethodNames ////////////
declare const names1: MethodNames<{ a: string }>;
names1; // $ExpectType never
declare const names2: MethodNames<{ a: () => number; b(arg: number): void }>;
names2; // $ExpectType "a" | "b"
declare const names3: MethodNames<{ a: () => 'hi'; b: null }>;
names3; // $ExpectType "a"
//////////// ExtendOptions ////////////
const extendOptions1: ExtendOptions<{}> = { a: 1, b: 'hi' };
const extendOptions2: ExtendOptions<{ a: number }> = { b: 'hi' };
const extendOptions3: ExtendOptions<{ a: number }> = { a: 5 };
const extendOptions4: ExtendOptions<{ a: number }> = { a: 'hi' }; // $ExpectError
//////////// ExtendThisType ////////////
declare function extendThisType1<T>(options: T & ExtendThisType<{ prop: string; method: () => number }, T>): void;
extendThisType1({
otherMethod() {
this.prop; // $ExpectType string
this.random; // $ExpectError
this._super.method.call(this); // $ExpectType number
this._super.random; // $ExpectError
}
});
//////////// CoreObject ////////////
const A = CoreObject.extend({
foo: 'hello',
method(): string {
return this.foo;
}
});
const a = new A();
a.foo; // $ExpectType string
a.bar; // $ExpectError
a.method(); // $ExpectType string
const B = A.extend({
bar: 123,
other(): string {
return this._super.method.call(this) + this.foo;
}
});
const b = new B();
b.foo; // $ExpectType string
b.bar; // $ExpectType number
b.method(); // $ExpectType string
b.other(); // $ExpectType string
class ClassWithMethods extends CoreObject.extend({
extendMethod(arg: number): string {
return 'ok';
}
}) {
esMethod(arg: string): number {
return 123;
}
}
const ExtendSubclass = ClassWithMethods.extend({
anotherMethod() {
this._super.extendMethod(1); // $ExpectError
this._super.esMethod('hi'); // $ExpectError
this._super.extendMethod.call(this, 1); // $ExpectType string
this._super.extendMethod.apply(this, [1]); // $ExpectType string
this._super.esMethod.call(this, 'hi'); // $ExpectType number
this._super.esMethod.apply(this, ['hi']); // $ExpectType number
}
});
class ESSubclass extends ClassWithMethods {
anotherMethod() {
this._super; // $ExpectError
super.extendMethod(1); // $ExpectType string
super.esMethod('hi'); // $ExpectType number
}
}
ClassWithMethods.extend({ extendMethod: null }); // $ExpectError
ClassWithMethods.extend({ extendMethod() {} }); // $ExpectError
ClassWithMethods.extend({ esMethod: null }); // $ExpectError
ClassWithMethods.extend({ esMethod() {} }); // $ExpectError
ClassWithMethods.extend({
extendMethod(arg: number): string {
const result = this._super.extendMethod.call(this, arg);
result; // $ExpectType string
return result;
},
esMethod(arg: string): number {
const result = this._super.esMethod.call(this, arg);
result; // $ExpectType number
return result;
}
});
declare class ClassWithManyMethods extends CoreObject {
method0(): 0;
method1(a: 'a'): 1;
method2(a: 'a', b: 'b'): 2;
method3(a: 'a', b: 'b', c: 'c'): 3;
method4(a: 'a', b: 'b', c: 'c', d: 'd'): 4;
method5(a: 'a', b: 'b', c: 'c', d: 'd', e: 'e'): 5;
}
ClassWithManyMethods.extend({
child(): void {
this._super.method0.call(this); // $ExpectType 0
this._super.method0.apply(this, []); // $ExpectType 0
this._super.method1.call(this, 'a'); // $ExpectType 1
this._super.method1.apply(this, ['a']); // $ExpectType 1
this._super.method1.call(this, 'x'); // $ExpectError
this._super.method1.apply(this, ['x']); // $ExpectError
this._super.method2.call(this, 'a', 'b'); // $ExpectType 2
this._super.method2.apply(this, ['a', 'b']); // $ExpectType 2
this._super.method2.call(this, 'a', 'x'); // $ExpectError
this._super.method2.apply(this, ['a', 'x']); // $ExpectError
this._super.method3.call(this, 'a', 'b', 'c'); // $ExpectType 3
this._super.method3.apply(this, ['a', 'b', 'c']); // $ExpectType 3
this._super.method3.call(this, 'a', 'b', 'x'); // $ExpectError
this._super.method3.apply(this, ['a', 'b', 'x']); // $ExpectError
this._super.method4.call(this, 'a', 'b', 'c', 'd'); // $ExpectType 4
this._super.method4.apply(this, ['a', 'b', 'c', 'd']); // $ExpectType 4
this._super.method4.call(this, 'a', 'b', 'c', 'x'); // $ExpectError
this._super.method4.apply(this, ['a', 'b', 'c', 'x']); // $ExpectError
// Arity 4 is as high as we go for arg checking
this._super.method5.call(this, 'foo', 'bar', 'baz'); // $ExpectType 5
this._super.method5.apply(this, ['foo', 'bar', 'baz']); // $ExpectType 5
}
});

23
types/core-object/index.d.ts vendored Normal file
View File

@@ -0,0 +1,23 @@
// Type definitions for core-object 3.0
// Project: https://github.com/ember-cli/core-object
// Definitions by: Dan Freeman <https://github.com/dfreeman>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.8
import { Mix, Super, Constructor } from './-private/utils';
/** The type of options allowed to be passed to `Base.extend()` */
export type ExtendOptions<Base> = { [K in keyof Base]?: Base[K] } & Record<string, any>;
/** The `this` type for any methods on the options passed to `Base.extend()` */
export type ExtendThisType<Base, Ext> = ThisType<Mix<Base, Ext> & { _super: Super<Base> }>;
export default class CoreObject {
// TODO restrict to `Record<string, unknown>` once we can restrict to 3.0+ on DT
init(options?: Record<string, any>): void;
static extend<BaseClass extends Constructor<any>, Ext extends ExtendOptions<InstanceType<BaseClass>>>(
this: BaseClass,
options: Ext & ExtendThisType<InstanceType<BaseClass>, Ext>
): BaseClass & Constructor<Mix<InstanceType<BaseClass>, Ext>>;
}

View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": [
"es6"
],
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"baseUrl": "../",
"typeRoots": [
"../"
],
"types": [],
"noEmit": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.d.ts",
"-private/utils.d.ts",
"core-object-tests.ts"
]
}

View File

@@ -0,0 +1 @@
{ "extends": "dtslint/dt.json" }