diff --git a/packages/@glimmer/destroyable/index.ts b/packages/@glimmer/destroyable/index.ts index 6c050178104..a92775d88c6 100644 --- a/packages/@glimmer/destroyable/index.ts +++ b/packages/@glimmer/destroyable/index.ts @@ -8,7 +8,7 @@ const DESTROYING_STATE = 1; const DESTROYED_STATE = 2; type DestroyableState = 0 | 1 | 2; -type OneOrMany = null | T | T[]; +type OneOrMany = null | T | BrandedArray; interface DestroyableMeta { source?: T; @@ -27,19 +27,28 @@ let DESTROYABLE_META: | Map> | WeakMap> = new WeakMap(); +const branded = Symbol('BrandedArray'); +type BrandedArray = T[] & { [branded]: true }; + +function isBrandedArray(collection: OneOrMany): collection is BrandedArray { + return Array.isArray(collection) && branded in collection; +} + function push(collection: OneOrMany, newItem: T): OneOrMany { if (collection === null) { return newItem; - } else if (Array.isArray(collection)) { + } else if (isBrandedArray(collection)) { collection.push(newItem); return collection; } else { - return [collection, newItem]; + const b = [collection, newItem] as BrandedArray; + b[branded] = true; + return b; } } function iterate(collection: OneOrMany, fn: (item: T) => void) { - if (Array.isArray(collection)) { + if (isBrandedArray(collection)) { collection.forEach(fn); } else if (collection !== null) { fn(collection); @@ -49,14 +58,14 @@ function iterate(collection: OneOrMany, fn: (item: T) => vo function remove(collection: OneOrMany, item: T, message: string | false) { if (DEBUG) { let collectionIsItem = collection === item; - let collectionContainsItem = Array.isArray(collection) && collection.indexOf(item) !== -1; + let collectionContainsItem = isBrandedArray(collection) && collection.indexOf(item) !== -1; if (!collectionIsItem && !collectionContainsItem) { throw new Error(String(message)); } } - if (Array.isArray(collection) && collection.length > 1) { + if (isBrandedArray(collection) && collection.length > 1) { let index = collection.indexOf(item); collection.splice(index, 1); return collection; diff --git a/packages/@glimmer/destroyable/test/destroyables-test.ts b/packages/@glimmer/destroyable/test/destroyables-test.ts index 3da761d58d5..3d0f75f4fc5 100644 --- a/packages/@glimmer/destroyable/test/destroyables-test.ts +++ b/packages/@glimmer/destroyable/test/destroyables-test.ts @@ -235,6 +235,16 @@ module('Destroyables', (hooks) => { assert.verifySteps(['parent'], 'parent destructor run'); }); + test('parent can be an array', (assert) => { + const parent: unknown[] = ['a']; + const child = {}; + associateDestroyableChild(parent, child); + registerDestructor(child, () => assert.step('child')); + destroy(child); + flush(); + assert.verifySteps(['child'], 'child destructor run'); + }); + test('children can have multiple parents, but only destroy once', (assert) => { const parent1 = {}; const parent2 = {};