From 8b80f1bb7d7f293ae402848ac1d8064e4eb7c1ce Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Sun, 16 Nov 2025 00:00:00 +0000 Subject: [PATCH 01/16] observable: remove any in spec test --- packages/observable/src/observable.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/observable/src/observable.spec.ts b/packages/observable/src/observable.spec.ts index dcde2b2845..2593a9b5eb 100644 --- a/packages/observable/src/observable.spec.ts +++ b/packages/observable/src/observable.spec.ts @@ -358,10 +358,10 @@ describe('Observable', () => { for await (const value of source) { results.push(value); } - } catch (err: any) { + } catch (err: unknown) { thrownError = err; } - + expect(thrownError instanceof Error).to.be.true; expect(thrownError?.message).to.equal('wee'); expect(results).to.deep.equal([1, 2]); }); From b6a9ac44eb0ecf03906034a4513741b6747aa1ec Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Sun, 16 Nov 2025 00:00:00 +0000 Subject: [PATCH 02/16] rxjs: fix type narrowing --- packages/rxjs/src/internal/operators/tap.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rxjs/src/internal/operators/tap.ts b/packages/rxjs/src/internal/operators/tap.ts index dfe43c5306..33ac849af0 100644 --- a/packages/rxjs/src/internal/operators/tap.ts +++ b/packages/rxjs/src/internal/operators/tap.ts @@ -1,5 +1,5 @@ import type { MonoTypeOperatorFunction, Observer } from '../types.js'; -import { Observable, operate, isFunction } from '@rxjs/observable'; +import { Observable, operate } from '@rxjs/observable'; import { identity } from '../util/identity.js'; /** @@ -155,7 +155,7 @@ export interface TapObserver extends Observer { */ export function tap(observerOrNext?: Partial> | ((value: T) => void) | null): MonoTypeOperatorFunction { // Just need to see if it's a function or a partial observer. - const tapObserver: Partial> | null | undefined = isFunction(observerOrNext) ? { next: observerOrNext } : observerOrNext; + const tapObserver: Partial> | null | undefined = typeof observerOrNext === 'function' ? { next: observerOrNext } : observerOrNext; return tapObserver ? (source) => From 457386df7d2f73ec38abc4c50bf532b12cd2aec4 Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Sun, 16 Nov 2025 00:00:00 +0000 Subject: [PATCH 03/16] rxjs: use template literal in test --- packages/rxjs/spec/Subject-spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rxjs/spec/Subject-spec.ts b/packages/rxjs/spec/Subject-spec.ts index 795f25b176..636a33a3ff 100644 --- a/packages/rxjs/spec/Subject-spec.ts +++ b/packages/rxjs/spec/Subject-spec.ts @@ -382,7 +382,7 @@ describe('Subject', () => { results3.push(x); }, error: function (err) { - expect(false).to.equal('should not throw error: ' + err.toString()); + expect(false).to.equal(`should not throw error: ${err}`); }, }); From f7372838103d66276477ca475fa6a55599da9fb2 Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Sun, 16 Nov 2025 00:00:00 +0000 Subject: [PATCH 04/16] rxjs: print entire error object in tests --- packages/rxjs/spec/operators/tap-spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/rxjs/spec/operators/tap-spec.ts b/packages/rxjs/spec/operators/tap-spec.ts index 05c1ca4bf0..e36a86d2aa 100644 --- a/packages/rxjs/spec/operators/tap-spec.ts +++ b/packages/rxjs/spec/operators/tap-spec.ts @@ -325,7 +325,7 @@ describe('tap', () => { tap({ subscribe: () => results.push('subscribe'), next: (value) => results.push(`next ${value}`), - error: (err) => results.push(`error: ${err.message}`), + error: (err) => results.push(`error: ${err}`), complete: () => results.push('complete'), unsubscribe: () => results.push('unsubscribe'), finalize: () => results.push('finalize'), @@ -351,7 +351,7 @@ describe('tap', () => { tap({ subscribe: () => results.push('subscribe'), next: (value) => results.push(`next ${value}`), - error: (err) => results.push(`error: ${err.message}`), + error: (err) => results.push(`error: ${err}`), complete: () => results.push('complete'), unsubscribe: () => results.push('unsubscribe'), finalize: () => results.push('finalize'), @@ -378,7 +378,7 @@ describe('tap', () => { tap({ subscribe: () => results.push('subscribe'), next: (value) => results.push(`next ${value}`), - error: (err) => results.push(`error: ${err.message}`), + error: (err) => results.push(`error: ${err}`), complete: () => results.push('complete'), unsubscribe: () => results.push('unsubscribe'), finalize: () => results.push('finalize'), From b08d739a98d759e2fcffa7734f6d9a659e9e5d0a Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Sun, 16 Nov 2025 00:00:00 +0000 Subject: [PATCH 05/16] rxjs: remove some 'any' from ajax --- packages/rxjs/src/internal/ajax/ajax.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/rxjs/src/internal/ajax/ajax.ts b/packages/rxjs/src/internal/ajax/ajax.ts index 0ee3fae22e..6bc62cf7c2 100644 --- a/packages/rxjs/src/internal/ajax/ajax.ts +++ b/packages/rxjs/src/internal/ajax/ajax.ts @@ -410,7 +410,7 @@ export function fromAjax(init: AjaxConfig): Observable> { * @param type The type of event we're treating as an error * @param errorFactory A function that creates the type of error to emit. */ - const addErrorEvent = (type: string, errorFactory: () => any) => { + const addErrorEvent = (type: string, errorFactory: () => Error) => { xhr.addEventListener(type, () => { const error = errorFactory(); progressSubscriber?.error?.(error); @@ -576,34 +576,34 @@ function extractContentTypeAndMaybeSerializeBody(body: any, headers: Record Date: Sun, 16 Nov 2025 00:00:00 +0000 Subject: [PATCH 06/16] rxjs: remove 'any' and wrong types from tests --- packages/rxjs/spec/Observable-spec.ts | 4 ++-- packages/rxjs/spec/Subject-spec.ts | 2 +- packages/rxjs/spec/observables/dom/ajax-spec.ts | 4 ++-- packages/rxjs/spec/observables/from-spec.ts | 2 +- packages/rxjs/spec/observables/fromEvent-spec.ts | 2 +- packages/rxjs/spec/subjects/ReplaySubject-spec.ts | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/rxjs/spec/Observable-spec.ts b/packages/rxjs/spec/Observable-spec.ts index 2ea5990a10..65dd982d13 100644 --- a/packages/rxjs/spec/Observable-spec.ts +++ b/packages/rxjs/spec/Observable-spec.ts @@ -195,8 +195,8 @@ describe('Observable', () => { it('should work with handlers with hacked bind methods, in the error case', () => { const source = throwError(() => 'an error'); - const results: any[] = []; - const error = function (value: string) { + const results: unknown[] = []; + const error = function (value: unknown) { results.push(value); }; diff --git a/packages/rxjs/spec/Subject-spec.ts b/packages/rxjs/spec/Subject-spec.ts index 636a33a3ff..f0b5f8790d 100644 --- a/packages/rxjs/spec/Subject-spec.ts +++ b/packages/rxjs/spec/Subject-spec.ts @@ -520,7 +520,7 @@ describe('Subject', () => { it('should not next after error', () => { const error = new Error('wut?'); const subject = new Subject(); - const results: string[] = []; + const results: unknown[] = []; subject.subscribe({ next: (x) => results.push(x), error: (err) => results.push(err) }); subject.next('a'); subject.error(error); diff --git a/packages/rxjs/spec/observables/dom/ajax-spec.ts b/packages/rxjs/spec/observables/dom/ajax-spec.ts index 05eebe240f..75aa5fcde3 100644 --- a/packages/rxjs/spec/observables/dom/ajax-spec.ts +++ b/packages/rxjs/spec/observables/dom/ajax-spec.ts @@ -292,7 +292,7 @@ describe('ajax', () => { next: () => { done(new Error('should not be called')); }, - error: (e: Error) => { + error: (e) => { expect(e).to.be.equal(expected); done(); }, @@ -663,7 +663,7 @@ describe('ajax', () => { next: () => { done(new Error('should not be called')); }, - error: (e: Error) => { + error: (e) => { expect(e).to.be.equal(expected); done(); }, diff --git a/packages/rxjs/spec/observables/from-spec.ts b/packages/rxjs/spec/observables/from-spec.ts index decb925f58..a56130617a 100644 --- a/packages/rxjs/spec/observables/from-spec.ts +++ b/packages/rxjs/spec/observables/from-spec.ts @@ -325,7 +325,7 @@ describe('from', () => { from(erroringIterator).subscribe({ next: (x) => results.push(x), - error: (err) => results.push(err.message), + error: (err) => results.push(err instanceof Error && err.message), }); expect(results).to.deep.equal([0, 1, 2, 'bad']); diff --git a/packages/rxjs/spec/observables/fromEvent-spec.ts b/packages/rxjs/spec/observables/fromEvent-spec.ts index ae4deba7cb..450fd6fc95 100644 --- a/packages/rxjs/spec/observables/fromEvent-spec.ts +++ b/packages/rxjs/spec/observables/fromEvent-spec.ts @@ -492,7 +492,7 @@ describe('fromEvent', () => { source.subscribe({ error: (err) => { expect(err).to.be.an.instanceOf(TypeError); - expect(err.message).to.equal('Invalid event target'); + expect(err instanceof TypeError && err.message).to.equal('Invalid event target'); done(); }, }); diff --git a/packages/rxjs/spec/subjects/ReplaySubject-spec.ts b/packages/rxjs/spec/subjects/ReplaySubject-spec.ts index c071760c06..313bd94bae 100644 --- a/packages/rxjs/spec/subjects/ReplaySubject-spec.ts +++ b/packages/rxjs/spec/subjects/ReplaySubject-spec.ts @@ -120,7 +120,7 @@ describe('ReplaySubject', () => { function feedNextIntoSubject(x: string) { replaySubject.next(x); } - function feedErrorIntoSubject(err: string) { + function feedErrorIntoSubject(err: unknown) { replaySubject.error(err); } function feedCompleteIntoSubject() { @@ -152,7 +152,7 @@ describe('ReplaySubject', () => { function feedNextIntoSubject(x: string) { replaySubject.next(x); } - function feedErrorIntoSubject(err: string) { + function feedErrorIntoSubject(err: unknown) { replaySubject.error(err); } function feedCompleteIntoSubject() { From e278e74ed6009ed89dd11972ab11df8458306442 Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Sun, 16 Nov 2025 00:00:00 +0000 Subject: [PATCH 07/16] rxjs: add instanceof to ajax timeout tests --- packages/rxjs/spec/observables/dom/ajax-spec.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/rxjs/spec/observables/dom/ajax-spec.ts b/packages/rxjs/spec/observables/dom/ajax-spec.ts index 75aa5fcde3..ef647e707c 100644 --- a/packages/rxjs/spec/observables/dom/ajax-spec.ts +++ b/packages/rxjs/spec/observables/dom/ajax-spec.ts @@ -1,8 +1,8 @@ /** @prettier */ import { expect } from 'chai'; import * as sinon from 'sinon'; -import type { AjaxConfig, AjaxResponse} from 'rxjs/ajax'; -import { ajax, AjaxError, AjaxTimeoutError } from 'rxjs/ajax'; +import type { AjaxConfig } from 'rxjs/ajax'; +import { ajax, AjaxError, AjaxResponse, AjaxTimeoutError } from 'rxjs/ajax'; import { TestScheduler } from 'rxjs/testing'; import { noop } from 'rxjs'; import * as nodeFormData from 'form-data'; @@ -479,7 +479,8 @@ describe('ajax', () => { }; ajax(obj).subscribe({ - next: (x: any) => { + next: (x) => { + expect(x instanceof AjaxResponse).to.be.true; expect(x.status).to.equal(200); expect(x.xhr.method).to.equal('GET'); expect(x.xhr.async).to.equal(true); @@ -515,6 +516,7 @@ describe('ajax', () => { throw 'should not have been called'; }, error: (e) => { + expect(e instanceof AjaxTimeoutError).to.be.true; expect(e.status).to.equal(0); expect(e.xhr.method).to.equal('GET'); expect(e.xhr.async).to.equal(true); From b437aa2c44097b439eedca9d7ebc43763d81e627 Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Sun, 16 Nov 2025 00:00:00 +0000 Subject: [PATCH 08/16] rxjs: type narrowing in ajax timeout tests --- packages/rxjs/spec/observables/dom/ajax-spec.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/rxjs/spec/observables/dom/ajax-spec.ts b/packages/rxjs/spec/observables/dom/ajax-spec.ts index ef647e707c..0393ee3fe5 100644 --- a/packages/rxjs/spec/observables/dom/ajax-spec.ts +++ b/packages/rxjs/spec/observables/dom/ajax-spec.ts @@ -481,9 +481,13 @@ describe('ajax', () => { ajax(obj).subscribe({ next: (x) => { expect(x instanceof AjaxResponse).to.be.true; + if (!(x instanceof AjaxResponse)) + return; // type-narrowing that @types/chai can't do + expect(x.status).to.equal(200); - expect(x.xhr.method).to.equal('GET'); - expect(x.xhr.async).to.equal(true); + // fields only in MockXMLHttpRequest: + expect('method' in x.xhr && x.xhr.method).to.equal('GET'); + expect('async' in x.xhr && x.xhr.async).to.equal(true); expect(x.xhr.timeout).to.equal(10); expect(x.xhr.responseType).to.equal('text'); }, @@ -517,9 +521,13 @@ describe('ajax', () => { }, error: (e) => { expect(e instanceof AjaxTimeoutError).to.be.true; + if (!(e instanceof AjaxTimeoutError)) + return; // type-narrowing that @types/chai can't do + expect(e.status).to.equal(0); - expect(e.xhr.method).to.equal('GET'); - expect(e.xhr.async).to.equal(true); + // fields only in MockXMLHttpRequest: + expect('method' in e.xhr && e.xhr.method).to.equal('GET'); + expect('async' in e.xhr && e.xhr.async).to.equal(true); expect(e.xhr.timeout).to.equal(10); expect(e.xhr.responseType).to.equal('text'); }, From 1c715675bad75bed09d21d40ca6f8c2b7d9cabe0 Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Sun, 16 Nov 2025 00:00:00 +0000 Subject: [PATCH 09/16] rxjs: add type narrowing for Error tests --- packages/rxjs/spec/Subject-spec.ts | 2 +- packages/rxjs/spec/Subscriber-spec.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/rxjs/spec/Subject-spec.ts b/packages/rxjs/spec/Subject-spec.ts index f0b5f8790d..d4328c400f 100644 --- a/packages/rxjs/spec/Subject-spec.ts +++ b/packages/rxjs/spec/Subject-spec.ts @@ -596,7 +596,7 @@ describe('Subject', () => { it('should not synchronously error when nexted into', (done) => { config.onUnhandledError = (err) => { - expect(err.message).to.equal('Boom!'); + expect(err instanceof Error && err.message).to.equal('Boom!'); done(); }; diff --git a/packages/rxjs/spec/Subscriber-spec.ts b/packages/rxjs/spec/Subscriber-spec.ts index 84220e7411..8ca07a4038 100644 --- a/packages/rxjs/spec/Subscriber-spec.ts +++ b/packages/rxjs/spec/Subscriber-spec.ts @@ -201,7 +201,7 @@ describe('Subscriber', () => { it('should report errors thrown from next', (done) => { config.onUnhandledError = (err) => { expect(err).to.be.an.instanceOf(Error); - expect(err.message).to.equal('test'); + expect(err instanceof Error && err.message).to.equal('test'); done(); }; @@ -217,7 +217,7 @@ describe('Subscriber', () => { it('should report errors thrown from complete', (done) => { config.onUnhandledError = (err) => { expect(err).to.be.an.instanceOf(Error); - expect(err.message).to.equal('test'); + expect(err instanceof Error && err.message).to.equal('test'); done(); }; @@ -233,7 +233,7 @@ describe('Subscriber', () => { it('should report errors thrown from error', (done) => { config.onUnhandledError = (err) => { expect(err).to.be.an.instanceOf(Error); - expect(err.message).to.equal('test'); + expect(err instanceof Error && err.message).to.equal('test'); done(); }; @@ -249,7 +249,7 @@ describe('Subscriber', () => { it('should report errors thrown from a full observer', (done) => { config.onUnhandledError = (err) => { expect(err).to.be.an.instanceOf(Error); - expect(err.message).to.equal('thrown from next'); + expect(err instanceof Error && err.message).to.equal('thrown from next'); done(); }; @@ -271,7 +271,7 @@ describe('Subscriber', () => { it('should report errors thrown from a full observer even if it is also shaped like a subscription', (done) => { config.onUnhandledError = (err) => { expect(err).to.be.an.instanceOf(Error); - expect(err.message).to.equal('thrown from next'); + expect(err instanceof Error && err.message).to.equal('thrown from next'); done(); }; From aae4d4d3650f4f3cb9f411c5759c13b8d2ce76eb Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Sun, 16 Nov 2025 00:00:00 +0000 Subject: [PATCH 10/16] replace 'any' with 'unknown' in types.ts --- packages/observable/src/types.ts | 32 ++++++++++++++--------------- packages/rxjs/src/internal/types.ts | 32 ++++++++++++++--------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/observable/src/types.ts b/packages/observable/src/types.ts index 8109e31b70..02663618e9 100644 --- a/packages/observable/src/types.ts +++ b/packages/observable/src/types.ts @@ -130,7 +130,7 @@ export interface NextNotification { export interface ErrorNotification { /** The kind of notification. Always "E" */ kind: 'E'; - error: any; + error: unknown; } /** @@ -151,21 +151,21 @@ export type ObservableNotification = NextNotification | ErrorNotification export interface NextObserver { closed?: boolean; next: (value: T) => void; - error?: (err: any) => void; + error?: (err: unknown) => void; complete?: () => void; } export interface ErrorObserver { closed?: boolean; next?: (value: T) => void; - error: (err: any) => void; + error: (err: unknown) => void; complete?: () => void; } export interface CompletionObserver { closed?: boolean; next?: (value: T) => void; - error?: (err: any) => void; + error?: (err: unknown) => void; complete: () => void; } @@ -196,7 +196,7 @@ export interface Observer { * * For more info, please refer to {@link guide/glossary-and-semantics#error this guide}. */ - error: (err: any) => void; + error: (err: unknown) => void; /** * A callback function that gets called by the producer if and when it has no more * values to provide (by calling `next` callback function). This means that no error @@ -237,15 +237,15 @@ export interface TimestampProvider { } /** - * Extracts the type from an `ObservableInput`. If you have - * `O extends ObservableInput` and you pass in `Observable`, or + * Extracts the type from an `ObservableInput`. If you have + * `O extends ObservableInput` and you pass in `Observable`, or * `Promise`, etc, it will type as `number`. */ export type ObservedValueOf = O extends ObservableInput ? T : never; /** - * Extracts a union of element types from an `ObservableInput[]`. - * If you have `O extends ObservableInput[]` and you pass in + * Extracts a union of element types from an `ObservableInput[]`. + * If you have `O extends ObservableInput[]` and you pass in * `Observable[]` or `Promise[]` you would get * back a type of `string`. * If you pass in `[Observable, Observable]` you would @@ -254,8 +254,8 @@ export type ObservedValueOf = O extends ObservableInput ? T : never; export type ObservedValueUnionFromArray = X extends Array> ? T : never; /** - * Extracts a tuple of element types from an `ObservableInput[]`. - * If you have `O extends ObservableInput[]` and you pass in + * Extracts a tuple of element types from an `ObservableInput[]`. + * If you have `O extends ObservableInput[]` and you pass in * `[Observable, Observable]` you would get back a type * of `[string, number]`. */ @@ -274,23 +274,23 @@ export type ObservableInputTuple = { * Constructs a new tuple with the specified type at the head. * If you declare `Cons` you will get back `[A, B, C]`. */ -export type Cons = ((arg: X, ...rest: Y) => any) extends (...args: infer U) => any ? U : never; +export type Cons = ((arg: X, ...rest: Y) => unknown) extends (...args: infer U) => unknown ? U : never; /** * Extracts the head of a tuple. * If you declare `Head<[A, B, C]>` you will get back `A`. */ -export type Head = ((...args: X) => any) extends (arg: infer U, ...rest: any[]) => any ? U : never; +export type Head = ((...args: X) => unknown) extends (arg: infer U, ...rest: unknown[]) => unknown ? U : never; /** * Extracts the tail of a tuple. * If you declare `Tail<[A, B, C]>` you will get back `[B, C]`. */ -export type Tail = ((...args: X) => any) extends (arg: any, ...rest: infer U) => any ? U : never; +export type Tail = ((...args: X) => unknown) extends (arg: unknown, ...rest: infer U) => unknown ? U : never; /** * Extracts the generic value from an Array type. - * If you have `T extends Array`, and pass a `string[]` to it, + * If you have `T extends Array`, and pass a `string[]` to it, * `ValueFromArray` will return the actual type of `string`. */ export type ValueFromArray = A extends Array ? T : never; @@ -299,7 +299,7 @@ export type ValueFromArray = A extends Array = T extends { kind: 'N' | 'E' | 'C' } - ? T extends NextNotification + ? T extends NextNotification ? T extends { value: infer V } ? V : undefined diff --git a/packages/rxjs/src/internal/types.ts b/packages/rxjs/src/internal/types.ts index bbb680342b..8da23b981f 100644 --- a/packages/rxjs/src/internal/types.ts +++ b/packages/rxjs/src/internal/types.ts @@ -130,7 +130,7 @@ export interface NextNotification { export interface ErrorNotification { /** The kind of notification. Always "E" */ kind: 'E'; - error: any; + error: unknown; } /** @@ -151,21 +151,21 @@ export type ObservableNotification = NextNotification | ErrorNotification export interface NextObserver { closed?: boolean; next: (value: T) => void; - error?: (err: any) => void; + error?: (err: unknown) => void; complete?: () => void; } export interface ErrorObserver { closed?: boolean; next?: (value: T) => void; - error: (err: any) => void; + error: (err: unknown) => void; complete?: () => void; } export interface CompletionObserver { closed?: boolean; next?: (value: T) => void; - error?: (err: any) => void; + error?: (err: unknown) => void; complete: () => void; } @@ -196,7 +196,7 @@ export interface Observer { * * For more info, please refer to {@link guide/glossary-and-semantics#error this guide}. */ - error: (err: any) => void; + error: (err: unknown) => void; /** * A callback function that gets called by the producer if and when it has no more * values to provide (by calling `next` callback function). This means that no error @@ -237,15 +237,15 @@ export interface TimestampProvider { } /** - * Extracts the type from an `ObservableInput`. If you have - * `O extends ObservableInput` and you pass in `Observable`, or + * Extracts the type from an `ObservableInput`. If you have + * `O extends ObservableInput` and you pass in `Observable`, or * `Promise`, etc, it will type as `number`. */ export type ObservedValueOf = O extends ObservableInput ? T : never; /** - * Extracts a union of element types from an `ObservableInput[]`. - * If you have `O extends ObservableInput[]` and you pass in + * Extracts a union of element types from an `ObservableInput[]`. + * If you have `O extends ObservableInput[]` and you pass in * `Observable[]` or `Promise[]` you would get * back a type of `string`. * If you pass in `[Observable, Observable]` you would @@ -254,8 +254,8 @@ export type ObservedValueOf = O extends ObservableInput ? T : never; export type ObservedValueUnionFromArray = X extends Array> ? T : never; /** - * Extracts a tuple of element types from an `ObservableInput[]`. - * If you have `O extends ObservableInput[]` and you pass in + * Extracts a tuple of element types from an `ObservableInput[]`. + * If you have `O extends ObservableInput[]` and you pass in * `[Observable, Observable]` you would get back a type * of `[string, number]`. */ @@ -274,23 +274,23 @@ export type ObservableInputTuple = { * Constructs a new tuple with the specified type at the head. * If you declare `Cons` you will get back `[A, B, C]`. */ -export type Cons = ((arg: X, ...rest: Y) => any) extends (...args: infer U) => any ? U : never; +export type Cons = ((arg: X, ...rest: Y) => unknown) extends (...args: infer U) => unknown ? U : never; /** * Extracts the head of a tuple. * If you declare `Head<[A, B, C]>` you will get back `A`. */ -export type Head = ((...args: X) => any) extends (arg: infer U, ...rest: any[]) => any ? U : never; +export type Head = ((...args: X) => unknown) extends (arg: infer U, ...rest: unknown[]) => unknown ? U : never; /** * Extracts the tail of a tuple. * If you declare `Tail<[A, B, C]>` you will get back `[B, C]`. */ -export type Tail = ((...args: X) => any) extends (arg: any, ...rest: infer U) => any ? U : never; +export type Tail = ((...args: X) => unknown) extends (arg: unknown, ...rest: infer U) => unknown ? U : never; /** * Extracts the generic value from an Array type. - * If you have `T extends Array`, and pass a `string[]` to it, + * If you have `T extends Array`, and pass a `string[]` to it, * `ValueFromArray` will return the actual type of `string`. */ export type ValueFromArray = A extends Array ? T : never; @@ -299,7 +299,7 @@ export type ValueFromArray = A extends Array = T extends { kind: 'N' | 'E' | 'C' } - ? T extends NextNotification + ? T extends NextNotification ? T extends { value: infer V } ? V : undefined From d78041ddbc61a57d2d9f9066b30a12df2bb77275 Mon Sep 17 00:00:00 2001 From: dynst <148708712+dynst@users.noreply.github.com> Date: Sun, 16 Nov 2025 00:00:00 +0000 Subject: [PATCH 11/16] observable: fix 'any' types in type narrowing helper functions --- packages/observable/src/observable.ts | 45 ++++++++++++++++----------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/packages/observable/src/observable.ts b/packages/observable/src/observable.ts index 31f8403d15..77a352f844 100644 --- a/packages/observable/src/observable.ts +++ b/packages/observable/src/observable.ts @@ -487,7 +487,7 @@ class ConsumerObserver implements Observer { } function createSafeObserver(observerOrNext?: Partial> | ((value: T) => void) | null): Observer { - return new ConsumerObserver(!observerOrNext || isFunction(observerOrNext) ? { next: observerOrNext ?? undefined } : observerOrNext); + return new ConsumerObserver(!observerOrNext || typeof observerOrNext === 'function' ? { next: observerOrNext ?? undefined } : observerOrNext); } /** @@ -1239,6 +1239,11 @@ export enum ObservableInputType { } export function getObservableInputType(input: unknown): ObservableInputType { + if (typeof input !== 'object' || input === null) { + throw new TypeError( + `You provided '${input}' where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.` + ); + } if (input instanceof Observable) { return ObservableInputType.Own; } @@ -1261,9 +1266,7 @@ export function getObservableInputType(input: unknown): ObservableInputType { return ObservableInputType.ReadableStreamLike; } throw new TypeError( - `You provided ${ - input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'` - } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.` + `You provided an invalid object where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.` ); } @@ -1271,12 +1274,16 @@ export function getObservableInputType(input: unknown): ObservableInputType { * Returns true if the object is a function. * @param value The value to check */ -export function isFunction(value: any): value is (...args: any[]) => any { +export function isFunction(value: unknown): value is (...args: unknown[]) => unknown { return typeof value === 'function'; } -function isAsyncIterable(obj: any): obj is AsyncIterable { - return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]); +function hasMethod(value: object, method: string | symbol): boolean { + return isFunction(Reflect.get(value, method)); +} + +function isAsyncIterable(obj: object): obj is AsyncIterable { + return Symbol.asyncIterator && hasMethod(obj, Symbol.asyncIterator); } export async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator { @@ -1294,42 +1301,42 @@ export async function* readableStreamLikeToAsyncGenerator(readableStream: Rea } } -function isReadableStreamLike(obj: any): obj is ReadableStreamLike { +function isReadableStreamLike(obj: object): obj is ReadableStreamLike { // We don't want to use instanceof checks because they would return // false for instances from another Realm, like an