From 872b0ec9f851616b8d5f12fec0d2b1d6d7d8509d Mon Sep 17 00:00:00 2001 From: Nicholas Jamieson Date: Fri, 27 Jul 2018 06:27:10 +1000 Subject: [PATCH] fix(pipe): replace rest parameters overload (#3945) * chore(test): add dtslint files and script * chore(dtslint): add zip operator * chore(dtslint): add zip observable * chore(dtslint): rename directory * fix(pipe): replace rest parameters overload Replace the rest parameters overload with a signature that also includes the A-I type parameters. Closes #3841 * test(first): fix problem exposed by pipe fix * test(reduce): fix problem exposed by pipe fix * test(scan): fix problem exposed by pipe fix * test(startWith): fix problem exposed by pipe fix * test(zipAll): fix problem exposed by pipe fix * chore(dtslint): move files * chore(dtslint): add generic custom operator test * chore(pipe): remove redundant type parameter * chore(pipe): remove rest params type param * chore(dtslint): add pipe rest params test --- spec-dtslint/Observable-spec.ts | 103 +++++++++++++++++++++++++++++++ spec-dtslint/util/pipe-spec.ts | 77 +++++++++++++++++++++++ spec/operators/first-spec.ts | 2 +- spec/operators/reduce-spec.ts | 8 +-- spec/operators/scan-spec.ts | 2 +- spec/operators/startWith-spec.ts | 2 +- spec/operators/zipAll-spec.ts | 12 ++-- src/internal/Observable.ts | 4 +- 8 files changed, 195 insertions(+), 15 deletions(-) create mode 100644 spec-dtslint/Observable-spec.ts create mode 100644 spec-dtslint/util/pipe-spec.ts diff --git a/spec-dtslint/Observable-spec.ts b/spec-dtslint/Observable-spec.ts new file mode 100644 index 0000000000..054d0d19a1 --- /dev/null +++ b/spec-dtslint/Observable-spec.ts @@ -0,0 +1,103 @@ +import { Observable, of, OperatorFunction } from 'rxjs'; +import { mapTo } from 'rxjs/operators'; + +function a(input: I, output: O): OperatorFunction; +function a(output: O): OperatorFunction; +function a(inputOrOutput: I | O, output?: O): OperatorFunction { + return mapTo(output === undefined ? inputOrOutput as O : output); +} + +describe('pipe', () => { + it('should infer for no arguments', () => { + const o = of('foo').pipe(); // $ExpectType Observable + }); + + it('should infer for 1 argument', () => { + const o = of('foo').pipe(a('1')); // $ExpectType Observable<"1"> + }); + + it('should infer for 2 arguments', () => { + const o = of('foo').pipe(a('1'), a('2')); // $ExpectType Observable<"2"> + }); + + it('should infer for 3 arguments', () => { + const o = of('foo').pipe(a('1'), a('2'), a('3')); // $ExpectType Observable<"3"> + }); + + it('should infer for 4 arguments', () => { + const o = of('foo').pipe(a('1'), a('2'), a('3'), a('4')); // $ExpectType Observable<"4"> + }); + + it('should infer for 5 arguments', () => { + const o = of('foo').pipe(a('1'), a('2'), a('3'), a('4'), a('5')); // $ExpectType Observable<"5"> + }); + + it('should infer for 6 arguments', () => { + const o = of('foo').pipe(a('1'), a('2'), a('3'), a('4'), a('5'), a('6')); // $ExpectType Observable<"6"> + }); + + it('should infer for 7 arguments', () => { + const o = of('foo').pipe(a('1'), a('2'), a('3'), a('4'), a('5'), a('6'), a('7')); // $ExpectType Observable<"7"> + }); + + it('should infer for 8 arguments', () => { + const o = of('foo').pipe(a('1'), a('2'), a('3'), a('4'), a('5'), a('6'), a('7'), a('8')); // $ExpectType Observable<"8"> + }); + + it('should infer for 9 arguments', () => { + const o = of('foo').pipe(a('1'), a('2'), a('3'), a('4'), a('5'), a('6'), a('7'), a('8'), a('9')); // $ExpectType Observable<"9"> + }); + + it('should infer {} for more than 9 arguments', () => { + const o = of('foo').pipe(a('1'), a('2'), a('3'), a('4'), a('5'), a('6'), a('7'), a('8'), a('9'), a('10')); // $ExpectType Observable<{}> + }); + + it('should require a type assertion for more than 9 arguments', () => { + const o: Observable<'10'> = of('foo').pipe(a('1'), a('2'), a('3'), a('4'), a('5'), a('6'), a('7'), a('8'), a('9'), a('10')); // $ExpectError + }); + + it('should enforce types for the 1st argument', () => { + const o = of('foo').pipe(a('#', '1')); // $ExpectError + }); + + it('should enforce types for the 2nd argument', () => { + const o = of('foo').pipe(a('1'), a('#', '2')); // $ExpectError + }); + + it('should enforce types for the 3rd argument', () => { + const o = of('foo').pipe(a('1'), a('2'), a('#', '3')); // $ExpectError + }); + + it('should enforce types for the 4th argument', () => { + const o = of('foo').pipe(a('1'), a('2'), a('3'), a('#', '4')); // $ExpectError + }); + + it('should enforce types for the 5th argument', () => { + const o = of('foo').pipe(a('1'), a('2'), a('3'), a('4'), a('#', '5')); // $ExpectError + }); + + it('should enforce types for the 6th argument', () => { + const o = of('foo').pipe(a('1'), a('2'), a('3'), a('4'), a('5'), a('#', '6')); // $ExpectError + }); + + it('should enforce types for the 7th argument', () => { + const o = of('foo').pipe(a('1'), a('2'), a('3'), a('4'), a('5'), a('6'), a('#', '7')); // $ExpectError + }); + + it('should enforce types for the 8th argument', () => { + const o = of('foo').pipe(a('1'), a('2'), a('3'), a('4'), a('5'), a('6'), a('7'), a('#', '8')); // $ExpectError + }); + + it('should enforce types for the 9th argument', () => { + const o = of('foo').pipe(a('1'), a('2'), a('3'), a('4'), a('5'), a('6'), a('7'), a('8'), a('#', '9')); // $ExpectError + }); + + it('should not enforce types beyond the 9th argument', () => { + const o = of('foo').pipe(a('1'), a('2'), a('3'), a('4'), a('5'), a('6'), a('7'), a('8'), a('9'), a('#', '10')); // $ExpectType Observable<{}> + }); + + it('should support operators that return generics', () => { + const customOperator = () => (a: Observable) => a; + const o = of('foo').pipe(customOperator()); // $ExpectType Observable + }); +}); diff --git a/spec-dtslint/util/pipe-spec.ts b/spec-dtslint/util/pipe-spec.ts new file mode 100644 index 0000000000..2bb21cdead --- /dev/null +++ b/spec-dtslint/util/pipe-spec.ts @@ -0,0 +1,77 @@ +import { pipe, UnaryFunction } from 'rxjs'; + +function a(input: I, output: O): UnaryFunction { + return i => output; +} + +it('should infer {} for no arguments', () => { + const o = pipe(); // $ExpectType UnaryFunction<{}, {}> +}); + +it('should infer for 1 argument', () => { + const o = pipe(a('0', '1')); // $ExpectType UnaryFunction<"0", "1"> +}); + +it('should infer for 2 arguments', () => { + const o = pipe(a('0', '1'), a('1', '2')); // $ExpectType UnaryFunction<"0", "2"> +}); + +it('should infer for 3 arguments', () => { + const o = pipe(a('0', '1'), a('1', '2'), a('2', '3')); // $ExpectType UnaryFunction<"0", "3"> +}); + +it('should infer for 4 arguments', () => { + const o = pipe(a('0', '1'), a('1', '2'), a('2', '3'), a('3', '4')); // $ExpectType UnaryFunction<"0", "4"> +}); + +it('should infer for 5 arguments', () => { + const o = pipe(a('0', '1'), a('1', '2'), a('2', '3'), a('3', '4'), a('4', '5')); // $ExpectType UnaryFunction<"0", "5"> +}); + +it('should infer for 6 arguments', () => { + const o = pipe(a('0', '1'), a('1', '2'), a('2', '3'), a('3', '4'), a('4', '5'), a('5', '6')); // $ExpectType UnaryFunction<"0", "6"> +}); + +it('should infer for 7 arguments', () => { + const o = pipe(a('0', '1'), a('1', '2'), a('2', '3'), a('3', '4'), a('4', '5'), a('5', '6'), a('6', '7')); // $ExpectType UnaryFunction<"0", "7"> +}); + +it('should infer for 8 arguments', () => { + const o = pipe(a('0', '1'), a('1', '2'), a('2', '3'), a('3', '4'), a('4', '5'), a('5', '6'), a('6', '7'), a('7', '8')); // $ExpectType UnaryFunction<"0", "8"> +}); + +it('should infer for 9 arguments', () => { + const o = pipe(a('0', '1'), a('1', '2'), a('2', '3'), a('3', '4'), a('4', '5'), a('5', '6'), a('6', '7'), a('7', '8'), a('8', '9')); // $ExpectType UnaryFunction<"0", "9"> +}); + +it('should enforce types for the 2nd argument', () => { + const o = pipe(a('0', '1'), a('#', '2')); // $ExpectError +}); + +it('should enforce types for the 3rd argument', () => { + const o = pipe(a('0', '1'), a('1', '2'), a('#', '3')); // $ExpectError +}); + +it('should enforce types for the 4th argument', () => { + const o = pipe(a('0', '1'), a('1', '2'), a('2', '3'), a('#', '4')); // $ExpectError +}); + +it('should enforce types for the 5th argument', () => { + const o = pipe(a('0', '1'), a('1', '2'), a('2', '3'), a('3', '4'), a('#', '5')); // $ExpectError +}); + +it('should enforce types for the 6th argument', () => { + const o = pipe(a('0', '1'), a('1', '2'), a('2', '3'), a('3', '4'), a('4', '5'), a('#', '6')); // $ExpectError +}); + +it('should enforce types for the 7th argument', () => { + const o = pipe(a('0', '1'), a('1', '2'), a('2', '3'), a('3', '4'), a('4', '5'), a('5', '6'), a('#', '7')); // $ExpectError +}); + +it('should enforce types for the 8th argument', () => { + const o = pipe(a('0', '1'), a('1', '2'), a('2', '3'), a('3', '4'), a('4', '5'), a('5', '6'), a('6', '7'), a('#', '8')); // $ExpectError +}); + +it('should enforce types for the 9th argument', () => { + const o = pipe(a('0', '1'), a('1', '2'), a('2', '3'), a('3', '4'), a('4', '5'), a('5', '6'), a('6', '7'), a('7', '8'), a('#', '9')); // $ExpectError +}); diff --git a/spec/operators/first-spec.ts b/spec/operators/first-spec.ts index 07c84c5575..11e58d7226 100644 --- a/spec/operators/first-spec.ts +++ b/spec/operators/first-spec.ts @@ -132,7 +132,7 @@ describe('Observable.prototype.first', () => { const e1 = hot('--a-^--b--c--a--c--|'); const expected = '---------------(d|)'; const sub = '^ !'; - expectObservable(e1.pipe(first(x => x === 's', 'd'))).toBe(expected); + expectObservable(e1.pipe(first(x => x === 's', 'd'))).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(sub); }); diff --git a/spec/operators/reduce-spec.ts b/spec/operators/reduce-spec.ts index 1ca33b12f4..ddff7cb57c 100644 --- a/spec/operators/reduce-spec.ts +++ b/spec/operators/reduce-spec.ts @@ -193,7 +193,7 @@ describe('reduce operator', () => { throw 'error'; }; - expectObservable(e1.pipe(reduce(reduceFunction, seed))).toBe(expected); + expectObservable(e1.pipe(reduce(reduceFunction, seed))).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(e1subs); }); @@ -292,7 +292,7 @@ describe('reduce operator', () => { type('should accept array typed reducers', () => { let a: Observable<{ a: number; b: string }>; - a.pipe(reduce((acc, value) => acc.concat(value), [])); + a.pipe(reduce<{ a: number; b: string }>((acc, value) => acc.concat(value), [])); }); type('should accept T typed reducers', () => { @@ -322,7 +322,7 @@ describe('reduce operator', () => { type('should accept R typed reduces when R is an array of T', () => { let a: Observable; - const reduced = a.pipe(reduce((acc, value) => { + const reduced = a.pipe(reduce((acc, value) => { acc.push(value); return acc; }, [])); @@ -380,7 +380,7 @@ describe('reduce operator', () => { type('should accept array of R typed reducers and reduce to array of R', () => { let a: Observable; - const reduced = a.pipe(reduce((acc, cur) => { + const reduced = a.pipe(reduce((acc, cur) => { console.log(acc); acc.push(cur.toString()); return acc; diff --git a/spec/operators/scan-spec.ts b/spec/operators/scan-spec.ts index b8513998f6..363f9f54ad 100644 --- a/spec/operators/scan-spec.ts +++ b/spec/operators/scan-spec.ts @@ -230,7 +230,7 @@ describe('scan operator', () => { type('should accept array typed reducers', () => { let a: Observable<{ a: number; b: string }>; - a.pipe(reduce((acc, value) => acc.concat(value), [])); + a.pipe(reduce<{ a: number; b: string }>((acc, value) => acc.concat(value), [])); }); type('should accept T typed reducers', () => { diff --git a/spec/operators/startWith-spec.ts b/spec/operators/startWith-spec.ts index 64a1b81559..3bae19a257 100644 --- a/spec/operators/startWith-spec.ts +++ b/spec/operators/startWith-spec.ts @@ -136,7 +136,7 @@ describe('startWith operator', () => { const e1subs = '^ !'; const expected = '-a-|'; - expectObservable(e1.pipe(startWith(rxTestScheduler))).toBe(expected); + expectObservable(e1.pipe(startWith(rxTestScheduler))).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(e1subs); }); diff --git a/spec/operators/zipAll-spec.ts b/spec/operators/zipAll-spec.ts index 0b0f32b16e..cc35fe6f3c 100644 --- a/spec/operators/zipAll-spec.ts +++ b/spec/operators/zipAll-spec.ts @@ -109,7 +109,7 @@ describe('zipAll operator', () => { z: ['d', 3] }; - expectObservable(of(e1, myIterator).pipe(zipAll())).toBe(expected, values); + expectObservable(of(e1, myIterator).pipe(zipAll())).toBe(expected, values); expectSubscriptions(e1.subscriptions).toBe(e1subs); }); @@ -160,7 +160,7 @@ describe('zipAll operator', () => { const b = [1]; const expected = '|'; - expectObservable(of(a, b).pipe(zipAll())).toBe(expected); + expectObservable(of(a, b).pipe(zipAll())).toBe(expected); expectSubscriptions(a.subscriptions).toBe(asubs); }); @@ -180,7 +180,7 @@ describe('zipAll operator', () => { const b = [1]; const expected = '-'; - expectObservable(of(a, b).pipe(zipAll())).toBe(expected); + expectObservable(of(a, b).pipe(zipAll())).toBe(expected); expectSubscriptions(a.subscriptions).toBe(asubs); }); @@ -190,7 +190,7 @@ describe('zipAll operator', () => { const b = [2]; const expected = '-----(x|)'; - expectObservable(of(a, b).pipe(zipAll())).toBe(expected, { x: ['1', 2] }); + expectObservable(of(a, b).pipe(zipAll())).toBe(expected, { x: ['1', 2] }); expectSubscriptions(a.subscriptions).toBe(asubs); }); @@ -210,7 +210,7 @@ describe('zipAll operator', () => { const b = [1]; const expected = '-----#'; - expectObservable(of(a, b).pipe(zipAll())).toBe(expected); + expectObservable(of(a, b).pipe(zipAll())).toBe(expected); expectSubscriptions(a.subscriptions).toBe(asubs); }); @@ -220,7 +220,7 @@ describe('zipAll operator', () => { const b = [4, 5, 6]; const expected = '---x--y--(z|)'; - expectObservable(of(a, b).pipe(zipAll())).toBe(expected, + expectObservable(of(a, b).pipe(zipAll())).toBe(expected, { x: ['1', 4], y: ['2', 5], z: ['3', 6] }); expectSubscriptions(a.subscriptions).toBe(asubs); }); diff --git a/src/internal/Observable.ts b/src/internal/Observable.ts index a60bcf8391..9f00589303 100644 --- a/src/internal/Observable.ts +++ b/src/internal/Observable.ts @@ -296,7 +296,7 @@ export class Observable implements Subscribable { pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction, op4: OperatorFunction, op5: OperatorFunction, op6: OperatorFunction, op7: OperatorFunction): Observable; pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction, op4: OperatorFunction, op5: OperatorFunction, op6: OperatorFunction, op7: OperatorFunction, op8: OperatorFunction): Observable; pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction, op4: OperatorFunction, op5: OperatorFunction, op6: OperatorFunction, op7: OperatorFunction, op8: OperatorFunction, op9: OperatorFunction): Observable; - pipe(...operations: OperatorFunction[]): Observable; + pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction, op4: OperatorFunction, op5: OperatorFunction, op6: OperatorFunction, op7: OperatorFunction, op8: OperatorFunction, op9: OperatorFunction, ...operations: OperatorFunction[]): Observable<{}>; /* tslint:enable:max-line-length */ /** @@ -318,7 +318,7 @@ export class Observable implements Subscribable { * .subscribe(x => console.log(x)) * ``` */ - pipe(...operations: OperatorFunction[]): Observable { + pipe(...operations: OperatorFunction[]): Observable { if (operations.length === 0) { return this as any; }