From 81a4498a8ff4d8bb349c65e6012529fa95413260 Mon Sep 17 00:00:00 2001 From: Kerry Date: Mon, 3 Apr 2023 20:26:55 +1200 Subject: [PATCH] Apply `strictNullChecks` to `src/utils/*!exportUtils` (#10455 * Apply `strictNullChecks` to `src/utils/exportUtils` * strict fix * fix strictNullChecks issues in some utils * fix error message * test coverage * lint * more strictNullChecks * small optimisation for getUniqueRoomsWithIndividuals * tidy * test coverage --- src/components/structures/InteractiveAuth.tsx | 11 ++-- .../views/dialogs/InteractiveAuthDialog.tsx | 20 ++++--- src/utils/AutoDiscoveryUtils.tsx | 17 +++--- src/utils/DMRoomMap.ts | 23 ++++---- src/utils/IdentityServerUtils.ts | 4 +- src/utils/MediaEventHelper.ts | 5 +- src/utils/TypeUtils.ts | 30 ----------- src/utils/UserInteractiveAuth.ts | 2 +- src/utils/WidgetUtils.ts | 1 + src/utils/leave-behaviour.ts | 6 +++ src/utils/location/map.ts | 6 ++- src/utils/location/parseGeoUri.ts | 15 ++++-- src/utils/stringOrderField.ts | 2 +- src/utils/threepids.ts | 3 ++ test/test-utils/test-utils.ts | 7 ++- test/utils/DMRoomMap-test.ts | 54 ++++++++++++++++++- test/utils/location/map-test.ts | 10 +++- test/utils/location/parseGeoUri-test.ts | 8 +++ 18 files changed, 143 insertions(+), 81 deletions(-) delete mode 100644 src/utils/TypeUtils.ts diff --git a/src/components/structures/InteractiveAuth.tsx b/src/components/structures/InteractiveAuth.tsx index d737c6dd5e..b3c8fc26fd 100644 --- a/src/components/structures/InteractiveAuth.tsx +++ b/src/components/structures/InteractiveAuth.tsx @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +import React, { createRef } from "react"; import { AuthType, IAuthData, @@ -23,8 +24,8 @@ import { IStageStatus, } from "matrix-js-sdk/src/interactive-auth"; import { MatrixClient } from "matrix-js-sdk/src/client"; -import React, { createRef } from "react"; import { logger } from "matrix-js-sdk/src/logger"; +import { UIAResponse } from "matrix-js-sdk/src/@types/uia"; import getEntryComponentForLoginType, { IStageComponent } from "../views/auth/InteractiveAuthEntryComponents"; import Spinner from "../views/elements/Spinner"; @@ -39,7 +40,7 @@ type InteractiveAuthCallbackSuccess = ( type InteractiveAuthCallbackFailure = (success: false, response: IAuthData | Error) => void; export type InteractiveAuthCallback = InteractiveAuthCallbackSuccess & InteractiveAuthCallbackFailure; -interface IProps { +export interface InteractiveAuthProps { // matrix client to use for UI auth requests matrixClient: MatrixClient; // response from initial request. If not supplied, will do a request on mount. @@ -61,7 +62,7 @@ interface IProps { continueText?: string; continueKind?: string; // callback - makeRequest(auth: IAuthData | null): Promise; + makeRequest(auth?: IAuthData): Promise>; // callback called when the auth process has finished, // successfully or unsuccessfully. // @param {boolean} status True if the operation requiring @@ -92,14 +93,14 @@ interface IState { submitButtonEnabled: boolean; } -export default class InteractiveAuthComponent extends React.Component { +export default class InteractiveAuthComponent extends React.Component, IState> { private readonly authLogic: InteractiveAuth; private readonly intervalId: number | null = null; private readonly stageComponent = createRef(); private unmounted = false; - public constructor(props: IProps) { + public constructor(props: InteractiveAuthProps) { super(props); this.state = { diff --git a/src/components/views/dialogs/InteractiveAuthDialog.tsx b/src/components/views/dialogs/InteractiveAuthDialog.tsx index 14b800243d..393808ddf8 100644 --- a/src/components/views/dialogs/InteractiveAuthDialog.tsx +++ b/src/components/views/dialogs/InteractiveAuthDialog.tsx @@ -22,7 +22,11 @@ import { AuthType, IAuthData } from "matrix-js-sdk/src/interactive-auth"; import { _t } from "../../../languageHandler"; import AccessibleButton from "../elements/AccessibleButton"; -import InteractiveAuth, { ERROR_USER_CANCELLED, InteractiveAuthCallback } from "../../structures/InteractiveAuth"; +import InteractiveAuth, { + ERROR_USER_CANCELLED, + InteractiveAuthCallback, + InteractiveAuthProps, +} from "../../structures/InteractiveAuth"; import { SSOAuthEntry } from "../auth/InteractiveAuthEntryComponents"; import BaseDialog from "./BaseDialog"; @@ -37,17 +41,11 @@ type DialogAesthetics = Partial<{ }; }>; -export interface InteractiveAuthDialogProps { +export interface InteractiveAuthDialogProps + extends Pick, "makeRequest" | "authData"> { // matrix client to use for UI auth requests matrixClient: MatrixClient; - // response from initial request. If not supplied, will do a request on - // mount. - authData?: IAuthData; - - // callback - makeRequest: (auth: IAuthData) => Promise; - // Optional title and body to show when not showing a particular stage title?: string; body?: string; @@ -83,8 +81,8 @@ interface IState { uiaStagePhase: number | null; } -export default class InteractiveAuthDialog extends React.Component { - public constructor(props: InteractiveAuthDialogProps) { +export default class InteractiveAuthDialog extends React.Component, IState> { + public constructor(props: InteractiveAuthDialogProps) { super(props); this.state = { diff --git a/src/utils/AutoDiscoveryUtils.tsx b/src/utils/AutoDiscoveryUtils.tsx index b25de6a971..70f40af883 100644 --- a/src/utils/AutoDiscoveryUtils.tsx +++ b/src/utils/AutoDiscoveryUtils.tsx @@ -20,7 +20,6 @@ import { logger } from "matrix-js-sdk/src/logger"; import { IClientWellKnown } from "matrix-js-sdk/src/matrix"; import { _t, UserFriendlyError } from "../languageHandler"; -import { makeType } from "./TypeUtils"; import SdkConfig from "../SdkConfig"; import { ValidatedServerConfig } from "./ValidatedServerConfig"; @@ -43,7 +42,7 @@ export default class AutoDiscoveryUtils { * @param {string | Error} error The error to check * @returns {boolean} True if the error is a liveliness error. */ - public static isLivelinessError(error: string | Error): boolean { + public static isLivelinessError(error?: string | Error | null): boolean { if (!error) return false; return !!LIVELINESS_DISCOVERY_ERRORS.find((e) => typeof error === "string" ? e === error : e === error.message, @@ -197,7 +196,7 @@ export default class AutoDiscoveryUtils { ): ValidatedServerConfig { if (!discoveryResult || !discoveryResult["m.homeserver"]) { // This shouldn't happen without major misconfiguration, so we'll log a bit of information - // in the log so we can find this bit of codee but otherwise tell teh user "it broke". + // in the log so we can find this bit of code but otherwise tell the user "it broke". logger.error("Ended up in a state of not knowing which homeserver to connect to."); throw new UserFriendlyError("Unexpected error resolving homeserver configuration"); } @@ -216,7 +215,7 @@ export default class AutoDiscoveryUtils { // of Element. let preferredIdentityUrl = defaultConfig && defaultConfig["isUrl"]; if (isResult && isResult.state === AutoDiscovery.SUCCESS) { - preferredIdentityUrl = isResult["base_url"]; + preferredIdentityUrl = isResult["base_url"] ?? undefined; } else if (isResult && isResult.state !== AutoDiscovery.PROMPT) { logger.error("Error determining preferred identity server URL:", isResult); if (isResult.state === AutoDiscovery.FAIL_ERROR) { @@ -244,6 +243,12 @@ export default class AutoDiscoveryUtils { } const preferredHomeserverUrl = hsResult["base_url"]; + + if (!preferredHomeserverUrl) { + logger.error("No homeserver URL configured"); + throw new UserFriendlyError("Unexpected error resolving homeserver configuration"); + } + let preferredHomeserverName = serverName ? serverName : hsResult["server_name"]; const url = new URL(preferredHomeserverUrl); @@ -255,7 +260,7 @@ export default class AutoDiscoveryUtils { throw new UserFriendlyError("Unexpected error resolving homeserver configuration"); } - return makeType(ValidatedServerConfig, { + return { hsUrl: preferredHomeserverUrl, hsName: preferredHomeserverName, hsNameIsDifferent: url.hostname !== preferredHomeserverName, @@ -263,6 +268,6 @@ export default class AutoDiscoveryUtils { isDefault: false, warning: hsResult.error, isNameResolvable: !isSynthetic, - }); + } as ValidatedServerConfig; } } diff --git a/src/utils/DMRoomMap.ts b/src/utils/DMRoomMap.ts index 1f95f5be61..37c0f873bc 100644 --- a/src/utils/DMRoomMap.ts +++ b/src/utils/DMRoomMap.ts @@ -103,8 +103,6 @@ export default class DMRoomMap { } private onAccountData = (ev: MatrixEvent): void => { - console.log("onAccountData"); - if (ev.getType() == EventType.Direct) { this.setMDirectFromContent(ev.getContent()); this.userToRooms = null; @@ -207,13 +205,16 @@ export default class DMRoomMap { public getUniqueRoomsWithIndividuals(): { [userId: string]: Room } { if (!this.roomToUser) return {}; // No rooms means no map. - return Object.keys(this.roomToUser) - .map((r) => ({ userId: this.getUserIdForRoomId(r), room: this.matrixClient.getRoom(r) })) - .filter((r) => r.userId && r.room?.getInvitedAndJoinedMemberCount() === 2) - .reduce((obj, r) => { - obj[r.userId] = r.room; - return obj; - }, {} as Record); + // map roomToUser to valid rooms with two participants + return Object.keys(this.roomToUser).reduce((acc, roomId: string) => { + const userId = this.getUserIdForRoomId(roomId); + const room = this.matrixClient.getRoom(roomId); + const hasTwoMembers = room?.getInvitedAndJoinedMemberCount() === 2; + if (userId && room && hasTwoMembers) { + acc[userId] = room; + } + return acc; + }, {} as Record); } /** @@ -236,9 +237,7 @@ export default class DMRoomMap { // to avoid multiple devices fighting to correct // the account data, only try to send the corrected // version once. - logger.warn( - `Invalid m.direct account data detected ` + `(self-chats that shouldn't be), patching it up.`, - ); + logger.warn(`Invalid m.direct account data detected (self-chats that shouldn't be), patching it up.`); if (neededPatching && !this.hasSentOutPatchDirectAccountDataPatch) { this.hasSentOutPatchDirectAccountDataPatch = true; this.matrixClient.setAccountData(EventType.Direct, userToRooms); diff --git a/src/utils/IdentityServerUtils.ts b/src/utils/IdentityServerUtils.ts index a9a5c754a0..06a01b8a9d 100644 --- a/src/utils/IdentityServerUtils.ts +++ b/src/utils/IdentityServerUtils.ts @@ -21,8 +21,8 @@ import SdkConfig from "../SdkConfig"; import { MatrixClientPeg } from "../MatrixClientPeg"; import { Policies } from "../Terms"; -export function getDefaultIdentityServerUrl(): string { - return SdkConfig.get("validated_server_config").isUrl; +export function getDefaultIdentityServerUrl(): string | undefined { + return SdkConfig.get("validated_server_config")?.isUrl; } export function setToDefaultIdentityServer(): void { diff --git a/src/utils/MediaEventHelper.ts b/src/utils/MediaEventHelper.ts index 2e576f9a11..06bac223cd 100644 --- a/src/utils/MediaEventHelper.ts +++ b/src/utils/MediaEventHelper.ts @@ -102,7 +102,10 @@ export class MediaEventHelper implements IDestroyable { } } - return fetch(this.media.thumbnailHttp).then((r) => r.blob()); + const thumbnailHttp = this.media.thumbnailHttp; + if (!thumbnailHttp) return Promise.resolve(null); + + return fetch(thumbnailHttp).then((r) => r.blob()); }; public static isEligible(event: MatrixEvent): boolean { diff --git a/src/utils/TypeUtils.ts b/src/utils/TypeUtils.ts deleted file mode 100644 index b732cd8ace..0000000000 --- a/src/utils/TypeUtils.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright 2019 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/** - * Creates a class of a given type using the objects defined. This - * is a stopgap function while we don't have TypeScript interfaces. - * In future, we'd define the `type` as an interface and just cast - * it instead of cheating like we are here. - * @param {Type} Type The type of class to construct. - * @param {*} opts The options (properties) to set on the object. - * @returns {*} The created object. - */ -export function makeType(Type: { new (): T }, opts: Partial): T { - const c = new Type(); - Object.assign(c, opts); - return c; -} diff --git a/src/utils/UserInteractiveAuth.ts b/src/utils/UserInteractiveAuth.ts index c5d1c84c47..c953f0c9bd 100644 --- a/src/utils/UserInteractiveAuth.ts +++ b/src/utils/UserInteractiveAuth.ts @@ -24,7 +24,7 @@ type FunctionWithUIA = (auth?: IAuthData, ...args: A[]) => Promise( requestFunction: FunctionWithUIA, - opts: Omit, + opts: Omit, "makeRequest" | "onFinished">, ): (...args: A[]) => Promise { return async function (...args): Promise { return new Promise((resolve, reject) => { diff --git a/src/utils/WidgetUtils.ts b/src/utils/WidgetUtils.ts index 9fb888bc63..00d10c9d3c 100644 --- a/src/utils/WidgetUtils.ts +++ b/src/utils/WidgetUtils.ts @@ -119,6 +119,7 @@ export default class WidgetUtils { if ( testUrl.protocol === scalarUrl.protocol && testUrl.host === scalarUrl.host && + scalarUrl.pathname && testUrl.pathname?.startsWith(scalarUrl.pathname) ) { return true; diff --git a/src/utils/leave-behaviour.ts b/src/utils/leave-behaviour.ts index 68e7e49cbc..bfa021167d 100644 --- a/src/utils/leave-behaviour.ts +++ b/src/utils/leave-behaviour.ts @@ -61,6 +61,12 @@ export async function leaveRoomBehaviour(roomId: string, retry = true, spinner = } const room = cli.getRoom(roomId); + + // should not encounter this + if (!room) { + throw new Error(`Expected to find room for id ${roomId}`); + } + // await any queued messages being sent so that they do not fail await Promise.all( room diff --git a/src/utils/location/map.ts b/src/utils/location/map.ts index 8fe698b5eb..8c8271f9c4 100644 --- a/src/utils/location/map.ts +++ b/src/utils/location/map.ts @@ -85,12 +85,14 @@ export const createMapSiteLinkFromEvent = (event: MatrixEvent): string | null => if (mLocation !== undefined) { const uri = mLocation["uri"]; if (uri !== undefined) { - return makeMapSiteLink(parseGeoUri(uri)); + const geoCoords = parseGeoUri(uri); + return geoCoords ? makeMapSiteLink(geoCoords) : null; } } else { const geoUri = content["geo_uri"]; if (geoUri) { - return makeMapSiteLink(parseGeoUri(geoUri)); + const geoCoords = parseGeoUri(geoUri); + return geoCoords ? makeMapSiteLink(geoCoords) : null; } } return null; diff --git a/src/utils/location/parseGeoUri.ts b/src/utils/location/parseGeoUri.ts index c0de9cf416..d891f19299 100644 --- a/src/utils/location/parseGeoUri.ts +++ b/src/utils/location/parseGeoUri.ts @@ -28,16 +28,23 @@ export const parseGeoUri = (uri: string): GeolocationCoordinates | undefined => if (!m) return; const parts = m[1].split(";"); const coords = parts[0].split(","); - let uncertainty: number | null; + let uncertainty: number | null | undefined = undefined; for (const param of parts.slice(1)) { const m = param.match(/u=(.*)/); if (m) uncertainty = parse(m[1]); } + const latitude = parse(coords[0]); + const longitude = parse(coords[1]); + + if (latitude === null || longitude === null) { + return; + } + return { - latitude: parse(coords[0]), - longitude: parse(coords[1]), + latitude: latitude!, + longitude: longitude!, altitude: parse(coords[2]), - accuracy: uncertainty, + accuracy: uncertainty!, altitudeAccuracy: null, heading: null, speed: null, diff --git a/src/utils/stringOrderField.ts b/src/utils/stringOrderField.ts index 22f5022f21..fb0be13ed1 100644 --- a/src/utils/stringOrderField.ts +++ b/src/utils/stringOrderField.ts @@ -120,7 +120,7 @@ export const reorderLexicographically = ( // verify the right move would be sufficient if ( rightBoundIdx === newOrder.length - 1 && - (newOrder[rightBoundIdx] ? stringToBase(newOrder[rightBoundIdx].order) : BigInt(Number.MAX_VALUE)) - + (newOrder[rightBoundIdx]?.order ? stringToBase(newOrder[rightBoundIdx].order!) : BigInt(Number.MAX_VALUE)) - prevBase <= rightBoundIdx - toIndex ) { diff --git a/src/utils/threepids.ts b/src/utils/threepids.ts index 57d42b5875..f77840329c 100644 --- a/src/utils/threepids.ts +++ b/src/utils/threepids.ts @@ -65,6 +65,9 @@ export const lookupThreePids = async ( if (threePids.length === 0) return []; const token = await client.identityServer.getAccessToken(); + + if (!token) return []; + const lookedUp = await client.bulkLookupThreePids( threePids.map((t) => [t.isEmail ? "email" : "msisdn", t.userId]), token, diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 35823af88c..92ba173aa9 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -47,7 +47,6 @@ import { MapperOpts } from "matrix-js-sdk/src/event-mapper"; import type { GroupCall } from "matrix-js-sdk/src/webrtc/groupCall"; import { MatrixClientPeg as peg } from "../../src/MatrixClientPeg"; -import { makeType } from "../../src/utils/TypeUtils"; import { ValidatedServerConfig } from "../../src/utils/ValidatedServerConfig"; import { EnhancedMap } from "../../src/utils/maps"; import { AsyncStoreWithClient } from "../../src/stores/AsyncStoreWithClient"; @@ -591,13 +590,13 @@ export function mkStubRoom( } as unknown as Room; } -export function mkServerConfig(hsUrl: string, isUrl: string) { - return makeType(ValidatedServerConfig, { +export function mkServerConfig(hsUrl: string, isUrl: string): ValidatedServerConfig { + return { hsUrl, hsName: "TEST_ENVIRONMENT", hsNameIsDifferent: false, // yes, we lie isUrl, - }); + } as ValidatedServerConfig; } // These methods make some use of some private methods on the AsyncStoreWithClient to simplify getting into a consistent diff --git a/test/utils/DMRoomMap-test.ts b/test/utils/DMRoomMap-test.ts index 03591ae54a..7fbb546101 100644 --- a/test/utils/DMRoomMap-test.ts +++ b/test/utils/DMRoomMap-test.ts @@ -16,7 +16,7 @@ limitations under the License. import { mocked, Mocked } from "jest-mock"; import { logger } from "matrix-js-sdk/src/logger"; -import { ClientEvent, EventType, IContent, MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; +import { ClientEvent, EventType, IContent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix"; import DMRoomMap from "../../src/utils/DMRoomMap"; import { mkEvent, stubClient } from "../test-utils"; @@ -137,4 +137,56 @@ describe("DMRoomMap", () => { expect(dmRoomMap.getRoomIds()).toEqual(new Set([roomId1, roomId2, roomId4])); }); }); + + describe("getUniqueRoomsWithIndividuals()", () => { + const bigRoom = { + roomId: "!bigRoom:server.org", + getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(5000), + } as unknown as Room; + const dmWithBob = { + roomId: "!dmWithBob:server.org", + getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(2), + } as unknown as Room; + const dmWithCharlie = { + roomId: "!dmWithCharlie:server.org", + getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(2), + } as unknown as Room; + const smallRoom = { + roomId: "!smallRoom:server.org", + getInvitedAndJoinedMemberCount: jest.fn().mockReturnValue(3), + } as unknown as Room; + + const mDirectContent = { + "@bob:server.org": [bigRoom.roomId, dmWithBob.roomId, smallRoom.roomId], + "@charlie:server.org": [dmWithCharlie.roomId, smallRoom.roomId], + }; + + beforeEach(() => { + client.getAccountData.mockReturnValue(mkMDirectEvent(mDirectContent)); + client.getRoom.mockImplementation((roomId: string) => + [bigRoom, smallRoom, dmWithCharlie, dmWithBob].find((room) => room.roomId === roomId), + ); + }); + + it("returns an empty object when room map has not been populated", () => { + const instance = new DMRoomMap(client); + expect(instance.getUniqueRoomsWithIndividuals()).toEqual({}); + }); + + it("returns map of users to rooms with 2 members", () => { + const dmRoomMap = new DMRoomMap(client); + dmRoomMap.start(); + expect(dmRoomMap.getUniqueRoomsWithIndividuals()).toEqual({ + "@bob:server.org": dmWithBob, + "@charlie:server.org": dmWithCharlie, + }); + }); + + it("excludes rooms that are not found by matrixClient", () => { + client.getRoom.mockReset().mockReturnValue(undefined); + const dmRoomMap = new DMRoomMap(client); + dmRoomMap.start(); + expect(dmRoomMap.getUniqueRoomsWithIndividuals()).toEqual({}); + }); + }); }); diff --git a/test/utils/location/map-test.ts b/test/utils/location/map-test.ts index 09e91d9fb3..4865cefc73 100644 --- a/test/utils/location/map-test.ts +++ b/test/utils/location/map-test.ts @@ -31,15 +31,23 @@ describe("createMapSiteLinkFromEvent", () => { ).toBeNull(); }); - it("returns OpenStreetMap link if event contains m.location", () => { + it("returns OpenStreetMap link if event contains m.location with valid uri", () => { expect(createMapSiteLinkFromEvent(makeLocationEvent("geo:51.5076,-0.1276"))).toEqual( "https://www.openstreetmap.org/" + "?mlat=51.5076&mlon=-0.1276" + "#map=16/51.5076/-0.1276", ); }); + it("returns null if event contains m.location with invalid uri", () => { + expect(createMapSiteLinkFromEvent(makeLocationEvent("123 Sesame St"))).toBeNull(); + }); + it("returns OpenStreetMap link if event contains geo_uri", () => { expect(createMapSiteLinkFromEvent(makeLegacyLocationEvent("geo:51.5076,-0.1276"))).toEqual( "https://www.openstreetmap.org/" + "?mlat=51.5076&mlon=-0.1276" + "#map=16/51.5076/-0.1276", ); }); + + it("returns null if event contains an invalid geo_uri", () => { + expect(createMapSiteLinkFromEvent(makeLegacyLocationEvent("123 Sesame St"))).toBeNull(); + }); }); diff --git a/test/utils/location/parseGeoUri-test.ts b/test/utils/location/parseGeoUri-test.ts index 027d0b3e85..d7ab8d7a5b 100644 --- a/test/utils/location/parseGeoUri-test.ts +++ b/test/utils/location/parseGeoUri-test.ts @@ -21,6 +21,14 @@ describe("parseGeoUri", () => { expect(parseGeoUri("")).toBeFalsy(); }); + it("returns undefined if latitude is not a number", () => { + expect(parseGeoUri("geo:ABCD,16.3695,183")).toBeUndefined(); + }); + + it("returns undefined if longitude is not a number", () => { + expect(parseGeoUri("geo:48.2010,EFGH,183")).toBeUndefined(); + }); + // We use some examples from the spec, but don't check semantics // like two textually-different URIs being equal, since we are // just a humble parser.