Skip to content

Commit

Permalink
feat: add array extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
rob893 committed Aug 31, 2022
1 parent 130202b commit e12161a
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 3 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ npm i typescript-extended-linq
You can optionally bind the Linq functions to native types (arrays, maps, sets, strings, etc).
Binding to native types adds the functions to the type's prototype. Always be mindful when modifying a native type's prototype. While these functions will not affect native functionality, it cannot be guaranteed that it will not affect other frameworks if they also modify prototypes.

Add the following at the start of your program to bind to native types (the eslint disable is only needed if you use that eslint rule):
Add the following at the start of your program to bind to native types (the eslint disable is only needed if you use that eslint rule)
Note, if adding the declarations to its own d.ts file, be sure to import IEnumerable and the Extension interfaces for IDEs to work right:

```typescript
/* eslint-disable @typescript-eslint/no-empty-interface */
import { bindLinqToNativeTypes } from 'typescript-extended-linq';

declare global {
interface Array<T> extends Omit<IEnumerable<T>, 'forEach' | 'toString' | 'toJSON' | symbol> {}
interface Array<T> extends Omit<IEnumerable<T>, 'forEach' | 'toString' | 'toJSON' | symbol>, IArrayExtensions<T> {}
interface Int8Array extends Omit<IEnumerable<number>, 'forEach' | 'toString' | 'toJSON' | symbol> {}
interface Int16Array extends Omit<IEnumerable<number>, 'forEach' | 'toString' | 'toJSON' | symbol> {}
interface Int32Array extends Omit<IEnumerable<number>, 'forEach' | 'toString' | 'toJSON' | symbol> {}
Expand Down
23 changes: 23 additions & 0 deletions src/__tests__/bindLinqToNativeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,27 @@ describe('bindLinqToNativeTypes', () => {
expect(() => bindLinqToNativeTypes({ types: collection })).toThrow();
}
);

it.each(['remove', 'clear', 'removeAll', 'insert', 'insertRange'])(
'should bind all array extensions to array prototype',
name => {
bindLinqToNativeTypes();

const arr: any = [1, 2, 3];

expect(typeof arr[name]).toBe('function');
}
);

it('should bind remove array extension to array prototype', () => {
bindLinqToNativeTypes();

const arr: any = [1, 2, 3];

const res = arr.remove(2);

expect(typeof arr['remove']).toBe('function');
expect(res).toBe(true);
expect(arr).toEqual([1, 3]);
});
});
56 changes: 56 additions & 0 deletions src/extensions/ArrayExtensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Important for first argument to be the array and for all functions to be static.
export class ArrayExtensions {
public static clear<TSource>(arr: TSource[]): void {
arr.length = 0;
}

public static insert<TSource>(arr: TSource[], index: number, item: TSource): void {
if (index < 0 || index >= arr.length) {
throw new Error('Index out of bounds.');
}

arr.splice(index, 0, item);
}

public static insertRange<TSource>(arr: TSource[], index: number, collection: Iterable<TSource>): void {
if (index < 0 || index >= arr.length) {
throw new Error('Index out of bounds.');
}

let i = index;

for (const item of collection) {
ArrayExtensions.insert(arr, i, item);
i++;
}
}

public static remove<TSource>(arr: TSource[], item: TSource): boolean {
const indexOfItem = arr.indexOf(item);

if (indexOfItem >= 0) {
arr.splice(indexOfItem, 1);
return true;
}

return false;
}

public static removeAll<TSource>(arr: TSource[], predicate: (item: TSource, index: number) => boolean): number {
let removed = 0;
const itemIndexesToRemove = [];

for (let i = 0; i < arr.length; i++) {
if (predicate(arr[i], i)) {
itemIndexesToRemove.push(i);
}
}

for (let i = itemIndexesToRemove.length - 1; i >= 0; i--) {
arr.splice(itemIndexesToRemove[i], 1);
removed++;
}

return removed;
}
}
13 changes: 12 additions & 1 deletion src/functions/bindLinqToNativeType.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BasicEnumerable } from '../enumerables/BasicEnumerable';
import { ArrayExtensions } from '../extensions/ArrayExtensions';
import { from } from './from';

export function bindLinqToNativeTypes(options?: {
Expand Down Expand Up @@ -47,10 +48,20 @@ export function bindLinqToNativeTypes(options?: {
for (const proto of protos) {
for (const prop of enumPropNames) {
if (proto[prop] === undefined) {
proto[prop] = function (...params: any[]) {
proto[prop] = function (...params: unknown[]) {
return (from(this) as any)[prop](...params);
};
}
}
}

const arrayExtensionPropNames = Object.getOwnPropertyNames(ArrayExtensions);

for (const prop of arrayExtensionPropNames) {
if ((Array as any)[prop] === undefined && (Array.prototype as any)[prop] === undefined) {
(Array.prototype as any)[prop] = function (...params: unknown[]) {
return (ArrayExtensions as any)[prop](this, ...params);
};
}
}
}
62 changes: 62 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,68 @@ export interface IEnumerableFactory {
createList<TSource>(generator: () => Generator<TSource>): IList<TSource>;
}

export interface IArrayExtensions<TSource> {
/**
* Removes all items from the array. Original array is mutated.
* @example
* ```typescript
* const arr = [1, 2, 3];
* arr.clear(); // arr is now []
* ```
*/
clear(): void;

/**
* Inserts an item at the given index into the array. Existing items at and after the index will be pushed back.
* No items will be deleted. Original array is mutated.
* @example
* ```typescript
* const arr = [1, 2, 3];
* arr.insert(1, 5); // arr is now [1, 5, 2, 3]
* ```
* @param index The index of which to insert the item.
* @param item The item to insert.
*/
insert(index: number, item: TSource): void;

/**
* Inserts an item at the given index into the array. Existing items at and after the index will be pushed back.
* No items will be deleted. Original array is mutated.
* * @example
* ```typescript
* const arr = [1, 2, 3];
* arr.insertRange(1, [5, 6]); // arr is now [1, 5, 6, 2, 3]
* ```
* @param index The index of which to start the insertion.
* @param collection The items to insert.
*/
insertRange(index: number, collection: Iterable<TSource>): void;

/**
* Removes an item from the array. Original array is mutated.
* @example
* ```typescript
* const numbers = [1, 2, 3];
* const res = numbers.remove(2); // numbers is modified in place. res is true because element was in the array.
* ```
* @param item Item to remove from array.
* @returns true if item was found and removed from array. False if item was not found in the array.
*/
remove(item: TSource): boolean;

/**
* Removes all items from the array that pass the predicate. Original array is mutated.
* @example
* ```typescript
* const arr = [1, 2, 3, 4];
* arr.removeAll(x => x > 2); // arr is now [1, 2]
* ```
* @param predicate The predicate to test for a condition.
* @returns The number of removed items.
*/
removeAll(predicate: (item: TSource, index: number) => boolean): number;
}

/**
* Interface that exposes an iterator, which supports a simple iteration and various methods.
* @typeparam TSource The type of elements in the IEnumerable.
Expand Down

0 comments on commit e12161a

Please sign in to comment.