Skip to content

Commit

Permalink
Merge pull request #19173 from emberjs/force-capabilities-usage
Browse files Browse the repository at this point in the history
  • Loading branch information
rwjblue authored Sep 30, 2020
2 parents 7fd407c + f776ac8 commit 66fb044
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 33 deletions.
27 changes: 11 additions & 16 deletions packages/@ember/-internals/glimmer/lib/component-managers/custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { EmberVMEnvironment } from '../environment';
import RuntimeResolver from '../resolver';
import { OwnedTemplate } from '../template';
import { argsProxyFor } from '../utils/args-proxy';
import { buildCapabilities, InternalCapabilities } from '../utils/managers';
import AbstractComponentManager from './abstract';

const CAPABILITIES = {
Expand Down Expand Up @@ -49,6 +50,12 @@ export interface OptionalCapabilities {
};
}

export interface Capabilities extends InternalCapabilities {
asyncLifeCycleCallbacks: boolean;
destructor: boolean;
updateHook: boolean;
}

export function capabilities<Version extends keyof OptionalCapabilities>(
managerAPI: Version,
options: OptionalCapabilities[Version] = {}
Expand All @@ -64,11 +71,11 @@ export function capabilities<Version extends keyof OptionalCapabilities>(
updateHook = Boolean((options as OptionalCapabilities['3.13']).updateHook);
}

return {
return buildCapabilities({
asyncLifeCycleCallbacks: Boolean(options.asyncLifecycleCallbacks),
destructor: Boolean(options.destructor),
updateHook,
};
}) as Capabilities;
}

export interface DefinitionState<ComponentInstance> {
Expand All @@ -77,21 +84,9 @@ export interface DefinitionState<ComponentInstance> {
template: OwnedTemplate;
}

export interface Capabilities {
asyncLifeCycleCallbacks: boolean;
destructor: boolean;
updateHook: boolean;
}

// TODO: export ICapturedArgumentsValue from glimmer and replace this
export interface Args {
named: Dict<unknown>;
positional: unknown[];
}

export interface ManagerDelegate<ComponentInstance> {
capabilities: Capabilities;
createComponent(factory: unknown, args: Args): ComponentInstance;
createComponent(factory: unknown, args: Arguments): ComponentInstance;
getContext(instance: ComponentInstance): unknown;
}

Expand All @@ -114,7 +109,7 @@ export function hasUpdateHook<ComponentInstance>(

export interface ManagerDelegateWithUpdateHook<ComponentInstance>
extends ManagerDelegate<ComponentInstance> {
updateComponent(instance: ComponentInstance, args: Args): void;
updateComponent(instance: ComponentInstance, args: Arguments): void;
}

export function hasAsyncUpdateHook<ComponentInstance>(
Expand Down
7 changes: 4 additions & 3 deletions packages/@ember/-internals/glimmer/lib/helpers/custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { DEBUG } from '@glimmer/env';
import { Arguments, Helper as GlimmerHelper } from '@glimmer/interfaces';
import { createComputeRef, UNDEFINED_REFERENCE } from '@glimmer/reference';
import { argsProxyFor } from '../utils/args-proxy';
import { buildCapabilities, InternalCapabilities } from '../utils/managers';

export type HelperDefinition = object;

export interface HelperCapabilities {
export interface HelperCapabilities extends InternalCapabilities {
hasValue: boolean;
hasDestroyable: boolean;
hasScheduledEffect: boolean;
Expand All @@ -29,11 +30,11 @@ export function helperCapabilities(
!options.hasScheduledEffect
);

return {
return buildCapabilities({
hasValue: Boolean(options.hasValue),
hasDestroyable: Boolean(options.hasDestroyable),
hasScheduledEffect: Boolean(options.hasScheduledEffect),
};
});
}

export interface HelperManager<HelperStateBucket = unknown> {
Expand Down
12 changes: 4 additions & 8 deletions packages/@ember/-internals/glimmer/lib/modifiers/custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { registerDestructor, reifyArgs } from '@glimmer/runtime';
import { createUpdatableTag, untrack, UpdatableTag } from '@glimmer/validator';
import { SimpleElement } from '@simple-dom/interface';
import { argsProxyFor } from '../utils/args-proxy';
import { buildCapabilities, InternalCapabilities } from '../utils/managers';

export interface CustomModifierDefinitionState<ModifierInstance> {
ModifierClass: Factory<ModifierInstance>;
Expand All @@ -25,7 +26,7 @@ export interface OptionalCapabilities {
};
}

export interface Capabilities {
export interface Capabilities extends InternalCapabilities {
disableAutoTracking: boolean;
useArgsProxy: boolean;
passFactoryToCreate: boolean;
Expand All @@ -40,11 +41,11 @@ export function capabilities<Version extends keyof OptionalCapabilities>(
managerAPI === '3.13' || managerAPI === '3.22'
);

return {
return buildCapabilities({
disableAutoTracking: Boolean(optionalFeatures.disableAutoTracking),
useArgsProxy: managerAPI === '3.13' ? false : true,
passFactoryToCreate: managerAPI === '3.13',
};
}) as Capabilities;
}

export class CustomModifierDefinition<ModifierInstance> {
Expand Down Expand Up @@ -124,11 +125,6 @@ class InteractiveCustomModifierManager<ModifierInstance>
let { delegate, ModifierClass } = definition;
let capturedArgs = vmArgs.capture();

assert(
'Custom modifier managers must define their capabilities using the capabilities() helper function',
typeof delegate.capabilities === 'object' && delegate.capabilities !== null
);

let { useArgsProxy, passFactoryToCreate } = delegate.capabilities;

let args = useArgsProxy ? argsProxyFor(capturedArgs, 'modifier') : reifyArgs(capturedArgs);
Expand Down
56 changes: 51 additions & 5 deletions packages/@ember/-internals/glimmer/lib/utils/managers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Owner } from '@ember/-internals/owner';
import { deprecate } from '@ember/debug';
import { assert, deprecate } from '@ember/debug';
import { COMPONENT_MANAGER_STRING_LOOKUP } from '@ember/deprecated-features';
import { DEBUG } from '@glimmer/env';
import { _WeakSet } from '@glimmer/util';
import { ManagerDelegate as ComponentManagerDelegate } from '../component-managers/custom';
import InternalComponentManager from '../component-managers/internal';
import InternalComponentManager, { isInternalManager } from '../component-managers/internal';
import { HelperManager } from '../helpers/custom';
import { ModifierManagerDelegate } from '../modifiers/custom';

Expand All @@ -17,6 +19,8 @@ const COMPONENT_MANAGERS = new WeakMap<
ManagerFactory<ComponentManagerDelegate<unknown> | InternalComponentManager>
>();

const FROM_CAPABILITIES = DEBUG ? new _WeakSet() : undefined;

const MODIFIER_MANAGERS = new WeakMap<object, ManagerFactory<ModifierManagerDelegate<unknown>>>();

const HELPER_MANAGERS = new WeakMap<object, ManagerFactory<HelperManager<unknown>>>();
Expand Down Expand Up @@ -71,6 +75,7 @@ function getManagerInstanceForOwner<D extends ManagerDelegate>(

if (instance === undefined) {
instance = factory(owner);

managers.set(factory, instance!);
}

Expand All @@ -94,7 +99,15 @@ export function getModifierManager(
const factory = getManager(MODIFIER_MANAGERS, definition);

if (factory !== undefined) {
return getManagerInstanceForOwner(owner, factory);
let manager = getManagerInstanceForOwner(owner, factory);
assert(
`Custom modifier managers must have a \`capabilities\` property that is the result of calling the \`capabilities('3.13' | '3.22')\` (imported via \`import { capabilities } from '@ember/modifier';\`). Received: \`${JSON.stringify(
manager.capabilities
)}\` for: \`${manager}\``,
FROM_CAPABILITIES!.has(manager.capabilities)
);

return manager;
}

return undefined;
Expand All @@ -114,7 +127,16 @@ export function getHelperManager(
const factory = getManager(HELPER_MANAGERS, definition);

if (factory !== undefined) {
return getManagerInstanceForOwner(owner, factory);
let manager = getManagerInstanceForOwner(owner, factory);

assert(
`Custom helper managers must have a \`capabilities\` property that is the result of calling the \`capabilities('3.23')\` (imported via \`import { capabilities } from '@ember/helper';\`). Received: \`${JSON.stringify(
manager.capabilities
)}\` for: \`${manager}\``,
FROM_CAPABILITIES!.has(manager.capabilities)
);

return manager;
}

return undefined;
Expand Down Expand Up @@ -161,8 +183,32 @@ export function getComponentManager(
);

if (factory !== undefined) {
return getManagerInstanceForOwner(owner, factory);
let manager = getManagerInstanceForOwner(owner, factory);

assert(
`Custom component managers must have a \`capabilities\` property that is the result of calling the \`capabilities('3.4' | '3.13')\` (imported via \`import { capabilities } from '@ember/component';\`). Received: \`${JSON.stringify(
(manager as ComponentManagerDelegate<unknown>).capabilities
)}\` for: \`${manager}\``,
isInternalManager(manager) || FROM_CAPABILITIES!.has(manager.capabilities)
);

return manager;
}

return undefined;
}

declare const INTERNAL_CAPABILITIES: unique symbol;

export interface InternalCapabilities {
[INTERNAL_CAPABILITIES]: true;
}

export function buildCapabilities<T extends object>(capabilities: T): T & InternalCapabilities {
if (DEBUG) {
FROM_CAPABILITIES!.add(capabilities);
Object.freeze(capabilities);
}

return capabilities as T & InternalCapabilities;
}
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,62 @@ moduleFor(
this.assertHTML(`<p>hello max</p>`);
assert.verifySteps(['updateComponent', 'didUpdateComponent']);
}

'@test capabilities helper function must be used to generate capabilities'(assert) {
let ComponentClass = setComponentManager(
() => {
return EmberObject.create({
capabilities: {
asyncLifecycleCallbacks: true,
destructor: true,
update: false,
},

createComponent(factory, args) {
assert.step('createComponent');
return factory.create({ args });
},

updateComponent(component, args) {
assert.step('updateComponent');
set(component, 'args', args);
},

destroyComponent(component) {
assert.step('destroyComponent');
component.destroy();
},

getContext(component) {
assert.step('getContext');
return component;
},

didCreateComponent() {
assert.step('didCreateComponent');
},

didUpdateComponent() {
assert.step('didUpdateComponent');
},
});
},
EmberObject.extend({
greeting: 'hello',
})
);

this.registerComponent('foo-bar', {
template: `<p>{{greeting}} {{@name}}</p>`,
ComponentClass,
});

expectAssertion(() => {
this.render('{{foo-bar name=name}}', { name: 'world' });
}, /Custom component managers must have a `capabilities` property that is the result of calling the `capabilities\('3.4' \| '3.13'\)` \(imported via `import \{ capabilities \} from '@ember\/component';`\). /);

assert.verifySteps([]);
}
}
);

Expand Down Expand Up @@ -774,5 +830,61 @@ moduleFor(
runTask(() => this.context.set('value', 'bar'));
assert.verifySteps([]);
}

'@test capabilities helper function must be used to generate capabilities'(assert) {
let ComponentClass = setComponentManager(
() => {
return EmberObject.create({
capabilities: {
asyncLifecycleCallbacks: true,
destructor: true,
update: false,
},

createComponent(factory, args) {
assert.step('createComponent');
return factory.create({ args });
},

updateComponent(component, args) {
assert.step('updateComponent');
set(component, 'args', args);
},

destroyComponent(component) {
assert.step('destroyComponent');
component.destroy();
},

getContext(component) {
assert.step('getContext');
return component;
},

didCreateComponent() {
assert.step('didCreateComponent');
},

didUpdateComponent() {
assert.step('didUpdateComponent');
},
});
},
EmberObject.extend({
greeting: 'hello',
})
);

this.registerComponent('foo-bar', {
template: `<p>{{greeting}} {{@name}}</p>`,
ComponentClass,
});

expectAssertion(() => {
this.render('<FooBar @name={{name}} />', { name: 'world' });
}, /Custom component managers must have a `capabilities` property that is the result of calling the `capabilities\('3.4' \| '3.13'\)` \(imported via `import \{ capabilities \} from '@ember\/component';`\). /);

assert.verifySteps([]);
}
}
);
Loading

0 comments on commit 66fb044

Please sign in to comment.