Skip to content

Commit 3e12f7a

Browse files
authored
Merge pull request #3902 from cartant/issue-3717
chore(typings): add type guard overloads for first/last
2 parents 2c676a9 + c31e7a9 commit 3e12f7a

File tree

6 files changed

+61
-24
lines changed

6 files changed

+61
-24
lines changed

compat/operator/first.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import { Observable } from 'rxjs';
22
import { first as higherOrder } from 'rxjs/operators';
33

4-
export function first<T>(this: Observable<T>, predicate?: (value: T, index: number, source: Observable<T>) => boolean,
5-
defaultValue?: T): Observable<T>;
4+
/* tslint:disable:max-line-length */
5+
export function first<T>(this: Observable<T>, predicate?: null, defaultValue?: T): Observable<T>;
6+
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>;
7+
export function first<T>(this: Observable<T>, predicate: (value: T, index: number, source: Observable<T>) => boolean, defaultValue?: T): Observable<T>;
8+
/* tslint:enable:max-line-length */
9+
610
/**
711
* Emits only the first value (or the first value that meets some condition)
812
* emitted by the source Observable.
@@ -47,5 +51,5 @@ export function first<T>(this: Observable<T>, predicate?: (value: T, index: numb
4751
* @owner Observable
4852
*/
4953
export function first<T>(this: Observable<T>, ...args: any[]): Observable<T> {
50-
return higherOrder<T>(...args)(this);
51-
}
54+
return higherOrder<T>(...args)(this);
55+
}

compat/operator/last.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { Observable } from 'rxjs';
22
import { last as higherOrder } from 'rxjs/operators';
33

4-
export function last<T>(this: Observable<T>, predicate?: (value: T, index: number, source: Observable<T>) => boolean,
5-
defaultValue?: T): Observable<T>;
4+
/* tslint:disable:max-line-length */
5+
export function last<T>(this: Observable<T>, predicate?: null, defaultValue?: T): Observable<T>;
6+
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>;
7+
export function last<T>(this: Observable<T>, predicate: (value: T, index: number, source: Observable<T>) => boolean, defaultValue?: T): Observable<T>;
8+
/* tslint:enable:max-line-length */
69
/**
710
* Returns an Observable that emits only the last item emitted by the source Observable.
811
* It optionally takes a predicate function as a parameter, in which case, rather than emitting

spec/operators/first-spec.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,6 @@ describe('Observable.prototype.first', () => {
170170
expectSubscriptions(e1.subscriptions).toBe(sub);
171171
});
172172

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

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

186-
const isBar = (x: any): x is Bar => x && (<Bar>x).bar !== undefined;
187-
const isBaz = (x: any): x is Baz => x && (<Baz>x).baz !== undefined;
182+
const isBar = (x: any): x is Bar => x && (x as Bar).bar !== undefined;
183+
const isBaz = (x: any): x is Baz => x && (x as Baz).baz !== undefined;
188184

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

220+
// null predicate preserves the type
221+
xs.pipe(first(null)).subscribe(x => x); // x is still string | number
222+
223+
// undefined predicate preserves the type
224+
xs.pipe(first(undefined)).subscribe(x => x); // x is still string | number
225+
224226
// After the type guard `first` predicates, the type is narrowed to string
225227
xs.pipe(first(isString))
226228
.subscribe(s => s.length); // s is string
@@ -232,5 +234,4 @@ describe('Observable.prototype.first', () => {
232234

233235
// tslint:disable enable
234236
});
235-
*/
236237
});

spec/operators/last-spec.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,6 @@ describe('Observable.prototype.last', () => {
113113
expectSubscriptions(e1.subscriptions).toBe(e1subs);
114114
});
115115

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

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

129-
const isBar = (x: any): x is Bar => x && (<Bar>x).bar !== undefined;
130-
const isBaz = (x: any): x is Baz => x && (<Baz>x).baz !== undefined;
125+
const isBar = (x: any): x is Bar => x && (x as Bar).bar !== undefined;
126+
const isBaz = (x: any): x is Baz => x && (x as Baz).baz !== undefined;
131127

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

163+
// null predicate preserves the type
164+
xs.pipe(last(null)).subscribe(x => x); // x is still string | number
165+
166+
// undefined predicate preserves the type
167+
xs.pipe(last(undefined)).subscribe(x => x); // x is still string | number
168+
167169
// After the type guard `last` predicates, the type is narrowed to string
168170
xs.pipe(last(isString))
169171
.subscribe(s => s.length); // s is string
170-
xs.pipe(last(isString, s => s.substr(0))) // s is string in predicate)
171-
.subscribe(s => s.length); // s is string
172172

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

178178
// tslint:disable enable
179179
});
180-
*/
181180
});

src/internal/operators/first.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,28 @@ import { Observable } from '../Observable';
22
import { Operator } from '../Operator';
33
import { Subscriber } from '../Subscriber';
44
import { EmptyError } from '../util/EmptyError';
5-
import { MonoTypeOperatorFunction } from '../../internal/types';
5+
import { MonoTypeOperatorFunction, OperatorFunction } from '../../internal/types';
66
import { filter } from './filter';
77
import { take } from './take';
88
import { defaultIfEmpty } from './defaultIfEmpty';
99
import { throwIfEmpty } from './throwIfEmpty';
1010
import { identity } from '../util/identity';
1111

12+
/* tslint:disable:max-line-length */
13+
export function first<T>(
14+
predicate?: null,
15+
defaultValue?: T
16+
): MonoTypeOperatorFunction<T>;
17+
export function first<T, S extends T>(
18+
predicate: (value: T, index: number, source: Observable<T>) => value is S,
19+
defaultValue?: T
20+
): OperatorFunction<T, S>;
21+
export function first<T>(
22+
predicate: (value: T, index: number, source: Observable<T>) => boolean,
23+
defaultValue?: T
24+
): MonoTypeOperatorFunction<T>;
25+
/* tslint:enable:max-line-length */
26+
1227
/**
1328
* Emits only the first value (or the first value that meets some condition)
1429
* emitted by the source Observable.

src/internal/operators/last.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,28 @@ import { Observable } from '../Observable';
22
import { Operator } from '../Operator';
33
import { Subscriber } from '../Subscriber';
44
import { EmptyError } from '../util/EmptyError';
5-
import { MonoTypeOperatorFunction } from '../../internal/types';
5+
import { MonoTypeOperatorFunction, OperatorFunction } from '../../internal/types';
66
import { filter } from './filter';
77
import { takeLast } from './takeLast';
88
import { throwIfEmpty } from './throwIfEmpty';
99
import { defaultIfEmpty } from './defaultIfEmpty';
1010
import { identity } from '../util/identity';
1111

12+
/* tslint:disable:max-line-length */
13+
export function last<T>(
14+
predicate?: null,
15+
defaultValue?: T
16+
): MonoTypeOperatorFunction<T>;
17+
export function last<T, S extends T>(
18+
predicate: (value: T, index: number, source: Observable<T>) => value is S,
19+
defaultValue?: T
20+
): OperatorFunction<T, S>;
21+
export function last<T>(
22+
predicate: (value: T, index: number, source: Observable<T>) => boolean,
23+
defaultValue?: T
24+
): MonoTypeOperatorFunction<T>;
25+
/* tslint:enable:max-line-length */
26+
1227
/**
1328
* Returns an Observable that emits only the last item emitted by the source Observable.
1429
* It optionally takes a predicate function as a parameter, in which case, rather than emitting

0 commit comments

Comments
 (0)