Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Predicate types #5842

Merged
merged 5 commits into from
Nov 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions api_guard/dist/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ export interface ErrorObserver<T> {

export declare type FactoryOrValue<T> = T | (() => T);

export declare type Falsy = null | undefined | false | 0 | -0 | 0n | '';

export declare function firstValueFrom<T>(source: Observable<T>): Promise<T>;

export declare function forkJoin(sources: []): Observable<never>;
Expand Down Expand Up @@ -371,9 +373,7 @@ export declare function of<T, T2, T3, T4, T5, T6, T7, T8, T9>(a: T, b: T2, c: T3
export declare function of(): Observable<never>;
export declare function of<T>(): Observable<T>;
export declare function of<T>(value: T): Observable<T>;
export declare function of<T, U>(value1: T, value2: U): Observable<T | U>;
export declare function of<T, U, V>(value1: T, value2: U, value3: V): Observable<T | U | V>;
export declare function of<A extends Array<any>>(...args: A): Observable<ValueFromArray<A>>;
export declare function of<A extends readonly unknown[]>(...args: A): Observable<ValueFromArray<A>>;

export declare function onErrorResumeNext(): Observable<never>;
export declare function onErrorResumeNext<O extends ObservableInput<any>>(arrayOfSources: O[]): Observable<ObservedValueOf<O>>;
Expand Down Expand Up @@ -533,6 +533,8 @@ export interface TimestampProvider {
now(): number;
}

export declare type TruthyTypesOf<T> = T extends Falsy ? never : T;

export interface UnaryFunction<T, R> {
(source: T): R;
}
Expand All @@ -549,7 +551,7 @@ export declare const UnsubscriptionError: UnsubscriptionErrorCtor;

export declare function using<T>(resourceFactory: () => Unsubscribable | void, observableFactory: (resource: Unsubscribable | void) => ObservableInput<T> | void): Observable<T>;

export declare type ValueFromArray<A> = A extends Array<infer T> ? T : never;
export declare type ValueFromArray<A extends readonly unknown[]> = A extends Array<infer T> ? T : never;

export declare type ValueFromNotification<T> = T extends {
kind: 'N' | 'E' | 'C';
Expand Down
25 changes: 24 additions & 1 deletion api_guard/dist/types/operators/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export declare function endWith<T, A, B, C, D, E>(v1: A, v2: B, v3: C, v4: D, v5
export declare function endWith<T, A, B, C, D, E, F>(v1: A, v2: B, v3: C, v4: D, v5: E, v6: F, scheduler: SchedulerLike): OperatorFunction<T, T | A | B | C | D | E | F>;
export declare function endWith<T, A extends any[] = T[]>(...args: A): OperatorFunction<T, T | ValueFromArray<A>>;

export declare function every<T>(predicate: BooleanConstructor, thisArg?: any): OperatorFunction<T, Exclude<T, Falsy> extends never ? false : boolean>;
export declare function every<T>(predicate: (value: T, index: number, source: Observable<T>) => boolean, thisArg?: any): OperatorFunction<T, boolean>;

export declare function exhaust<T>(): OperatorFunction<ObservableInput<T>, T>;
Expand All @@ -108,19 +109,28 @@ export declare function exhaustMap<T, I, R>(project: (value: T, index: number) =
export declare function expand<T, R>(project: (value: T, index: number) => ObservableInput<R>, concurrent?: number, scheduler?: SchedulerLike): OperatorFunction<T, R>;
export declare function expand<T, R>(project: (value: T, index: number) => ObservableInput<R>, concurrent: number | undefined, scheduler: SchedulerLike): OperatorFunction<T, R>;

export declare function filter<T>(predicate: (value: T, index: number) => false, thisArg?: any): OperatorFunction<T, never>;
export declare function filter<T, S extends T>(predicate: (value: T, index: number) => value is S, thisArg?: any): OperatorFunction<T, S>;
export declare function filter<T>(predicate: BooleanConstructor): OperatorFunction<T, T extends null | undefined | false | 0 | -0 | 0n | '' ? never : T>;
export declare function filter<T>(predicate: BooleanConstructor): OperatorFunction<T, TruthyTypesOf<T>>;
export declare function filter<T>(predicate: (value: T, index: number) => boolean, thisArg?: any): MonoTypeOperatorFunction<T>;

export declare function finalize<T>(callback: () => void): MonoTypeOperatorFunction<T>;

export declare function find<T>(predicate: BooleanConstructor): OperatorFunction<T, TruthyTypesOf<T>>;
export declare function find<T, S extends T>(predicate: (value: T, index: number, source: Observable<T>) => value is S, thisArg?: any): OperatorFunction<T, S | undefined>;
export declare function find<T>(predicate: (value: T, index: number, source: Observable<T>) => boolean, thisArg?: any): OperatorFunction<T, T | undefined>;

export declare function findIndex<T>(predicate: (value: T, index: number, source: Observable<T>) => false, thisArg?: any): OperatorFunction<T, -1>;
export declare function findIndex<T>(predicate: BooleanConstructor, thisArg?: any): OperatorFunction<T, T extends Falsy ? -1 : number>;
export declare function findIndex<T>(predicate: (value: T, index: number, source: Observable<T>) => boolean, thisArg?: any): OperatorFunction<T, number>;

export declare function first<T, D = T>(predicate?: null, defaultValue?: D): OperatorFunction<T, T | D>;
export declare function first<T>(predicate: BooleanConstructor): OperatorFunction<T, TruthyTypesOf<T>>;
export declare function first<T, D>(predicate: BooleanConstructor, defaultValue: D): OperatorFunction<T, TruthyTypesOf<T> | D>;
export declare function first<T, S extends T>(predicate: (value: T, index: number, source: Observable<T>) => value is S, defaultValue?: S): OperatorFunction<T, S>;
export declare function first<T, S extends T, D>(predicate: (value: T, index: number, source: Observable<T>) => value is S, defaultValue: D): OperatorFunction<T, S | D>;
export declare function first<T, D>(predicate: (value: T, index: number, source: Observable<T>) => false, defaultValue: D): OperatorFunction<T, D>;
export declare function first<T>(predicate: (value: T, index: number, source: Observable<T>) => false): OperatorFunction<T, never>;
export declare function first<T, D = T>(predicate: (value: T, index: number, source: Observable<T>) => boolean, defaultValue?: D): OperatorFunction<T, T | D>;

export declare const flatMap: typeof mergeMap;
Expand All @@ -135,8 +145,12 @@ export declare function ignoreElements(): OperatorFunction<any, never>;

export declare function isEmpty<T>(): OperatorFunction<T, boolean>;

export declare function last<T>(predicate: BooleanConstructor): OperatorFunction<T, TruthyTypesOf<T>>;
export declare function last<T, D>(predicate: BooleanConstructor, defaultValue: D): OperatorFunction<T, TruthyTypesOf<T> | D>;
export declare function last<T, D = T>(predicate?: null, defaultValue?: D): OperatorFunction<T, T | D>;
export declare function last<T, S extends T>(predicate: (value: T, index: number, source: Observable<T>) => value is S, defaultValue?: S): OperatorFunction<T, S>;
export declare function last<T, D>(predicate: (value: T, index: number, source: Observable<T>) => false, defaultValue: D): OperatorFunction<T, D>;
export declare function last<T>(predicate: (value: T, index: number, source: Observable<T>) => false): OperatorFunction<T, never>;
export declare function last<T, D = T>(predicate: (value: T, index: number, source: Observable<T>) => boolean, defaultValue?: D): OperatorFunction<T, T | D>;

export declare function map<T, R>(project: (value: T, index: number) => R, thisArg?: any): OperatorFunction<T, R>;
Expand Down Expand Up @@ -240,6 +254,8 @@ export declare function share<T>(): MonoTypeOperatorFunction<T>;
export declare function shareReplay<T>(config: ShareReplayConfig): MonoTypeOperatorFunction<T>;
export declare function shareReplay<T>(bufferSize?: number, windowTime?: number, scheduler?: SchedulerLike): MonoTypeOperatorFunction<T>;

export declare function single<T>(predicate: BooleanConstructor): OperatorFunction<T, TruthyTypesOf<T>>;
export declare function single<T>(predicate: (value: T, index: number, source: Observable<T>) => false): OperatorFunction<T, never>;
export declare function single<T>(predicate?: (value: T, index: number, source: Observable<T>) => boolean): MonoTypeOperatorFunction<T>;

export declare function skip<T>(count: number): MonoTypeOperatorFunction<T>;
Expand All @@ -248,6 +264,8 @@ export declare function skipLast<T>(skipCount: number): MonoTypeOperatorFunction

export declare function skipUntil<T>(notifier: Observable<any>): MonoTypeOperatorFunction<T>;

export declare function skipWhile<T>(predicate: BooleanConstructor): OperatorFunction<T, Extract<T, Falsy> extends never ? never : T>;
export declare function skipWhile<T>(predicate: (value: T, index: number) => true): OperatorFunction<T, never>;
export declare function skipWhile<T>(predicate: (value: T, index: number) => boolean): MonoTypeOperatorFunction<T>;

export declare function startWith<T>(scheduler: SchedulerLike): MonoTypeOperatorFunction<T>;
Expand Down Expand Up @@ -279,6 +297,11 @@ export declare function takeLast<T>(count: number): MonoTypeOperatorFunction<T>;

export declare function takeUntil<T>(notifier: ObservableInput<any>): MonoTypeOperatorFunction<T>;

export declare function takeWhile<T>(predicate: (value: T, index: number) => false, inclusive: true): MonoTypeOperatorFunction<T>;
export declare function takeWhile<T>(predicate: (value: T, index: number) => false, inclusive?: false): OperatorFunction<T, never>;
export declare function takeWhile<T>(predicate: BooleanConstructor): OperatorFunction<T, Exclude<T, Falsy> extends never ? never : T>;
export declare function takeWhile<T>(predicate: BooleanConstructor, inclusive: false): OperatorFunction<T, Exclude<T, Falsy> extends never ? never : T>;
export declare function takeWhile<T>(predicate: BooleanConstructor, inclusive: true): MonoTypeOperatorFunction<T>;
export declare function takeWhile<T, S extends T>(predicate: (value: T, index: number) => value is S): OperatorFunction<T, S>;
export declare function takeWhile<T, S extends T>(predicate: (value: T, index: number) => value is S, inclusive: false): OperatorFunction<T, S>;
export declare function takeWhile<T>(predicate: (value: T, index: number) => boolean, inclusive?: boolean): MonoTypeOperatorFunction<T>;
Expand Down
2 changes: 1 addition & 1 deletion spec-dtslint/observables/of-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,4 @@ it('should deprecate correctly', () => {
of(a, b); // $ExpectNoDeprecation
of(a, b, c); // $ExpectNoDeprecation
of(a, b, c, d); // $ExpectNoDeprecation
});
});
8 changes: 8 additions & 0 deletions spec-dtslint/operators/every-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,11 @@ it('should enforce index type of number', () => {
it('should expect function parameter', () => {
const a = of(1, 2, 3).pipe(every(9)); // $ExpectError
});

it('should handle the Boolean constructor', () => {
const a = of(0 as const, '' as const, false as const, null, undefined, -0 as const, 0n as const).pipe(every(Boolean)); // $ExpectType Observable<false>
const b = of(0 as const, '' as const, 'hi there' as const, false as const, null, undefined, -0 as const, 0n as const).pipe(every(Boolean)); // $ExpectType Observable<boolean>
const c = of('test' as const, true as const, 1 as const, [], {}).pipe(every(Boolean)); // $ExpectType Observable<boolean>
const d = of(NaN, NaN, NaN).pipe(every(Boolean)); // $ExpectType Observable<boolean>
const e = of(0, 1, 0).pipe(every(Boolean)); // $ExpectType Observable<boolean>
})
6 changes: 6 additions & 0 deletions spec-dtslint/operators/filter-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ it('should support Boolean as a predicate', () => {
const u = of(0 as const, -0 as const).pipe(filter(Boolean)); // $ExpectType Observable<never>
const v = of('' as const, "foo" as const, "bar" as const).pipe(filter(Boolean)); // $ExpectType Observable<"foo" | "bar">
const w = of('' as const).pipe(filter(Boolean)); // $ExpectType Observable<never>
// Intentionally weird looking test... `false` is `boolean`, which is `true | false`.
const x = of(false, false, false, false).pipe(filter(Boolean)); // $ExpectType Observable<true>
});

it('should narrow on always-false predicates', () => {
const o = of(1, 2, 3).pipe(filter(() => false)); // $ExpectType Observable<never>
});

// I've not been able to effect a failing dtslint test for this situation and a
Expand Down
9 changes: 9 additions & 0 deletions spec-dtslint/operators/find-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,12 @@ it('should support a predicate that takes an index', () => {
it('should support a predicate that takes an index and the source', () => {
const o = of('foo').pipe(find((s, index, source) => true)); // $ExpectType Observable<string | undefined>
});

it('should support Boolean properly', () => {
const o1 = of('' as const).pipe(find(Boolean)); // $ExpectType Observable<never>
const o2 = of('' as const, 'hi' as const).pipe(find(Boolean)); // $ExpectType Observable<"hi">
const o3 = of('' as const, 0 as const, 'test' as const, 'what' as const).pipe(find(Boolean)); // $ExpectType Observable<"test" | "what">
const o5 = of(false as const, null, undefined, '' as const, 0 as const, 0 as const).pipe(find(Boolean)); // $ExpectType Observable<never>
// Intentionally weird looking: Because `Observable<boolean>` is `Observable<true | false>` and `true` is the truthy bit.
const o4 = of(false, false, false, false).pipe(find(Boolean)); // $ExpectType Observable<true>
});
9 changes: 9 additions & 0 deletions spec-dtslint/operators/findIndex-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,12 @@ it('should enforce predicate types', () => {
it('should enforce predicate return type', () => {
const o = of('foo', 'bar', 'baz').pipe(findIndex(p => p)); // $ExpectError
});

it('should support Boolean constructor', () => {
const a = of(0 as const, -0 as const, null, undefined, false as const, '' as const).pipe(findIndex(Boolean)); // $ExpectType Observable<-1>
const b = of(0 as const, -0 as const, null, 'hi there' as const, undefined, false as const, '' as const).pipe(findIndex(Boolean)); // $ExpectType Observable<number>
});

it('should properly narrow an always false predicate', () => {
const a = of('foo', 'bar', 'baz').pipe(findIndex(() => false)); // $ExpectType Observable<-1>
})
12 changes: 8 additions & 4 deletions spec-dtslint/operators/first-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ it('should support a user-defined type guard with an S default', () => {
});

it('should widen a user-defined type guard with a non-S default', () => {
const o = of('foo').pipe(first(isFooBar, false)); // $ExpectType Observable<string | boolean>
const o = of('foo').pipe(first(isFooBar, false)); // $ExpectType Observable<boolean | "foo" | "bar">
});

it('should support a predicate with no default', () => {
Expand All @@ -59,6 +59,10 @@ it('should support a predicate with a non-T default', () => {
const o = of('foo').pipe(first(x => !!x, false)); // $ExpectType Observable<string | boolean>
});

it('should default D to T with a predicate', () => {
const o = of('foo').pipe(first<string>(x => !!x)); // $Observable<string>
});
it('should work properly with the Boolean constructor', () => {
const o1 = of('' as const).pipe(first(Boolean)); // $ExpectType Observable<never>
const o2 = of('', 'hi').pipe(first(Boolean)); // $ExpectType Observable<string>
const o3 = of('' as const, 'hi' as const).pipe(first(Boolean)); // $ExpectType Observable<"hi">
const o4 = of(0 as const, 'hi' as const).pipe(first(Boolean)); // $ExpectType Observable<"hi">
const o5 = of(0 as const, 'hi' as const, 'what' as const).pipe(first(Boolean)); // $ExpectType Observable<"hi" | "what">
})
14 changes: 13 additions & 1 deletion spec-dtslint/operators/last-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,17 @@ it('should support a predicate with a non-T default', () => {
});

it('should default D to T with a predicate', () => {
const o = of('foo').pipe(last<string>(x => !!x)); // $Observable<string>
const o = of('foo').pipe(last<string>(x => !!x)); // $ExpectType Observable<string>
});

it('should handle predicates that always return false properly', () => {
const a = of('foo', 'bar').pipe(last(() => false as const)); // $ExpectType Observable<never>
const b = of('foo', 'bar').pipe(last(() => false as const, 1337 as const)); // $ExpectType Observable<1337>
});

it('should handle Boolean constructor properly', () => {
const a = of(0 as const, -0 as const, null, undefined, false as const, '' as const, 0n as const).pipe(last(Boolean)); // $ExpectType Observable<never>
const b = of(0 as const, -0 as const, null, undefined, false as const, '' as const, 0n as const).pipe(last(Boolean, 'test' as const)); // $ExpectType Observable<"test">
const c = of(0 as const, -0 as const, null, 'hi' as const, undefined, false as const, '' as const, 0n as const).pipe(last(Boolean)); // $ExpectType Observable<"hi">
const d = of(0 as const, -0 as const, null, 'hi' as const, undefined, false as const, '' as const, 0n as const).pipe(last(Boolean, 'test' as const)); // $ExpectType Observable<"test" | "hi">
})
9 changes: 9 additions & 0 deletions spec-dtslint/operators/single-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,12 @@ it('should enforce index type', () => {
it('should enforce source type', () => {
const o = of('foo').pipe(single(((value, index, source: Observable<number>) => value === 'foo'))); // $ExpectError
});

it('should handle Boolean constructor properly', () => {
const a = of(null, undefined, 0 as const, -0 as const, 0n as const, '' as const).pipe(single(Boolean)); // $ExpectType Observable<never>
const b = of(null, undefined, 0 as const, 'test' as const, -0 as const, 0n as const, '' as const).pipe(single(Boolean)); // $ExpectType Observable<"test">
});

it('should handle predicates that always return false properly', () => {
const a = of(1, 2, 3, 4).pipe(single(() => false as const)); // $ExpectType Observable<never>
});
12 changes: 12 additions & 0 deletions spec-dtslint/operators/skipWhile-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,15 @@ it('should enforce predicate types', () => {
it('should enforce predicate return type', () => {
const o = of('foo', 'bar', 'baz').pipe(skipWhile(value => value)); // $ExpectError
});

it('should handle Boolean constructor properly', () => {
// this one is a bit odd, but probably okay.
const a = of(null, undefined, 0 as const, -0 as const, '' as const, 0n as const, false as const).pipe(skipWhile(Boolean)); // $ExpectType Observable<Falsy>
const b = of(null, 0 as const, -0 as const, '' as const, 0n as const, false as const).pipe(skipWhile(Boolean)); // $ExpectType Observable<false | "" | 0 | 0n | null>
const c = of(1, 2, 3, '' as const, 0n as const, false as const, 4).pipe(skipWhile(Boolean)) // $ExpectType Observable<number | false | "" | 0n>
const d = of(true as const, 123 as const, 'HI' as const, {}, []).pipe(skipWhile(Boolean)); // $ExpectType Observable<never>
});

it('should handle predicates that always return true properly', () => {
const a = of(1, 2, 3, 4).pipe(skipWhile(() => true as const)); // $ExpectType Observable<never>
});
14 changes: 14 additions & 0 deletions spec-dtslint/operators/takeWhile-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,17 @@ it('should support a predicate', () => {
it('should support a predicate with inclusive option', () => {
const o = of('foo').pipe(takeWhile(s => true, true)); // $ExpectType Observable<string>
});

it('should properly support Boolean constructor', () => {
const a = of(false as const, 0 as const, -0 as const, 0n as const, '' as const, null, undefined).pipe(takeWhile(Boolean)); // $ExpectType Observable<never>
// This is a weird one... but `Falsy` is equivalent here. I think this is TS trying to be "nice"?
const b = of(false as const, 0 as const, -0 as const, 0n as const, '' as const, null, undefined).pipe(takeWhile(Boolean, true)); // $ExpectType Observable<Falsy>
const c = of(false as const, 0 as const, 'hi' as const, -0 as const, 0n as const, '' as const, null, undefined).pipe(takeWhile(Boolean)); // $ExpectType Observable<false | "" | 0 | 0n | "hi" | null | undefined>
const d = of(false as const, 0 as const, 'hi' as const, -0 as const, 0n as const, '' as const, null, undefined).pipe(takeWhile(Boolean, true)); // $ExpectType Observable<false | "" | 0 | 0n | "hi" | null | undefined>
const e = of(1, ['hi'], false as const, 0 as const, -0 as const, 0n as const, '' as const, null, undefined).pipe(takeWhile(Boolean, true)); // $ExpectType Observable<number | false | "" | 0n | string[] | null | undefined>
});

it('should properly handle predicates that always return false', () => {
const a = of(1, 2, 3).pipe(takeWhile(() => false as const)); // $ExpectType Observable<never>
const b = of(1, 2, 3).pipe(takeWhile(() => false as const, true)); // $ExpectType Observable<number>
});
Loading