diff --git a/.eslintrc.js b/.eslintrc.js index 1afb795562b..0f63b611601 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -35,7 +35,7 @@ module.exports = { overrides: [ { - files: ['*.ts'], + files: ['*.ts', '*.d.ts'], extends: ['plugin:@typescript-eslint/recommended', 'prettier/@typescript-eslint'], diff --git a/package.json b/package.json index 6cd42c30e89..bedb2f2af7a 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,7 @@ "eslint-plugin-prettier": "^3.3.1", "eslint-plugin-qunit": "^4.0.0", "execa": "^2.0.4", + "expect-type": "^0.11.0", "express": "^4.17.1", "finalhandler": "^1.1.2", "fs-extra": "^9.0.1", @@ -147,7 +148,8 @@ "simple-dom": "^1.4.0", "testem": "^3.1.0", "testem-failure-only-reporter": "^0.0.1", - "tslint": "^5.20.1" + "tslint": "^5.20.1", + "typescript": "^4.2.4" }, "engines": { "node": "10.* || >= 12.*" diff --git a/packages/@ember/-internals/glimmer/tests/integration/components/angle-bracket-invocation-test.js b/packages/@ember/-internals/glimmer/tests/integration/components/angle-bracket-invocation-test.js index 883dc285295..34a696ab571 100644 --- a/packages/@ember/-internals/glimmer/tests/integration/components/angle-bracket-invocation-test.js +++ b/packages/@ember/-internals/glimmer/tests/integration/components/angle-bracket-invocation-test.js @@ -550,10 +550,13 @@ moduleFor( } '@test positional parameters are not allowed'() { + let TestComponent = class extends Component {}; + TestComponent.reopenClass({ + positionalParams: ['first', 'second'], + }); + this.registerComponent('sample-component', { - ComponentClass: Component.extend().reopenClass({ - positionalParams: ['first', 'second'], - }), + ComponentClass: TestComponent, template: '{{this.first}}{{this.second}}', }); diff --git a/packages/@ember/-internals/metal/lib/mixin.ts b/packages/@ember/-internals/metal/lib/mixin.ts index 7073f79f8c6..876b7ce997a 100644 --- a/packages/@ember/-internals/metal/lib/mixin.ts +++ b/packages/@ember/-internals/metal/lib/mixin.ts @@ -273,7 +273,15 @@ function mergeMixins( } } } else { - mergeProps(meta, currentMixin, descs, values, base, keys, keysWithSuper); + mergeProps( + meta, + currentMixin as { [key: string]: any }, + descs, + values, + base, + keys, + keysWithSuper + ); } } } diff --git a/packages/@ember/-internals/routing/lib/location/api.ts b/packages/@ember/-internals/routing/lib/location/api.ts index 208f0756b59..cc4ac1d2040 100644 --- a/packages/@ember/-internals/routing/lib/location/api.ts +++ b/packages/@ember/-internals/routing/lib/location/api.ts @@ -10,6 +10,7 @@ export interface EmberLocation { formatURL(url: string): string; detect?(): void; initState?(): void; + destroy(): void; } export type UpdateCallback = (url: string) => void; diff --git a/packages/@ember/-internals/routing/lib/location/auto_location.ts b/packages/@ember/-internals/routing/lib/location/auto_location.ts index 8122bdc623e..2dc61ce7524 100644 --- a/packages/@ember/-internals/routing/lib/location/auto_location.ts +++ b/packages/@ember/-internals/routing/lib/location/auto_location.ts @@ -62,13 +62,94 @@ import { @protected */ export default class AutoLocation extends EmberObject implements EmberLocation { - cancelRouterSetup?: boolean | undefined; getURL!: () => string; setURL!: (url: string) => void; onUpdateURL!: (callback: UpdateCallback) => void; formatURL!: (url: string) => string; + concreteImplementation?: EmberLocation; + implementation = 'auto'; + + // FIXME: This is never set + // See https://github.com/emberjs/ember.js/issues/19515 + documentMode: number | undefined; + + /** + @private + + Will be pre-pended to path upon state change. + + @since 1.5.1 + @property rootURL + @default '/' + */ + // Added in reopen to allow overriding via extend + rootURL!: string; + + /** + @private + + The browser's `location` object. This is typically equivalent to + `window.location`, but may be overridden for testing. + + @property location + @default environment.location + */ + // Added in reopen to allow overriding via extend + location!: any; + + /** + @private + + The browser's `history` object. This is typically equivalent to + `window.history`, but may be overridden for testing. + + @since 1.5.1 + @property history + @default environment.history + */ + // Added in reopen to allow overriding via extend + history!: any; + + /** + @private + + The user agent's global variable. In browsers, this will be `window`. + + @since 1.11 + @property global + @default window + */ + // Added in reopen to allow overriding via extend + global!: any; + + /** + @private + + The browser's `userAgent`. This is typically equivalent to + `navigator.userAgent`, but may be overridden for testing. + + @since 1.5.1 + @property userAgent + @default environment.history + */ + // Added in reopen to allow overriding via extend + userAgent!: any; + + /** + @private + + This property is used by the router to know whether to cancel the routing + setup process, which is needed while we redirect the browser. + + @since 1.5.1 + @property cancelRouterSetup + @default false + */ + // Added in reopen to allow overriding via extend + cancelRouterSetup!: boolean | undefined; + /** Called by the router to instruct the location to do any feature detection necessary. In the case of AutoLocation, we detect whether to use history @@ -115,16 +196,8 @@ export default class AutoLocation extends EmberObject implements EmberLocation { } AutoLocation.reopen({ - /** - @private - - Will be pre-pended to path upon state change. - - @since 1.5.1 - @property rootURL - @default '/' - */ rootURL: '/', + initState: delegateToConcreteImplementation('initState'), getURL: delegateToConcreteImplementation('getURL'), setURL: delegateToConcreteImplementation('setURL'), @@ -132,62 +205,14 @@ AutoLocation.reopen({ onUpdateURL: delegateToConcreteImplementation('onUpdateURL'), formatURL: delegateToConcreteImplementation('formatURL'), - /** - @private - - The browser's `location` object. This is typically equivalent to - `window.location`, but may be overridden for testing. - - @property location - @default environment.location - */ location: location, - /** - @private - - The browser's `history` object. This is typically equivalent to - `window.history`, but may be overridden for testing. - - @since 1.5.1 - @property history - @default environment.history - */ history: history, - /** - @private - - The user agent's global variable. In browsers, this will be `window`. - - @since 1.11 - @property global - @default window - */ global: window, - /** - @private - - The browser's `userAgent`. This is typically equivalent to - `navigator.userAgent`, but may be overridden for testing. - - @since 1.5.1 - @property userAgent - @default environment.history - */ userAgent: userAgent, - /** - @private - - This property is used by the router to know whether to cancel the routing - setup process, which is needed while we redirect the browser. - - @since 1.5.1 - @property cancelRouterSetup - @default false - */ cancelRouterSetup: false, }); @@ -196,7 +221,7 @@ function delegateToConcreteImplementation(methodName: string) { let { concreteImplementation } = this; assert( "AutoLocation's detect() method should be called before calling any other hooks.", - Boolean(concreteImplementation) + concreteImplementation ); return concreteImplementation[methodName]?.(...args); }; diff --git a/packages/@ember/-internals/routing/lib/location/hash_location.ts b/packages/@ember/-internals/routing/lib/location/hash_location.ts index 2ced27940bd..4b8828a6836 100644 --- a/packages/@ember/-internals/routing/lib/location/hash_location.ts +++ b/packages/@ember/-internals/routing/lib/location/hash_location.ts @@ -40,6 +40,9 @@ export default class HashLocation extends EmberObject implements EmberLocation { implementation = 'hash'; _hashchangeHandler?: EventListener; + private _location?: any; + declare location: any; + init(): void { set(this, 'location', this._location || window.location); this._hashchangeHandler = undefined; @@ -114,6 +117,8 @@ export default class HashLocation extends EmberObject implements EmberLocation { set(this, 'lastSetURL', path); } + lastSetURL: string | null = null; + /** Register a callback to be invoked when the hash changes. These callbacks will execute when the user presses the back or forward diff --git a/packages/@ember/-internals/routing/lib/location/history_location.ts b/packages/@ember/-internals/routing/lib/location/history_location.ts index 7747f06b74b..f79e02a71b8 100644 --- a/packages/@ember/-internals/routing/lib/location/history_location.ts +++ b/packages/@ember/-internals/routing/lib/location/history_location.ts @@ -59,6 +59,11 @@ function _uuid() { @protected */ export default class HistoryLocation extends EmberObject implements EmberLocation { + location!: any; + baseURL!: string; + + history?: any; + implementation = 'history'; _previousURL?: string; _popstateHandler?: EventListener; @@ -86,9 +91,9 @@ export default class HistoryLocation extends EmberObject implements EmberLocatio this._super(...arguments); let base = document.querySelector('base'); - let baseURL: string | null = ''; + let baseURL = ''; if (base !== null && base.hasAttribute('href')) { - baseURL = base.getAttribute('href'); + baseURL = base.getAttribute('href') ?? ''; } set(this, 'baseURL', baseURL); diff --git a/packages/@ember/-internals/routing/lib/location/none_location.ts b/packages/@ember/-internals/routing/lib/location/none_location.ts index 5ee44089909..435f370c393 100644 --- a/packages/@ember/-internals/routing/lib/location/none_location.ts +++ b/packages/@ember/-internals/routing/lib/location/none_location.ts @@ -25,6 +25,19 @@ export default class NoneLocation extends EmberObject implements EmberLocation { updateCallback!: UpdateCallback; implementation = 'none'; + // Set in reopen so it can be overwritten with extend + path!: string; + + /** + Will be pre-pended to path. + + @private + @property rootURL + @default '/' + */ + // Set in reopen so it can be overwritten with extend + rootURL!: string; + detect(): void { let { rootURL } = this; @@ -114,13 +127,5 @@ export default class NoneLocation extends EmberObject implements EmberLocation { NoneLocation.reopen({ path: '', - - /** - Will be pre-pended to path. - - @private - @property rootURL - @default '/' - */ rootURL: '/', }); diff --git a/packages/@ember/-internals/routing/lib/system/route.ts b/packages/@ember/-internals/routing/lib/system/route.ts index c3914cb6fe2..de4babf3ff3 100644 --- a/packages/@ember/-internals/routing/lib/system/route.ts +++ b/packages/@ember/-internals/routing/lib/system/route.ts @@ -96,19 +96,20 @@ export function hasDefaultSerialize(route: Route): boolean { @public */ -class Route extends EmberObject implements IRoute { +class Route extends EmberObject.extend(ActionHandler, Evented) implements IRoute, Evented { static isRouteFactory = true; - routeName!: string; - fullRouteName!: string; context: {} = {}; - controller!: Controller; currentModel: unknown; _bucketCache!: BucketCache; _internalName!: string; - _names: unknown; + + private _names: unknown; + _router!: EmberRouter; + _topLevelViewTemplate!: any; + _environment!: any; constructor(owner: Owner) { super(...arguments); @@ -129,6 +130,13 @@ class Route extends EmberObject implements IRoute { } } + // Implement Evented + on!: (name: string, method: ((...args: any[]) => void) | string) => this; + one!: (name: string, method: string | ((...args: any[]) => void)) => this; + trigger!: (name: string, ...args: any[]) => any; + off!: (name: string, method: string | ((...args: any[]) => void)) => this; + has!: (name: string) => boolean; + serialize!: ( model: {}, params: string[] @@ -138,6 +146,139 @@ class Route extends EmberObject implements IRoute { } | undefined; + /** + Configuration hash for this route's queryParams. The possible + configuration options and their defaults are as follows + (assuming a query param whose controller property is `page`): + + ```javascript + queryParams: { + page: { + // By default, controller query param properties don't + // cause a full transition when they are changed, but + // rather only cause the URL to update. Setting + // `refreshModel` to true will cause an "in-place" + // transition to occur, whereby the model hooks for + // this route (and any child routes) will re-fire, allowing + // you to reload models (e.g., from the server) using the + // updated query param values. + refreshModel: false, + + // By default, changes to controller query param properties + // cause the URL to update via `pushState`, which means an + // item will be added to the browser's history, allowing + // you to use the back button to restore the app to the + // previous state before the query param property was changed. + // Setting `replace` to true will use `replaceState` (or its + // hash location equivalent), which causes no browser history + // item to be added. This options name and default value are + // the same as the `link-to` helper's `replace` option. + replace: false, + + // By default, the query param URL key is the same name as + // the controller property name. Use `as` to specify a + // different URL key. + as: 'page' + } + } + ``` + + @property queryParams + @for Route + @type Object + @since 1.6.0 + @public + */ + // Set in reopen so it can be overriden with extend + queryParams!: any; + + /** + The name of the template to use by default when rendering this routes + template. + + ```app/routes/posts/list.js + import Route from '@ember/routing/route'; + + export default class extends Route { + templateName = 'posts/list' + }); + ``` + + ```app/routes/posts/index.js + import PostsList from '../posts/list'; + + export default class extends PostsList {}; + ``` + + ```app/routes/posts/archived.js + import PostsList from '../posts/list'; + + export default class extends PostsList {}; + ``` + + @property templateName + @type String + @default null + @since 1.4.0 + @public + */ + // Set in reopen so it can be overriden with extend + templateName!: string | null; + + /** + The name of the controller to associate with this route. + + By default, Ember will lookup a route's controller that matches the name + of the route (i.e. `posts.new`). However, + if you would like to define a specific controller to use, you can do so + using this property. + + This is useful in many ways, as the controller specified will be: + + * passed to the `setupController` method. + * used as the controller for the template being rendered by the route. + * returned from a call to `controllerFor` for the route. + + @property controllerName + @type String + @default null + @since 1.4.0 + @public + */ + // Set in reopen so it can be overriden with extend + controllerName!: string | null; + + /** + The controller associated with this route. + + Example + + ```app/routes/form.js + import Route from '@ember/routing/route'; + import { action } from '@ember/object'; + + export default class FormRoute extends Route { + @action + willTransition(transition) { + if (this.controller.get('userHasEnteredData') && + !confirm('Are you sure you want to abandon progress?')) { + transition.abort(); + } else { + // Bubble the `willTransition` action so that + // parent routes can decide whether or not to abort. + return true; + } + } + } + ``` + + @property controller + @type Controller + @since 1.6.0 + @public + */ + controller!: Controller; + /** The name of the route, dot-delimited. @@ -150,6 +291,7 @@ class Route extends EmberObject implements IRoute { @since 1.0.0 @public */ + routeName!: string; /** The name of the route, dot-delimited, including the engine prefix @@ -164,6 +306,7 @@ class Route extends EmberObject implements IRoute { @since 2.10.0 @public */ + fullRouteName!: string; /** Sets the name for this route, including a fully resolved name for routes @@ -1879,11 +2022,7 @@ class Route extends EmberObject implements IRoute { */ buildRouteInfoMetadata() {} - /** - * @method _paramsFor - * @private - */ - _paramsFor(routeName: string, params: {}) { + private _paramsFor(routeName: string, params: {}) { let transition = this._router._routerMicrolib.activeTransition; if (transition !== undefined) { return this.paramsFor(routeName); @@ -1892,7 +2031,6 @@ class Route extends EmberObject implements IRoute { return params; } - /** Store property provides a hook for data persistence libraries to inject themselves. @@ -1930,10 +2068,7 @@ class Route extends EmberObject implements IRoute { modelClass = modelClass.class; - assert( - `${classify(name)} has no method \`find\`.`, - typeof modelClass.find === 'function' - ); + assert(`${classify(name)} has no method \`find\`.`, typeof modelClass.find === 'function'); return modelClass.find(value); }, @@ -2070,6 +2205,60 @@ class Route extends EmberObject implements IRoute { }, }; } + + // Set in reopen + actions!: Record; + + /** + Sends an action to the router, which will delegate it to the currently + active route hierarchy per the bubbling rules explained under `actions`. + + Example + + ```app/router.js + // ... + + Router.map(function() { + this.route('index'); + }); + + export default Router; + ``` + + ```app/routes/application.js + import Route from '@ember/routing/route'; + import { action } from '@ember/object'; + + export default class ApplicationRoute extends Route { + @action + track(arg) { + console.log(arg, 'was clicked'); + } + } + ``` + + ```app/routes/index.js + import Route from '@ember/routing/route'; + import { action } from '@ember/object'; + + export default class IndexRoute extends Route { + @action + trackIfDebug(arg) { + if (debug) { + this.send('track', arg); + } + } + } + ``` + + @method send + @param {String} name the name of the action to trigger + @param {...*} args + @since 1.0.0 + @public + */ + // Set with reopen to override parent behavior + send!: (name: string, ...args: any[]) => unknown; } function parentRoute(route: Route) { @@ -2133,7 +2322,7 @@ function buildRenderOptions( name = route.routeName; templateName = route.templateName || name; } else { - name = _name.replace(/\//g, '.'); + name = _name!.replace(/\//g, '.'); templateName = name; } @@ -2387,156 +2576,13 @@ function getEngineRouteName(engine: Owner, routeName: string) { */ Route.prototype.serialize = defaultSerialize; -Route.reopen(ActionHandler, Evented, { +// Set these here so they can be overridden with extend +Route.reopen({ mergedProperties: ['queryParams'], - - /** - Configuration hash for this route's queryParams. The possible - configuration options and their defaults are as follows - (assuming a query param whose controller property is `page`): - - ```javascript - queryParams: { - page: { - // By default, controller query param properties don't - // cause a full transition when they are changed, but - // rather only cause the URL to update. Setting - // `refreshModel` to true will cause an "in-place" - // transition to occur, whereby the model hooks for - // this route (and any child routes) will re-fire, allowing - // you to reload models (e.g., from the server) using the - // updated query param values. - refreshModel: false, - - // By default, changes to controller query param properties - // cause the URL to update via `pushState`, which means an - // item will be added to the browser's history, allowing - // you to use the back button to restore the app to the - // previous state before the query param property was changed. - // Setting `replace` to true will use `replaceState` (or its - // hash location equivalent), which causes no browser history - // item to be added. This options name and default value are - // the same as the `link-to` helper's `replace` option. - replace: false, - - // By default, the query param URL key is the same name as - // the controller property name. Use `as` to specify a - // different URL key. - as: 'page' - } - } - ``` - - @property queryParams - @for Route - @type Object - @since 1.6.0 - @public - */ queryParams: {}, - - /** - The name of the template to use by default when rendering this routes - template. - - ```app/routes/posts/list.js - import Route from '@ember/routing/route'; - - export default class extends Route { - templateName = 'posts/list' - }); - ``` - - ```app/routes/posts/index.js - import PostsList from '../posts/list'; - - export default class extends PostsList {}; - ``` - - ```app/routes/posts/archived.js - import PostsList from '../posts/list'; - - export default class extends PostsList {}; - ``` - - @property templateName - @type String - @default null - @since 1.4.0 - @public - */ templateName: null, - - /** - The name of the controller to associate with this route. - - By default, Ember will lookup a route's controller that matches the name - of the route (i.e. `posts.new`). However, - if you would like to define a specific controller to use, you can do so - using this property. - - This is useful in many ways, as the controller specified will be: - - * passed to the `setupController` method. - * used as the controller for the template being rendered by the route. - * returned from a call to `controllerFor` for the route. - - @property controllerName - @type String - @default null - @since 1.4.0 - @public - */ controllerName: null, - /** - Sends an action to the router, which will delegate it to the currently - active route hierarchy per the bubbling rules explained under `actions`. - - Example - - ```app/router.js - // ... - - Router.map(function() { - this.route('index'); - }); - - export default Router; - ``` - - ```app/routes/application.js - import Route from '@ember/routing/route'; - import { action } from '@ember/object'; - - export default class ApplicationRoute extends Route { - @action - track(arg) { - console.log(arg, 'was clicked'); - } - } - ``` - - ```app/routes/index.js - import Route from '@ember/routing/route'; - import { action } from '@ember/object'; - - export default class IndexRoute extends Route { - @action - trackIfDebug(arg) { - if (debug) { - this.send('track', arg); - } - } - } - ``` - - @method send - @param {String} name the name of the action to trigger - @param {...*} args - @since 1.0.0 - @public - */ send(...args: any[]) { assert( `Attempted to call .send() with the action '${args[0]}' on the destroyed route '${this.routeName}'.`, diff --git a/packages/@ember/-internals/routing/lib/system/router.ts b/packages/@ember/-internals/routing/lib/system/router.ts index b2466ece517..ea864f7a464 100644 --- a/packages/@ember/-internals/routing/lib/system/router.ts +++ b/packages/@ember/-internals/routing/lib/system/router.ts @@ -53,6 +53,7 @@ function defaultDidTransition(this: EmberRouter, infos: PrivateRouteInfo[]) { once(this, this.trigger, 'didTransition'); if (DEBUG) { + // @ts-expect-error namespace isn't public if (this.namespace.LOG_TRANSITIONS) { // eslint-disable-next-line no-console console.log(`Transitioned into '${EmberRouter._routePath(infos)}'`); @@ -69,6 +70,7 @@ function defaultWillTransition( once(this, this.trigger, 'willTransition', transition); if (DEBUG) { + // @ts-expect-error namespace isn't public if (this.namespace.LOG_TRANSITIONS) { // eslint-disable-next-line no-console console.log( @@ -140,9 +142,39 @@ const { slice } = Array.prototype; @uses Evented @public */ -class EmberRouter extends EmberObject { - location!: string | IEmberLocation; +class EmberRouter extends EmberObject.extend(Evented) implements Evented { + /** + Represents the URL of the root of the application, often '/'. This prefix is + assumed on all routes defined on this router. + + @property rootURL + @default '/' + @public + */ + // Set with reopen to allow overriding via extend rootURL!: string; + + /** + The `location` property determines the type of URL's that your + application will use. + + The following location types are currently available: + + * `history` - use the browser's history API to make the URLs look just like any standard URL + * `hash` - use `#` to separate the server part of the URL from the Ember part: `/blog/#/posts/new` + * `none` - do not store the Ember URL in the actual browser URL (mainly used for testing) + * `auto` - use the best option based on browser capabilities: `history` if possible, then `hash` if possible, otherwise `none` + + This value is defaulted to `auto` by the `locationType` setting of `/config/environment.js` + + @property location + @default 'hash' + @see {Location} + @public + */ + // Set with reopen to allow overriding via extend + location!: string | IEmberLocation; + _routerMicrolib!: Router; _didSetupRouter = false; _initialTransitionStarted = false; @@ -165,6 +197,104 @@ class EmberRouter extends EmberObject { _slowTransitionTimer: unknown; + private namespace: any; + + // Begin Evented + on!: (name: string, method: ((...args: any[]) => void) | string) => this; + one!: (name: string, method: string | ((...args: any[]) => void)) => this; + trigger!: (name: string, ...args: any[]) => any; + off!: (name: string, method: string | ((...args: any[]) => void)) => this; + has!: (name: string) => boolean; + // End Evented + + // Set with reopenClass + private static dslCallbacks?: MatchCallback[]; + + /** + The `Router.map` function allows you to define mappings from URLs to routes + in your application. These mappings are defined within the + supplied callback function using `this.route`. + + The first parameter is the name of the route which is used by default as the + path name as well. + + The second parameter is the optional options hash. Available options are: + + * `path`: allows you to provide your own path as well as mark dynamic + segments. + * `resetNamespace`: false by default; when nesting routes, ember will + combine the route names to form the fully-qualified route name, which is + used with `{{link-to}}` or manually transitioning to routes. Setting + `resetNamespace: true` will cause the route not to inherit from its + parent route's names. This is handy for preventing extremely long route names. + Keep in mind that the actual URL path behavior is still retained. + + The third parameter is a function, which can be used to nest routes. + Nested routes, by default, will have the parent route tree's route name and + path prepended to it's own. + + ```app/router.js + Router.map(function(){ + this.route('post', { path: '/post/:post_id' }, function() { + this.route('edit'); + this.route('comments', { resetNamespace: true }, function() { + this.route('new'); + }); + }); + }); + ``` + + @method map + @param callback + @public + */ + static map(callback: MatchCallback) { + if (!this.dslCallbacks) { + this.dslCallbacks = []; + // FIXME: Can we remove this? + this.reopenClass({ dslCallbacks: this.dslCallbacks }); + } + + this.dslCallbacks.push(callback); + + return this; + } + + static _routePath(routeInfos: PrivateRouteInfo[]) { + let path: string[] = []; + + // We have to handle coalescing resource names that + // are prefixed with their parent's names, e.g. + // ['foo', 'foo.bar.baz'] => 'foo.bar.baz', not 'foo.foo.bar.baz' + + function intersectionMatches(a1: string[], a2: string[]) { + for (let i = 0; i < a1.length; ++i) { + if (a1[i] !== a2[i]) { + return false; + } + } + return true; + } + + let name, nameParts, oldNameParts; + for (let i = 1; i < routeInfos.length; i++) { + name = routeInfos[i].name; + nameParts = name.split('.'); + oldNameParts = slice.call(path); + + while (oldNameParts.length) { + if (intersectionMatches(oldNameParts, nameParts)) { + break; + } + oldNameParts.shift(); + } + + path.push(...nameParts.slice(oldNameParts.length)); + } + + return path.join('.'); + } + constructor(owner: Owner) { super(...arguments); @@ -180,7 +310,7 @@ class EmberRouter extends EmberObject { this._routerService = routerService; } - _initRouterJs() { + _initRouterJs(): void { let location = get(this, 'location'); let router = this; let owner = getOwner(this); @@ -204,7 +334,8 @@ class EmberRouter extends EmberObject { let route = routeOwner.lookup(fullRouteName); if (seen[name]) { - return route!; + assert('seen routes should exist', route); + return route; } seen[name] = true; @@ -589,6 +720,7 @@ class EmberRouter extends EmberObject { let infos = this._routerMicrolib.currentRouteInfos; if (this.namespace.LOG_TRANSITIONS) { // eslint-disable-next-line no-console + assert('expected infos to be set', infos); console.log(`Intermediate-transitioned into '${EmberRouter._routePath(infos)}'`); } } @@ -600,7 +732,8 @@ class EmberRouter extends EmberObject { generate(name: string, ...args: any[]) { let url = this._routerMicrolib.generate(name, ...args); - return (this.location as IEmberLocation).formatURL(url); + assert('expected non-string location', typeof this.location !== 'string'); + return this.location.formatURL(url); } /** @@ -1239,6 +1372,66 @@ class EmberRouter extends EmberObject { return engineInstance; } + + /** + Handles updating the paths and notifying any listeners of the URL + change. + + Triggers the router level `didTransition` hook. + + For example, to notify google analytics when the route changes, + you could use this hook. (Note: requires also including GA scripts, etc.) + + ```javascript + import config from './config/environment'; + import EmberRouter from '@ember/routing/router'; + import { inject as service } from '@ember/service'; + + let Router = EmberRouter.extend({ + location: config.locationType, + + router: service(), + + didTransition: function() { + this._super(...arguments); + + ga('send', 'pageview', { + page: this.router.currentURL, + title: this.router.currentRouteName, + }); + } + }); + ``` + + @method didTransition + @public + @since 1.2.0 + */ + // Set with reopen to allow overriding via extend + didTransition!: typeof defaultDidTransition; + + /** + Handles notifying any listeners of an impending URL + change. + + Triggers the router level `willTransition` hook. + + @method willTransition + @public + @since 1.11.0 + */ + // Set with reopen to allow overriding via extend + willTransition!: typeof defaultWillTransition; + + /** + Represents the current URL. + + @property url + @type {String} + @private + */ + // Set with reopen to allow overriding via extend + url!: string; } /* @@ -1527,7 +1720,9 @@ function updatePaths(router: EmberRouter) { let path = EmberRouter._routePath(infos); let currentRouteName = infos[infos.length - 1].name; - let currentURL = router.get('location').getURL(); + let location = router.location; + assert('expected location to not be a string', typeof location !== 'string'); + let currentURL = location.getURL(); set(router, 'currentPath', path); set(router, 'currentRouteName', currentRouteName); @@ -1590,92 +1785,6 @@ function updatePaths(router: EmberRouter) { } } -EmberRouter.reopenClass({ - /** - The `Router.map` function allows you to define mappings from URLs to routes - in your application. These mappings are defined within the - supplied callback function using `this.route`. - - The first parameter is the name of the route which is used by default as the - path name as well. - - The second parameter is the optional options hash. Available options are: - - * `path`: allows you to provide your own path as well as mark dynamic - segments. - * `resetNamespace`: false by default; when nesting routes, ember will - combine the route names to form the fully-qualified route name, which is - used with `{{link-to}}` or manually transitioning to routes. Setting - `resetNamespace: true` will cause the route not to inherit from its - parent route's names. This is handy for preventing extremely long route names. - Keep in mind that the actual URL path behavior is still retained. - - The third parameter is a function, which can be used to nest routes. - Nested routes, by default, will have the parent route tree's route name and - path prepended to it's own. - - ```app/router.js - Router.map(function(){ - this.route('post', { path: '/post/:post_id' }, function() { - this.route('edit'); - this.route('comments', { resetNamespace: true }, function() { - this.route('new'); - }); - }); - }); - ``` - - @method map - @param callback - @public - */ - map(callback: MatchCallback) { - if (!this.dslCallbacks) { - this.dslCallbacks = []; - this.reopenClass({ dslCallbacks: this.dslCallbacks }); - } - - this.dslCallbacks.push(callback); - - return this; - }, - - _routePath(routeInfos: PrivateRouteInfo[]) { - let path: string[] = []; - - // We have to handle coalescing resource names that - // are prefixed with their parent's names, e.g. - // ['foo', 'foo.bar.baz'] => 'foo.bar.baz', not 'foo.foo.bar.baz' - - function intersectionMatches(a1: string[], a2: string[]) { - for (let i = 0; i < a1.length; ++i) { - if (a1[i] !== a2[i]) { - return false; - } - } - return true; - } - - let name, nameParts, oldNameParts; - for (let i = 1; i < routeInfos.length; i++) { - name = routeInfos[i].name; - nameParts = name.split('.'); - oldNameParts = slice.call(path); - - while (oldNameParts.length) { - if (intersectionMatches(oldNameParts, nameParts)) { - break; - } - oldNameParts.shift(); - } - - path.push(...nameParts.slice(oldNameParts.length)); - } - - return path.join('.'); - }, -}); - function didBeginTransition(transition: Transition, router: EmberRouter) { let routerState = new RouterState(router, router._routerMicrolib, transition[STATE_SYMBOL]!); @@ -1786,91 +1895,13 @@ function representEmptyRoute( } } -EmberRouter.reopen(Evented, { - /** - Handles updating the paths and notifying any listeners of the URL - change. - - Triggers the router level `didTransition` hook. - - For example, to notify google analytics when the route changes, - you could use this hook. (Note: requires also including GA scripts, etc.) - - ```javascript - import config from './config/environment'; - import EmberRouter from '@ember/routing/router'; - import { inject as service } from '@ember/service'; - - let Router = EmberRouter.extend({ - location: config.locationType, - - router: service(), - - didTransition: function() { - this._super(...arguments); - - ga('send', 'pageview', { - page: this.router.currentURL, - title: this.router.currentRouteName, - }); - } - }); - ``` - - @method didTransition - @public - @since 1.2.0 - */ +EmberRouter.reopen({ didTransition: defaultDidTransition, - - /** - Handles notifying any listeners of an impending URL - change. - - Triggers the router level `willTransition` hook. - - @method willTransition - @public - @since 1.11.0 - */ willTransition: defaultWillTransition, - /** - Represents the URL of the root of the application, often '/'. This prefix is - assumed on all routes defined on this router. - - @property rootURL - @default '/' - @public - */ rootURL: '/', - - /** - The `location` property determines the type of URL's that your - application will use. - - The following location types are currently available: - - * `history` - use the browser's history API to make the URLs look just like any standard URL - * `hash` - use `#` to separate the server part of the URL from the Ember part: `/blog/#/posts/new` - * `none` - do not store the Ember URL in the actual browser URL (mainly used for testing) - * `auto` - use the best option based on browser capabilities: `history` if possible, then `hash` if possible, otherwise `none` - - This value is defaulted to `auto` by the `locationType` setting of `/config/environment.js` - - @property location - @default 'hash' - @see {Location} - @public - */ location: 'hash', - /** - Represents the current URL. - - @property url - @type {String} - @private - */ + // FIXME: Does this need to be overrideable via extend? url: computed(function (this: Router) { let location = get(this, 'location'); diff --git a/packages/@ember/-internals/runtime/index.d.ts b/packages/@ember/-internals/runtime/index.d.ts index 9b80d3da7d7..af32a6e2ad2 100644 --- a/packages/@ember/-internals/runtime/index.d.ts +++ b/packages/@ember/-internals/runtime/index.d.ts @@ -1,3 +1,8 @@ +import { EmberClassConstructor, Objectify, ObserverMethod } from './types/-private/types'; +import PublicCoreObject from './types/core'; +import Evented from './types/evented'; +import Observable from './types/observable'; + export const TargetActionSupport: any; export function isArray(arr: any): boolean; export const ControllerMixin: any; @@ -10,13 +15,50 @@ export function deprecatingAlias( } ): any; -export const CoreObject: any; +// The public version doesn't export some deprecated methods. +// However, these are still used internally. Returning `any` is +// very loose, but it's essentially what was here before. +export class CoreObject extends PublicCoreObject { + static extend( + this: Statics & EmberClassConstructor, + ...args: any[] + ): Objectify & EmberClassConstructor; + static reopen(...args: any[]): any; + static reopenClass(...args: any[]): any; +} + export const FrameworkObject: any; -export const Object: any; + +export class Object extends CoreObject implements Observable { + get(key: K): unknown; + getProperties(list: K[]): Record; + getProperties(...list: K[]): Record; + set(key: K, value: this[K]): this[K]; + set(key: keyof this, value: T): T; + setProperties(hash: Pick): Record; + setProperties( + // tslint:disable-next-line:unified-signatures + hash: { [KK in K]: any } + ): Record; + notifyPropertyChange(keyName: string): this; + addObserver(key: keyof this, target: Target, method: ObserverMethod): this; + addObserver(key: keyof this, method: ObserverMethod): this; + removeObserver( + key: keyof this, + target: Target, + method: ObserverMethod + ): this; + removeObserver(key: keyof this, method: ObserverMethod): this; + getWithDefault(key: K, defaultValue: any): unknown; + incrementProperty(keyName: keyof this, increment?: number): number; + decrementProperty(keyName: keyof this, decrement?: number): number; + toggleProperty(keyName: keyof this): boolean; + cacheFor(key: K): unknown; +} export function _contentFor(proxy: any): any; export const A: any; export const ActionHandler: any; -export const Evented: any; +export { Evented }; export function typeOf(obj: any): string; diff --git a/packages/@ember/-internals/runtime/type-tests/core.test.ts b/packages/@ember/-internals/runtime/type-tests/core.test.ts new file mode 100644 index 00000000000..727f1213eea --- /dev/null +++ b/packages/@ember/-internals/runtime/type-tests/core.test.ts @@ -0,0 +1,25 @@ +import { expectTypeOf } from 'expect-type'; +import CoreObject from '../types/core'; + +/** Newable tests */ +const co1 = new CoreObject(); + +expectTypeOf(co1.concatenatedProperties).toEqualTypeOf(); +expectTypeOf(co1.isDestroyed).toEqualTypeOf(); +expectTypeOf(co1.isDestroying).toEqualTypeOf(); +expectTypeOf(co1.destroy()).toEqualTypeOf(); +expectTypeOf(co1.toString()).toEqualTypeOf(); + +/** .create tests */ +const co2 = CoreObject.create(); +expectTypeOf(co2.concatenatedProperties).toEqualTypeOf(); +expectTypeOf(co2.isDestroyed).toEqualTypeOf(); +expectTypeOf(co2.isDestroying).toEqualTypeOf(); +expectTypeOf(co2.destroy()).toEqualTypeOf(); +expectTypeOf(co2.toString()).toEqualTypeOf(); + +/** .create tests w/ initial instance data passed in */ +const co3 = CoreObject.create({ foo: '123', bar: 456 }); + +expectTypeOf(co3.foo).toEqualTypeOf(); +expectTypeOf(co3.bar).toEqualTypeOf(); diff --git a/packages/@ember/-internals/runtime/types/-private/types.d.ts b/packages/@ember/-internals/runtime/types/-private/types.d.ts new file mode 100644 index 00000000000..2d58b2fa56a --- /dev/null +++ b/packages/@ember/-internals/runtime/types/-private/types.d.ts @@ -0,0 +1,41 @@ +/** + * Map type `T` to a plain object hash with the identity mapping. + * + * Discards any additional object identity like the ability to `new()` up the class. + * The `new()` capability is added back later by merging `EmberClassConstructor` + * + * Implementation is carefully chosen for the reasons described in + * https://github.com/typed-ember/ember-typings/pull/29 + */ +export type Objectify = Readonly; + +export type ExtractPropertyNamesOfType = { + [K in keyof T]: T[K] extends S ? K : never; +}[keyof T]; + +/** + * Used to infer the type of ember classes of type `T`. + * + * Generally you would use `EmberClass.create()` instead of `new EmberClass()`. + * + * The single-arg constructor is required by the typescript compiler. + * The multi-arg constructor is included for better ergonomics. + * + * Implementation is carefully chosen for the reasons described in + * https://github.com/typed-ember/ember-typings/pull/29 + */ +export type EmberClassConstructor = (new (properties?: object) => T) & + (new (...args: any[]) => T); + +/** + * Check that any arguments to `create()` match the type's properties. + * + * Accept any additional properties and add merge them into the instance. + */ +export type EmberInstanceArguments = Partial & { + [key: string]: any; +}; + +export type ObserverMethod = + | keyof Target + | ((this: Target, sender: Sender, key: string, value: any, rev: number) => void); diff --git a/packages/@ember/-internals/runtime/types/core.d.ts b/packages/@ember/-internals/runtime/types/core.d.ts new file mode 100644 index 00000000000..304747df018 --- /dev/null +++ b/packages/@ember/-internals/runtime/types/core.d.ts @@ -0,0 +1,87 @@ +import { EmberClassConstructor, EmberInstanceArguments, Objectify } from './-private/types'; + +type MergeArray = Arr extends [infer T, ...infer Rest] + ? T & MergeArray + : unknown; // TODO: Is this correct? + +export default class CoreObject { + /** + * CoreObject constructor takes initial object properties as an argument. + */ + constructor(properties?: object); + + _super(...args: any[]): any; + + /** + * An overridable method called when objects are instantiated. By default, + * does nothing unless it is overridden during class definition. + */ + init(): void; + + /** + * Defines the properties that will be concatenated from the superclass (instead of overridden). + * @default null + */ + concatenatedProperties: string[]; + + /** + * Destroyed object property flag. If this property is true the observers and bindings were + * already removed by the effect of calling the destroy() method. + * @default false + */ + isDestroyed: boolean; + /** + * Destruction scheduled flag. The destroy() method has been called. The object stays intact + * until the end of the run loop at which point the isDestroyed flag is set. + * @default false + */ + isDestroying: boolean; + + /** + * Destroys an object by setting the `isDestroyed` flag and removing its + * metadata, which effectively destroys observers and bindings. + * If you try to set a property on a destroyed object, an exception will be + * raised. + * Note that destruction is scheduled for the end of the run loop and does not + * happen immediately. It will set an isDestroying flag immediately. + * @return receiver + */ + destroy(): CoreObject; + + /** + * Override to implement teardown. + */ + willDestroy(): void; + + /** + * Returns a string representation which attempts to provide more information than Javascript's toString + * typically does, in a generic way for all Ember objects (e.g., ""). + * @return string representation + */ + toString(): string; + + static create>>( + this: Class, + ...args: Args + ): InstanceType & MergeArray; + + static detect( + this: Statics & EmberClassConstructor, + obj: any + ): obj is Objectify & EmberClassConstructor; + + static detectInstance(this: EmberClassConstructor, obj: any): obj is Instance; + + /** + * Iterate over each computed property for the class, passing its name and any + * associated metadata (see metaForProperty) to the callback. + */ + static eachComputedProperty(callback: (...args: any[]) => any, binding: {}): void; + /** + * Returns the original hash that was passed to meta(). + * @param key property name + */ + static metaForProperty(key: string): {}; + static isClass: boolean; + static isMethod: boolean; +} diff --git a/packages/@ember/-internals/runtime/types/evented.d.ts b/packages/@ember/-internals/runtime/types/evented.d.ts new file mode 100644 index 00000000000..dbf86d62a8f --- /dev/null +++ b/packages/@ember/-internals/runtime/types/evented.d.ts @@ -0,0 +1,48 @@ +import Mixin from '../../metal/lib/mixin'; + +/** + * This mixin allows for Ember objects to subscribe to and emit events. + */ +interface Evented { + /** + * Subscribes to a named event with given function. + */ + on( + name: string, + target: Target, + method: string | ((this: Target, ...args: any[]) => void) + ): this; + on(name: string, method: ((...args: any[]) => void) | string): this; + /** + * Subscribes a function to a named event and then cancels the subscription + * after the first time the event is triggered. It is good to use ``one`` when + * you only care about the first time an event has taken place. + */ + one( + name: string, + target: Target, + method: string | ((this: Target, ...args: any[]) => void) + ): this; + one(name: string, method: string | ((...args: any[]) => void)): this; + /** + * Triggers a named event for the object. Any additional arguments + * will be passed as parameters to the functions that are subscribed to the + * event. + */ + trigger(name: string, ...args: any[]): any; + /** + * Cancels subscription for given name, target, and method. + */ + off( + name: string, + target: Target, + method: string | ((this: Target, ...args: any[]) => void) + ): this; + off(name: string, method: string | ((...args: any[]) => void)): this; + /** + * Checks to see if object has any subscriptions for named event. + */ + has(name: string): boolean; +} +declare const Evented: Mixin; +export default Evented; diff --git a/packages/@ember/-internals/runtime/types/mixin.d.ts b/packages/@ember/-internals/runtime/types/mixin.d.ts new file mode 100644 index 00000000000..abe42379680 --- /dev/null +++ b/packages/@ember/-internals/runtime/types/mixin.d.ts @@ -0,0 +1,3 @@ +import { Mixin } from '../../metal/lib/mixin'; + +export default Mixin; diff --git a/packages/@ember/-internals/runtime/types/observable.d.ts b/packages/@ember/-internals/runtime/types/observable.d.ts new file mode 100644 index 00000000000..96b2ae03d5d --- /dev/null +++ b/packages/@ember/-internals/runtime/types/observable.d.ts @@ -0,0 +1,81 @@ +import { ObserverMethod } from './-private/types'; + +/** + * This mixin provides properties and property observing functionality, core features of the Ember object model. + */ +interface Observable { + /** + * Retrieves the value of a property from the object. + */ + get(key: K): unknown; + // get(key: K): UnwrapComputedPropertyGetter; + /** + * To get the values of multiple properties at once, call `getProperties` + * with a list of strings or an array: + */ + getProperties(list: K[]): Record; + getProperties(...list: K[]): Record; + /** + * Sets the provided key or path to the value. + */ + set(key: K, value: this[K]): this[K]; + set(key: keyof this, value: T): T; + /** + * Sets a list of properties at once. These properties are set inside + * a single `beginPropertyChanges` and `endPropertyChanges` batch, so + * observers will be buffered. + */ + setProperties(hash: Pick): Record; + setProperties( + // tslint:disable-next-line:unified-signatures + hash: { [KK in K]: any } + ): Record; + /** + * Convenience method to call `propertyWillChange` and `propertyDidChange` in + * succession. + */ + notifyPropertyChange(keyName: string): this; + /** + * Adds an observer on a property. + */ + addObserver(key: keyof this, target: Target, method: ObserverMethod): this; + addObserver(key: keyof this, method: ObserverMethod): this; + /** + * Remove an observer you have previously registered on this object. Pass + * the same key, target, and method you passed to `addObserver()` and your + * target will no longer receive notifications. + */ + removeObserver( + key: keyof this, + target: Target, + method: ObserverMethod + ): this; + removeObserver(key: keyof this, method: ObserverMethod): this; + /** + * Retrieves the value of a property, or a default value in the case that the + * property returns `undefined`. + */ + getWithDefault(key: K, defaultValue: any): unknown; + /** + * Set the value of a property to the current value plus some amount. + */ + incrementProperty(keyName: keyof this, increment?: number): number; + /** + * Set the value of a property to the current value minus some amount. + */ + decrementProperty(keyName: keyof this, decrement?: number): number; + /** + * Set the value of a boolean property to the opposite of its + * current value. + */ + toggleProperty(keyName: keyof this): boolean; + /** + * Returns the cached value of a computed property, if it exists. + * This allows you to inspect the value of a computed property + * without accidentally invoking it if it is intended to be + * generated lazily. + */ + cacheFor(key: K): unknown; +} +// declare const Observable: Mixin; +export default Observable; diff --git a/packages/@ember/object/index.d.ts b/packages/@ember/object/index.d.ts index edc356417e7..1403dd37996 100644 --- a/packages/@ember/object/index.d.ts +++ b/packages/@ember/object/index.d.ts @@ -1 +1,17 @@ export let action: MethodDecorator; + +// export { +// aliasMethod, +// computed, +// defineProperty, +// get, +// getProperties, +// getWithDefault, +// notifyPropertyChange, +// observer, +// set, +// setProperties, +// trySet, +// } from '@ember/-internals/metal'; + +export { Object as default } from '@ember/-internals/runtime'; diff --git a/packages/@ember/object/type-tests/core.test.ts b/packages/@ember/object/type-tests/core.test.ts new file mode 100644 index 00000000000..ba8ee6c6b04 --- /dev/null +++ b/packages/@ember/object/type-tests/core.test.ts @@ -0,0 +1,5 @@ +import { expectTypeOf } from 'expect-type'; + +import CoreObject from '@ember/object/core'; + +expectTypeOf(CoreObject.create()).toEqualTypeOf(); diff --git a/packages/@ember/object/type-tests/index.test.ts b/packages/@ember/object/type-tests/index.test.ts new file mode 100644 index 00000000000..8e8c492bf59 --- /dev/null +++ b/packages/@ember/object/type-tests/index.test.ts @@ -0,0 +1,5 @@ +import { expectTypeOf } from 'expect-type'; + +import EmberObject from '@ember/object'; + +expectTypeOf(EmberObject.create()).toEqualTypeOf(); diff --git a/packages/@ember/object/type-tests/observable.test.ts b/packages/@ember/object/type-tests/observable.test.ts new file mode 100644 index 00000000000..78e25c0c36e --- /dev/null +++ b/packages/@ember/object/type-tests/observable.test.ts @@ -0,0 +1,5 @@ +import { expectTypeOf } from 'expect-type'; + +import Observable from '@ember/object/observable'; + +expectTypeOf().toEqualTypeOf<(key: K) => unknown>(); diff --git a/packages/@ember/object/types/core.d.ts b/packages/@ember/object/types/core.d.ts new file mode 100644 index 00000000000..63d98c5ee17 --- /dev/null +++ b/packages/@ember/object/types/core.d.ts @@ -0,0 +1 @@ +export { CoreObject as default } from '../../-internals/runtime'; diff --git a/packages/@ember/object/types/observable.d.ts b/packages/@ember/object/types/observable.d.ts new file mode 100644 index 00000000000..9f633dd12fd --- /dev/null +++ b/packages/@ember/object/types/observable.d.ts @@ -0,0 +1,2 @@ +import Observable from '@ember/-internals/runtime/types/observable'; +export default Observable; diff --git a/tests/docs/expected.js b/tests/docs/expected.js index 48245ae8c15..7c3c0bf92be 100644 --- a/tests/docs/expected.js +++ b/tests/docs/expected.js @@ -37,7 +37,6 @@ module.exports = { '_invoke', '_lazyInjections', '_logLookup', - '_names', '_normalizeCache', '_onLookup', '_options', diff --git a/tsconfig.json b/tsconfig.json index 03567256c1a..8e2f30e6958 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,17 +24,12 @@ "allowJs": false, "paths": { - "@glimmer/*": ["../node_modules/@glimmer/*"] + "@glimmer/*": ["../node_modules/@glimmer/*"], + "@ember/object/*": ["@ember/object/*", "@ember/object/types/*"] } }, - "include": [ - "packages/**/*.ts", - ], + "include": ["packages/**/*.ts"], - "exclude": [ - "dist", - "node_modules", - "tmp", - ] + "exclude": ["dist", "node_modules", "tmp"] } diff --git a/yarn.lock b/yarn.lock index 9a4c984b524..01ed852a482 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5007,6 +5007,11 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" +expect-type@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-0.11.0.tgz#bce1a3e283f0334eedb39699b57dd27be7009cc1" + integrity sha512-hkObxepDKhTYloH/UZoxYTT2uUzdhvDEwAi0oqdk29XEkHF8p+5ZRpX/BZES2PtGN9YgyEqutIjXfnL9iMflMw== + express@^4.10.7, express@^4.13.1, express@^4.16.4, express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -10042,6 +10047,11 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typescript@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" + integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== + typescript@~4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.3.tgz#153bbd468ef07725c1df9c77e8b453f8d36abba5"