From bc60a9b59457629cb09c0e144ab0b8aa6d1ff06d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 4 Apr 2023 11:41:46 +0100 Subject: [PATCH] Conform more of the codebase to `strictNullChecks` (#10504 * Conform more of the codebase to `strictNullChecks` * Iterate --- src/LegacyCallHandler.tsx | 2 +- .../dialogs/AddExistingToSpaceDialog.tsx | 13 +++++---- .../dialogs/ConfirmAndWaitRedactDialog.tsx | 2 +- src/components/views/dialogs/ExportDialog.tsx | 14 +++++----- .../views/dialogs/IncomingSasDialog.tsx | 27 ++++++++++--------- .../views/dialogs/LeaveSpaceDialog.tsx | 9 +++---- .../security/CreateCrossSigningDialog.tsx | 6 ++--- src/components/views/messages/MPollBody.tsx | 2 +- .../views/right_panel/PinnedMessagesCard.tsx | 2 +- src/components/views/rooms/MemberList.tsx | 8 +++--- .../right-panel/RightPanelStoreIPanelState.ts | 2 +- src/utils/beacon/useBeacon.ts | 2 +- src/utils/permalinks/Permalinks.ts | 4 +-- .../hooks/useHasRoomLiveVoiceBroadcast.ts | 2 +- test/LegacyCallHandler-test.ts | 8 +++--- .../room/NotificationSettingsTab-test.tsx | 2 +- .../widgets/StopGapWidgetDriver-test.ts | 2 +- test/utils/DMRoomMap-test.ts | 7 ++--- 18 files changed, 60 insertions(+), 54 deletions(-) diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx index 99cd063de07..e6a4388e653 100644 --- a/src/LegacyCallHandler.tsx +++ b/src/LegacyCallHandler.tsx @@ -905,7 +905,7 @@ export default class LegacyCallHandler extends EventEmitter { const timeUntilTurnCresExpire = MatrixClientPeg.get().getTurnServersExpiry() - Date.now(); logger.log("Current turn creds expire in " + timeUntilTurnCresExpire + " ms"); - const call = MatrixClientPeg.get().createCall(mappedRoomId); + const call = MatrixClientPeg.get().createCall(mappedRoomId)!; try { this.addCallForRoom(roomId, call); diff --git a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx index 9156e1b2e08..39104498335 100644 --- a/src/components/views/dialogs/AddExistingToSpaceDialog.tsx +++ b/src/components/views/dialogs/AddExistingToSpaceDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { ReactElement, ReactNode, useContext, useMemo, useRef, useState } from "react"; +import React, { ReactElement, ReactNode, RefObject, useContext, useMemo, useRef, useState } from "react"; import classNames from "classnames"; import { Room } from "matrix-js-sdk/src/models/room"; import { sleep } from "matrix-js-sdk/src/utils"; @@ -140,11 +140,12 @@ export const AddExistingToSpace: React.FC = ({ const cli = useContext(MatrixClientContext); const msc3946ProcessDynamicPredecessor = useSettingValue("feature_dynamic_room_predecessors"); const visibleRooms = useMemo( - () => cli.getVisibleRooms(msc3946ProcessDynamicPredecessor).filter((r) => r.getMyMembership() === "join"), + () => + cli?.getVisibleRooms(msc3946ProcessDynamicPredecessor).filter((r) => r.getMyMembership() === "join") ?? [], [cli, msc3946ProcessDynamicPredecessor], ); - const scrollRef = useRef>(); + const scrollRef = useRef() as RefObject>; const [scrollState, setScrollState] = useState({ // these are estimates which update as soon as it mounts scrollTop: 0, @@ -212,7 +213,7 @@ export const AddExistingToSpace: React.FC = ({ throw e; }); - setProgress((i) => i + 1); + setProgress((i) => (i ?? 0) + 1); } catch (e) { logger.error("Failed to add rooms to space", e); error = e; @@ -305,13 +306,15 @@ export const AddExistingToSpace: React.FC = ({ const onScroll = (): void => { const body = scrollRef.current?.containerRef.current; + if (!body) return; setScrollState({ scrollTop: body.scrollTop, height: body.clientHeight, }); }; - const wrappedRef = (body: HTMLDivElement): void => { + const wrappedRef = (body: HTMLDivElement | null): void => { + if (!body) return; setScrollState({ scrollTop: body.scrollTop, height: body.clientHeight, diff --git a/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx b/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx index 250b90e61df..a7329d6fdee 100644 --- a/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx +++ b/src/components/views/dialogs/ConfirmAndWaitRedactDialog.tsx @@ -29,7 +29,7 @@ interface IProps { interface IState { isRedacting: boolean; - redactionErrorCode: string | number; + redactionErrorCode: string | number | null; } /* diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx index 9ae9272a9bf..7402c3413cc 100644 --- a/src/components/views/dialogs/ExportDialog.tsx +++ b/src/components/views/dialogs/ExportDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useRef, useState, Dispatch, SetStateAction } from "react"; +import React, { useRef, useState, Dispatch, SetStateAction, RefObject } from "react"; import { Room } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; @@ -104,8 +104,8 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { } = useExportFormState(); const [isExporting, setExporting] = useState(false); - const sizeLimitRef = useRef(); - const messageCountRef = useRef(); + const sizeLimitRef = useRef() as RefObject; + const messageCountRef = useRef() as RefObject; const [exportProgressText, setExportProgressText] = useState(_t("Processing…")); const [displayCancel, setCancelWarning] = useState(false); const [exportCancelled, setExportCancelled] = useState(false); @@ -144,18 +144,18 @@ const ExportDialog: React.FC = ({ room, onFinished }) => { const onExportClick = async (): Promise => { const isValidSize = !setSizeLimit || - (await sizeLimitRef.current.validate({ + (await sizeLimitRef.current?.validate({ focused: false, })); if (!isValidSize) { - sizeLimitRef.current.validate({ focused: true }); + sizeLimitRef.current?.validate({ focused: true }); return; } if (exportType === ExportType.LastNMessages) { - const isValidNumberOfMessages = await messageCountRef.current.validate({ focused: false }); + const isValidNumberOfMessages = await messageCountRef.current?.validate({ focused: false }); if (!isValidNumberOfMessages) { - messageCountRef.current.validate({ focused: true }); + messageCountRef.current?.validate({ focused: true }); return; } } diff --git a/src/components/views/dialogs/IncomingSasDialog.tsx b/src/components/views/dialogs/IncomingSasDialog.tsx index 1edb3d9a536..b0bb9cbc814 100644 --- a/src/components/views/dialogs/IncomingSasDialog.tsx +++ b/src/components/views/dialogs/IncomingSasDialog.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactNode } from "react"; import { IGeneratedSas, ISasEvent, SasEvent } from "matrix-js-sdk/src/crypto/verification/SAS"; import { VerificationBase, VerificationEvent } from "matrix-js-sdk/src/crypto/verification/Base"; import { logger } from "matrix-js-sdk/src/logger"; @@ -48,13 +48,13 @@ interface IState { // eslint-disable-next-line camelcase avatar_url?: string; displayname?: string; - }; - opponentProfileError: Error; - sas: IGeneratedSas; + } | null; + opponentProfileError: Error | null; + sas: IGeneratedSas | null; } export default class IncomingSasDialog extends React.Component { - private showSasEvent: ISasEvent; + private showSasEvent: ISasEvent | null; public constructor(props: IProps) { super(props); @@ -93,7 +93,7 @@ export default class IncomingSasDialog extends React.Component { }); } catch (e) { this.setState({ - opponentProfileError: e, + opponentProfileError: e as Error, }); } } @@ -133,7 +133,7 @@ export default class IncomingSasDialog extends React.Component { }; private onSasMatchesClick = (): void => { - this.showSasEvent.confirm(); + this.showSasEvent?.confirm(); this.setState({ phase: PHASE_WAIT_FOR_PARTNER_TO_CONFIRM, }); @@ -143,7 +143,7 @@ export default class IncomingSasDialog extends React.Component { this.props.onFinished(true); }; - private renderPhaseStart(): JSX.Element { + private renderPhaseStart(): ReactNode { const isSelf = this.props.verifier.userId === MatrixClientPeg.get().getUserId(); let profile; @@ -227,7 +227,8 @@ export default class IncomingSasDialog extends React.Component { ); } - private renderPhaseShowSas(): JSX.Element { + private renderPhaseShowSas(): ReactNode { + if (!this.showSasEvent) return null; return ( { ); } - private renderPhaseWaitForPartnerToConfirm(): JSX.Element { + private renderPhaseWaitForPartnerToConfirm(): ReactNode { return (
@@ -248,15 +249,15 @@ export default class IncomingSasDialog extends React.Component { ); } - private renderPhaseVerified(): JSX.Element { + private renderPhaseVerified(): ReactNode { return ; } - private renderPhaseCancelled(): JSX.Element { + private renderPhaseCancelled(): ReactNode { return ; } - public render(): React.ReactNode { + public render(): ReactNode { let body; switch (this.state.phase) { case PHASE_START: diff --git a/src/components/views/dialogs/LeaveSpaceDialog.tsx b/src/components/views/dialogs/LeaveSpaceDialog.tsx index f42697aba4f..83870665eca 100644 --- a/src/components/views/dialogs/LeaveSpaceDialog.tsx +++ b/src/components/views/dialogs/LeaveSpaceDialog.tsx @@ -23,6 +23,7 @@ import DialogButtons from "../elements/DialogButtons"; import BaseDialog from "../dialogs/BaseDialog"; import SpaceStore from "../../../stores/spaces/SpaceStore"; import SpaceChildrenPicker from "../spaces/SpaceChildrenPicker"; +import { filterBoolean } from "../../../utils/arrays"; interface IProps { space: Room; @@ -30,8 +31,8 @@ interface IProps { } const isOnlyAdmin = (room: Room): boolean => { - const userId = room.client.getUserId(); - if (room.getMember(userId).powerLevelNorm !== 100) { + const userId = room.client.getSafeUserId(); + if (room.getMember(userId)?.powerLevelNorm !== 100) { return false; // user is not an admin } return room.getJoinedMembers().every((member) => { @@ -51,9 +52,7 @@ const LeaveSpaceDialog: React.FC = ({ space, onFinished }) => { }, false, ); - return Array.from(roomSet) - .map((roomId) => space.client.getRoom(roomId)) - .filter(Boolean); + return filterBoolean(Array.from(roomSet).map((roomId) => space.client.getRoom(roomId))); }, [space]); const [roomsToLeave, setRoomsToLeave] = useState([]); const selectedRooms = useMemo(() => new Set(roomsToLeave), [roomsToLeave]); diff --git a/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx b/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx index 3f44cfe9d6a..2bac1f028c9 100644 --- a/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx +++ b/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx @@ -32,12 +32,12 @@ import InteractiveAuthDialog from "../InteractiveAuthDialog"; interface IProps { accountPassword?: string; tokenLogin?: boolean; - onFinished?: (success?: boolean) => void; + onFinished: (success?: boolean) => void; } interface IState { error: Error | null; - canUploadKeysWithPasswordOnly?: boolean; + canUploadKeysWithPasswordOnly: boolean | null; accountPassword: string; } @@ -73,7 +73,7 @@ export default class CreateCrossSigningDialog extends React.PureComponent { try { - await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {} as CrossSigningKeys); + await MatrixClientPeg.get().uploadDeviceSigningKeys(undefined, {} as CrossSigningKeys); // We should never get here: the server should always require // UI auth to upload device signing keys. If we do, we upload // no keys which would be a no-op. diff --git a/src/components/views/messages/MPollBody.tsx b/src/components/views/messages/MPollBody.tsx index 21c3ee7ec1f..c701cb4d012 100644 --- a/src/components/views/messages/MPollBody.tsx +++ b/src/components/views/messages/MPollBody.tsx @@ -236,7 +236,7 @@ export default class MPollBody extends React.Component { * @returns userId -> UserVote */ private collectUserVotes(): Map { - if (!this.state.voteRelations) { + if (!this.state.voteRelations || !this.context) { return new Map(); } return collectUserVotes(allVotes(this.state.voteRelations), this.context.getUserId(), this.state.selected); diff --git a/src/components/views/right_panel/PinnedMessagesCard.tsx b/src/components/views/right_panel/PinnedMessagesCard.tsx index ca1bf2ed9d8..b8f250e2856 100644 --- a/src/components/views/right_panel/PinnedMessagesCard.tsx +++ b/src/components/views/right_panel/PinnedMessagesCard.tsx @@ -106,7 +106,7 @@ const PinnedMessagesCard: React.FC = ({ room, onClose, permalinkCreator const newlyRead = pinnedEventIds.filter((id) => !readPinnedEvents.has(id)); if (newlyRead.length > 0) { // clear out any read pinned events which no longer are pinned - cli.setRoomAccountData(room.roomId, ReadPinsEventId, { + cli?.setRoomAccountData(room.roomId, ReadPinsEventId, { event_ids: pinnedEventIds, }); } diff --git a/src/components/views/rooms/MemberList.tsx b/src/components/views/rooms/MemberList.tsx index 6c5a871a9a3..44dba2e32b1 100644 --- a/src/components/views/rooms/MemberList.tsx +++ b/src/components/views/rooms/MemberList.tsx @@ -77,7 +77,7 @@ export default class MemberList extends React.Component { public constructor(props: IProps, context: React.ContextType) { super(props); this.state = this.getMembersState([], []); - this.showPresence = context.memberListStore.isPresenceEnabled(); + this.showPresence = context?.memberListStore.isPresenceEnabled() ?? true; this.mounted = true; this.listenForMembersChanges(); } @@ -278,7 +278,7 @@ export default class MemberList extends React.Component { }); }; - private getPending3PidInvites(): MatrixEvent[] | undefined { + private getPending3PidInvites(): MatrixEvent[] { // include 3pid invites (m.room.third_party_invite) state events. // The HS may have already converted these into m.room.member invites so // we shouldn't add them if the 3pid invite state key (token) is in the @@ -291,11 +291,13 @@ export default class MemberList extends React.Component { // discard all invites which have a m.room.member event since we've // already added them. - const memberEvent = room.currentState.getInviteForThreePidToken(e.getStateKey()); + const memberEvent = room.currentState.getInviteForThreePidToken(e.getStateKey()!); if (memberEvent) return false; return true; }); } + + return []; } private makeMemberTiles(members: Array): JSX.Element[] { diff --git a/src/stores/right-panel/RightPanelStoreIPanelState.ts b/src/stores/right-panel/RightPanelStoreIPanelState.ts index 8064f24c438..826a767ee2f 100644 --- a/src/stores/right-panel/RightPanelStoreIPanelState.ts +++ b/src/stores/right-panel/RightPanelStoreIPanelState.ts @@ -57,7 +57,7 @@ export interface IRightPanelCard { } export interface IRightPanelCardStored { - phase: RightPanelPhases; + phase: RightPanelPhases | null; state?: IRightPanelCardStateStored; } diff --git a/src/utils/beacon/useBeacon.ts b/src/utils/beacon/useBeacon.ts index f857fc8b66d..c664376a577 100644 --- a/src/utils/beacon/useBeacon.ts +++ b/src/utils/beacon/useBeacon.ts @@ -28,7 +28,7 @@ export const useBeacon = (beaconInfoEvent: MatrixEvent): Beacon | undefined => { const roomId = beaconInfoEvent.getRoomId(); const beaconIdentifier = getBeaconInfoIdentifier(beaconInfoEvent); - const room = matrixClient.getRoom(roomId); + const room = matrixClient?.getRoom(roomId); const beaconInstance = room?.currentState.beacons.get(beaconIdentifier); // TODO could this be less stupid? diff --git a/src/utils/permalinks/Permalinks.ts b/src/utils/permalinks/Permalinks.ts index caae2eb6c38..e42f40e8af6 100644 --- a/src/utils/permalinks/Permalinks.ts +++ b/src/utils/permalinks/Permalinks.ts @@ -475,8 +475,8 @@ function isHostnameIpAddress(hostname: string): boolean { return isIp(hostname); } -export const calculateRoomVia = (room: Room): string[] | undefined => { +export const calculateRoomVia = (room: Room): string[] => { const permalinkCreator = new RoomPermalinkCreator(room); permalinkCreator.load(); - return permalinkCreator.serverCandidates; + return permalinkCreator.serverCandidates ?? []; }; diff --git a/src/voice-broadcast/hooks/useHasRoomLiveVoiceBroadcast.ts b/src/voice-broadcast/hooks/useHasRoomLiveVoiceBroadcast.ts index 638e5c3c034..7d4bf6f79fb 100644 --- a/src/voice-broadcast/hooks/useHasRoomLiveVoiceBroadcast.ts +++ b/src/voice-broadcast/hooks/useHasRoomLiveVoiceBroadcast.ts @@ -26,7 +26,7 @@ export const useHasRoomLiveVoiceBroadcast = (room: Room): boolean => { const [hasLiveVoiceBroadcast, setHasLiveVoiceBroadcast] = useState(false); const update = useMemo(() => { - return sdkContext.client + return sdkContext?.client ? () => { hasRoomLiveVoiceBroadcast(sdkContext.client!, room).then( ({ hasBroadcast }) => { diff --git a/test/LegacyCallHandler-test.ts b/test/LegacyCallHandler-test.ts index 11c796b00da..aafbc1275c7 100644 --- a/test/LegacyCallHandler-test.ts +++ b/test/LegacyCallHandler-test.ts @@ -365,7 +365,7 @@ describe("LegacyCallHandler", () => { fakeCall!.getRemoteAssertedIdentity = jest.fn().mockReturnValue({ id: NATIVE_BOB, }); - fakeCall!.emit(CallEvent.AssertedIdentityChanged, fakeCall); + fakeCall!.emit(CallEvent.AssertedIdentityChanged, fakeCall!); // Now set the config option SdkConfig.add({ @@ -378,7 +378,7 @@ describe("LegacyCallHandler", () => { fakeCall!.getRemoteAssertedIdentity = jest.fn().mockReturnValue({ id: NATIVE_CHARLIE, }); - fakeCall!.emit(CallEvent.AssertedIdentityChanged, fakeCall); + fakeCall!.emit(CallEvent.AssertedIdentityChanged, fakeCall!); await roomChangePromise; callHandler.removeAllListeners(); @@ -624,7 +624,7 @@ describe("LegacyCallHandler without third party protocols", () => { // call added to call map expect(callHandler.getCallForRoom(roomId)).toEqual(call); - call.emit(CallEvent.State, CallState.Ringing, CallState.Connected, fakeCall); + call.emit(CallEvent.State, CallState.Ringing, CallState.Connected, fakeCall!); // ringer audio element started expect(mockAudioElement.play).toHaveBeenCalled(); @@ -641,7 +641,7 @@ describe("LegacyCallHandler without third party protocols", () => { // call added to call map expect(callHandler.getCallForRoom(roomId)).toEqual(call); - call.emit(CallEvent.State, CallState.Ringing, CallState.Connected, fakeCall); + call.emit(CallEvent.State, CallState.Ringing, CallState.Connected, fakeCall!); // ringer audio element started expect(mockAudioElement.play).not.toHaveBeenCalled(); diff --git a/test/components/views/settings/tabs/room/NotificationSettingsTab-test.tsx b/test/components/views/settings/tabs/room/NotificationSettingsTab-test.tsx index 94e1a42289c..1b9a393e616 100644 --- a/test/components/views/settings/tabs/room/NotificationSettingsTab-test.tsx +++ b/test/components/views/settings/tabs/room/NotificationSettingsTab-test.tsx @@ -42,7 +42,7 @@ describe("NotificatinSettingsTab", () => { const room = mkStubRoom(roomId, "test room", cli); roomProps = EchoChamber.forRoom(room); - NotificationSettingsTab.contextType = React.createContext(cli); + NotificationSettingsTab.contextType = React.createContext(cli); }); it("should prevent »Settings« link click from bubbling up to radio buttons", async () => { diff --git a/test/stores/widgets/StopGapWidgetDriver-test.ts b/test/stores/widgets/StopGapWidgetDriver-test.ts index 34ac357ca85..756da3f5c74 100644 --- a/test/stores/widgets/StopGapWidgetDriver-test.ts +++ b/test/stores/widgets/StopGapWidgetDriver-test.ts @@ -185,7 +185,7 @@ describe("StopGapWidgetDriver", () => { const aliceMobile = new DeviceInfo("aliceMobile"); const bobDesktop = new DeviceInfo("bobDesktop"); - mocked(client.crypto.deviceList).downloadKeys.mockResolvedValue( + mocked(client.crypto!.deviceList).downloadKeys.mockResolvedValue( new Map([ [ "@alice:example.org", diff --git a/test/utils/DMRoomMap-test.ts b/test/utils/DMRoomMap-test.ts index 7fbb5461019..63f8ef486c3 100644 --- a/test/utils/DMRoomMap-test.ts +++ b/test/utils/DMRoomMap-test.ts @@ -163,8 +163,9 @@ describe("DMRoomMap", () => { beforeEach(() => { client.getAccountData.mockReturnValue(mkMDirectEvent(mDirectContent)); - client.getRoom.mockImplementation((roomId: string) => - [bigRoom, smallRoom, dmWithCharlie, dmWithBob].find((room) => room.roomId === roomId), + client.getRoom.mockImplementation( + (roomId: string) => + [bigRoom, smallRoom, dmWithCharlie, dmWithBob].find((room) => room.roomId === roomId) ?? null, ); }); @@ -183,7 +184,7 @@ describe("DMRoomMap", () => { }); it("excludes rooms that are not found by matrixClient", () => { - client.getRoom.mockReset().mockReturnValue(undefined); + client.getRoom.mockReset().mockReturnValue(null); const dmRoomMap = new DMRoomMap(client); dmRoomMap.start(); expect(dmRoomMap.getUniqueRoomsWithIndividuals()).toEqual({});