From 70b7904bb123a5de011bc4267fac4d9783c26ebf Mon Sep 17 00:00:00 2001 From: Chris Krycho Date: Thu, 3 Oct 2019 16:29:51 -0600 Subject: [PATCH] Fix a number of gaps in Ember's types (#38690) * @ember/polyfills: support `undefined`. Fixes #38681. * @ember/utils: support JS objects in `typeOf`. - Fixes #36809. - Fixes typed-ember/ember-cli-typescript#799 * @ember/debug: add `deprecate` type and tests Fixes #38682. --- types/ember__debug/ember__debug-tests.ts | 21 ++++- types/ember__debug/index.d.ts | 33 +++++++ .../ember__polyfills-tests.ts | 22 +++-- types/ember__polyfills/index.d.ts | 1 + types/ember__utils/-private/types.d.ts | 7 ++ types/ember__utils/ember__utils-tests.ts | 88 +++++++++---------- types/ember__utils/index.d.ts | 4 +- 7 files changed, 117 insertions(+), 59 deletions(-) diff --git a/types/ember__debug/ember__debug-tests.ts b/types/ember__debug/ember__debug-tests.ts index 0fbdcbc174..b93b04dcc2 100644 --- a/types/ember__debug/ember__debug-tests.ts +++ b/types/ember__debug/ember__debug-tests.ts @@ -1,4 +1,12 @@ -import { runInDebug, warn, debug, assert, registerWarnHandler, registerDeprecationHandler } from "@ember/debug"; +import { + runInDebug, + warn, + debug, + assert, + registerWarnHandler, + registerDeprecationHandler, + deprecate, +} from '@ember/debug'; /** * @ember/debug tests @@ -43,3 +51,14 @@ registerDeprecationHandler((message, { id, until }, next) => { // $ExpectType vo until; // $ExpectType string next; // $ExpectType () => void }); + +deprecate(); // $ExpectError +deprecate('missing test and options'); // $ExpectError +deprecate('missing options', true); // $ExpectError +deprecate('missing options', false); // $ExpectError +deprecate('missing options body', true, {}); // $ExpectError +deprecate('missing options id', true, { until: 'v4.0.0' }); // $ExpectError +deprecate('missing options until', true, { id: 'some.deprecation' }); // $ExpectError +deprecate('a valid deprecation without url', true, { id: 'some.deprecation', until: 'v4.0.0' }); // $ExpectType void +deprecate('incorrect options url', true, { id: 'some.deprecation', until: 'v4.0.0', url: 123 }); // $ExpectError +deprecate('a valid deprecation with url', true, { id: 'some.deprecation', until: 'v4.0.0', url: 'https://example.com/ember-deprecations-yo' }); // $ExpectType void diff --git a/types/ember__debug/index.d.ts b/types/ember__debug/index.d.ts index 0287f91792..4babe25524 100644 --- a/types/ember__debug/index.d.ts +++ b/types/ember__debug/index.d.ts @@ -51,3 +51,36 @@ export function warn(message: string, test: boolean, options?: { id?: string }): * @deprecated Missing deprecation options: https://emberjs.com/deprecations/v2.x/#toc_ember-debug-function-options */ export function warn(message: string, options?: { id?: string }): void; + +/** + * Display a deprecation warning with the provided message and a stack trace + * (Chrome and Firefox only). + * + * In a production build, this method is defined as an empty function (NOP). + * Uses of this method in Ember itself are stripped from the ember.prod.js build. + * + * @param message A description of the deprecation. + * @param test If falsy, the deprecation will be displayed. + * @param options The deprecation options. + */ +export function deprecate( + message: string, + test: boolean, + options: { + /** + * A unique id for this deprecation. The id can be used by Ember debugging + * tools to change the behavior (raise, log or silence) for that specific + * deprecation. The id should be namespaced by dots, e.g. + * `"view.helper.select"`. + */ + id: string; + /** + * The version of Ember when this deprecation warning will be removed. + */ + until: string; + /** + * An optional url to the transition guide on the emberjs.com website. + */ + url?: string; + }, +): void; diff --git a/types/ember__polyfills/ember__polyfills-tests.ts b/types/ember__polyfills/ember__polyfills-tests.ts index 533fbd86e4..3ed870ddd0 100644 --- a/types/ember__polyfills/ember__polyfills-tests.ts +++ b/types/ember__polyfills/ember__polyfills-tests.ts @@ -1,9 +1,10 @@ import { assign, merge } from '@ember/polyfills'; -(() => { /* assign */ - assign({}, { a: 'b'}); - assign({}, { a: 'b'}).a; // $ExpectType string - assign({ a: 6 }, { a: 'b'}).a; // $ExpectType string +(() => { + /* assign */ + assign({}, { a: 'b' }); + assign({}, { a: 'b' }).a; // $ExpectType string + assign({ a: 6 }, { a: 'b' }).a; // $ExpectType string assign({ a: 6 }, {}).a; // $ExpectType number assign({ b: 6 }, {}).a; // $ExpectError assign({}, { b: 6 }, {}).b; // $ExpectType number @@ -12,12 +13,17 @@ import { assign, merge } from '@ember/polyfills'; assign({ a: 'hello' }, '', { a: true }).a; // $ExpectError assign({ d: ['gobias industries'] }, { a: 'hello' }, { b: 6 }, { a: true }).d; // $ExpectType string[] assign({}, { a: 0 }, { b: 1 }, { c: 2 }, { d: 3 }).a; // $ExpectType any + + // matches Object.assign + assign({}, null); // $ExpectType never + assign({}, undefined); // $ExpectType never })(); -(() => { /* merge */ - merge({}, { a: 'b'}); - merge({}, { a: 'b'}).a; // $ExpectType string - merge({ a: 6 }, { a: 'b'}).a; // $ExpectType string +(() => { + /* merge */ + merge({}, { a: 'b' }); + merge({}, { a: 'b' }).a; // $ExpectType string + merge({ a: 6 }, { a: 'b' }).a; // $ExpectType string merge({ a: 6 }, {}).a; // $ExpectType number merge({ b: 6 }, {}).a; // $ExpectError })(); diff --git a/types/ember__polyfills/index.d.ts b/types/ember__polyfills/index.d.ts index 01820351e5..e125c558ca 100644 --- a/types/ember__polyfills/index.d.ts +++ b/types/ember__polyfills/index.d.ts @@ -13,6 +13,7 @@ export function assign(target: T, source: U) export function assign(target: T, source1: U, source2: V): Mix3; export function assign(target: T, source1: U, source2: V, source3: W): Mix4; export function assign(target: object, ...sources: object[]): any; +export function assign(target: object, final: undefined | null): never; /** * Merge the contents of two objects together into the first object. diff --git a/types/ember__utils/-private/types.d.ts b/types/ember__utils/-private/types.d.ts index 36e1ae42f5..970806c3c0 100644 --- a/types/ember__utils/-private/types.d.ts +++ b/types/ember__utils/-private/types.d.ts @@ -2,6 +2,13 @@ export type KeysOfType = keyof Pick; +// Since `TypeLookup` resolves all *other* types, including `null` and +// `undefined`, we can assume that if the type does *not* resolve from +// `KeysOfType`, it is safe to treat it as 'object'. +export type TypeOf = KeysOfType extends never + ? 'object' + : KeysOfType; + export interface TypeLookup { string: string; number: number; diff --git a/types/ember__utils/ember__utils-tests.ts b/types/ember__utils/ember__utils-tests.ts index 53121a4e75..69f8fa52cf 100644 --- a/types/ember__utils/ember__utils-tests.ts +++ b/types/ember__utils/ember__utils-tests.ts @@ -1,13 +1,4 @@ -import { - compare, - isBlank, - isEmpty, - isEqual, - isNone, - isPresent, - tryInvoke, - typeOf -} from '@ember/utils'; +import { compare, isBlank, isEmpty, isEqual, isNone, isPresent, tryInvoke, typeOf } from '@ember/utils'; (function() { /** isNone */ @@ -16,11 +7,11 @@ import { return; } const anotherString = maybeUndefined + 'another string'; - isNone(); // $ExpectType boolean - isNone(null); // $ExpectType boolean - isNone(undefined); // $ExpectType boolean - isNone(''); // $ExpectType boolean - isNone([]); // $ExpectType boolean + isNone(); // $ExpectType boolean + isNone(null); // $ExpectType boolean + isNone(undefined); // $ExpectType boolean + isNone(''); // $ExpectType boolean + isNone([]); // $ExpectType boolean isNone(function() {}); // $ExpectType boolean })(); @@ -28,8 +19,8 @@ import { /** tryInvoke */ let d = new Date('03/15/2013'); - tryInvoke(d, 'getTime'); // $ExpectType number - tryInvoke(d, 'setFullYear', [2014]); // $ExpectType number + tryInvoke(d, 'getTime'); // $ExpectType number + tryInvoke(d, 'setFullYear', [2014]); // $ExpectType number tryInvoke(d, 'noSuchMethod', [2014]); // $ExpectType undefined tryInvoke(d, 'getTime'); tryInvoke(d, 'setFullYear', [2014]); @@ -38,46 +29,47 @@ import { (function() { /** isPresent */ - isPresent(); // $ExpectType boolean - isPresent(null); // $ExpectType boolean - isPresent(undefined); // $ExpectType boolean - isPresent(''); // $ExpectType boolean - isPresent(' '); // $ExpectType boolean - isPresent('\n\t'); // $ExpectType boolean - isPresent([]); // $ExpectType boolean - isPresent({ length: 0 }); // $ExpectType boolean - isPresent(false); // $ExpectType boolean - isPresent(true); // $ExpectType boolean - isPresent('string'); // $ExpectType boolean - isPresent(0); // $ExpectType boolean - isPresent(function() {}); // $ExpectType boolean - isPresent({}); // $ExpectType boolean - isPresent(false); // $ExpectType boolean - isPresent('\n\t Hello'); // $ExpectType boolean - isPresent([1, 2, 3]); // $ExpectType boolean + isPresent(); // $ExpectType boolean + isPresent(null); // $ExpectType boolean + isPresent(undefined); // $ExpectType boolean + isPresent(''); // $ExpectType boolean + isPresent(' '); // $ExpectType boolean + isPresent('\n\t'); // $ExpectType boolean + isPresent([]); // $ExpectType boolean + isPresent({ length: 0 }); // $ExpectType boolean + isPresent(false); // $ExpectType boolean + isPresent(true); // $ExpectType boolean + isPresent('string'); // $ExpectType boolean + isPresent(0); // $ExpectType boolean + isPresent(function() {}); // $ExpectType boolean + isPresent({}); // $ExpectType boolean + isPresent(false); // $ExpectType boolean + isPresent('\n\t Hello'); // $ExpectType boolean + isPresent([1, 2, 3]); // $ExpectType boolean })(); (function() { /** typeOf */ - typeOf(null); // $ExpectType "null" - typeOf(undefined); // $ExpectType "undefined" - typeOf('michael'); // $ExpectType "string" + typeOf(null); // $ExpectType "null" + typeOf(undefined); // $ExpectType "undefined" + typeOf('michael'); // $ExpectType "string" // tslint:disable-next-line:no-construct - typeOf(new String('michael')); // $ExpectType "string" - typeOf(101); // $ExpectType "number" + typeOf(new String('michael')); // $ExpectType "string" + typeOf(101); // $ExpectType "number" // tslint:disable-next-line:no-construct - typeOf(new Number(101)); // $ExpectType "number" - typeOf(true); // $ExpectType "boolean" + typeOf(new Number(101)); // $ExpectType "number" + typeOf(true); // $ExpectType "boolean" // tslint:disable-next-line:no-construct - typeOf(new Boolean(true)); // $ExpectType "boolean" - typeOf(() => 4); // $ExpectType "function" - typeOf([1, 2, 90]); // $ExpectType "array" - typeOf(/abc/); // $ExpectType "regexp" - typeOf(new Date()); // $ExpectType "date" - typeOf(new FileList()); // $ExpectType "filelist" + typeOf(new Boolean(true)); // $ExpectType "boolean" + typeOf(() => 4); // $ExpectType "function" + typeOf([1, 2, 90]); // $ExpectType "array" + typeOf(/abc/); // $ExpectType "regexp" + typeOf(new Date()); // $ExpectType "date" + typeOf(new FileList()); // $ExpectType "filelist" // typeOf(EmberObject.extend()); // $ExpectType "class" // typeOf(EmberObject.create()); // $ExpectType "instance" - typeOf(new Error('teamocil')); // $ExpectType "error" + typeOf(new Error('teamocil')); // $ExpectType "error" + typeOf({ justAPojo: true }); // $ExpectType "object" typeOf(); typeOf(null); diff --git a/types/ember__utils/index.d.ts b/types/ember__utils/index.d.ts index 70574e4a76..62251a7ca7 100644 --- a/types/ember__utils/index.d.ts +++ b/types/ember__utils/index.d.ts @@ -4,7 +4,7 @@ // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 2.8 -import { TypeLookup, KeysOfType, FunctionArgs } from "./-private/types"; +import { TypeLookup, TypeOf, FunctionArgs } from './-private/types'; /** * Compares two javascript values and returns: @@ -55,6 +55,6 @@ export function tryInvoke(obj: object, methodName: string, args?: any[]): undefi /** * Returns a consistent type for the passed object. */ -export function typeOf(value: T): KeysOfType; +export function typeOf(value: T): TypeOf; export function typeOf(): 'undefined'; export function typeOf(item: any): string;