Skip to content

Commit

Permalink
Merge pull request #56 from ReactiveX/user-defined-typeguards
Browse files Browse the repository at this point in the history
feat(operators): support user defined type guards in boolean predicates
  • Loading branch information
trxcllnt authored Oct 5, 2017
2 parents 653c142 + ef8764a commit 5b58b78
Show file tree
Hide file tree
Showing 35 changed files with 143 additions and 51 deletions.
20 changes: 20 additions & 0 deletions spec/asynciterable-operators/filter-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,26 @@ test('AsyncIterable#filter with index', async t => {
t.end();
});

test('AsyncIterable#filter with typeguard', async t => {
const xs = of<any>(
new String('8'), 5,
new String('7'), 4,
new String('6'), 9,
new String('2'), 1,
new String('0')
);
const ys = filter(xs, (x): x is string => x instanceof String);

const it = ys[Symbol.asyncIterator]();
await hasNext(t, it, new String('8'));
await hasNext(t, it, new String('7'));
await hasNext(t, it, new String('6'));
await hasNext(t, it, new String('2'));
await hasNext(t, it, new String('0'));
await noNext(t, it);
t.end();
});

test('AsyncIterable#filter throws part way through', async t => {
const xs = of(8, 5, 7, 4, 6, 9, 2, 1, 0);
const err = new Error();
Expand Down
20 changes: 20 additions & 0 deletions spec/iterable-operators/filter-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ test('Iterable#filter with index', t => {
t.end();
});

test('Iterable#filter with typeguard', t => {
const xs = [
new String('8'), 5,
new String('7'), 4,
new String('6'), 9,
new String('2'), 1,
new String('0')
];
const ys = filter(xs, (x): x is string => x instanceof String);

const it = ys[Symbol.iterator]();
hasNext(t, it, new String('8'));
hasNext(t, it, new String('7'));
hasNext(t, it, new String('6'));
hasNext(t, it, new String('2'));
hasNext(t, it, new String('0'));
noNext(t, it);
t.end();
});

test('Iterable#filter throws part way through', t => {
const xs = [8, 5, 7, 4, 6, 9, 2, 1, 0];
const err = new Error();
Expand Down
2 changes: 1 addition & 1 deletion spec/tape-async.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
declare module "tape-async" {
declare module 'tape-async' {
import * as tape from 'tape';
export = tape;
}
3 changes: 2 additions & 1 deletion src/add/asynciterable-operators/every.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { AsyncIterableX } from '../../asynciterable';
import { every } from '../../asynciterable/every';
import { booleanAsyncPredicate } from '../../internal/predicates';

/**
* @ignore
*/
export function everyProto<T>(
this: AsyncIterableX<T>,
comparer: (value: T, index: number) => boolean | Promise<boolean>): Promise<boolean> {
comparer: booleanAsyncPredicate<T>): Promise<boolean> {
return every<T>(this, comparer);
}

Expand Down
3 changes: 2 additions & 1 deletion src/add/asynciterable-operators/filter.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { AsyncIterableX } from '../../asynciterable';
import { filter } from '../../asynciterable/filter';
import { booleanAsyncPredicate } from '../../internal/predicates';

/**
* @ignore
*/
export function filterProto<TSource>(
this: AsyncIterable<TSource>,
predicate: (value: TSource, index: number) => Promise<boolean> | boolean,
predicate: booleanAsyncPredicate<TSource>,
thisArg?: any): AsyncIterableX<TSource> {
return filter<TSource>(this, predicate, thisArg);
}
Expand Down
3 changes: 2 additions & 1 deletion src/add/asynciterable-operators/find.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { AsyncIterableX } from '../../asynciterable';
import { find } from '../../asynciterable/find';
import { booleanAsyncPredicate } from '../../internal/predicates';

/**
* @ignore
*/
export function findProto<T>(
this: AsyncIterableX<T>,
predicate: (value: T, index: number) => boolean | Promise<boolean>,
predicate: booleanAsyncPredicate<T>,
thisArg?: any): Promise<T | undefined> {
return find(this, predicate, thisArg);
}
Expand Down
3 changes: 2 additions & 1 deletion src/add/asynciterable-operators/first.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { AsyncIterableX } from '../../asynciterable';
import { first } from '../../asynciterable/first';
import { booleanAsyncPredicate } from '../../internal/predicates';

/**
* @ignore
*/
export function firstProto<T>(
this: AsyncIterableX<T>,
fn?: (value: T) => boolean | Promise<boolean>): Promise<T | undefined> {
fn?: booleanAsyncPredicate<T>): Promise<T | undefined> {
return first(this, fn);
}

Expand Down
3 changes: 2 additions & 1 deletion src/add/asynciterable-operators/last.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { AsyncIterableX } from '../../asynciterable';
import { last } from '../../asynciterable/last';
import { booleanAsyncPredicate } from '../../internal/predicates';

/**
* @ignore
*/
export function lastProto<T>(
this: AsyncIterableX<T>,
selector: (value: T) => boolean | Promise<boolean> = async () => true): Promise<T | undefined> {
selector?: booleanAsyncPredicate<T>): Promise<T | undefined> {
return last(this, selector);
}

Expand Down
3 changes: 2 additions & 1 deletion src/add/asynciterable-operators/partition.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { AsyncIterableX } from '../../asynciterable';
import { partition } from '../../asynciterable/partition';
import { booleanAsyncPredicate } from '../../internal/predicates';

/**
* @ignore
*/
export function partitionProto<T>(
this: AsyncIterableX<T>,
fn: (value: T, index: number) => boolean,
fn: booleanAsyncPredicate<T>,
thisArg?: any): AsyncIterableX<T>[] {
return partition<T>(this, fn, thisArg);
}
Expand Down
3 changes: 2 additions & 1 deletion src/add/asynciterable-operators/single.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { AsyncIterableX } from '../../asynciterable';
import { single } from '../../asynciterable/single';
import { booleanAsyncPredicate } from '../../internal/predicates';

/**
* @ignore
*/
export function singleProto<T>(
this: AsyncIterableX<T>,
selector: (value: T) => boolean | Promise<boolean> = async () => true): Promise<T | undefined> {
selector?: booleanAsyncPredicate<T>): Promise<T | undefined> {
return single(this, selector);
}

Expand Down
3 changes: 2 additions & 1 deletion src/add/asynciterable-operators/skipwhile.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { AsyncIterableX } from '../../asynciterable';
import { skipWhile } from '../../asynciterable/skipwhile';
import { booleanAsyncPredicate } from '../../internal/predicates';

/**
* @ignore
*/
export function skipWhileProto<TSource>(
this: AsyncIterableX<TSource>,
predicate: (value: TSource, index: number) => boolean | Promise<boolean>): AsyncIterableX<TSource> {
predicate: booleanAsyncPredicate<TSource>): AsyncIterableX<TSource> {
return skipWhile(this, predicate);
}

Expand Down
3 changes: 2 additions & 1 deletion src/add/asynciterable-operators/some.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { AsyncIterableX } from '../../asynciterable';
import { some } from '../../asynciterable/some';
import { booleanAsyncPredicate } from '../../internal/predicates';

/**
* @ignore
*/
export function someProto<T>(
this: AsyncIterableX<T>,
comparer: (value: T, index: number) => boolean | Promise<boolean>): Promise<boolean> {
comparer: booleanAsyncPredicate<T>): Promise<boolean> {
return some(this, comparer);
}

Expand Down
3 changes: 2 additions & 1 deletion src/add/asynciterable-operators/takewhile.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { AsyncIterableX } from '../../asynciterable';
import { takeWhile } from '../../asynciterable/takewhile';
import { booleanAsyncPredicate } from '../../internal/predicates';

/**
* @ignore
*/
export function takeWhileProto<TSource>(
this: AsyncIterableX<TSource>,
predicate: (value: TSource, index: number) => boolean | Promise<boolean>): AsyncIterableX<TSource> {
predicate: booleanAsyncPredicate<TSource>): AsyncIterableX<TSource> {
return takeWhile(this, predicate);
}

Expand Down
4 changes: 3 additions & 1 deletion src/asynciterable/every.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { booleanAsyncPredicate } from '../internal/predicates';

export async function every<T>(
source: AsyncIterable<T>,
comparer: (value: T, index: number) => boolean | Promise<boolean>): Promise<boolean> {
comparer: booleanAsyncPredicate<T>): Promise<boolean> {
let i = 0;
for await (let item of source) {
if (!await comparer(item, i++)) { return false; }
Expand Down
7 changes: 4 additions & 3 deletions src/asynciterable/filter.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { AsyncIterableX } from '../asynciterable';
import { bindCallback } from '../internal/bindcallback';
import { booleanAsyncPredicate } from '../internal/predicates';

class FilterAsyncIterable<TSource> extends AsyncIterableX<TSource> {
private _source: Iterable<TSource | PromiseLike<TSource>> | AsyncIterable<TSource>;
private _predicate: (value: TSource, index: number) => Promise<boolean> | boolean;
private _predicate: booleanAsyncPredicate<TSource>;

constructor(
source: Iterable<TSource | PromiseLike<TSource>> | AsyncIterable<TSource>,
predicate: (value: TSource, index: number) => Promise<boolean> | boolean) {
predicate: booleanAsyncPredicate<TSource>) {
super();
this._source = source;
this._predicate = predicate;
Expand All @@ -25,7 +26,7 @@ class FilterAsyncIterable<TSource> extends AsyncIterableX<TSource> {

export function filter<TSource>(
source: Iterable<TSource | PromiseLike<TSource>> | AsyncIterable<TSource>,
predicate: (value: TSource, index: number) => Promise<boolean> | boolean,
predicate: booleanAsyncPredicate<TSource>,
thisArg?: any): AsyncIterableX<TSource> {
return new FilterAsyncIterable<TSource>(source, bindCallback(predicate, thisArg, 2));
}
3 changes: 2 additions & 1 deletion src/asynciterable/find.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { bindCallback } from '../internal/bindcallback';
import { booleanAsyncPredicate } from '../internal/predicates';

export async function find<T>(
source: AsyncIterable<T>,
predicate: (value: T, index: number) => boolean | Promise<boolean>,
predicate: booleanAsyncPredicate<T>,
thisArg?: any): Promise<T | undefined> {
const fn = bindCallback(predicate, thisArg, 2);
let i = 0;
Expand Down
7 changes: 5 additions & 2 deletions src/asynciterable/first.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { booleanAsyncPredicate } from '../internal/predicates';

export async function first<T>(
source: AsyncIterable<T>,
predicate: (value: T) => boolean | Promise<boolean> = async () => true): Promise<T | undefined> {
predicate: booleanAsyncPredicate<T> = async () => true): Promise<T | undefined> {
let i = 0;
for await (let item of source) {
if (await predicate(item)) {
if (await predicate(item, i++)) {
return item;
}
}
Expand Down
8 changes: 5 additions & 3 deletions src/asynciterable/last.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { booleanAsyncPredicate } from '../internal/predicates';

export async function last<T>(
source: AsyncIterable<T>,
fn: (value: T) => boolean | Promise<boolean> = async () => true): Promise<T | undefined> {
let result: T | undefined;
fn: booleanAsyncPredicate<T> = async () => true): Promise<T | undefined> {
let i = 0, result: T | undefined;
for await (let item of source) {
if (await fn(item)) {
if (await fn(item, i++)) {
result = item;
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/asynciterable/partition.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { AsyncIterableX } from '../asynciterable';
import { filter } from './filter';
import { booleanAsyncPredicate } from '../internal/predicates';

export function partition<TSource>(
source: AsyncIterable<TSource>,
predicate: (value: TSource, index: number) => boolean | Promise<boolean>,
predicate: booleanAsyncPredicate<TSource>,
thisArg?: any): AsyncIterableX<TSource>[] {
return [
filter(source, predicate, thisArg),
Expand Down
9 changes: 6 additions & 3 deletions src/asynciterable/single.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { booleanAsyncPredicate } from '../internal/predicates';

export async function single<T>(
source: AsyncIterable<T>,
selector: (value: T) => boolean | Promise<boolean> = () => true): Promise<T | undefined> {
selector: booleanAsyncPredicate<T> = () => true): Promise<T | undefined> {
let result: T | undefined;
let hasResult = false;
let i = 0;
for await (let item of source) {
if (hasResult && await selector(item)) {
if (hasResult && await selector(item, i++)) {
throw new Error('More than one element was found');
}
if (await selector(item)) {
if (await selector(item, i++)) {
result = item;
hasResult = true;
}
Expand Down
7 changes: 4 additions & 3 deletions src/asynciterable/skipwhile.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { AsyncIterableX } from '../asynciterable';
import { booleanAsyncPredicate } from '../internal/predicates';

class SkipWhileAsyncIterable<TSource> extends AsyncIterableX<TSource> {
private _source: AsyncIterable<TSource>;
private _predicate: (value: TSource, index: number) => boolean | Promise<boolean>;
private _predicate: booleanAsyncPredicate<TSource>;

constructor(
source: AsyncIterable<TSource>,
predicate: (value: TSource, index: number) => boolean | Promise<boolean>) {
predicate: booleanAsyncPredicate<TSource>) {
super();
this._source = source;
this._predicate = predicate;
Expand All @@ -23,6 +24,6 @@ class SkipWhileAsyncIterable<TSource> extends AsyncIterableX<TSource> {

export function skipWhile<TSource>(
source: AsyncIterable<TSource>,
predicate: (value: TSource, index: number) => boolean | Promise<boolean>): AsyncIterableX<TSource> {
predicate: booleanAsyncPredicate<TSource>): AsyncIterableX<TSource> {
return new SkipWhileAsyncIterable<TSource>(source, predicate);
}
4 changes: 3 additions & 1 deletion src/asynciterable/some.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { booleanAsyncPredicate } from '../internal/predicates';

export async function some<T>(
source: AsyncIterable<T>,
comparer: (value: T, index: number) => boolean | Promise<boolean>): Promise<boolean> {
comparer: booleanAsyncPredicate<T>): Promise<boolean> {
let i = 0;
for await (let item of source) {
if (await comparer(item, i++)) { return true; }
Expand Down
7 changes: 4 additions & 3 deletions src/asynciterable/takewhile.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { AsyncIterableX } from '../asynciterable';
import { booleanAsyncPredicate } from '../internal/predicates';

class TakeWhileAsyncIterable<TSource> extends AsyncIterableX<TSource> {
private _source: AsyncIterable<TSource>;
private _predicate: (value: TSource, index: number) => boolean | Promise<boolean>;
private _predicate: booleanAsyncPredicate<TSource>;

constructor(
source: AsyncIterable<TSource>,
predicate: (value: TSource, index: number) => boolean | Promise<boolean>) {
predicate: booleanAsyncPredicate<TSource>) {
super();
this._source = source;
this._predicate = predicate;
Expand All @@ -23,6 +24,6 @@ class TakeWhileAsyncIterable<TSource> extends AsyncIterableX<TSource> {

export function takeWhile<TSource>(
source: AsyncIterable<TSource>,
predicate: (value: TSource, index: number) => boolean| Promise<boolean>): AsyncIterableX<TSource> {
predicate: booleanAsyncPredicate<TSource>): AsyncIterableX<TSource> {
return new TakeWhileAsyncIterable<TSource>(source, predicate);
}
8 changes: 8 additions & 0 deletions src/internal/predicates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type booleanPredicate<T> =
{ (value: T, index: number): boolean } |
{ (value: T, index: number): value is T };

export type booleanAsyncPredicate<T> =
{ (value: T, index: number): boolean } |
{ (value: T, index: number): value is T } |
{ (value: T, index: number): Promise<boolean> } ;
3 changes: 2 additions & 1 deletion src/iterable/every.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { booleanPredicate } from '../internal/predicates';
/**
* Determines whether every element of a sequence satisfy a condition.
* @param {Iterable<T>} source Source sequence.
Expand All @@ -7,7 +8,7 @@
*/
export function every<T>(
source: Iterable<T>,
comparer: (value: T, index: number) => boolean): boolean {
comparer: booleanPredicate<T>): boolean {
let i = 0;
for (let item of source) {
if (!comparer(item, i++)) { return false; }
Expand Down
Loading

0 comments on commit 5b58b78

Please sign in to comment.