Skip to content

Commit

Permalink
Merge pull request #3902 from cartant/issue-3717
Browse files Browse the repository at this point in the history
chore(typings): add type guard overloads for first/last
  • Loading branch information
benlesh authored Jul 13, 2018
2 parents 2c676a9 + c31e7a9 commit 3e12f7a
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 24 deletions.
12 changes: 8 additions & 4 deletions compat/operator/first.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Observable } from 'rxjs';
import { first as higherOrder } from 'rxjs/operators';

export function first<T>(this: Observable<T>, predicate?: (value: T, index: number, source: Observable<T>) => boolean,
defaultValue?: T): Observable<T>;
/* tslint:disable:max-line-length */
export function first<T>(this: Observable<T>, predicate?: null, defaultValue?: T): Observable<T>;
export function first<T, S extends T>(this: Observable<T>, predicate: (value: T, index: number, source: Observable<T>) => value is S, defaultValue?: T): Observable<S>;
export function first<T>(this: Observable<T>, predicate: (value: T, index: number, source: Observable<T>) => boolean, defaultValue?: T): Observable<T>;
/* tslint:enable:max-line-length */

/**
* Emits only the first value (or the first value that meets some condition)
* emitted by the source Observable.
Expand Down Expand Up @@ -47,5 +51,5 @@ export function first<T>(this: Observable<T>, predicate?: (value: T, index: numb
* @owner Observable
*/
export function first<T>(this: Observable<T>, ...args: any[]): Observable<T> {
return higherOrder<T>(...args)(this);
}
return higherOrder<T>(...args)(this);
}
7 changes: 5 additions & 2 deletions compat/operator/last.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Observable } from 'rxjs';
import { last as higherOrder } from 'rxjs/operators';

export function last<T>(this: Observable<T>, predicate?: (value: T, index: number, source: Observable<T>) => boolean,
defaultValue?: T): Observable<T>;
/* tslint:disable:max-line-length */
export function last<T>(this: Observable<T>, predicate?: null, defaultValue?: T): Observable<T>;
export function last<T, S extends T>(this: Observable<T>, predicate: (value: T, index: number, source: Observable<T>) => value is S, defaultValue?: T): Observable<S>;
export function last<T>(this: Observable<T>, predicate: (value: T, index: number, source: Observable<T>) => boolean, defaultValue?: T): Observable<T>;
/* tslint:enable:max-line-length */
/**
* Returns an Observable that emits only the last item emitted by the source Observable.
* It optionally takes a predicate function as a parameter, in which case, rather than emitting
Expand Down
15 changes: 8 additions & 7 deletions spec/operators/first-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,6 @@ describe('Observable.prototype.first', () => {
expectSubscriptions(e1.subscriptions).toBe(sub);
});

// The current signature for first suggests that this test is not rquired. In
// fact, with type checking enabled, it will fail. See:
// https://github.com/ReactiveX/rxjs/issues/3717
/*
it('should support type guards without breaking previous behavior', () => {
// tslint:disable no-unused-variable

Expand All @@ -183,8 +179,8 @@ describe('Observable.prototype.first', () => {
interface Baz { baz?: number; }
class Foo implements Bar, Baz { constructor(public bar: string = 'name', public baz: number = 42) {} }

const isBar = (x: any): x is Bar => x && (<Bar>x).bar !== undefined;
const isBaz = (x: any): x is Baz => x && (<Baz>x).baz !== undefined;
const isBar = (x: any): x is Bar => x && (x as Bar).bar !== undefined;
const isBaz = (x: any): x is Baz => x && (x as Baz).baz !== undefined;

const foo: Foo = new Foo();
Observable.of(foo).pipe(first())
Expand Down Expand Up @@ -221,6 +217,12 @@ describe('Observable.prototype.first', () => {
// missing predicate preserves the type
xs.pipe(first()).subscribe(x => x); // x is still string | number

// null predicate preserves the type
xs.pipe(first(null)).subscribe(x => x); // x is still string | number

// undefined predicate preserves the type
xs.pipe(first(undefined)).subscribe(x => x); // x is still string | number

// After the type guard `first` predicates, the type is narrowed to string
xs.pipe(first(isString))
.subscribe(s => s.length); // s is string
Expand All @@ -232,5 +234,4 @@ describe('Observable.prototype.first', () => {

// tslint:disable enable
});
*/
});
17 changes: 8 additions & 9 deletions spec/operators/last-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,6 @@ describe('Observable.prototype.last', () => {
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});

// The current signature for last suggests that this test is not required. In
// fact, with type checking enabled, it will fail. See:
// https://github.com/ReactiveX/rxjs/issues/3717
/*
it('should support type guards without breaking previous behavior', () => {
// tslint:disable no-unused-variable

Expand All @@ -126,8 +122,8 @@ describe('Observable.prototype.last', () => {
interface Baz { baz?: number; }
class Foo implements Bar, Baz { constructor(public bar: string = 'name', public baz: number = 42) {} }

const isBar = (x: any): x is Bar => x && (<Bar>x).bar !== undefined;
const isBaz = (x: any): x is Baz => x && (<Baz>x).baz !== undefined;
const isBar = (x: any): x is Bar => x && (x as Bar).bar !== undefined;
const isBaz = (x: any): x is Baz => x && (x as Baz).baz !== undefined;

const foo: Foo = new Foo();
of(foo).pipe(last())
Expand Down Expand Up @@ -164,11 +160,15 @@ describe('Observable.prototype.last', () => {
// missing predicate preserves the type
xs.pipe(last()).subscribe(x => x); // x is still string | number

// null predicate preserves the type
xs.pipe(last(null)).subscribe(x => x); // x is still string | number

// undefined predicate preserves the type
xs.pipe(last(undefined)).subscribe(x => x); // x is still string | number

// After the type guard `last` predicates, the type is narrowed to string
xs.pipe(last(isString))
.subscribe(s => s.length); // s is string
xs.pipe(last(isString, s => s.substr(0))) // s is string in predicate)
.subscribe(s => s.length); // s is string

// boolean predicates preserve the type
xs.pipe(last(x => typeof x === 'string'))
Expand All @@ -177,5 +177,4 @@ describe('Observable.prototype.last', () => {

// tslint:disable enable
});
*/
});
17 changes: 16 additions & 1 deletion src/internal/operators/first.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,28 @@ import { Observable } from '../Observable';
import { Operator } from '../Operator';
import { Subscriber } from '../Subscriber';
import { EmptyError } from '../util/EmptyError';
import { MonoTypeOperatorFunction } from '../../internal/types';
import { MonoTypeOperatorFunction, OperatorFunction } from '../../internal/types';
import { filter } from './filter';
import { take } from './take';
import { defaultIfEmpty } from './defaultIfEmpty';
import { throwIfEmpty } from './throwIfEmpty';
import { identity } from '../util/identity';

/* tslint:disable:max-line-length */
export function first<T>(
predicate?: null,
defaultValue?: T
): MonoTypeOperatorFunction<T>;
export function first<T, S extends T>(
predicate: (value: T, index: number, source: Observable<T>) => value is S,
defaultValue?: T
): OperatorFunction<T, S>;
export function first<T>(
predicate: (value: T, index: number, source: Observable<T>) => boolean,
defaultValue?: T
): MonoTypeOperatorFunction<T>;
/* tslint:enable:max-line-length */

/**
* Emits only the first value (or the first value that meets some condition)
* emitted by the source Observable.
Expand Down
17 changes: 16 additions & 1 deletion src/internal/operators/last.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,28 @@ import { Observable } from '../Observable';
import { Operator } from '../Operator';
import { Subscriber } from '../Subscriber';
import { EmptyError } from '../util/EmptyError';
import { MonoTypeOperatorFunction } from '../../internal/types';
import { MonoTypeOperatorFunction, OperatorFunction } from '../../internal/types';
import { filter } from './filter';
import { takeLast } from './takeLast';
import { throwIfEmpty } from './throwIfEmpty';
import { defaultIfEmpty } from './defaultIfEmpty';
import { identity } from '../util/identity';

/* tslint:disable:max-line-length */
export function last<T>(
predicate?: null,
defaultValue?: T
): MonoTypeOperatorFunction<T>;
export function last<T, S extends T>(
predicate: (value: T, index: number, source: Observable<T>) => value is S,
defaultValue?: T
): OperatorFunction<T, S>;
export function last<T>(
predicate: (value: T, index: number, source: Observable<T>) => boolean,
defaultValue?: T
): MonoTypeOperatorFunction<T>;
/* tslint:enable:max-line-length */

/**
* Returns an Observable that emits only the last item emitted by the source Observable.
* It optionally takes a predicate function as a parameter, in which case, rather than emitting
Expand Down

0 comments on commit 3e12f7a

Please sign in to comment.