diff --git a/packages/@ember/-internals/metal/index.ts b/packages/@ember/-internals/metal/index.ts index 608bfa75037..1e736db557e 100644 --- a/packages/@ember/-internals/metal/index.ts +++ b/packages/@ember/-internals/metal/index.ts @@ -70,8 +70,8 @@ export { } from './lib/observer'; export { default as inject, DEBUG_INJECTION_FUNCTIONS } from './lib/injected_property'; export { tagForProperty, tagForObject, markObjectAsDirty } from './lib/tags'; -export { tracked, TrackedDescriptor } from './lib/tracked'; -export { cached } from './lib/cached'; +export { tracked, TrackedDescriptor, isTrackedProperty } from './lib/tracked'; +export { cached, isCachedProperty } from './lib/cached'; export { createCache, getValue, isConst } from './lib/cache'; export { diff --git a/packages/@ember/-internals/metal/lib/cached.ts b/packages/@ember/-internals/metal/lib/cached.ts index b40ccf4a2de..7785a1a1ce1 100644 --- a/packages/@ember/-internals/metal/lib/cached.ts +++ b/packages/@ember/-internals/metal/lib/cached.ts @@ -4,6 +4,8 @@ import { DEBUG } from '@glimmer/env'; import { createCache, getValue } from '@glimmer/validator'; +const CacheMap = new WeakMap(); + /** * @decorator * @@ -84,7 +86,7 @@ import { createCache, getValue } from '@glimmer/validator'; the subsequent cache invalidations of the `@cached` properties who were using this `trackedProp`. - Remember that setting tracked data should only be done during initialization, + Remember that setting tracked data should only be done during initialization, or as the result of a user action. Setting tracked data during render (such as in a getter), is not supported. @@ -111,6 +113,8 @@ export const cached: MethodDecorator = (...args: any[]) => { throwCachedGetterOnlyError(key); } + CacheMap.set(target, [...(CacheMap.get(target) || []), key]); + const caches = new WeakMap(); const getter = descriptor.get; @@ -144,3 +148,7 @@ function throwCachedInvalidArgsError(args: unknown[] = []): never { )}), which is not supported. Dependencies are automatically tracked, so you can just use ${'`@cached`'}` ); } + +export function isCachedProperty(object: object, prop: string) { + return (CacheMap.get(object) || []).includes(prop); +} diff --git a/packages/@ember/-internals/metal/lib/tracked.ts b/packages/@ember/-internals/metal/lib/tracked.ts index f5f696997f5..36b6167b95f 100644 --- a/packages/@ember/-internals/metal/lib/tracked.ts +++ b/packages/@ember/-internals/metal/lib/tracked.ts @@ -200,3 +200,7 @@ export class TrackedDescriptor { this._set.call(obj, value); } } + +export function isTrackedProperty(object: object, prop: string): boolean { + return metaFor(object).peekDescriptors(prop) instanceof TrackedDescriptor; +} diff --git a/packages/@ember/-internals/utils/index.ts b/packages/@ember/-internals/utils/index.ts index 329a6f7e793..50ebbca6f85 100644 --- a/packages/@ember/-internals/utils/index.ts +++ b/packages/@ember/-internals/utils/index.ts @@ -32,4 +32,5 @@ export { setupMandatorySetter, teardownMandatorySetter, setWithMandatorySetter, + isMandatorySetter, } from './lib/mandatory-setter'; diff --git a/packages/@ember/-internals/utils/lib/mandatory-setter.ts b/packages/@ember/-internals/utils/lib/mandatory-setter.ts index 92e157f8d8e..e031c2073e7 100644 --- a/packages/@ember/-internals/utils/lib/mandatory-setter.ts +++ b/packages/@ember/-internals/utils/lib/mandatory-setter.ts @@ -9,6 +9,8 @@ export let setupMandatorySetter: export let teardownMandatorySetter: ((obj: object, keyName: string | symbol) => void) | undefined; export let setWithMandatorySetter: ((obj: object, keyName: string, value: any) => void) | undefined; +export let isMandatorySetter: ((obj: object, keyName: string) => boolean) | undefined; + type PropertyDescriptorWithMeta = PropertyDescriptor & { hadOwnProperty?: boolean }; function isElementKey(key: string | number | symbol) { @@ -93,6 +95,11 @@ if (DEBUG) { }); }; + isMandatorySetter = function (obj: object, keyName: string) { + let setters = MANDATORY_SETTERS.get(obj); + return setters !== undefined && setters[keyName] !== undefined; + }; + teardownMandatorySetter = function (obj: object, keyName: string | symbol) { let setters = MANDATORY_SETTERS.get(obj); diff --git a/packages/@ember/debug/ember-inspector.ts b/packages/@ember/debug/ember-inspector.ts new file mode 100644 index 00000000000..51ad12c7559 --- /dev/null +++ b/packages/@ember/debug/ember-inspector.ts @@ -0,0 +1,123 @@ +// required for inspector +import { isTesting } from './lib/testing'; +import { _backburner, cancel, debounce, join, later, scheduleOnce } from '@ember/runloop'; +import { cacheFor, guidFor } from '@ember/object/internals'; +import { default as MutableArray } from '@ember/array/mutable'; +import { default as Namespace } from '@ember/application/namespace'; +import { default as MutableEnumerable } from '@ember/enumerable/mutable'; +import { NativeArray } from '@ember/array'; +import { ControllerMixin } from '@ember/controller'; +import { default as CoreObject } from '@ember/object/core'; +import { default as Application } from '@ember/application'; +import { default as EmberComponent } from '@ember/component'; +import { default as Observable } from '@ember/object/observable'; +import { default as Evented } from '@ember/object/evented'; +import { default as PromiseProxyMixin } from '@ember/object/promise-proxy-mixin'; +import { default as EmberObject } from '@ember/object'; +import { default as VERSION } from 'ember/version'; +import { + ComputedProperty, + isComputed, + descriptorForProperty, + descriptorForDecorator, + tagForProperty, +} from '@ember/-internals/metal'; +import { isMandatorySetter } from '@ember/-internals/utils'; +import { meta } from '@ember/-internals/meta'; +import { TargetActionSupport } from '@ember/-internals/runtime'; +import { + ViewStateSupport, + ViewMixin, + ActionSupport, + ClassNamesSupport, + ChildViewsSupport, + CoreView, +} from '@ember/-internals/views'; +import { set, get } from '@ember/object'; +import { isTrackedProperty } from '@ember/-internals/metal'; +import { isCachedProperty } from '@ember/-internals/metal'; +import { default as inspect } from './lib/inspect'; +import { subscribe } from '../instrumentation'; +import { default as captureRenderTree } from './lib/capture-render-tree'; +import { registerHandler as registerDeprecationHandler } from './lib/deprecate'; +import * as GlimmerValidator from '@glimmer/validator'; +import * as GlimmerRuntime from '@glimmer/runtime'; +import { getOwner } from '@ember/owner'; +import RSVP from 'rsvp'; + +export function setupInspectorSupport() { + if (typeof window !== 'undefined' && window.addEventListener) { + window.addEventListener( + 'ember-inspector-debug-request', + () => { + const event = new CustomEvent('ember-inspector-debug-response', { + detail: { + runloop: { + _backburner, + cancel, + debounce, + join, + later, + scheduleOnce, + }, + object: { + cacheFor, + guidFor, + getOwner, + set, + get, + meta, + }, + debug: { + isComputed, + isTrackedProperty, + isCachedProperty, + descriptorForProperty, + descriptorForDecorator, + isMandatorySetter, + meta, + captureRenderTree, + isTesting, + inspect, + registerDeprecationHandler, + tagForProperty, + ComputedProperty, + }, + classes: { + EmberObject, + MutableArray, + Namespace, + MutableEnumerable, + NativeArray, + TargetActionSupport, + ControllerMixin, + CoreObject, + Application, + EmberComponent, + Observable, + Evented, + PromiseProxyMixin, + }, + VERSION, + instrumentation: { + subscribe, + }, + Views: { + ViewStateSupport, + ViewMixin, + ActionSupport, + ClassNamesSupport, + ChildViewsSupport, + CoreView, + }, + GlimmerValidator, + GlimmerRuntime, + RSVP, + }, + }); + window.dispatchEvent(event); + }, + false + ); + } +} diff --git a/packages/@ember/debug/package.json b/packages/@ember/debug/package.json index b81e541d3a6..382355da6a7 100644 --- a/packages/@ember/debug/package.json +++ b/packages/@ember/debug/package.json @@ -5,6 +5,7 @@ "exports": { ".": "./index.ts", "./container-debug-adapter": "./container-debug-adapter.ts", + "./ember-inspector": "./ember-inspector.ts", "./data-adapter": "./data-adapter.ts" }, "dependencies": { diff --git a/packages/ember/index.ts b/packages/ember/index.ts index 89436fd3743..dcac4b95b6f 100644 --- a/packages/ember/index.ts +++ b/packages/ember/index.ts @@ -13,6 +13,7 @@ import { meta as internalMeta } from '@ember/-internals/meta'; import * as metal from '@ember/-internals/metal'; import { FEATURES as EmberFEATURES, isEnabled } from '@ember/canary-features'; import * as EmberDebug from '@ember/debug'; +import { setupInspectorSupport } from '@ember/debug/ember-inspector'; import { assert as emberAssert, captureRenderTree } from '@ember/debug'; import Backburner from 'backburner.js'; import EmberController, { @@ -745,6 +746,7 @@ function defineEmberTestingLazyLoad(key: 'Test' | 'setupForTesting') { defineEmberTestingLazyLoad('Test'); defineEmberTestingLazyLoad('setupForTesting'); +setupInspectorSupport(); // @ts-expect-error Per types, runLoadHooks requires a second parameter. Should we loosen types? applicationRunLoadHooks('Ember');