diff --git a/ember-cli-build.js b/ember-cli-build.js index 5c92e7a6152..759cb39c434 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -232,6 +232,8 @@ function templateCompilerBundle(emberPackages, transpileTree) { '@ember/-internals/*/tests/**' /* internal packages */, '*/*/tests/**' /* scoped packages */, '*/tests/**' /* packages */, + '*/*/type-tests/**' /* scoped packages */, + '*/type-tests/**' /* packages */, ], }), templateCompilerDependencies(), diff --git a/packages/@ember/-internals/glimmer/lib/helpers/action.ts b/packages/@ember/-internals/glimmer/lib/helpers/action.ts index b6045fce724..f75dec64867 100644 --- a/packages/@ember/-internals/glimmer/lib/helpers/action.ts +++ b/packages/@ember/-internals/glimmer/lib/helpers/action.ts @@ -362,7 +362,7 @@ function makeArgsProcessor(valuePathRef: Reference | false, actionArgsRef: Refer function makeDynamicClosureAction( context: object, targetRef: Reference, - actionRef: Reference, + actionRef: Reference any)>, processArgs: (args: unknown[]) => unknown[], debugKey: string ) { @@ -389,34 +389,32 @@ function makeDynamicClosureAction( } interface MaybeActionHandler { - actions?: Record; + actions?: Record any>; } function makeClosureAction( context: object, target: MaybeActionHandler, - action: string | Function, + action: string | ((...args: any[]) => any), processArgs: (args: unknown[]) => unknown[], debugKey: string ) { let self: object; - let fn: Function; + let fn: (...args: any[]) => any; assert( `Action passed is null or undefined in (action) from ${target}.`, action !== undefined && action !== null ); - let typeofAction = typeof action; - - if (typeofAction === 'string') { + if (typeof action === 'string') { self = target; fn = (target.actions && target.actions[action as string])!; assert(`An action named '${action}' was not found in ${target}`, Boolean(fn)); - } else if (typeofAction === 'function') { + } else if (typeof action === 'function') { self = context; - fn = action as Function; + fn = action; } else { assert( `An action could not be made for \`${ diff --git a/packages/@ember/-internals/glimmer/lib/views/outlet.ts b/packages/@ember/-internals/glimmer/lib/views/outlet.ts index bc6a4117c4d..1d4eadfa193 100644 --- a/packages/@ember/-internals/glimmer/lib/views/outlet.ts +++ b/packages/@ember/-internals/glimmer/lib/views/outlet.ts @@ -6,6 +6,7 @@ import { createComputeRef, Reference, updateRef } from '@glimmer/reference'; import { consumeTag, createTag, dirtyTag } from '@glimmer/validator'; import { SimpleElement } from '@simple-dom/interface'; import { OutletDefinitionState } from '../component-managers/outlet'; +import { Renderer } from '../renderer'; import { OutletState } from '../utils/outlet'; export interface BootEnvironment { @@ -95,9 +96,11 @@ export default class OutletView { target = selector; } - let renderer = this.owner.lookup('renderer:-dom'); + let renderer = this.owner.lookup('renderer:-dom') as Renderer; - schedule('render', renderer, 'appendOutletView', this, target); + // SAFETY: It's not clear that this cast is safe. + // The types for appendOutletView may be incorrect or this is a potential bug. + schedule('render', renderer, 'appendOutletView', this, target as SimpleElement); } rerender(): void { diff --git a/packages/@ember/-internals/metal/lib/is_blank.ts b/packages/@ember/-internals/metal/lib/is_blank.ts index ee666ef1ff6..2caef0a9043 100644 --- a/packages/@ember/-internals/metal/lib/is_blank.ts +++ b/packages/@ember/-internals/metal/lib/is_blank.ts @@ -8,7 +8,6 @@ import isEmpty from './is_empty'; ```javascript import { isBlank } from '@ember/utils'; - isBlank(); // true isBlank(null); // true isBlank(undefined); // true isBlank(''); // true @@ -29,6 +28,6 @@ import isEmpty from './is_empty'; @since 1.5.0 @public */ -export default function isBlank(obj: any): boolean { +export default function isBlank(obj: unknown): boolean { return isEmpty(obj) || (typeof obj === 'string' && /\S/.test(obj) === false); } diff --git a/packages/@ember/-internals/metal/lib/is_empty.ts b/packages/@ember/-internals/metal/lib/is_empty.ts index 5339ac8cd84..c9227858130 100644 --- a/packages/@ember/-internals/metal/lib/is_empty.ts +++ b/packages/@ember/-internals/metal/lib/is_empty.ts @@ -1,4 +1,4 @@ -import { get } from './property_get'; +import { get, MaybeHasUnknownProperty } from './property_get'; /** @module @ember/utils */ @@ -13,7 +13,6 @@ import { get } from './property_get'; to check emptiness. ```javascript - isEmpty(); // true isEmpty(null); // true isEmpty(undefined); // true isEmpty(''); // true @@ -35,33 +34,40 @@ import { get } from './property_get'; @return {Boolean} @public */ -export default function isEmpty(obj: any): boolean { - let none = obj === null || obj === undefined; - if (none) { - return none; +export default function isEmpty(obj: unknown): boolean { + if (obj === null || obj === undefined) { + return true; } - if (typeof obj.unknownProperty !== 'function' && typeof obj.size === 'number') { - return !obj.size; + if ( + typeof (obj as MaybeHasUnknownProperty).unknownProperty !== 'function' && + typeof (obj as HasSize).size === 'number' + ) { + return !(obj as HasSize).size; } - let objectType = typeof obj; - - if (objectType === 'object') { + if (typeof obj === 'object') { let size = get(obj, 'size'); if (typeof size === 'number') { return !size; } - let length = get(obj, 'length'); if (typeof length === 'number') { return !length; } } - if (typeof obj.length === 'number' && objectType !== 'function') { - return !obj.length; + if (typeof (obj as HasLength).length === 'number' && typeof obj !== 'function') { + return !(obj as HasLength).length; } return false; } + +interface HasSize { + size: number; +} + +interface HasLength { + length: number; +} diff --git a/packages/@ember/-internals/metal/lib/is_none.ts b/packages/@ember/-internals/metal/lib/is_none.ts index 99fa853fde8..2b29252a21b 100644 --- a/packages/@ember/-internals/metal/lib/is_none.ts +++ b/packages/@ember/-internals/metal/lib/is_none.ts @@ -7,7 +7,6 @@ confusing. ```javascript - isNone(); // true isNone(null); // true isNone(undefined); // true isNone(''); // false diff --git a/packages/@ember/-internals/metal/lib/is_present.ts b/packages/@ember/-internals/metal/lib/is_present.ts index d80d3672d3c..62327739e65 100644 --- a/packages/@ember/-internals/metal/lib/is_present.ts +++ b/packages/@ember/-internals/metal/lib/is_present.ts @@ -6,7 +6,6 @@ import isBlank from './is_blank'; A value is present if it not `isBlank`. ```javascript - isPresent(); // false isPresent(null); // false isPresent(undefined); // false isPresent(''); // false @@ -32,6 +31,6 @@ import isBlank from './is_blank'; @since 1.8.0 @public */ -export default function isPresent(obj: object): boolean { +export default function isPresent(obj: T | null | undefined): obj is T { return !isBlank(obj); } diff --git a/packages/@ember/-internals/metal/lib/property_get.ts b/packages/@ember/-internals/metal/lib/property_get.ts index e92033bd3b2..b8abcc10c99 100644 --- a/packages/@ember/-internals/metal/lib/property_get.ts +++ b/packages/@ember/-internals/metal/lib/property_get.ts @@ -23,7 +23,7 @@ if (DEBUG) { }; } -interface MaybeHasUnknownProperty { +export interface MaybeHasUnknownProperty { unknownProperty?: (keyName: string) => any; } diff --git a/packages/@ember/-internals/routing/lib/location/hash_location.ts b/packages/@ember/-internals/routing/lib/location/hash_location.ts index afcc655c9c4..3f249865134 100644 --- a/packages/@ember/-internals/routing/lib/location/hash_location.ts +++ b/packages/@ember/-internals/routing/lib/location/hash_location.ts @@ -130,7 +130,7 @@ export default class HashLocation extends EmberObject implements EmberLocation { */ onUpdateURL(callback: UpdateCallback): void { this._removeEventListener(); - this._hashchangeHandler = bind(this, function (this: HashLocation) { + this._hashchangeHandler = bind(this, function (this: HashLocation, _event: Event) { let path = this.getURL(); if (this.lastSetURL === path) { return; diff --git a/packages/@ember/-internals/routing/lib/system/router.ts b/packages/@ember/-internals/routing/lib/system/router.ts index 39d40c624b0..5b28ad13595 100644 --- a/packages/@ember/-internals/routing/lib/system/router.ts +++ b/packages/@ember/-internals/routing/lib/system/router.ts @@ -44,6 +44,7 @@ import Router, { TransitionError, TransitionState, } from 'router_js'; +import type { Timer } from 'backburner'; import { EngineRouteInfo } from './engines'; import EngineInstance from '@ember/engine/instance'; @@ -199,7 +200,7 @@ class EmberRouter extends EmberObject.extend(Evented) i _engineInfoByRoute = Object.create(null); _routerService: RouterService; - _slowTransitionTimer: unknown; + _slowTransitionTimer: Timer | null = null; private namespace: any; @@ -810,10 +811,12 @@ class EmberRouter extends EmberObject.extend(Evented) i let instances = this._engineInstances; for (let name in instances) { - let instance = instances[name]; - assert('has instance', instance); - for (let id in instance) { - run(instance[id], 'destroy'); + let instanceMap = instances[name]; + assert('has instanceMap', instanceMap); + for (let id in instanceMap) { + let instance = instanceMap[id]; + assert('has instance', instance); + run(instance, 'destroy'); } } } @@ -1334,7 +1337,7 @@ class EmberRouter extends EmberObject.extend(Evented) i this._slowTransitionTimer = scheduleOnce( 'routerTransitions', this, - '_handleSlowTransition', + this._handleSlowTransition, transition, originRoute ); diff --git a/packages/@ember/-internals/runtime/lib/compare.js b/packages/@ember/-internals/runtime/lib/compare.ts similarity index 70% rename from packages/@ember/-internals/runtime/lib/compare.js rename to packages/@ember/-internals/runtime/lib/compare.ts index dc70eba4c09..480933e9212 100644 --- a/packages/@ember/-internals/runtime/lib/compare.js +++ b/packages/@ember/-internals/runtime/lib/compare.ts @@ -1,5 +1,6 @@ import { typeOf } from './type-of'; import Comparable from './mixins/comparable'; +import { assert } from '@ember/debug'; const TYPE_ORDER = { undefined: 0, @@ -15,6 +16,8 @@ const TYPE_ORDER = { date: 10, }; +type Compare = -1 | 0 | 1; + // // the spaceship operator // @@ -32,9 +35,10 @@ const TYPE_ORDER = { // | `._ `. \ // `._________`-. `. `.___ // SSt `------'` -function spaceship(a, b) { +function spaceship(a: number, b: number): Compare { let diff = a - b; - return (diff > 0) - (diff < 0); + // SAFETY: Number casts true into 1 and false into 0. Therefore, this must end up as one of the Compare values. + return (Number(diff > 0) - Number(diff < 0)) as Compare; } /** @@ -87,7 +91,7 @@ function spaceship(a, b) { @return {Number} -1 if v < w, 0 if v = w and 1 if v > w. @public */ -export default function compare(v, w) { +export default function compare(v: unknown, w: unknown): Compare { if (v === w) { return 0; } @@ -95,12 +99,13 @@ export default function compare(v, w) { let type1 = typeOf(v); let type2 = typeOf(w); - if (type1 === 'instance' && Comparable.detect(v) && v.constructor.compare) { + if (type1 === 'instance' && isComparable(v) && v.constructor.compare) { return v.constructor.compare(v, w); } - if (type2 === 'instance' && Comparable.detect(w) && w.constructor.compare) { - return w.constructor.compare(w, v) * -1; + if (type2 === 'instance' && isComparable(w) && w.constructor.compare) { + // SAFETY: Multiplying by a negative just changes the sign + return (w.constructor.compare(w, v) * -1) as Compare; } let res = spaceship(TYPE_ORDER[type1], TYPE_ORDER[type2]); @@ -112,13 +117,17 @@ export default function compare(v, w) { // types are equal - so we have to check values now switch (type1) { case 'boolean': + assert('both are boolean', typeof v === 'boolean' && typeof w === 'boolean'); + return spaceship(Number(v), Number(w)); case 'number': + assert('both are numbers', typeof v === 'number' && typeof w === 'number'); return spaceship(v, w); - case 'string': + assert('both are strings', typeof v === 'string' && typeof w === 'string'); return spaceship(v.localeCompare(w), 0); case 'array': { + assert('both are arrays', Array.isArray(v) && Array.isArray(w)); let vLen = v.length; let wLen = w.length; let len = Math.min(vLen, wLen); @@ -135,15 +144,24 @@ export default function compare(v, w) { return spaceship(vLen, wLen); } case 'instance': - if (Comparable.detect(v)) { + if (isComparable(v) && v.compare) { return v.compare(v, w); } return 0; case 'date': + assert('both are dates', v instanceof Date && w instanceof Date); return spaceship(v.getTime(), w.getTime()); default: return 0; } } + +interface ComparableConstructor { + constructor: Comparable; +} + +function isComparable(value: unknown): value is Comparable & ComparableConstructor { + return Comparable.detect(value); +} diff --git a/packages/@ember/-internals/runtime/lib/copy.d.ts b/packages/@ember/-internals/runtime/lib/copy.d.ts deleted file mode 100644 index 610262eb251..00000000000 --- a/packages/@ember/-internals/runtime/lib/copy.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare function copy(obj: T, deep: true): T; -declare function copy(obj: any, deep?: boolean): any; -export default copy; diff --git a/packages/@ember/-internals/runtime/lib/is-equal.js b/packages/@ember/-internals/runtime/lib/is-equal.ts similarity index 86% rename from packages/@ember/-internals/runtime/lib/is-equal.js rename to packages/@ember/-internals/runtime/lib/is-equal.ts index afebf55990d..41bddd71378 100644 --- a/packages/@ember/-internals/runtime/lib/is-equal.js +++ b/packages/@ember/-internals/runtime/lib/is-equal.ts @@ -47,9 +47,9 @@ @return {Boolean} @public */ -export default function isEqual(a, b) { - if (a && typeof a.isEqual === 'function') { - return a.isEqual(b); +export default function isEqual(a: unknown, b: unknown): boolean { + if (a && typeof (a as IsEqual).isEqual === 'function') { + return (a as IsEqual).isEqual(b); } if (a instanceof Date && b instanceof Date) { @@ -58,3 +58,7 @@ export default function isEqual(a, b) { return a === b; } + +interface IsEqual { + isEqual(val: unknown): boolean; +} diff --git a/packages/@ember/-internals/runtime/lib/mixins/action_handler.d.ts b/packages/@ember/-internals/runtime/lib/mixins/action_handler.d.ts index 5a09e39d957..07e4a62c7d0 100644 --- a/packages/@ember/-internals/runtime/lib/mixins/action_handler.d.ts +++ b/packages/@ember/-internals/runtime/lib/mixins/action_handler.d.ts @@ -1,3 +1,3 @@ -import Mixin from '../../types/mixin'; +import { Mixin } from '@ember/-internals/metal'; export default class ActionHandler extends Mixin {} diff --git a/packages/@ember/-internals/runtime/lib/mixins/comparable.d.ts b/packages/@ember/-internals/runtime/lib/mixins/comparable.d.ts new file mode 100644 index 00000000000..6cb017e9802 --- /dev/null +++ b/packages/@ember/-internals/runtime/lib/mixins/comparable.d.ts @@ -0,0 +1,8 @@ +import { Mixin } from '@ember/-internals/metal'; + +interface Comparable { + compare: ((a: unknown, b: unknown) => -1 | 0 | 1) | null; +} +declare const Comparable: Mixin; + +export default Comparable; diff --git a/packages/@ember/-internals/runtime/lib/mixins/container_proxy.d.ts b/packages/@ember/-internals/runtime/lib/mixins/container_proxy.d.ts index 2f9cbf8e112..74549deec9f 100644 --- a/packages/@ember/-internals/runtime/lib/mixins/container_proxy.d.ts +++ b/packages/@ember/-internals/runtime/lib/mixins/container_proxy.d.ts @@ -1,7 +1,7 @@ import { Container } from '@ember/-internals/container'; import { TypeOptions } from '@ember/-internals/container/lib/registry'; +import { Mixin } from '@ember/-internals/metal'; import { Factory } from '@ember/-internals/owner'; -import Mixin from '../../types/mixin'; interface ContainerProxy { /** @internal */ diff --git a/packages/@ember/-internals/runtime/lib/mixins/evented.d.ts b/packages/@ember/-internals/runtime/lib/mixins/evented.d.ts index 7bf3d4485fa..5d0bb160322 100644 --- a/packages/@ember/-internals/runtime/lib/mixins/evented.d.ts +++ b/packages/@ember/-internals/runtime/lib/mixins/evented.d.ts @@ -1,4 +1,4 @@ -import Mixin from '../../types/mixin'; +import { Mixin } from '@ember/-internals/metal'; /** * This mixin allows for Ember objects to subscribe to and emit events. diff --git a/packages/@ember/-internals/runtime/lib/mixins/registry_proxy.d.ts b/packages/@ember/-internals/runtime/lib/mixins/registry_proxy.d.ts index 08b5901250a..8e68a3df3ac 100644 --- a/packages/@ember/-internals/runtime/lib/mixins/registry_proxy.d.ts +++ b/packages/@ember/-internals/runtime/lib/mixins/registry_proxy.d.ts @@ -1,7 +1,7 @@ import { Registry } from '@ember/-internals/container'; import { TypeOptions } from '@ember/-internals/container/lib/registry'; +import { Mixin } from '@ember/-internals/metal'; import { Factory } from '@ember/-internals/owner'; -import Mixin from '../../types/mixin'; interface RegistryProxyMixin { /** @internal */ diff --git a/packages/@ember/-internals/runtime/lib/type-of.js b/packages/@ember/-internals/runtime/lib/type-of.ts similarity index 92% rename from packages/@ember/-internals/runtime/lib/type-of.js rename to packages/@ember/-internals/runtime/lib/type-of.ts index 4fd77f74760..0f39f4888d2 100644 --- a/packages/@ember/-internals/runtime/lib/type-of.js +++ b/packages/@ember/-internals/runtime/lib/type-of.ts @@ -1,9 +1,25 @@ import CoreObject from './system/core_object'; +type TypeName = + | 'undefined' + | 'null' + | 'string' + | 'number' + | 'boolean' + | 'function' + | 'array' + | 'regexp' + | 'date' + | 'filelist' + | 'class' + | 'instance' + | 'error' + | 'object'; + // ........................................ // TYPING & ARRAY MESSAGING // -const TYPE_MAP = { +const TYPE_MAP: Record = { '[object Boolean]': 'boolean', '[object Number]': 'number', '[object String]': 'string', @@ -14,7 +30,7 @@ const TYPE_MAP = { '[object RegExp]': 'regexp', '[object Object]': 'object', '[object FileList]': 'filelist', -}; +} as const; const { toString } = Object.prototype; @@ -82,7 +98,7 @@ const { toString } = Object.prototype; @public @static */ -export function typeOf(item) { +export function typeOf(item: unknown): TypeName { if (item === null) { return 'null'; } diff --git a/packages/@ember/-internals/runtime/types/mixin.d.ts b/packages/@ember/-internals/runtime/types/mixin.d.ts deleted file mode 100644 index abe42379680..00000000000 --- a/packages/@ember/-internals/runtime/types/mixin.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Mixin } from '../../metal/lib/mixin'; - -export default Mixin; diff --git a/packages/@ember/canary-features/type-tests/index.test.ts b/packages/@ember/canary-features/type-tests/index.test.ts new file mode 100644 index 00000000000..e16f8cacf21 --- /dev/null +++ b/packages/@ember/canary-features/type-tests/index.test.ts @@ -0,0 +1,8 @@ +import { DEFAULT_FEATURES, FEATURES, isEnabled } from '@ember/canary-features'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(DEFAULT_FEATURES).toMatchTypeOf>(); + +expectTypeOf(FEATURES).toMatchTypeOf>(); + +expectTypeOf(isEnabled('foo')).toMatchTypeOf(); diff --git a/packages/@ember/debug/lib/deprecate.ts b/packages/@ember/debug/lib/deprecate.ts index 66e0e77215b..45d79281587 100644 --- a/packages/@ember/debug/lib/deprecate.ts +++ b/packages/@ember/debug/lib/deprecate.ts @@ -71,14 +71,14 @@ export type MissingOptionDeprecateFunc = (id: string, missingOption: string) => @param handler {Function} A function to handle deprecation calls. @since 2.1.0 */ -let registerHandler: (handler: HandlerCallback) => void = () => {}; +let registerHandler: (handler: HandlerCallback) => void = () => {}; let missingOptionsDeprecation: string; let missingOptionsIdDeprecation: string; let missingOptionDeprecation: MissingOptionDeprecateFunc = () => ''; let deprecate: DeprecateFunc = () => {}; if (DEBUG) { - registerHandler = function registerHandler(handler: HandlerCallback): void { + registerHandler = function registerHandler(handler: HandlerCallback): void { genericRegisterHandler('deprecate', handler); }; diff --git a/packages/@ember/debug/lib/handlers.ts b/packages/@ember/debug/lib/handlers.ts index 1cf86cf43f3..a7df6c3979a 100644 --- a/packages/@ember/debug/lib/handlers.ts +++ b/packages/@ember/debug/lib/handlers.ts @@ -1,28 +1,41 @@ import { DEBUG } from '@glimmer/env'; export type Options = object; -export type Handler = (message: string, options?: Options) => void; -export type HandlerCallback = (message: string, options: any, nextHandler: Handler) => void; +export type Handler = (message: string, options?: O) => void; +export type HandlerCallback = ( + message: string, + options: O | undefined, + nextHandler: Handler +) => void; export interface Handlers { - [key: string]: Handler; + [key: string]: Handler; } export let HANDLERS: Handlers = {}; -export type RegisterHandlerFunc = (type: string, callback: HandlerCallback) => void; +export type RegisterHandlerFunc = ( + type: string, + callback: HandlerCallback +) => void; export type InvokeFunc = (type: string, message: string, test?: boolean, options?: Options) => void; -let registerHandler: RegisterHandlerFunc = () => {}; +let registerHandler = function registerHandler( + _type: string, + _callback: HandlerCallback +) {}; let invoke: InvokeFunc = () => {}; if (DEBUG) { - registerHandler = function registerHandler(type, callback) { - let nextHandler = HANDLERS[type] || (() => {}); + registerHandler = function registerHandler( + type: string, + callback: HandlerCallback + ): void { + let nextHandler: Handler = HANDLERS[type] || (() => {}); - HANDLERS[type] = (message, options) => { + HANDLERS[type] = ((message: string, options?: O) => { callback(message, options, nextHandler); - }; + }) as Handler; }; invoke = function invoke(type, message, test, options) { diff --git a/packages/@ember/debug/lib/warn.ts b/packages/@ember/debug/lib/warn.ts index fcadf0e64ad..9ef2f7c1eea 100644 --- a/packages/@ember/debug/lib/warn.ts +++ b/packages/@ember/debug/lib/warn.ts @@ -7,7 +7,7 @@ export interface WarnOptions { id: string; } -export type RegisterHandlerFunc = (handler: HandlerCallback) => void; +export type RegisterHandlerFunc = (handler: HandlerCallback) => void; export type WarnFunc = (message: string, test?: boolean, options?: WarnOptions) => void; let registerHandler: RegisterHandlerFunc = () => {}; diff --git a/packages/@ember/debug/tests/main_test.js b/packages/@ember/debug/tests/main_test.js index 8e476f2bab3..74c485c0555 100644 --- a/packages/@ember/debug/tests/main_test.js +++ b/packages/@ember/debug/tests/main_test.js @@ -59,7 +59,7 @@ moduleFor( id: 'test', until: 'forever', for: 'me', - since: { enabled: '1.0.0' }, + since: { available: '1.0.0', enabled: '1.0.0' }, }); assert.ok(true, 'deprecate did not throw'); } catch (e) { @@ -80,7 +80,7 @@ moduleFor( id: 'test', until: 'forever', for: 'me', - since: { enabled: '1.0.0' }, + since: { available: '1.0.0', enabled: '1.0.0' }, }); assert.ok(true, 'deprecate did not throw'); } catch (e) { @@ -94,7 +94,7 @@ moduleFor( id: 'test', until: 'forever', for: 'me', - since: { enabled: '1.0.0' }, + since: { available: '1.0.0', enabled: '1.0.0' }, }); }, /Should throw/); } @@ -116,7 +116,7 @@ moduleFor( id: 'my-deprecation', until: 'forever', for: 'me', - since: { enabled: '1.0.0' }, + since: { available: '1.0.0', enabled: '1.0.0' }, }); assert.ok(true, 'Did not throw when level is set by id'); } catch (e) { @@ -128,7 +128,7 @@ moduleFor( id: 'test', until: 'forever', for: 'me', - since: { enabled: '1.0.0' }, + since: { available: '1.0.0', enabled: '1.0.0' }, }); }, /Should throw with no matching id/); @@ -137,7 +137,7 @@ moduleFor( id: 'other-id', until: 'forever', for: 'me', - since: { enabled: '1.0.0' }, + since: { available: '1.0.0', enabled: '1.0.0' }, }); }, /Should throw with non-matching id/); } @@ -150,7 +150,7 @@ moduleFor( id: 'test', until: 'forever', for: 'me', - since: { enabled: '1.0.0' }, + since: { available: '1.0.0', enabled: '1.0.0' }, }) ); assert.throws(() => @@ -158,7 +158,7 @@ moduleFor( id: 'test', until: 'forever', for: 'me', - since: { enabled: '1.0.0' }, + since: { available: '1.0.0', enabled: '1.0.0' }, }) ); assert.throws(() => @@ -166,7 +166,7 @@ moduleFor( id: 'test', until: 'forever', for: 'me', - since: { enabled: '1.0.0' }, + since: { available: '1.0.0', enabled: '1.0.0' }, }) ); } @@ -179,7 +179,7 @@ moduleFor( function () { assert.ok(false, 'this function should not be invoked'); }, - { id: 'test', until: 'forever', for: 'me', since: { enabled: '1.0.0' } } + { id: 'test', until: 'forever', for: 'me', since: { available: '1.0.0', enabled: '1.0.0' } } ); assert.ok(true, 'deprecations were not thrown'); @@ -192,19 +192,19 @@ moduleFor( id: 'test', until: 'forever', for: 'me', - since: { enabled: '1.0.0' }, + since: { available: '1.0.0', enabled: '1.0.0' }, }); deprecate('Deprecation is thrown', '1', { id: 'test', until: 'forever', for: 'me', - since: { enabled: '1.0.0' }, + since: { available: '1.0.0', enabled: '1.0.0' }, }); deprecate('Deprecation is thrown', 1, { id: 'test', until: 'forever', for: 'me', - since: { enabled: '1.0.0' }, + since: { available: '1.0.0', enabled: '1.0.0' }, }); assert.ok(true, 'deprecations were not thrown'); @@ -316,7 +316,12 @@ moduleFor( assert.expect(1); assert.throws( - () => deprecate('foo', false, { until: 'forever', for: 'me', since: { enabled: '1.0.0' } }), + () => + deprecate('foo', false, { + until: 'forever', + for: 'me', + since: { available: '1.0.0', enabled: '1.0.0' }, + }), new RegExp(missingOptionsIdDeprecation), 'proper assertion is triggered when options.id is missing' ); diff --git a/packages/@ember/debug/type-tests/assert.test.ts b/packages/@ember/debug/type-tests/assert.test.ts new file mode 100644 index 00000000000..ed26bd456f7 --- /dev/null +++ b/packages/@ember/debug/type-tests/assert.test.ts @@ -0,0 +1,18 @@ +import { assert } from '@ember/debug'; +import { expectTypeOf } from 'expect-type'; + +let str: unknown; +assert('Must pass a string', typeof str === 'string'); + +// Fail unconditionally +assert('This code path should never be run'); + +expectTypeOf(assert('foo')).toEqualTypeOf(); + +// @ts-expect-error inverted order +assert(typeof str === 'string', 'Must pass a string'); + +// Functions as a type guard +let otherStr: unknown; +assert('Must pass a string', typeof otherStr === 'string'); +expectTypeOf(otherStr).toEqualTypeOf(); diff --git a/packages/@ember/debug/type-tests/debug.test.ts b/packages/@ember/debug/type-tests/debug.test.ts new file mode 100644 index 00000000000..8d9b424e886 --- /dev/null +++ b/packages/@ember/debug/type-tests/debug.test.ts @@ -0,0 +1,7 @@ +import { debug } from '@ember/debug'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(debug("I'm a debug notice!")).toEqualTypeOf(); + +// @ts-expect-error requires a string +debug(1); diff --git a/packages/@ember/debug/type-tests/deprecate.test.ts b/packages/@ember/debug/type-tests/deprecate.test.ts new file mode 100644 index 00000000000..06b2050f5cd --- /dev/null +++ b/packages/@ember/debug/type-tests/deprecate.test.ts @@ -0,0 +1,70 @@ +import { deprecate } from '@ember/debug'; +import { expectTypeOf } from 'expect-type'; + +let str: unknown; +deprecate('Must pass a string', typeof str === 'string'); + +// Always deprecated +deprecate('This code path should never be run'); + +expectTypeOf(deprecate('foo')).toEqualTypeOf(); + +deprecate('foo', true, { + id: 'foo', + until: '5.0.0', + url: 'http://example.com/deprecation', + for: 'my-module', + since: { available: '1.0.0', enabled: '1.0.0' }, +}); + +deprecate('foo', true, { + id: 'foo', + until: '5.0.0', + for: 'my-module', + since: { available: '1.0.0', enabled: '1.0.0' }, +}); + +deprecate('foo', true, { + id: 'foo', + until: '5.0.0', + for: 'my-module', + since: { available: '1.0.0' }, +}); + +// @ts-expect-error requires id +deprecate('foo', true, { + until: '5.0.0', + for: 'my-module', + since: { available: '1.0.0', enabled: '1.0.0' }, +}); + +// @ts-expect-error requires until +deprecate('foo', true, { + id: 'foo', + for: 'my-module', + since: { available: '1.0.0', enabled: '1.0.0' }, +}); + +// @ts-expect-error requires for +deprecate('foo', true, { + id: 'foo', + until: '5.0.0', + since: { available: '1.0.0', enabled: '1.0.0' }, +}); + +// @ts-expect-error requires since +deprecate('foo', true, { + id: 'foo', + until: '5.0.0', + for: 'my-module', +}); + +deprecate('foo', true, { + id: 'foo', + until: '5.0.0', + // @ts-expect-error requires 'available' if 'enabled' present + since: { enabled: '1.0.0' }, +}); + +// @ts-expect-error inverted order +deprecate(typeof str === 'string', 'Must pass a string'); diff --git a/packages/@ember/debug/type-tests/register-deprecation-handler.test.ts b/packages/@ember/debug/type-tests/register-deprecation-handler.test.ts new file mode 100644 index 00000000000..f56f9090f48 --- /dev/null +++ b/packages/@ember/debug/type-tests/register-deprecation-handler.test.ts @@ -0,0 +1,18 @@ +import { registerDeprecationHandler } from '@ember/debug'; +import { expectTypeOf } from 'expect-type'; +import { DeprecationOptions } from '../lib/deprecate'; +import { Handler } from '../lib/handlers'; + +let ret = registerDeprecationHandler((message, options, next) => { + expectTypeOf(message).toEqualTypeOf(); + expectTypeOf(options).toEqualTypeOf(); + expectTypeOf(next).toEqualTypeOf>(); + + if (message.indexOf('should') !== -1) { + throw new Error(`Deprecation message with should: ${message}`); + } else { + // defer to whatever handler was registered before this one + next(message, options); + } +}); +expectTypeOf(ret).toEqualTypeOf(); diff --git a/packages/@ember/debug/type-tests/register-warn-handler.test.ts b/packages/@ember/debug/type-tests/register-warn-handler.test.ts new file mode 100644 index 00000000000..4faf33ff7ff --- /dev/null +++ b/packages/@ember/debug/type-tests/register-warn-handler.test.ts @@ -0,0 +1,18 @@ +import { registerWarnHandler } from '@ember/debug'; +import { expectTypeOf } from 'expect-type'; +import { Handler } from '../lib/handlers'; +import { WarnOptions } from '../lib/warn'; + +let ret = registerWarnHandler((message, options, next) => { + expectTypeOf(message).toEqualTypeOf(); + expectTypeOf(options).toEqualTypeOf(); + expectTypeOf(next).toEqualTypeOf>(); + + if (message.indexOf('should') !== -1) { + throw new Error(`Warn message with should: ${message}`); + } else { + // defer to whatever handler was registered before this one + next(message, options); + } +}); +expectTypeOf(ret).toEqualTypeOf(); diff --git a/packages/@ember/debug/type-tests/run-in-debug.test.ts b/packages/@ember/debug/type-tests/run-in-debug.test.ts new file mode 100644 index 00000000000..040bdd58228 --- /dev/null +++ b/packages/@ember/debug/type-tests/run-in-debug.test.ts @@ -0,0 +1,10 @@ +import { runInDebug } from '@ember/debug'; +import { expectTypeOf } from 'expect-type'; + +let ret = runInDebug(() => { + // Do expensive thing +}); +expectTypeOf(ret).toEqualTypeOf(); + +// @ts-expect-error no arguments to callbacks +runInDebug((_foo: boolean) => {}); diff --git a/packages/@ember/debug/type-tests/warn.test.ts b/packages/@ember/debug/type-tests/warn.test.ts new file mode 100644 index 00000000000..2b6508e0195 --- /dev/null +++ b/packages/@ember/debug/type-tests/warn.test.ts @@ -0,0 +1,16 @@ +import { warn } from '@ember/debug'; +import { expectTypeOf } from 'expect-type'; + +let foo = true; +let ret = warn('Too much foo!', foo, { + id: 'ember-debug.too-much-foo', +}); +expectTypeOf(ret).toEqualTypeOf(); + +warn('No options'); + +// @ts-expect-error Invalid condition +warn('Invalid condition', { id: 1 }); + +// @ts-expect-error Invalid options +warn('Invalid options', true, { id: 1 }); diff --git a/packages/@ember/destroyable/type-tests/assert-destroyables-destroyed.test.ts b/packages/@ember/destroyable/type-tests/assert-destroyables-destroyed.test.ts new file mode 100644 index 00000000000..87d732dd5a0 --- /dev/null +++ b/packages/@ember/destroyable/type-tests/assert-destroyables-destroyed.test.ts @@ -0,0 +1,6 @@ +import { assertDestroyablesDestroyed } from '@ember/destroyable'; +import { expectTypeOf } from 'expect-type'; + +if (assertDestroyablesDestroyed) { + expectTypeOf(assertDestroyablesDestroyed()).toEqualTypeOf(); +} diff --git a/packages/@ember/destroyable/type-tests/associate-destroyable-child.test.ts b/packages/@ember/destroyable/type-tests/associate-destroyable-child.test.ts new file mode 100644 index 00000000000..59eb1898d91 --- /dev/null +++ b/packages/@ember/destroyable/type-tests/associate-destroyable-child.test.ts @@ -0,0 +1,16 @@ +import { associateDestroyableChild } from '@ember/destroyable'; +import { expectTypeOf } from 'expect-type'; + +class Foo {} +class Bar {} + +let foo = new Foo(); +let bar = new Bar(); + +expectTypeOf(associateDestroyableChild(foo, bar)).toEqualTypeOf(); +// @ts-expect-error number is not destroyable +associateDestroyableChild(1, bar); +// @ts-expect-error number is not destroyable +associateDestroyableChild(foo, 1); +// @ts-expect-error two values required +associateDestroyableChild(foo); diff --git a/packages/@ember/destroyable/type-tests/destroy.test.ts b/packages/@ember/destroyable/type-tests/destroy.test.ts new file mode 100644 index 00000000000..1c5b19bc4fb --- /dev/null +++ b/packages/@ember/destroyable/type-tests/destroy.test.ts @@ -0,0 +1,15 @@ +import { destroy, registerDestructor } from '@ember/destroyable'; +import { expectTypeOf } from 'expect-type'; + +let obj = {}; + +registerDestructor(obj, () => { + /* Will get called when destroyed */ +}); + +expectTypeOf(destroy(obj)).toEqualTypeOf(); + +// @ts-expect-error not destroyable +destroy(1); +// @ts-expect-error requires arg +destroy(); diff --git a/packages/@ember/destroyable/type-tests/enable-destroy-tracking.test.ts b/packages/@ember/destroyable/type-tests/enable-destroy-tracking.test.ts new file mode 100644 index 00000000000..5a67e89ae5a --- /dev/null +++ b/packages/@ember/destroyable/type-tests/enable-destroy-tracking.test.ts @@ -0,0 +1,6 @@ +import { enableDestroyableTracking } from '@ember/destroyable'; +import { expectTypeOf } from 'expect-type'; + +if (enableDestroyableTracking) { + expectTypeOf(enableDestroyableTracking()).toEqualTypeOf(); +} diff --git a/packages/@ember/destroyable/type-tests/is-destroyed.test.ts b/packages/@ember/destroyable/type-tests/is-destroyed.test.ts new file mode 100644 index 00000000000..afae820b4ea --- /dev/null +++ b/packages/@ember/destroyable/type-tests/is-destroyed.test.ts @@ -0,0 +1,16 @@ +import { destroy, isDestroyed } from '@ember/destroyable'; +import { expectTypeOf } from 'expect-type'; + +let obj = {}; + +expectTypeOf(isDestroyed(obj)).toEqualTypeOf(); // false +destroy(obj); + +// ...sometime later, after scheduled destruction + +expectTypeOf(isDestroyed(obj)).toEqualTypeOf(); // true + +// @ts-expect-error requires a destroyable value +isDestroyed(1); +// @ts-expect-error requires a value +isDestroyed(); diff --git a/packages/@ember/destroyable/type-tests/is-destroying.test.ts b/packages/@ember/destroyable/type-tests/is-destroying.test.ts new file mode 100644 index 00000000000..e5f6eb6c691 --- /dev/null +++ b/packages/@ember/destroyable/type-tests/is-destroying.test.ts @@ -0,0 +1,19 @@ +import { destroy, isDestroying, isDestroyed } from '@ember/destroyable'; +import { expectTypeOf } from 'expect-type'; + +let obj = {}; + +expectTypeOf(isDestroying(obj)).toEqualTypeOf(); // false + +destroy(obj); + +isDestroying(obj); // true + +// ...sometime later, after scheduled destruction +isDestroyed(obj); // true +isDestroying(obj); // true + +// @ts-expect-error requires a destroyable value +isDestroying(1); +// @ts-expect-error requires a value +isDestroying(); diff --git a/packages/@ember/destroyable/type-tests/register-destructor.test.ts b/packages/@ember/destroyable/type-tests/register-destructor.test.ts new file mode 100644 index 00000000000..d7540273a0c --- /dev/null +++ b/packages/@ember/destroyable/type-tests/register-destructor.test.ts @@ -0,0 +1,27 @@ +import { destroy, registerDestructor } from '@ember/destroyable'; +import { expectTypeOf } from 'expect-type'; + +class Foo {} +let obj = new Foo(); + +expectTypeOf( + registerDestructor(obj, () => { + /* Will get called when destroyed */ + }) +).toEqualTypeOf<(destroyable: {}) => void>(); + +registerDestructor(obj, (_obj: Foo) => {}); + +destroy(obj); + +// @ts-expect-error not destroyable +registerDestructor(1, () => {}); + +// @ts-expect-error requires object +registerDestructor(() => {}); + +// @ts-expect-error requires callback +registerDestructor(obj); + +// @ts-expect-error invalid callback +registerDestructor(obj, (blah: number) => {}); diff --git a/packages/@ember/destroyable/type-tests/unregister-destructor.test.ts b/packages/@ember/destroyable/type-tests/unregister-destructor.test.ts new file mode 100644 index 00000000000..99bdfa8e665 --- /dev/null +++ b/packages/@ember/destroyable/type-tests/unregister-destructor.test.ts @@ -0,0 +1,30 @@ +import { registerDestructor, unregisterDestructor } from '@ember/destroyable'; +import { expectTypeOf } from 'expect-type'; + +class Foo { + type: 'foo' = 'foo'; +} +let obj = new Foo(); + +let destructor = registerDestructor(obj, () => { + /* Will get called when destroyed */ +}); + +expectTypeOf(unregisterDestructor(obj, destructor)).toEqualTypeOf(); + +// @ts-expect-error invalid destructor +unregisterDestructor(obj, 1); + +// @ts-expect-error requires destructor +unregisterDestructor(obj); + +// @ts-expect-error requires object +unregisterDestructor(destructor); + +class Bar { + type: 'blah' = 'blah'; +} +let obj2 = new Bar(); + +// @ts-expect-error destroyable type mismatch +unregisterDestructor(obj2, destructor); diff --git a/packages/@ember/error/type-tests/index.test.ts b/packages/@ember/error/type-tests/index.test.ts new file mode 100644 index 00000000000..a0120bdacc5 --- /dev/null +++ b/packages/@ember/error/type-tests/index.test.ts @@ -0,0 +1,4 @@ +import EmberError from '@ember/error'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(EmberError).toEqualTypeOf(); diff --git a/packages/@ember/helper/type-tests/capabilities.test.ts b/packages/@ember/helper/type-tests/capabilities.test.ts new file mode 100644 index 00000000000..cae8caa04b4 --- /dev/null +++ b/packages/@ember/helper/type-tests/capabilities.test.ts @@ -0,0 +1,14 @@ +import { capabilities } from '@ember/helper'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(capabilities('3.23')).toMatchTypeOf<{ + hasValue: boolean; + hasDestroyable: boolean; + hasScheduledEffect: boolean; +}>(); + +capabilities('3.23', { hasValue: true }); +// @ts-expect-error invalid capabilities +capabilities('3.23', { hasValue: 1 }); +// @ts-expect-error invalid verison +capabilities('3.22'); diff --git a/packages/@ember/helper/type-tests/invoke-helper.test.ts b/packages/@ember/helper/type-tests/invoke-helper.test.ts new file mode 100644 index 00000000000..596b856bc8c --- /dev/null +++ b/packages/@ember/helper/type-tests/invoke-helper.test.ts @@ -0,0 +1,30 @@ +import Component from '@ember/-internals/glimmer/lib/component'; +import { getValue } from '@ember/-internals/metal'; +import Helper from '@ember/component/helper'; +import { invokeHelper } from '@ember/helper'; +import { Cache } from '@glimmer/validator'; +import { expectTypeOf } from 'expect-type'; + +// NOTE: The types should probably be stricter, but they're from glimmer itself + +class PlusOne extends Helper { + compute([number]: [number]) { + return number + 1; + } +} + +export default class PlusOneComponent extends Component { + plusOne = invokeHelper(this, PlusOne, () => { + return { + positional: [this.args.number], + }; + }); + + get value() { + return getValue(this.plusOne); + } +} + +let component = new PlusOneComponent(); + +expectTypeOf(component.plusOne).toEqualTypeOf>(); diff --git a/packages/@ember/helper/type-tests/set-helper-manager.test.ts b/packages/@ember/helper/type-tests/set-helper-manager.test.ts new file mode 100644 index 00000000000..9ad7937b270 --- /dev/null +++ b/packages/@ember/helper/type-tests/set-helper-manager.test.ts @@ -0,0 +1,50 @@ +import { HelperFactory, HelperFunction, SimpleHelper } from '@ember/-internals/glimmer/lib/helper'; +import { getDebugName } from '@ember/-internals/utils'; +import { capabilities, setHelperManager } from '@ember/helper'; +import { Arguments, HelperManager } from '@glimmer/interfaces'; +import { expectTypeOf } from 'expect-type'; + +class Wrapper implements HelperFactory { + isHelperFactory: true = true; + + constructor(public compute: HelperFunction) {} + + create() { + // needs new instance or will leak containers + return { + compute: this.compute, + }; + } +} + +class SimpleClassicHelperManager implements HelperManager<() => unknown> { + capabilities = capabilities('3.23', { + hasValue: true, + }); + + createHelper(definition: Wrapper, args: Arguments) { + let { compute } = definition; + + return () => compute.call(null, args.positional as unknown[], args.named); + } + + getValue(fn: () => unknown) { + return fn(); + } + + getDebugName(definition: Wrapper) { + return getDebugName!(definition.compute); + } +} + +export const SIMPLE_CLASSIC_HELPER_MANAGER = new SimpleClassicHelperManager(); + +expectTypeOf( + setHelperManager(() => SIMPLE_CLASSIC_HELPER_MANAGER, Wrapper.prototype) +).toEqualTypeOf(); + +// @ts-expect-error invalid factory +setHelperManager(1, Wrapper.prototype); + +// @ts-expect-error requires a second param +setHelperManager(() => SIMPLE_CLASSIC_HELPER_MANAGER); diff --git a/packages/@ember/polyfills/type-tests/assign.test.ts b/packages/@ember/polyfills/type-tests/assign.test.ts new file mode 100644 index 00000000000..3d7f0484266 --- /dev/null +++ b/packages/@ember/polyfills/type-tests/assign.test.ts @@ -0,0 +1,14 @@ +import { assign } from '@ember/polyfills'; +import { expectTypeOf } from 'expect-type'; + +// NOTE: Actual types could be better, but this is deprecated. + +let a = { first: 'Yehuda' }; +let b = { last: 'Katz' }; +let c = { company: 'Other Company' }; +let d = { company: 'Tilde Inc.' }; +expectTypeOf(assign(a, b, c, d)).toEqualTypeOf<{ + first: string; + last: string; + company: string; +}>(); diff --git a/packages/@ember/runloop/index.d.ts b/packages/@ember/runloop/index.d.ts deleted file mode 100644 index fa5bea17840..00000000000 --- a/packages/@ember/runloop/index.d.ts +++ /dev/null @@ -1,25 +0,0 @@ -import Backburner, { DeferredActionQueues, Timer } from 'backburner.js'; - -export const _backburner: Backburner; - -export const run: Backburner['run']; -export const schedule: Backburner['schedule']; -export const later: Backburner['later']; -export const join: Backburner['join']; -export const cancel: Backburner['cancel']; -export const scheduleOnce: Backburner['scheduleOnce']; - -export function _getCurrentRunLoop(): DeferredActionQueues; - -export function once(method: Function): Timer; -export function once(target: T, method: U, ...args: any[]): Timer; -export function once(target: unknown, method: unknown | Function, ...args: any[]): Timer; - -export function bind(target: T): T; -export function bind( - target: unknown, - method?: T, - ...args: U -): T; -export function bind(target: unknown, method?: string, ...args: T): Function; -export function bind(target: unknown, method?: T, ...args: unknown[]): T; diff --git a/packages/@ember/runloop/index.js b/packages/@ember/runloop/index.ts similarity index 72% rename from packages/@ember/runloop/index.js rename to packages/@ember/runloop/index.ts index e91e934ee3a..ae87b46b46e 100644 --- a/packages/@ember/runloop/index.js +++ b/packages/@ember/runloop/index.ts @@ -1,24 +1,53 @@ import { assert } from '@ember/debug'; import { onErrorTarget } from '@ember/-internals/error-handling'; import { flushAsyncObservers } from '@ember/-internals/metal'; -import Backburner from 'backburner'; - -let currentRunLoop = null; +import Backburner, { DeferredActionQueues, Timer } from 'backburner'; + +export { Timer }; + +// Partial types from https://medium.com/codex/currying-in-typescript-ca5226c85b85 + +type PartialParams

= P extends [infer First, ...infer Rest] + ? [] | [First] | [First, ...PartialParams] + : // This is necessary to handle optional tuple values + Required

extends [infer First, ...infer Rest] + ? [] | [First | undefined] | [First | undefined, ...PartialParams>] + : never; + +type RemainingParams = PartialParams extends [ + infer First, + ...infer Rest +] + ? All extends [infer AllFirst, ...infer AllRest] + ? First extends AllFirst + ? RemainingParams + : never + : // This is necessary to handle optional tuple values + Required extends [infer AllFirst, ...infer AllRest] + ? First extends AllFirst | undefined + ? Partial> + : never + : never + : PartialParams extends [] + ? All + : never; + +let currentRunLoop: DeferredActionQueues | null = null; export function _getCurrentRunLoop() { return currentRunLoop; } -function onBegin(current) { +function onBegin(current: DeferredActionQueues) { currentRunLoop = current; } -function onEnd(current, next) { +function onEnd(_current: DeferredActionQueues, next: DeferredActionQueues) { currentRunLoop = next; flushAsyncObservers(); } -function flush(queueName, next) { +function flush(queueName: string, next: () => void) { if (queueName === 'render' || queueName === _rsvpErrorQueue) { flushAsyncObservers(); } @@ -99,8 +128,20 @@ export const _backburner = new Backburner(_queues, { @return {Object} return value from invoking the passed function. @public */ -export function run() { - return _backburner.run(...arguments); +export function run any>(method: F, ...args: Parameters): void; +export function run any>( + target: T, + method: F, + ...args: Parameters +): void; +export function run( + target: T, + method: U, + ...args: T[U] extends (...args: any[]) => any ? Parameters : [] +): void; +export function run(...args: any[]): void { + // @ts-expect-error TS doesn't like our spread args + return _backburner.run(...args); } /** @@ -147,8 +188,22 @@ export function run() { when called within an existing loop, no return value is possible. @public */ -export function join() { - return _backburner.join(...arguments); +export function join any>( + method: F, + ...args: Parameters +): ReturnType | void; +export function join any>( + target: T, + method: F, + ...args: Parameters +): ReturnType | void; +export function join( + target: T, + method: U, + ...args: T[U] extends (...args: any[]) => any ? Parameters : [] +): T[U] extends (...args: any[]) => any ? ReturnType | void : void; +export function join(methodOrTarget: any, methodOrArg?: any, ...additionalArgs: any[]): any { + return _backburner.join(methodOrTarget, methodOrArg, ...additionalArgs); } /** @@ -213,7 +268,31 @@ export function join() { @since 1.4.0 @public */ -export const bind = (...curried) => { +export function bind< + T, + F extends (this: T, ...args: any[]) => any, + A extends PartialParams> +>( + target: T, + method: F, + ...args: A +): (...args: RemainingParams>) => ReturnType | void; +export function bind any, A extends PartialParams>>( + method: F, + ...args: A +): (...args: RemainingParams>) => ReturnType | void; +export function bind< + T, + U extends keyof T, + A extends T[U] extends (...args: any[]) => any ? PartialParams> : [] +>( + target: T, + method: U, + ...args: A +): T[U] extends (...args: any[]) => any + ? (...args: RemainingParams>) => ReturnType | void + : never; +export function bind(...curried: any[]): any { assert( 'could not find a suitable method to bind', (function (methodOrTarget, methodOrArg) { @@ -226,17 +305,20 @@ export const bind = (...curried) => { } else if (length === 1) { return typeof methodOrTarget === 'function'; } else { - let type = typeof methodOrArg; return ( - type === 'function' || // second argument is a function - (methodOrTarget !== null && type === 'string' && methodOrArg in methodOrTarget) || // second argument is the name of a method in first argument + typeof methodOrArg === 'function' || // second argument is a function + (methodOrTarget !== null && + typeof methodOrArg === 'string' && + methodOrArg in methodOrTarget) || // second argument is the name of a method in first argument typeof methodOrTarget === 'function' //first argument is a function ); } + // @ts-expect-error TS doesn't like our spread args })(...curried) ); - return (...args) => join(...curried.concat(args)); -}; + // @ts-expect-error TS doesn't like our spread args + return (...args: any[]) => join(...curried.concat(args)); +} /** Begins a new RunLoop. Any deferred actions invoked after the begin will @@ -325,8 +407,26 @@ export function end() { @return {*} Timer information for use in canceling, see `cancel`. @public */ -export function schedule(/* queue, target, method */) { - return _backburner.schedule(...arguments); +export function schedule any>( + queueName: string, + method: F, + ...args: Parameters +): Timer; +export function schedule any>( + queueName: string, + target: T, + method: F, + ...args: Parameters +): Timer; +export function schedule( + queueName: string, + target: T, + method: U, + ...args: T[U] extends (...args: any[]) => any ? Parameters : [] +): Timer; +export function schedule(...args: any[]): Timer { + // @ts-expect-error TS doesn't like the rest args here + return _backburner.schedule(...args); } // Used by global test teardown @@ -369,8 +469,25 @@ export function _cancelTimers() { @return {*} Timer information for use in canceling, see `cancel`. @public */ -export function later(/*target, method*/) { - return _backburner.later(...arguments); +export function later any>( + target: T, + method: F, + ...args: [...args: Parameters, wait: string | number] +): Timer; +export function later any>( + method: F, + ...args: [...args: Parameters, wait: string | number] +): Timer; +export function later( + target: T, + method: U, + ...args: [ + ...args: T[U] extends (...args: any[]) => any ? Parameters : [], + wait: string | number + ] +): Timer; +export function later(...args: any): Timer { + return _backburner.later(...args); } /** @@ -388,9 +505,20 @@ export function later(/*target, method*/) { @return {Object} Timer information for use in canceling, see `cancel`. @public */ -export function once(...args) { - args.unshift('actions'); - return _backburner.scheduleOnce(...args); +export function once any>(method: F, ...args: Parameters): Timer; +export function once any>( + target: T, + method: F, + ...args: Parameters +): Timer; +export function once( + target: T, + method: U, + ...args: T[U] extends (...args: any[]) => any ? Parameters : [] +): Timer; +export function once(...args: any[]): Timer { + // @ts-expect-error TS doesn't like the rest args here + return _backburner.scheduleOnce('actions', ...args); } /** @@ -465,8 +593,26 @@ export function once(...args) { @return {Object} Timer information for use in canceling, see `cancel`. @public */ -export function scheduleOnce(/* queue, target, method*/) { - return _backburner.scheduleOnce(...arguments); +export function scheduleOnce any>( + queueName: string, + method: F, + ...args: Parameters +): Timer; +export function scheduleOnce any>( + queueName: string, + target: T, + method: F, + ...args: Parameters +): Timer; +export function scheduleOnce( + queueName: string, + target: T, + method: U, + ...args: T[U] extends (...args: any[]) => any ? Parameters : [] +): Timer; +export function scheduleOnce(...args: any[]): Timer { + // @ts-expect-error TS doesn't like the rest args here + return _backburner.scheduleOnce(...args); } /** @@ -539,9 +685,19 @@ export function scheduleOnce(/* queue, target, method*/) { @return {Object} Timer information for use in canceling, see `cancel`. @public */ -export function next(...args) { - args.push(1); - return _backburner.later(...args); +export function next any>(method: F, ...args: Parameters): Timer; +export function next any>( + target: T, + method: F, + ...args: Parameters +): Timer; +export function next( + target: T, + method: U, + ...args: T[U] extends (...args: any[]) => any ? Parameters : [] +): Timer; +export function next(...args: any[]) { + return _backburner.later(...args, 1); } /** @@ -611,7 +767,7 @@ export function next(...args) { @return {Boolean} true if canceled or false/undefined if it wasn't found @public */ -export function cancel(timer) { +export function cancel(timer: Timer): boolean { return _backburner.cancel(timer); } @@ -689,8 +845,27 @@ export function cancel(timer) { @return {Array} Timer information for use in canceling, see `cancel`. @public */ -export function debounce() { - return _backburner.debounce(...arguments); +export function debounce any>( + method: F, + ...args: [...args: Parameters, wait: string | number, immediate?: boolean] +): Timer; +export function debounce any>( + target: T, + method: F, + ...args: [...args: Parameters, wait: string | number, immediate?: boolean] +): Timer; +export function debounce( + target: T, + method: U, + ...args: [ + ...args: T[U] extends (...args: any[]) => any ? Parameters : [], + wait: string | number, + immediate?: boolean + ] +): Timer; +export function debounce(...args: any[]) { + // @ts-expect-error TS doesn't like the rest args here + return _backburner.debounce(...args); } /** @@ -736,6 +911,25 @@ export function debounce() { @return {Array} Timer information for use in canceling, see `cancel`. @public */ -export function throttle() { - return _backburner.throttle(...arguments); +export function throttle any>( + method: F, + ...args: [...args: Parameters, wait?: string | number, immediate?: boolean] +): Timer; +export function throttle any>( + target: T, + method: F, + ...args: [...args: Parameters, wait?: string | number, immediate?: boolean] +): Timer; +export function throttle( + target: T, + method: U, + ...args: [ + ...args: T[U] extends (...args: any[]) => any ? Parameters : [], + wait?: string | number, + immediate?: boolean + ] +): Timer; +export function throttle(...args: any[]): Timer { + // @ts-expect-error TS doesn't like the rest args here + return _backburner.throttle(...args); } diff --git a/packages/@ember/runloop/type-tests.ts/begin-end.test.ts b/packages/@ember/runloop/type-tests.ts/begin-end.test.ts new file mode 100644 index 00000000000..fc890450b8e --- /dev/null +++ b/packages/@ember/runloop/type-tests.ts/begin-end.test.ts @@ -0,0 +1,7 @@ +import { begin, end } from '@ember/runloop'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(begin()).toEqualTypeOf(); + +// code to be executed within a RunLoop +expectTypeOf(end()).toEqualTypeOf(); diff --git a/packages/@ember/runloop/type-tests.ts/bind.test.ts b/packages/@ember/runloop/type-tests.ts/bind.test.ts new file mode 100644 index 00000000000..f8886aed0c2 --- /dev/null +++ b/packages/@ember/runloop/type-tests.ts/bind.test.ts @@ -0,0 +1,138 @@ +import { bind } from '@ember/runloop'; +import { expectTypeOf } from 'expect-type'; + +class Foo { + test(_foo: number, _bar: boolean, _baz?: string): number { + return 1; + } +} + +let foo = new Foo(); + +// With only function +expectTypeOf( + bind((_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }) +).toEqualTypeOf<(foo: number, bar: boolean, baz?: string) => number | void>(); + +expectTypeOf( + bind((_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, 1) +).toEqualTypeOf<(bar: boolean, baz?: string) => number | void>(); + +expectTypeOf( + bind( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true + ) +).toEqualTypeOf<(baz?: string) => number | void>(); + +expectTypeOf( + bind( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + 'baz' + ) +).toEqualTypeOf<() => number | void>(); + +expectTypeOf( + bind( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + undefined + ) +).toEqualTypeOf<() => number | void>(); + +bind((_foo: number): number => { + return 1; + // @ts-expect-error invalid argument +}, 'string'); + +// With target and function +expectTypeOf( + bind(foo, function (_foo: number, _bar: boolean, _baz?: string): number { + expectTypeOf(this).toEqualTypeOf(); + return 1; + }) +).toEqualTypeOf<(foo: number, bar: boolean, baz?: string) => number | void>(); + +expectTypeOf( + bind( + foo, + function (this: Foo, _foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1 + ) +).toEqualTypeOf<(bar: boolean, baz?: string) => number | void>(); + +expectTypeOf( + bind( + foo, + function (this: Foo, _foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + true + ) +).toEqualTypeOf<(baz?: string) => number | void>(); + +expectTypeOf( + bind( + foo, + function (this: Foo, _foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + true, + 'baz' + ) +).toEqualTypeOf<() => number | void>(); + +expectTypeOf( + bind( + foo, + function (this: Foo, _foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + true, + undefined + ) +).toEqualTypeOf<() => number | void>(); + +// @ts-expect-error Invalid args +bind( + foo, + function (this: Foo, _foo: number): number { + return 1; + }, + 'string' +); + +// With function string reference +expectTypeOf(bind(foo, 'test')).toEqualTypeOf< + (foo: number, bar: boolean, baz?: string) => number | void +>(); + +expectTypeOf(bind(foo, 'test', 1)).toEqualTypeOf<(bar: boolean, baz?: string) => number | void>(); + +expectTypeOf(bind(foo, 'test', 1, true)).toEqualTypeOf<(baz?: string) => number | void>(); + +expectTypeOf(bind(foo, 'test', 1, true, 'baz')).toEqualTypeOf<() => number | void>(); + +expectTypeOf(bind(foo, 'test', 1, true, undefined)).toEqualTypeOf<() => number | void>(); + +// @ts-expect-error Invalid args +bind(foo, 'test', 'string'); diff --git a/packages/@ember/runloop/type-tests.ts/cancel.test.ts b/packages/@ember/runloop/type-tests.ts/cancel.test.ts new file mode 100644 index 00000000000..a02c22132b2 --- /dev/null +++ b/packages/@ember/runloop/type-tests.ts/cancel.test.ts @@ -0,0 +1,8 @@ +import { cancel, next } from '@ember/runloop'; +import { expectTypeOf } from 'expect-type'; + +let runNext = next(null, () => { + // will not be executed +}); + +expectTypeOf(cancel(runNext)).toEqualTypeOf(); diff --git a/packages/@ember/runloop/type-tests.ts/debounce.test.ts b/packages/@ember/runloop/type-tests.ts/debounce.test.ts new file mode 100644 index 00000000000..b3b0d1c71f7 --- /dev/null +++ b/packages/@ember/runloop/type-tests.ts/debounce.test.ts @@ -0,0 +1,148 @@ +import { debounce } from '@ember/runloop'; +import { Timer } from 'backburner.js'; +import { expectTypeOf } from 'expect-type'; + +// From Docs + +function whoRan() { + // Do stuff +} + +let myContext = { name: 'debounce', test(_foo: number, _baz?: boolean): void {} }; + +debounce(myContext, whoRan, 150); + +// less than 150ms passes +debounce(myContext, whoRan, 150); + +debounce(myContext, whoRan, 150, true); + +// console logs 'debounce ran.' one time immediately. +// 100ms passes +debounce(myContext, whoRan, 150, true); + +// 150ms passes and nothing else is logged to the console and +// the debouncee is no longer being watched +debounce(myContext, whoRan, 150, true); + +// console logs 'debounce ran.' one time immediately. +// 150ms passes and nothing else is logged to the console and +// the debouncee is no longer being watched + +// Method only +expectTypeOf(debounce((_foo: number, _baz?: boolean): void => {}, 1, undefined, 1)).toEqualTypeOf< + Timer +>(); + +// @ts-expect-error Requires wait +debounce((_foo: number, _baz?: boolean): void => {}, 1, true); + +// @ts-expect-error Requires all args +debounce((_foo: number, _baz?: boolean): void => {}, 1, 1); + +// Can set immediate +debounce((_foo: number, _baz?: boolean): void => {}, 1, true, 1, true); + +// With target +debounce( + myContext, + function (_foo: number, _baz?: boolean): void { + expectTypeOf(this).toEqualTypeOf(myContext); + }, + 1, + true, + 1, + true +); + +// With key +debounce(myContext, 'test', 1, true, 1, true); + +// @ts-expect-error invalid key +debounce(myContext, 'invalid'); + +class Foo { + test(_foo: number, _bar: boolean, _baz?: string): number { + return 1; + } +} + +let foo = new Foo(); + +// With only function +expectTypeOf( + debounce( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + undefined, + 1 + ) +).toEqualTypeOf(); + +expectTypeOf( + debounce( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + 'string', + 1 + ) +).toEqualTypeOf(); + +debounce((_foo: number): number => { + return 1; + // @ts-expect-error invalid argument +}, 'string'); + +// With target and function +expectTypeOf( + debounce( + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + expectTypeOf(this).toEqualTypeOf(); + return 1; + }, + 1, + true, + undefined, + 1 + ) +).toEqualTypeOf(); + +expectTypeOf( + debounce( + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + true, + 'string', + 1 + ) +).toEqualTypeOf(); + +// @ts-expect-error invalid args +debounce( + foo, + function (this: Foo, _foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + 'string', + true, + 1 +); + +// With function string reference +expectTypeOf(debounce(foo, 'test', 1, true, 'string', 1)).toEqualTypeOf(); + +expectTypeOf(debounce(foo, 'test', 1, true, undefined, 1)).toEqualTypeOf(); + +// @ts-expect-error Invalid args +debounce(foo, 'test', 'string'); diff --git a/packages/@ember/runloop/type-tests.ts/join.test.ts b/packages/@ember/runloop/type-tests.ts/join.test.ts new file mode 100644 index 00000000000..a867cde90d7 --- /dev/null +++ b/packages/@ember/runloop/type-tests.ts/join.test.ts @@ -0,0 +1,80 @@ +import { join } from '@ember/runloop'; +import { expectTypeOf } from 'expect-type'; + +class Foo { + test(_foo: number, _bar: boolean, _baz?: string): number { + return 1; + } +} + +let foo = new Foo(); + +// With only function +expectTypeOf( + join( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true + ) +).toEqualTypeOf(); + +expectTypeOf( + join( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + 'string' + ) +).toEqualTypeOf(); + +join((_foo: number): number => { + return 1; + // @ts-expect-error invalid argument +}, 'string'); + +// With target and function +expectTypeOf( + join( + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + expectTypeOf(this).toEqualTypeOf(); + return 1; + }, + 1, + true + ) +).toEqualTypeOf(); + +expectTypeOf( + join( + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + true, + 'string' + ) +).toEqualTypeOf(); + +// @ts-expect-error invalid args +join( + foo, + function (this: Foo, _foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + 'string' +); + +// With function string reference +expectTypeOf(join(foo, 'test', 1, true)).toEqualTypeOf(); + +expectTypeOf(join(foo, 'test', 1, true, 'string')).toEqualTypeOf(); + +// @ts-expect-error Invalid args +join(foo, 'test', 'string'); diff --git a/packages/@ember/runloop/type-tests.ts/later.test.ts b/packages/@ember/runloop/type-tests.ts/later.test.ts new file mode 100644 index 00000000000..44019658816 --- /dev/null +++ b/packages/@ember/runloop/type-tests.ts/later.test.ts @@ -0,0 +1,88 @@ +import { later, Timer } from '@ember/runloop'; +import { expectTypeOf } from 'expect-type'; + +class Foo { + test(_foo: number, _bar: boolean, _baz?: string): number { + return 1; + } +} + +let foo = new Foo(); + +// With only function +expectTypeOf( + later( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + undefined, + 1 + ) +).toEqualTypeOf(); + +expectTypeOf( + later( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + 'string', + 1 + ) +).toEqualTypeOf(); + +later((_foo: number): number => { + return 1; + // @ts-expect-error invalid argument +}, 'string'); + +// With target and function +expectTypeOf( + later( + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + expectTypeOf(this).toEqualTypeOf(); + return 1; + }, + 1, + true, + undefined, + 1 + ) +).toEqualTypeOf(); + +expectTypeOf( + later( + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + true, + 'string', + 1 + ) +).toEqualTypeOf(); + +// @ts-expect-error invalid args +later( + foo, + function (this: Foo, _foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + 'string', + true, + 1 +); + +// With function string reference +expectTypeOf(later(foo, 'test', 1, true, 'string', 1)).toEqualTypeOf(); + +expectTypeOf(later(foo, 'test', 1, true, undefined, 1)).toEqualTypeOf(); + +// @ts-expect-error Invalid args +later(foo, 'test', 'string'); diff --git a/packages/@ember/runloop/type-tests.ts/next.test.ts b/packages/@ember/runloop/type-tests.ts/next.test.ts new file mode 100644 index 00000000000..145471531f5 --- /dev/null +++ b/packages/@ember/runloop/type-tests.ts/next.test.ts @@ -0,0 +1,82 @@ +import { next, Timer } from '@ember/runloop'; +import { expectTypeOf } from 'expect-type'; + +class Foo { + test(_foo: number, _bar: boolean, _baz?: string): number { + return 1; + } +} + +let foo = new Foo(); + +// With only function +expectTypeOf( + next( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + undefined + ) +).toEqualTypeOf(); + +expectTypeOf( + next( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + 'string' + ) +).toEqualTypeOf(); + +next((_foo: number): number => { + return 1; + // @ts-expect-error invalid argument +}, 'string'); + +// With target and function +expectTypeOf( + next( + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + expectTypeOf(this).toEqualTypeOf(); + return 1; + }, + 1, + true + ) +).toEqualTypeOf(); + +expectTypeOf( + next( + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + true, + 'string' + ) +).toEqualTypeOf(); + +// @ts-expect-error invalid args +next( + foo, + function (this: Foo, _foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + 'string', + true +); + +// With function string reference +expectTypeOf(next(foo, 'test', 1, true, 'string')).toEqualTypeOf(); + +expectTypeOf(next(foo, 'test', 1, true)).toEqualTypeOf(); + +// @ts-expect-error Invalid args +next(foo, 'test', 'string'); diff --git a/packages/@ember/runloop/type-tests.ts/once.test.ts b/packages/@ember/runloop/type-tests.ts/once.test.ts new file mode 100644 index 00000000000..1a1ad5dd8bb --- /dev/null +++ b/packages/@ember/runloop/type-tests.ts/once.test.ts @@ -0,0 +1,82 @@ +import { once, Timer } from '@ember/runloop'; +import { expectTypeOf } from 'expect-type'; + +class Foo { + test(_foo: number, _bar: boolean, _baz?: string): number { + return 1; + } +} + +let foo = new Foo(); + +// With only function +expectTypeOf( + once( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + undefined + ) +).toEqualTypeOf(); + +expectTypeOf( + once( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + 'string' + ) +).toEqualTypeOf(); + +once((_foo: number): number => { + return 1; + // @ts-expect-error invalid argument +}, 'string'); + +// With target and function +expectTypeOf( + once( + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + expectTypeOf(this).toEqualTypeOf(); + return 1; + }, + 1, + true + ) +).toEqualTypeOf(); + +expectTypeOf( + once( + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + true, + 'string' + ) +).toEqualTypeOf(); + +// @ts-expect-error invalid args +once( + foo, + function (this: Foo, _foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + 'string', + true +); + +// With function string reference +expectTypeOf(once(foo, 'test', 1, true, 'string')).toEqualTypeOf(); + +expectTypeOf(once(foo, 'test', 1, true)).toEqualTypeOf(); + +// @ts-expect-error Invalid args +once(foo, 'test', 'string'); diff --git a/packages/@ember/runloop/type-tests.ts/run.test.ts b/packages/@ember/runloop/type-tests.ts/run.test.ts new file mode 100644 index 00000000000..9e8088fa403 --- /dev/null +++ b/packages/@ember/runloop/type-tests.ts/run.test.ts @@ -0,0 +1,82 @@ +import { run } from '@ember/runloop'; +import { expectTypeOf } from 'expect-type'; + +class Foo { + test(_foo: number, _bar: boolean, _baz?: string): number { + return 1; + } +} + +let foo = new Foo(); + +// With only function +expectTypeOf( + run( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + undefined + ) +).toEqualTypeOf(); + +expectTypeOf( + run( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + 'string' + ) +).toEqualTypeOf(); + +run((_foo: number): number => { + return 1; + // @ts-expect-error invalid argument +}, 'string'); + +// With target and function +expectTypeOf( + run( + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + expectTypeOf(this).toEqualTypeOf(); + return 1; + }, + 1, + true + ) +).toEqualTypeOf(); + +expectTypeOf( + run( + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + true, + 'string' + ) +).toEqualTypeOf(); + +// @ts-expect-error invalid args +run( + foo, + function (this: Foo, _foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + 'string', + true +); + +// With function string reference +expectTypeOf(run(foo, 'test', 1, true, 'string')).toEqualTypeOf(); + +expectTypeOf(run(foo, 'test', 1, true)).toEqualTypeOf(); + +// @ts-expect-error Invalid args +run(foo, 'test', 'string'); diff --git a/packages/@ember/runloop/type-tests.ts/schedule-once.test.ts b/packages/@ember/runloop/type-tests.ts/schedule-once.test.ts new file mode 100644 index 00000000000..6137504c06a --- /dev/null +++ b/packages/@ember/runloop/type-tests.ts/schedule-once.test.ts @@ -0,0 +1,91 @@ +import { scheduleOnce, Timer } from '@ember/runloop'; +import { expectTypeOf } from 'expect-type'; + +class Foo { + test(_foo: number, _bar: boolean, _baz?: string): number { + return 1; + } +} + +let foo = new Foo(); + +// With only function +expectTypeOf( + scheduleOnce( + 'my-queue', + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + undefined + ) +).toEqualTypeOf(); + +expectTypeOf( + scheduleOnce( + 'my-queue', + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + 'string' + ) +).toEqualTypeOf(); + +scheduleOnce( + 'my-queue', + (_foo: number): number => { + return 1; + }, + // @ts-expect-error invalid argument + 'string' +); + +// With target and function +expectTypeOf( + scheduleOnce( + 'my-queue', + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + expectTypeOf(this).toEqualTypeOf(); + return 1; + }, + 1, + true + ) +).toEqualTypeOf(); + +expectTypeOf( + scheduleOnce( + 'my-queue', + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + true, + 'string' + ) +).toEqualTypeOf(); + +// @ts-expect-error invalid args +scheduleOnce( + 'my-queue', + foo, + function (this: Foo, _foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + 'string', + true +); + +// With function string reference +expectTypeOf(scheduleOnce('my-queue', foo, 'test', 1, true, 'string')).toEqualTypeOf(); + +expectTypeOf(scheduleOnce('my-queue', foo, 'test', 1, true)).toEqualTypeOf(); + +// @ts-expect-error Invalid args +scheduleOnce('my-queue', foo, 'test', 'string'); diff --git a/packages/@ember/runloop/type-tests.ts/schedule.test.ts b/packages/@ember/runloop/type-tests.ts/schedule.test.ts new file mode 100644 index 00000000000..79297cb9eee --- /dev/null +++ b/packages/@ember/runloop/type-tests.ts/schedule.test.ts @@ -0,0 +1,91 @@ +import { schedule, Timer } from '@ember/runloop'; +import { expectTypeOf } from 'expect-type'; + +class Foo { + test(_foo: number, _bar: boolean, _baz?: string): number { + return 1; + } +} + +let foo = new Foo(); + +// With only function +expectTypeOf( + schedule( + 'my-queue', + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + undefined + ) +).toEqualTypeOf(); + +expectTypeOf( + schedule( + 'my-queue', + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + 'string' + ) +).toEqualTypeOf(); + +schedule( + 'my-queue', + (_foo: number): number => { + return 1; + }, + // @ts-expect-error invalid argument + 'string' +); + +// With target and function +expectTypeOf( + schedule( + 'my-queue', + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + expectTypeOf(this).toEqualTypeOf(); + return 1; + }, + 1, + true + ) +).toEqualTypeOf(); + +expectTypeOf( + schedule( + 'my-queue', + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + true, + 'string' + ) +).toEqualTypeOf(); + +// @ts-expect-error invalid args +schedule( + 'my-queue', + foo, + function (this: Foo, _foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + 'string', + true +); + +// With function string reference +expectTypeOf(schedule('my-queue', foo, 'test', 1, true, 'string')).toEqualTypeOf(); + +expectTypeOf(schedule('my-queue', foo, 'test', 1, true)).toEqualTypeOf(); + +// @ts-expect-error Invalid args +schedule('my-queue', foo, 'test', 'string'); diff --git a/packages/@ember/runloop/type-tests.ts/throttle.test.ts b/packages/@ember/runloop/type-tests.ts/throttle.test.ts new file mode 100644 index 00000000000..6888c491a9d --- /dev/null +++ b/packages/@ember/runloop/type-tests.ts/throttle.test.ts @@ -0,0 +1,148 @@ +import { throttle } from '@ember/runloop'; +import { Timer } from 'backburner.js'; +import { expectTypeOf } from 'expect-type'; + +// From Docs + +function whoRan() { + // Do stuff +} + +let myContext = { name: 'throttle', test(_foo: number, _baz?: boolean): void {} }; + +throttle(myContext, whoRan, 150); + +// less than 150ms passes +throttle(myContext, whoRan, 150); + +throttle(myContext, whoRan, 150, true); + +// console logs 'throttle ran.' one time immediately. +// 100ms passes +throttle(myContext, whoRan, 150, true); + +// 150ms passes and nothing else is logged to the console and +// the throttlee is no longer being watched +throttle(myContext, whoRan, 150, true); + +// console logs 'throttle ran.' one time immediately. +// 150ms passes and nothing else is logged to the console and +// the throttlee is no longer being watched + +// Method only +expectTypeOf(throttle((_foo: number, _baz?: boolean): void => {}, 1, undefined, 1)).toEqualTypeOf< + Timer +>(); + +// Wait is optional +throttle((_foo: number, _baz?: boolean): void => {}, 1, true); + +// @ts-expect-error Requires all args +throttle((_foo: number, _baz?: boolean): void => {}, 1, 1); + +// Can set immediate +throttle((_foo: number, _baz?: boolean): void => {}, 1, true, 1, true); + +// With target +throttle( + myContext, + function (_foo: number, _baz?: boolean): void { + expectTypeOf(this).toEqualTypeOf(myContext); + }, + 1, + true, + 1, + true +); + +// With key +throttle(myContext, 'test', 1, true, 1, true); + +// @ts-expect-error invalid key +throttle(myContext, 'invalid'); + +class Foo { + test(_foo: number, _bar: boolean, _baz?: string): number { + return 1; + } +} + +let foo = new Foo(); + +// With only function +expectTypeOf( + throttle( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + undefined, + 1 + ) +).toEqualTypeOf(); + +expectTypeOf( + throttle( + (_foo: number, _bar: boolean, _baz?: string): number => { + return 1; + }, + 1, + true, + 'string', + 1 + ) +).toEqualTypeOf(); + +throttle((_foo: number): number => { + return 1; + // @ts-expect-error invalid argument +}, 'string'); + +// With target and function +expectTypeOf( + throttle( + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + expectTypeOf(this).toEqualTypeOf(); + return 1; + }, + 1, + true, + undefined, + 1 + ) +).toEqualTypeOf(); + +expectTypeOf( + throttle( + foo, + function (_foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + true, + 'string', + 1 + ) +).toEqualTypeOf(); + +// @ts-expect-error invalid args +throttle( + foo, + function (this: Foo, _foo: number, _bar: boolean, _baz?: string): number { + return 1; + }, + 1, + 'string', + true, + 1 +); + +// With function string reference +expectTypeOf(throttle(foo, 'test', 1, true, 'string', 1)).toEqualTypeOf(); + +expectTypeOf(throttle(foo, 'test', 1, true, undefined, 1)).toEqualTypeOf(); + +// @ts-expect-error Invalid args +throttle(foo, 'test', 'string'); diff --git a/packages/@ember/template/type-tests/index.test.ts b/packages/@ember/template/type-tests/index.test.ts new file mode 100644 index 00000000000..8caa197c852 --- /dev/null +++ b/packages/@ember/template/type-tests/index.test.ts @@ -0,0 +1,13 @@ +import { SafeString } from '@ember/-internals/glimmer'; +import { htmlSafe, isHTMLSafe } from '@ember/template'; +import { expectTypeOf } from 'expect-type'; + +let safe = htmlSafe('

someString
'); +expectTypeOf(safe).toEqualTypeOf(); + +expectTypeOf(isHTMLSafe(safe)).toEqualTypeOf(); + +isHTMLSafe('unsafe'); +isHTMLSafe(1); +isHTMLSafe(null); +isHTMLSafe(undefined); diff --git a/packages/@ember/utils/type-tests/compare.test.ts b/packages/@ember/utils/type-tests/compare.test.ts new file mode 100644 index 00000000000..5836b65d75c --- /dev/null +++ b/packages/@ember/utils/type-tests/compare.test.ts @@ -0,0 +1,8 @@ +import { compare } from '@ember/utils'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(compare('hello', 'hello')).toEqualTypeOf<-1 | 0 | 1>(); // 0 +compare('abc', 'dfg'); // -1 +compare(2, 1); // 1 +compare('hello', 50); // 1 +compare(50, 'hello'); // -1 diff --git a/packages/@ember/utils/type-tests/is-blank.test.ts b/packages/@ember/utils/type-tests/is-blank.test.ts new file mode 100644 index 00000000000..9f8af277821 --- /dev/null +++ b/packages/@ember/utils/type-tests/is-blank.test.ts @@ -0,0 +1,17 @@ +import { isBlank } from '@ember/utils'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(isBlank(null)).toEqualTypeOf(); // true + +isBlank(undefined); // true +isBlank(''); // true +isBlank([]); // true +isBlank('\n\t'); // true +isBlank(' '); // true +isBlank({}); // false +isBlank('\n\t Hello'); // false +isBlank('Hello world'); // false +isBlank([1, 2, 3]); // false + +// @ts-expect-error requires an argument +isBlank(); diff --git a/packages/@ember/utils/type-tests/is-empty.test.ts b/packages/@ember/utils/type-tests/is-empty.test.ts new file mode 100644 index 00000000000..a8d0549e203 --- /dev/null +++ b/packages/@ember/utils/type-tests/is-empty.test.ts @@ -0,0 +1,19 @@ +import { isEmpty } from '@ember/utils'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(isEmpty(null)).toEqualTypeOf(); // true + +isEmpty(undefined); // true +isEmpty(''); // true +isEmpty([]); // true +isEmpty({ size: 0 }); // true +isEmpty({}); // false +isEmpty('Adam Hawkins'); // false +isEmpty([0, 1, 2]); // false +isEmpty('\n\t'); // false +isEmpty(' '); // false +isEmpty({ size: 1 }); // false +isEmpty({ size: () => 0 }); // false + +// @ts-expect-error requires an argument +isEmpty(); diff --git a/packages/@ember/utils/type-tests/is-equal.test.ts b/packages/@ember/utils/type-tests/is-equal.test.ts new file mode 100644 index 00000000000..34f7510c177 --- /dev/null +++ b/packages/@ember/utils/type-tests/is-equal.test.ts @@ -0,0 +1,21 @@ +import { isEqual } from '@ember/utils'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(isEqual('hello', 'hello')).toEqualTypeOf(); // true + +isEqual(1, 2); // false + +class Person { + constructor(public ssn: string) {} + + isEqual(other: Person) { + return this.ssn == other.ssn; + } +} + +let personA = new Person('123-45-6789'); +let personB = new Person('123-45-6789'); + +isEqual(personA, personB); // true + +isEqual([4, 2], [4, 2]); // false diff --git a/packages/@ember/utils/type-tests/is-none.test.ts b/packages/@ember/utils/type-tests/is-none.test.ts new file mode 100644 index 00000000000..df1be5db731 --- /dev/null +++ b/packages/@ember/utils/type-tests/is-none.test.ts @@ -0,0 +1,17 @@ +import { isNone } from '@ember/utils'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(isNone(null)).toEqualTypeOf(); // true +isNone(undefined); // true +isNone(''); // false +isNone([]); // false +isNone(function () {}); // false + +// It functions as a type guard +let foo: unknown; +if (isNone(foo)) { + expectTypeOf(foo).toEqualTypeOf(); +} + +// @ts-expect-error argument is required +isNone(); // true diff --git a/packages/@ember/utils/type-tests/is-present.test.ts b/packages/@ember/utils/type-tests/is-present.test.ts new file mode 100644 index 00000000000..489b9491c9c --- /dev/null +++ b/packages/@ember/utils/type-tests/is-present.test.ts @@ -0,0 +1,28 @@ +import { isPresent } from '@ember/utils'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(isPresent(null)).toEqualTypeOf(); // false + +isPresent(undefined); // false +isPresent(''); // false +isPresent(' '); // false +isPresent('\n\t'); // false +isPresent([]); // false +isPresent({ length: 0 }); // false +isPresent(false); // true +isPresent(true); // true +isPresent('string'); // true +isPresent(0); // true +isPresent(function () {}); // true +isPresent({}); // true +isPresent('\n\t Hello'); // true +isPresent([1, 2, 3]); // true + +// It functions as a type guard +let foo: string | null | undefined; +if (isPresent(foo)) { + expectTypeOf(foo).toEqualTypeOf(); +} + +// @ts-expect-error it requires an argument +isPresent(); // false diff --git a/packages/@ember/utils/type-tests/type-of.test.ts b/packages/@ember/utils/type-tests/type-of.test.ts new file mode 100644 index 00000000000..2313bcfd8ef --- /dev/null +++ b/packages/@ember/utils/type-tests/type-of.test.ts @@ -0,0 +1,44 @@ +/* eslint-disable no-new-wrappers */ + +import { typeOf } from '@ember/utils'; +import { A } from '@ember/array'; +import EmberObject from '@ember/object'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(typeOf(null)).toEqualTypeOf< + | 'undefined' + | 'null' + | 'string' + | 'number' + | 'boolean' + | 'function' + | 'array' + | 'regexp' + | 'date' + | 'filelist' + | 'class' + | 'instance' + | 'error' + | 'object' +>(); + +typeOf(undefined); // 'undefined' +typeOf('michael'); // 'string' +typeOf(new String('michael')); // 'string' +typeOf(101); // 'number' +typeOf(new Number(101)); // 'number' +typeOf(true); // 'boolean' +typeOf(new Boolean(true)); // 'boolean' +typeOf(A); // 'function' +typeOf(A()); // 'array' +typeOf([1, 2, 90]); // 'array' +typeOf(/abc/); // 'regexp' +typeOf(new Date()); // 'date' +typeOf((({} as Event).target as HTMLInputElement).files); // 'filelist' +typeOf(EmberObject.extend()); // 'class' +typeOf(EmberObject.create()); // 'instance' +typeOf(new Error('teamocil')); // 'error' +typeOf({ a: 'b' }); // 'object' + +// @ts-expect-error it requires an argument +typeOf(); diff --git a/packages/@ember/version/type-tests/index.test.ts b/packages/@ember/version/type-tests/index.test.ts new file mode 100644 index 00000000000..ba5efac4aaa --- /dev/null +++ b/packages/@ember/version/type-tests/index.test.ts @@ -0,0 +1,4 @@ +import { VERSION } from '@ember/version'; +import { expectTypeOf } from 'expect-type'; + +expectTypeOf(VERSION).toEqualTypeOf(); diff --git a/packages/ember/index.js b/packages/ember/index.js index b88304e57db..98e2b6b3593 100644 --- a/packages/ember/index.js +++ b/packages/ember/index.js @@ -436,6 +436,7 @@ const deprecateImportFromString = function ( id: 'ember-string.htmlsafe-ishtmlsafe', for: 'ember-source', since: { + available: '3.25', enabled: '3.25', }, until: '4.0.0', diff --git a/packages/internal-test-helpers/lib/ember-dev/run-loop.ts b/packages/internal-test-helpers/lib/ember-dev/run-loop.ts index c1199c811f4..131fcf67844 100644 --- a/packages/internal-test-helpers/lib/ember-dev/run-loop.ts +++ b/packages/internal-test-helpers/lib/ember-dev/run-loop.ts @@ -1,4 +1,3 @@ -// @ts-ignore import { end, _cancelTimers, _getCurrentRunLoop, _hasScheduledTimers } from '@ember/runloop'; export function setupRunLoopCheck(hooks: NestedHooks) { diff --git a/packages/internal-test-helpers/lib/run.ts b/packages/internal-test-helpers/lib/run.ts index 2735811bfa9..026a7146634 100644 --- a/packages/internal-test-helpers/lib/run.ts +++ b/packages/internal-test-helpers/lib/run.ts @@ -1,11 +1,4 @@ -import { - // @ts-expect-error 'next' is not typed - next, - run, - _getCurrentRunLoop, - // @ts-expect-error '_hasScheduledTimers' is not typed - _hasScheduledTimers, -} from '@ember/runloop'; +import { next, run, _getCurrentRunLoop, _hasScheduledTimers } from '@ember/runloop'; import { destroy } from '@glimmer/destroyable'; import { Promise } from 'rsvp'; @@ -20,19 +13,19 @@ export function runDestroy(toDestroy: any): void { } } -export function runTask(callback: Function): Function { +export function runTask(callback: (...args: any[]) => any): void { return run(callback); } export function runTaskNext(): Promise { - return new Promise((resolve: Function) => { + return new Promise((resolve) => { return next(resolve); }); } // TODO: Find a better name 😎 export function runLoopSettled(event?: any): Promise { - return new Promise(function (resolve: Function) { + return new Promise(function (resolve) { // Every 5ms, poll for the async thing to have finished let watcher = setInterval(() => { // If there are scheduled timers or we are inside of a run loop, keep polling diff --git a/tsconfig.json b/tsconfig.json index 1e884f6219f..fdef4e6beff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,8 +27,7 @@ "checkJs": false, "paths": { - "@glimmer/*": ["../node_modules/@glimmer/*"], - "@ember/object/*": ["@ember/object/*", "@ember/object/types/*"] + "backburner": ["../node_modules/backburner.js/dist/backburner.d.ts"] } },