From d3bdeb73f5ee6b38a9a54d93449447efebaf7279 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 4 Dec 2025 11:27:43 +0000 Subject: [PATCH] Avoid use of Optional type (#5093) * Avoid use of Optional type As we are likely to remove dependency on matrix-events-sdk Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Tweak params Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Prettier Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update test Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- code_style.md | 7 +++---- spec/integ/matrix-client-event-timeline.spec.ts | 8 ++++---- spec/unit/room.spec.ts | 8 ++++---- spec/unit/timeline-window.spec.ts | 4 ++-- src/@types/extensible_events.ts | 7 ++----- src/NamespacedValue.ts | 4 +--- src/client.ts | 14 ++++++-------- src/extensible_events_v1/MessageEvent.ts | 6 ++---- src/models/event.ts | 10 +++++----- src/models/room.ts | 6 +++--- src/models/thread.ts | 10 ++++------ src/sync.ts | 4 +--- src/timeline-window.ts | 4 +--- src/utils.ts | 3 +-- 14 files changed, 39 insertions(+), 56 deletions(-) diff --git a/code_style.md b/code_style.md index 26f422a78..62913d79c 100644 --- a/code_style.md +++ b/code_style.md @@ -71,7 +71,7 @@ Unless otherwise specified, the following applies to all code: 11. If a variable is not receiving a value on declaration, its type must be defined. ```typescript - let errorMessage: Optional; + let errorMessage: string; ``` 12. Objects can use shorthand declarations, including mixing of types. @@ -150,8 +150,7 @@ Unless otherwise specified, the following applies to all code: 1. When using `any`, a comment explaining why must be present. 27. `import` should be used instead of `require`, as `require` does not have types. 28. Export only what can be reused. -29. Prefer a type like `Optional` (`type Optional = T | null | undefined`) instead - of truly optional parameters. +29. Prefer a type like `X | null` instead of truly optional parameters. 1. A notable exception is when the likelihood of a bug is minimal, such as when a function takes an argument that is more often not required than required. An example where the `?` operator is inappropriate is when taking a room ID: typically the caller should @@ -161,7 +160,7 @@ Unless otherwise specified, the following applies to all code: ```typescript function doThingWithRoom( thing: string, - room: Optional, // require the caller to specify + room: string | null, // require the caller to specify ) { // ... } diff --git a/spec/integ/matrix-client-event-timeline.spec.ts b/spec/integ/matrix-client-event-timeline.spec.ts index 972e87517..2676338eb 100644 --- a/spec/integ/matrix-client-event-timeline.spec.ts +++ b/spec/integ/matrix-client-event-timeline.spec.ts @@ -672,7 +672,7 @@ describe("MatrixClient event timelines", function () { expect(timeline!.getEvents().find((e) => e.getId() === THREAD_ROOT.event_id!)).toBeTruthy(); }); - it("should return undefined when event is not in the thread that the given timelineSet is representing", () => { + it("should return null when event is not in the thread that the given timelineSet is representing", () => { // @ts-ignore client.clientOpts.threadSupport = true; Thread.setServerSideSupport(FeatureSupport.Experimental); @@ -696,12 +696,12 @@ describe("MatrixClient event timelines", function () { }); return Promise.all([ - expect(client.getEventTimeline(timelineSet, EVENTS[0].event_id!)).resolves.toBeUndefined(), + expect(client.getEventTimeline(timelineSet, EVENTS[0].event_id!)).resolves.toBeNull(), httpBackend.flushAllExpected(), ]); }); - it("should return undefined when event is within a thread but timelineSet is not", () => { + it("should return null when event is within a thread but timelineSet is not", () => { // @ts-ignore client.clientOpts.threadSupport = true; Thread.setServerSideSupport(FeatureSupport.Experimental); @@ -723,7 +723,7 @@ describe("MatrixClient event timelines", function () { }); return Promise.all([ - expect(client.getEventTimeline(timelineSet, THREAD_REPLY.event_id!)).resolves.toBeUndefined(), + expect(client.getEventTimeline(timelineSet, THREAD_REPLY.event_id!)).resolves.toBeNull(), httpBackend.flushAllExpected(), ]); }); diff --git a/spec/unit/room.spec.ts b/spec/unit/room.spec.ts index 66e342801..a6e69fdf5 100644 --- a/spec/unit/room.spec.ts +++ b/spec/unit/room.spec.ts @@ -19,7 +19,7 @@ limitations under the License. */ import { mocked } from "jest-mock"; -import { M_POLL_KIND_DISCLOSED, M_POLL_RESPONSE, M_POLL_START, type Optional, PollStartEvent } from "matrix-events-sdk"; +import { M_POLL_KIND_DISCLOSED, M_POLL_RESPONSE, M_POLL_START, PollStartEvent } from "matrix-events-sdk"; import * as utils from "../test-utils/test-utils"; import { emitPromise, type IMessageOpts } from "../test-utils/test-utils"; @@ -197,8 +197,8 @@ describe("Room", function () { const addRoomThreads = ( room: Room, - thread1EventTs: Optional, - thread2EventTs: Optional, + thread1EventTs?: number, + thread2EventTs?: number, ): { thread1?: Thread; thread2?: Thread } => { const result: { thread1?: Thread; thread2?: Thread } = {}; @@ -4159,7 +4159,7 @@ describe("Room", function () { }); it("when there is only one thread, it should return this one", () => { - const { thread1 } = addRoomThreads(room, 23, null); + const { thread1 } = addRoomThreads(room, 23); expect(room.getLastThread()).toBe(thread1); }); diff --git a/spec/unit/timeline-window.spec.ts b/spec/unit/timeline-window.spec.ts index a59211779..de4fbaef1 100644 --- a/spec/unit/timeline-window.spec.ts +++ b/spec/unit/timeline-window.spec.ts @@ -98,7 +98,7 @@ function createLinkedTimelines(): [EventTimeline, EventTimeline] { describe("TimelineIndex", function () { beforeEach(() => { jest.clearAllMocks(); - mockClient.getEventTimeline.mockResolvedValue(undefined); + mockClient.getEventTimeline.mockResolvedValue(null); }); describe("minIndex", function () { @@ -193,7 +193,7 @@ describe("TimelineWindow", function () { beforeEach(() => { jest.clearAllMocks(); - mockClient.getEventTimeline.mockResolvedValue(undefined); + mockClient.getEventTimeline.mockResolvedValue(null); mockClient.paginateEventTimeline.mockResolvedValue(false); }); diff --git a/src/@types/extensible_events.ts b/src/@types/extensible_events.ts index efd8f5145..7c61b458d 100644 --- a/src/@types/extensible_events.ts +++ b/src/@types/extensible_events.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { type EitherAnd, NamespacedValue, type Optional, UnstableValue } from "matrix-events-sdk"; +import { type EitherAnd, NamespacedValue, UnstableValue } from "matrix-events-sdk"; import { isProvided } from "../extensible_events_v1/utilities.ts"; @@ -125,10 +125,7 @@ export type ExtensibleEventType = NamespacedValue | string; * @param expected - The expected event type. * @returns True if the given type matches the expected type. */ -export function isEventTypeSame( - given: Optional, - expected: Optional, -): boolean { +export function isEventTypeSame(given: ExtensibleEventType | null, expected: ExtensibleEventType | null): boolean { if (typeof given === "string") { if (typeof expected === "string") { return expected === given; diff --git a/src/NamespacedValue.ts b/src/NamespacedValue.ts index e1a29cdb7..679d5dae9 100644 --- a/src/NamespacedValue.ts +++ b/src/NamespacedValue.ts @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { type Optional } from "matrix-events-sdk"; - /** * Represents a simple Matrix namespaced value. This will assume that if a stable prefix * is provided that the stable prefix should be used when representing the identifier. @@ -62,7 +60,7 @@ export class NamespacedValue { // this desperately wants https://github.com/microsoft/TypeScript/pull/26349 at the top level of the class // so we can instantiate `NamespacedValue` as a default type for that namespace. - public findIn(obj: any): Optional { + public findIn(obj: any): T | undefined { let val: T | undefined = undefined; if (this.name) { val = obj?.[this.name]; diff --git a/src/client.ts b/src/client.ts index 84996c151..26cba498f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -18,8 +18,6 @@ limitations under the License. * This is an internal module. See {@link MatrixClient} for the public class. */ -import { type Optional } from "matrix-events-sdk"; - import type { IDeviceKeys, IOneTimeKey } from "./@types/crypto.ts"; import { type ISyncStateData, type SetPresence, SyncApi, type SyncApiOptions, SyncState } from "./sync.ts"; import { @@ -4501,7 +4499,7 @@ export class MatrixClient extends TypedEventEmitter> { + public async getEventTimeline(timelineSet: EventTimelineSet, eventId: string): Promise { // don't allow any timeline support unless it's been enabled. if (!this.timelineSupport) { throw new Error( @@ -4519,7 +4517,7 @@ export class MatrixClient extends TypedEventEmitter = res.end; + let nextBatch = res.end; while (nextBatch) { const resNewer: IRelationsResponse = await this.fetchRelations( timelineSet.room.roomId, @@ -4674,7 +4672,7 @@ export class MatrixClient extends TypedEventEmitter> { + public async getLatestTimeline(timelineSet: EventTimelineSet): Promise { // don't allow any timeline support unless it's been enabled. if (!this.timelineSupport) { throw new Error( diff --git a/src/extensible_events_v1/MessageEvent.ts b/src/extensible_events_v1/MessageEvent.ts index fba88b840..6fd1c091f 100644 --- a/src/extensible_events_v1/MessageEvent.ts +++ b/src/extensible_events_v1/MessageEvent.ts @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { type Optional } from "matrix-events-sdk"; - import { ExtensibleEvent } from "./ExtensibleEvent.ts"; import { type ExtensibleEventType, @@ -46,7 +44,7 @@ export class MessageEvent extends ExtensibleEvent; + public readonly html?: string; /** * All the different renderings of the message. Note that this is the same @@ -82,7 +80,7 @@ export class MessageEvent extends ExtensibleEvent = undefined; + private _cachedExtEv?: ExtensibleEvent = undefined; /** If we failed to decrypt this event, the reason for the failure. Otherwise, `null`. */ private _decryptionFailureReason: DecryptionFailureCode | null = null; @@ -481,9 +481,9 @@ export class MatrixEvent extends TypedEventEmitter { + public get unstableExtensibleEvent(): ExtensibleEvent | undefined { if (!this._hasCachedExtEv) { - this._cachedExtEv = ExtensibleEvents.parse(this.getEffectiveEvent()); + this._cachedExtEv = ExtensibleEvents.parse(this.getEffectiveEvent()) ?? undefined; } return this._cachedExtEv; } @@ -789,7 +789,7 @@ export class MatrixEvent extends TypedEventEmitter { + public getMembershipAtEvent(): Membership | string | undefined { const unsigned = this.getUnsigned(); return UNSIGNED_MEMBERSHIP_FIELD.findIn(unsigned); } diff --git a/src/models/room.ts b/src/models/room.ts index 57f636a26..072847c28 100644 --- a/src/models/room.ts +++ b/src/models/room.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { M_POLL_START, type Optional } from "matrix-events-sdk"; +import { M_POLL_START } from "matrix-events-sdk"; import { DuplicateStrategy, @@ -1196,7 +1196,7 @@ export class Room extends ReadReceipt { // Get the main TimelineSet const timelineSet = this.getUnfilteredTimelineSet(); - let newTimeline: Optional; + let newTimeline: EventTimeline | null = null; // If there isn't any event in the timeline, let's go fetch the latest // event and construct a timeline from it. // @@ -2490,7 +2490,7 @@ export class Room extends ReadReceipt { }; private updateThreadRootEvent = ( - timelineSet: Optional, + timelineSet: EventTimelineSet | undefined, thread: Thread, toStartOfTimeline: boolean, recreateEvent: boolean, diff --git a/src/models/thread.ts b/src/models/thread.ts index 274d0d06b..871875dfc 100644 --- a/src/models/thread.ts +++ b/src/models/thread.ts @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { type Optional } from "matrix-events-sdk"; - import { type MatrixClient, PendingEventOrdering } from "../client.ts"; import { TypedReEmitter } from "../ReEmitter.ts"; import { RelationType } from "../@types/event.ts"; @@ -476,7 +474,7 @@ export class Thread extends ReadReceipt): Promise { + public async processEvent(event: MatrixEvent | null | undefined): Promise { if (event) { this.setEventMetadata(event); await this.fetchEditsWhereNeeded(event); @@ -686,14 +684,14 @@ export class Thread extends ReadReceipt): void { + public setEventMetadata(event: MatrixEvent | null | undefined): void { if (event) { EventTimeline.setEventMetadata(event, this.roomState, false); event.setThread(this); } } - public clearEventMetadata(event: Optional): void { + public clearEventMetadata(event: MatrixEvent | null | undefined): void { if (event) { event.setThread(undefined); delete event.event?.unsigned?.["m.relations"]?.[THREAD_RELATION_TYPE.name]; @@ -739,7 +737,7 @@ export class Thread extends ReadReceipt { + public get replyToEvent(): MatrixEvent | null { return this.lastPendingEvent ?? this.lastEvent ?? this.lastReply(); } diff --git a/src/sync.ts b/src/sync.ts index a191fa877..0eba8a498 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -23,8 +23,6 @@ limitations under the License. * for HTTP and WS at some point. */ -import { type Optional } from "matrix-events-sdk"; - import type { SyncCryptoCallbacks } from "./common-crypto/CryptoBackend.ts"; import { User } from "./models/user.ts"; import { NotificationCountType, Room, RoomEvent } from "./models/room.ts"; @@ -208,7 +206,7 @@ export class SyncApi { private readonly opts: IStoredClientOpts; private readonly syncOpts: SyncApiOptions; - private _peekRoom: Optional = null; + private _peekRoom: Room | null = null; private currentSyncRequest?: Promise; private abortController?: AbortController; private syncState: SyncState | null = null; diff --git a/src/timeline-window.ts b/src/timeline-window.ts index b6eb8634f..22861399c 100644 --- a/src/timeline-window.ts +++ b/src/timeline-window.ts @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { type Optional } from "matrix-events-sdk"; - import { type Direction, EventTimeline } from "./models/event-timeline.ts"; import { logger } from "./logger.ts"; import { type MatrixClient } from "./client.ts"; @@ -105,7 +103,7 @@ export class TimelineWindow { public load(initialEventId?: string, initialWindowSize = 20): Promise { // given an EventTimeline, find the event we were looking for, and initialise our // fields so that the event in question is in the middle of the window. - const initFields = (timeline: Optional): void => { + const initFields = (timeline: EventTimeline | null): void => { if (!timeline) { throw new Error("No timeline given to initFields"); } diff --git a/src/utils.ts b/src/utils.ts index 4366bc89c..302f4c85f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -20,7 +20,6 @@ limitations under the License. import unhomoglyph from "unhomoglyph"; import promiseRetry from "p-retry"; -import { type Optional } from "matrix-events-sdk"; import { type IEvent, type MatrixEvent } from "./models/event.ts"; import { M_TIMESTAMP } from "./@types/location.ts"; @@ -115,7 +114,7 @@ export function decodeParams(query: string): Record { * variables with. E.g. `{ "$bar": "baz" }`. * @returns The result of replacing all template variables e.g. '/foo/baz'. */ -export function encodeUri(pathTemplate: string, variables: Record>): string { +export function encodeUri(pathTemplate: string, variables: Record): string { for (const key in variables) { if (!variables.hasOwnProperty(key)) { continue;