Skip to content

Commit f8a90a1

Browse files
committed
Improve types for some array internals
1 parent 59637e7 commit f8a90a1

File tree

3 files changed

+30
-21
lines changed

3 files changed

+30
-21
lines changed

packages/@ember/-internals/metal/lib/array.ts

+13-10
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { addListener, removeListener } from './events';
44

55
const EMPTY_ARRAY = Object.freeze([]);
66

7-
interface ObservedObject<T> extends EmberArray<T> {
7+
type ObservedArray<T> = (T[] | EmberArray<T>) & ObservedObject;
8+
9+
interface ObservedObject {
810
_revalidate?: () => void;
911
}
1012

@@ -17,13 +19,14 @@ export function objectAt<T>(array: T[] | EmberArray<T>, index: number): T | unde
1719
}
1820

1921
export function replace<T>(
20-
array: NativeArray<T> | MutableArray<T>,
22+
array: T[] | MutableArray<T>,
2123
start: number,
2224
deleteCount: number,
23-
items: T[] = EMPTY_ARRAY as []
25+
items: T[] | EmberArray<T> = EMPTY_ARRAY as []
2426
): void {
2527
if (Array.isArray(array)) {
26-
replaceInNativeArray(array, start, deleteCount, items);
28+
let itemsArray = Array.isArray(items) ? items : items.toArray();
29+
replaceInNativeArray(array, start, deleteCount, itemsArray);
2730
} else {
2831
array.replace(start, deleteCount, items);
2932
}
@@ -61,18 +64,18 @@ interface ArrayObserverOptions {
6164
}
6265

6366
type Operation<T> = (
64-
obj: ObservedObject<T>,
67+
obj: ObservedArray<T>,
6568
eventName: string,
6669
target: object | Function | null,
6770
callbackName: string
6871
) => void;
6972

7073
function arrayObserversHelper<T>(
71-
obj: ObservedObject<T>,
74+
obj: ObservedArray<T>,
7275
target: object | Function | null,
7376
opts: ArrayObserverOptions,
7477
operation: Operation<T>
75-
): ObservedObject<T> {
78+
): ObservedArray<T> {
7679
let { willChange, didChange } = opts;
7780

7881
operation(obj, '@array:before', target, willChange);
@@ -91,14 +94,14 @@ export function addArrayObserver<T>(
9194
array: EmberArray<T>,
9295
target: object | Function | null,
9396
opts: ArrayObserverOptions
94-
): ObservedObject<T> {
97+
): ObservedArray<T> {
9598
return arrayObserversHelper(array, target, opts, addListener);
9699
}
97100

98101
export function removeArrayObserver<T>(
99-
array: EmberArray<T>,
102+
array: T[] | EmberArray<T>,
100103
target: object | Function | null,
101104
opts: ArrayObserverOptions
102-
): ObservedObject<T> {
105+
): ObservedArray<T> {
103106
return arrayObserversHelper(array, target, opts, removeListener);
104107
}

packages/@ember/-internals/runtime/lib/mixins/array.d.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ declare const EmberArray: Mixin;
7171
export default EmberArray;
7272

7373
interface MutableArray<T> extends EmberArray<T>, MutableEnumerable {
74-
replace(idx: number, amt: number, objects?: T[]): void;
74+
replace(idx: number, amt: number, objects?: T[] | EmberArray<T>): void;
7575
clear(): this;
7676
insertAt(idx: number, object: T): this;
7777
removeAt(start: number, len: number): this;
@@ -107,4 +107,4 @@ export function uniqBy<T>(
107107
array: T[] | EmberArray<T>,
108108
keyOrFunc: string | ((item: T) => unknown)
109109
): T[] | EmberArray<T>;
110-
export function isArray(obj: unknown): boolean;
110+
export function isArray(obj: unknown): obj is ArrayLike<unknown> | EmberArray<unknown>;

packages/@ember/-internals/runtime/lib/system/array_proxy.ts

+15-9
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from '@ember/-internals/metal';
1717
import { isObject } from '@ember/-internals/utils';
1818
import EmberObject from './object';
19-
import EmberArray, { isArray, MutableArray } from '../mixins/array';
19+
import EmberArray, { MutableArray } from '../mixins/array';
2020
import { assert } from '@ember/debug';
2121
import { setCustomTagFor } from '@glimmer/manager';
2222
import {
@@ -30,7 +30,7 @@ import {
3030
} from '@glimmer/validator';
3131
import { PropertyDidChange } from '@ember/-internals/metal/lib/property_events';
3232

33-
function isMutable<T>(obj: EmberArray<T>): obj is MutableArray<T> {
33+
function isMutable<T>(obj: T[] | EmberArray<T>): obj is T[] | MutableArray<T> {
3434
return Array.isArray(obj) || typeof (obj as MutableArray<T>).replace === 'function';
3535
}
3636

@@ -116,8 +116,8 @@ function customTagForArrayProxy(proxy: object, key: string) {
116116
@public
117117
*/
118118
// eslint-disable-next-line @typescript-eslint/no-empty-interface, @typescript-eslint/no-unused-vars
119-
interface ArrayProxy<T, C extends EmberArray<T> = EmberArray<T>> extends MutableArray<T> {}
120-
class ArrayProxy<T, C extends EmberArray<T> = EmberArray<T>>
119+
interface ArrayProxy<T, C extends EmberArray<T> | T[] = T[]> extends MutableArray<T> {}
120+
class ArrayProxy<T, C extends EmberArray<T> | T[] = T[]>
121121
extends EmberObject
122122
implements PropertyDidChange
123123
{
@@ -200,7 +200,7 @@ class ArrayProxy<T, C extends EmberArray<T> = EmberArray<T>>
200200

201201
// See additional docs for `replace` from `MutableArray`:
202202
// https://api.emberjs.com/ember/release/classes/MutableArray/methods/replace?anchor=replace
203-
replace(idx: number, amt: number, objects?: T[]) {
203+
replace(idx: number, amt: number, objects?: T[] | EmberArray<T>) {
204204
assert(
205205
'Mutating an arranged ArrayProxy is not allowed',
206206
get(this, 'arrangedContent') === get(this, 'content')
@@ -222,11 +222,11 @@ class ArrayProxy<T, C extends EmberArray<T> = EmberArray<T>>
222222
@return {void}
223223
@public
224224
*/
225-
replaceContent(idx: number, amt: number, objects?: T[]) {
225+
replaceContent(idx: number, amt: number, objects?: T[] | EmberArray<T>) {
226226
let content = get(this, 'content');
227227
assert('[BUG] Called objectAtContent without content', content);
228228
assert('Mutating a non-mutable array is not allowed', isMutable(content));
229-
content.replace(idx, amt, objects);
229+
replace<T>(content, idx, amt, objects);
230230
}
231231

232232
// Overriding objectAt is not supported.
@@ -312,8 +312,14 @@ class ArrayProxy<T, C extends EmberArray<T> = EmberArray<T>>
312312
// @ts-expect-error This check is still good for ensuring correctness
313313
assert("Can't set ArrayProxy's content to itself", arrangedContent !== this);
314314
assert(
315-
`ArrayProxy expects an Array or ArrayProxy, but you passed ${typeof arrangedContent}`,
316-
isArray(arrangedContent) || (arrangedContent as any).isDestroyed
315+
`ArrayProxy expects a native Array, EmberArray, or ArrayProxy, but you passed ${typeof arrangedContent}`,
316+
(function (arr: unknown): arr is EmberArray<unknown> {
317+
return Array.isArray(arr) || EmberArray.detect(arr);
318+
})(arrangedContent)
319+
);
320+
assert(
321+
'ArrayProxy expected its contents to not be destroyed',
322+
!(arrangedContent as any).isDestroyed
317323
);
318324

319325
addArrayObserver(arrangedContent, this, ARRAY_OBSERVER_MAPPING);

0 commit comments

Comments
 (0)