From 0c482535e194f19dba20bee82a82828a5cdb382a Mon Sep 17 00:00:00 2001
From: Graham Langford <30706330+grahamlangford@users.noreply.github.com>
Date: Thu, 1 Aug 2024 08:40:05 -0500
Subject: [PATCH] Strict null checks -- 93.2% (#8961)
* JQueryReaderOptions
* getAllReaders
* auto-add
* Tabs.tsx
* ApiTaskOptions.tsx
* PushOptions.tsx
* ActivateModDefinitionPage.tsx
* auto-add
* DeploymentModal.tsx
* auto-add
* ListView.tsx
* test updates
* auto-add
* ConvertToModModalBody.tsx
* auto-add
* CancelPublishContent.tsx
* EditPublishContent.tsx
* PublishedContent.tsx
* PublishModContent.tsx
* auto-add
* PublishModModals.tsx
* auto-add
* ModEditorPane.tsx
* cleanup type
---
src/bricks/readers/getAllReaders.ts | 6 +-
.../jquery/JQueryReaderOptions.tsx | 19 ++++--
src/contrib/automationanywhere/RunApiTask.ts | 3 +-
src/contrib/zapier/PushOptions.tsx | 2 +-
.../pages/activateMod/ActivateModCard.tsx | 2 +-
.../activateMod/ActivateModDefinitionPage.tsx | 4 +-
.../pages/deployments/DeploymentModal.tsx | 4 +-
.../pages/mods/listView/ListView.tsx | 3 +
.../ConvertToModModalBody.tsx | 10 +--
.../shareModals/CancelPublishContent.tsx | 20 ++++--
.../modals/shareModals/EditPublishContent.tsx | 7 +-
.../modals/shareModals/PublishModContent.tsx | 19 +++++-
.../modals/shareModals/PublishModModals.tsx | 6 +-
.../modals/shareModals/PublishedContent.tsx | 9 ++-
src/pageEditor/panes/ModEditorPane.tsx | 8 ++-
src/sidebar/Tabs.tsx | 12 ++--
src/sidebar/sidebarSelectors.ts | 13 ++--
src/store/extensionsSelectors.ts | 7 +-
src/store/settings/settingsSelectors.ts | 3 +-
src/store/sidebar/eventKeyUtils.test.ts | 8 +--
src/store/sidebar/eventKeyUtils.tsx | 14 ++--
src/store/sidebar/initialState.ts | 1 -
src/store/sidebar/sidebarSlice.test.ts | 2 +-
src/tsconfig.strictNullChecks.json | 68 ++++++++++++++-----
src/types/sidebarTypes.ts | 4 +-
src/utils/registryUtils.ts | 10 ++-
26 files changed, 179 insertions(+), 85 deletions(-)
diff --git a/src/bricks/readers/getAllReaders.ts b/src/bricks/readers/getAllReaders.ts
index 1ac1ff36f..7d52cf23e 100644
--- a/src/bricks/readers/getAllReaders.ts
+++ b/src/bricks/readers/getAllReaders.ts
@@ -22,8 +22,8 @@ import { ImageReader } from "./ImageReader";
import { SelectionReader } from "./SelectionReader";
import { ImageExifReader } from "./ImageExifReader";
import { ElementReader } from "./ElementReader";
-import { registerFactory } from "./factory";
-import { frameworkReadFactory } from "./frameworkReader";
+import { type Read, registerFactory } from "./factory";
+import { type FrameworkConfig, frameworkReadFactory } from "./frameworkReader";
import { readJQuery } from "@/bricks/readers/jquery";
import { HtmlReader } from "./HtmlReader";
import DocumentReader from "./DocumentReader";
@@ -59,7 +59,7 @@ export function registerReaderFactories(): void {
registerFactory("react", frameworkReadFactory("react"));
registerFactory("vue", frameworkReadFactory("vue"));
registerFactory("vuejs", frameworkReadFactory("vue"));
- registerFactory("jquery", readJQuery);
+ registerFactory("jquery", readJQuery as unknown as Read);
}
export default getAllReaders;
diff --git a/src/bricks/transformers/jquery/JQueryReaderOptions.tsx b/src/bricks/transformers/jquery/JQueryReaderOptions.tsx
index ee9ad9fc4..bff212bfd 100644
--- a/src/bricks/transformers/jquery/JQueryReaderOptions.tsx
+++ b/src/bricks/transformers/jquery/JQueryReaderOptions.tsx
@@ -47,6 +47,8 @@ import { joinName } from "@/utils/formUtils";
import { freshIdentifier } from "@/utils/variableUtils";
import useAsyncEffect from "use-async-effect";
import { inspectedTab } from "@/pageEditor/context/connection";
+import { assertNotNullish } from "@/utils/nullishUtils";
+import { type SetOptional } from "type-fest";
/**
* Version of SelectorConfig where fields may be expressions.
@@ -182,9 +184,9 @@ const SelectorCard: React.FC<{
*/
onChange: (item: SelectorItem) => void;
/**
- * Delete handler, or null if selector item cannot be deleted.
+ * Delete handler, or undefined if selector item cannot be deleted.
*/
- onDelete: (() => void) | null;
+ onDelete: (() => void) | undefined;
/**
* The Formik path to selector configuration.
*/
@@ -225,9 +227,11 @@ const SelectorCard: React.FC<{
return [];
}, [selectorDefinition.selector, rootSelector]),
- [],
+ [] as AttributeExample[],
);
+ assertNotNullish(attributeExamples, "attributeExamples is nullish");
+
const typeOption = inferActiveTypeOption(selectorDefinition);
const typeOptions = typeOptionsFactory(attributeExamples, typeOption);
@@ -313,9 +317,10 @@ const SelectorCard: React.FC<{
selector: produce(selectorDefinition, (draft) => {
// `draft` is either a SingleSelector or a ChildrenSelector. Cast as intersection type so we can clean
// up the values in the alternative type.
- const commonDraft = draft as SingleSelector & ChildrenSelector;
+ const commonDraft = draft as SingleSelector &
+ SetOptional;
- if (next.startsWith(ATTRIBUTE_OPTION_VALUE_PREFIX)) {
+ if (next?.startsWith(ATTRIBUTE_OPTION_VALUE_PREFIX)) {
const attributeName = next.slice(
ATTRIBUTE_OPTION_VALUE_PREFIX.length,
);
@@ -376,7 +381,7 @@ const SelectorCard: React.FC<{
const SelectorsOptions: React.FC<{
path: string;
- rootSelector: string;
+ rootSelector: string | null;
nestingLevel: number;
}> = ({ path, rootSelector, nestingLevel }) => {
const configName = partial(joinName, path);
@@ -468,7 +473,7 @@ const SelectorsOptions: React.FC<{
selectorItems.filter((_, i) => i !== index),
);
}
- : null
+ : undefined
}
path={configName(name)}
/>
diff --git a/src/contrib/automationanywhere/RunApiTask.ts b/src/contrib/automationanywhere/RunApiTask.ts
index c9ef2e2a4..b2039804e 100644
--- a/src/contrib/automationanywhere/RunApiTask.ts
+++ b/src/contrib/automationanywhere/RunApiTask.ts
@@ -31,8 +31,9 @@ import { type ApiTaskArgs } from "@/contrib/automationanywhere/aaTypes";
import { type BrickArgs, type BrickOptions } from "@/types/runtimeTypes";
import { BusinessError } from "@/errors/businessErrors";
import { minimalSchemaFactory } from "@/utils/schemaUtils";
+import { type SetRequired } from "type-fest";
-export const RUN_API_TASK_INPUT_SCHEMA: Schema = {
+export const RUN_API_TASK_INPUT_SCHEMA: SetRequired = {
$schema: "https://json-schema.org/draft/2019-09/schema#",
type: "object",
properties: {
diff --git a/src/contrib/zapier/PushOptions.tsx b/src/contrib/zapier/PushOptions.tsx
index 290bd6130..642c9ad3c 100644
--- a/src/contrib/zapier/PushOptions.tsx
+++ b/src/contrib/zapier/PushOptions.tsx
@@ -55,7 +55,7 @@ function useHooks(): AsyncState {
}
const ZapField: React.FunctionComponent<
- SchemaFieldProps & { hooks: Webhook[]; error: unknown }
+ SchemaFieldProps & { hooks?: Webhook[]; error: unknown }
> = ({ hooks, error, ...props }) => {
const options = useMemo(
() =>
diff --git a/src/extensionConsole/pages/activateMod/ActivateModCard.tsx b/src/extensionConsole/pages/activateMod/ActivateModCard.tsx
index 117cd5e73..147a1b8da 100644
--- a/src/extensionConsole/pages/activateMod/ActivateModCard.tsx
+++ b/src/extensionConsole/pages/activateMod/ActivateModCard.tsx
@@ -76,7 +76,7 @@ const WizardHeader: React.VoidFunctionComponent<{
const ActivateModCard: React.FC<{
modDefinition: ModDefinition;
isReactivate: boolean;
- forceModComponentId: UUID;
+ forceModComponentId?: UUID;
}> = ({ modDefinition, isReactivate, forceModComponentId }) => {
const dispatch = useDispatch();
diff --git a/src/extensionConsole/pages/activateMod/ActivateModDefinitionPage.tsx b/src/extensionConsole/pages/activateMod/ActivateModDefinitionPage.tsx
index 5487ba012..ee7089b3d 100644
--- a/src/extensionConsole/pages/activateMod/ActivateModDefinitionPage.tsx
+++ b/src/extensionConsole/pages/activateMod/ActivateModDefinitionPage.tsx
@@ -33,6 +33,7 @@ import { BusinessError } from "@/errors/businessErrors";
import { DefinitionKinds } from "@/types/registryTypes";
import { truncate } from "lodash";
import { type UUID } from "@/types/stringTypes";
+import { assertNotNullish } from "@/utils/nullishUtils";
/**
* Effect to automatically redirect the user to the mods screen if the mod is not found.
@@ -114,12 +115,13 @@ const ActivateModDefinitionPage: React.FC<{
}
const title = `${isReactivate ? "Reactivate" : "Activate"} ${truncate(
- modDefinition.metadata.name,
+ modDefinition?.metadata.name,
{
length: 15,
},
)}`;
+ assertNotNullish(modDefinitionQuery.data, "modDefinition is nullish");
// Require that bricks have been fetched at least once before showing. Handles new mod activation where the bricks
// haven't been completely fetched yet.
// XXX: we might also want to enforce a full re-sync of the brick registry to ensure the latest brick
diff --git a/src/extensionConsole/pages/deployments/DeploymentModal.tsx b/src/extensionConsole/pages/deployments/DeploymentModal.tsx
index 49757d1a0..affeb6839 100644
--- a/src/extensionConsole/pages/deployments/DeploymentModal.tsx
+++ b/src/extensionConsole/pages/deployments/DeploymentModal.tsx
@@ -58,11 +58,11 @@ function useCurrentTime() {
*/
export const CountdownTimer: React.FunctionComponent<{
duration: number;
- start: number;
+ start: number | null;
onFinish?: () => void;
}> = ({ duration, start, onFinish = noop }) => {
const now = useCurrentTime();
- const remaining = duration - (now - start);
+ const remaining = duration - (now - Number(start));
const isExpired = remaining < 0;
useEffect(() => {
diff --git a/src/extensionConsole/pages/mods/listView/ListView.tsx b/src/extensionConsole/pages/mods/listView/ListView.tsx
index 1c25dcd42..442f37d0c 100644
--- a/src/extensionConsole/pages/mods/listView/ListView.tsx
+++ b/src/extensionConsole/pages/mods/listView/ListView.tsx
@@ -23,6 +23,7 @@ import ListGroupHeader from "@/extensionConsole/pages/mods/listView/ListGroupHea
import { uuidv4 } from "@/types/helpers";
import ListItemErrorBoundary from "@/extensionConsole/pages/mods/listView/ListItemErrorBoundary";
import { type ModsPageContentProps } from "@/extensionConsole/pages/mods/modsPageTypes";
+import { assertNotNullish } from "@/utils/nullishUtils";
const ROW_HEIGHT_PX = 90;
const HEADER_ROW_HEIGHT_PX = 43;
@@ -42,6 +43,7 @@ const ListView: React.VoidFunctionComponent = ({
const getItemSize = useCallback(
(index: number) => {
const row = expandedRows.at(index);
+ assertNotNullish(row, `Unable to find row at index ${index}`);
return row.isGrouped ? HEADER_ROW_HEIGHT_PX : ROW_HEIGHT_PX;
},
[expandedRows],
@@ -66,6 +68,7 @@ const ListView: React.VoidFunctionComponent = ({
>
{({ index, style }) => {
const row = expandedRows.at(index);
+ assertNotNullish(row, `Unable to find row at index ${index}`);
tableInstance.prepareRow(row);
return row.isGrouped ? (
diff --git a/src/extensionConsole/pages/mods/modals/convertToModModal/ConvertToModModalBody.tsx b/src/extensionConsole/pages/mods/modals/convertToModModal/ConvertToModModalBody.tsx
index 646fe4a5c..766808477 100644
--- a/src/extensionConsole/pages/mods/modals/convertToModModal/ConvertToModModalBody.tsx
+++ b/src/extensionConsole/pages/mods/modals/convertToModModal/ConvertToModModalBody.tsx
@@ -52,6 +52,7 @@ import { generatePackageId } from "@/utils/registryUtils";
import { FieldDescriptions } from "@/modDefinitions/modDefinitionConstants";
import { pickModDefinitionMetadata } from "@/modDefinitions/util/pickModDefinitionMetadata";
+import { assertNotNullish } from "@/utils/nullishUtils";
type ConvertModFormState = {
blueprintId: RegistryId;
@@ -125,7 +126,7 @@ const ConvertToModModalBody: React.FunctionComponent = () => {
standaloneModDefinitions?.find((x) => x.id === modComponentId);
if (modComponent == null) {
throw new Error(
- `No persisted extension exists with id: ${modComponentId}`,
+ `No persisted mod component exists with id: ${modComponentId}`,
);
}
@@ -136,8 +137,8 @@ const ConvertToModModalBody: React.FunctionComponent = () => {
const initialValues: ConvertModFormState = useMemo(
() => ({
- blueprintId: generatePackageId(scope, modComponent.label),
- name: modComponent.label,
+ blueprintId: generatePackageId(scope, modComponent?.label),
+ name: modComponent?.label ?? "",
version: normalizeSemVerString("1.0.0"),
description: "Created with the PixieBrix Page Editor",
}),
@@ -156,6 +157,7 @@ const ConvertToModModalBody: React.FunctionComponent = () => {
helpers: FormikHelpers,
) => {
try {
+ assertNotNullish(modComponent, "modComponent is nullish");
const unsavedModDefinition = mapModComponentToUnsavedModDefinition(
modComponent,
{
@@ -212,7 +214,7 @@ const ConvertToModModalBody: React.FunctionComponent = () => {
);
}
} catch (error) {
- if (isSingleObjectBadRequestError(error) && error.response.data.config) {
+ if (isSingleObjectBadRequestError(error) && error.response?.data.config) {
helpers.setStatus(error.response.data.config);
return;
}
diff --git a/src/extensionConsole/pages/mods/modals/shareModals/CancelPublishContent.tsx b/src/extensionConsole/pages/mods/modals/shareModals/CancelPublishContent.tsx
index f9a68b355..92d274c29 100644
--- a/src/extensionConsole/pages/mods/modals/shareModals/CancelPublishContent.tsx
+++ b/src/extensionConsole/pages/mods/modals/shareModals/CancelPublishContent.tsx
@@ -30,14 +30,16 @@ import {
import notify from "@/utils/notify";
import { isSingleObjectBadRequestError } from "@/errors/networkErrorHelpers";
import { getErrorMessage } from "@/errors/errorHelpers";
+import { assertNotNullish } from "@/utils/nullishUtils";
const CancelPublishContent: React.FunctionComponent = () => {
const [isCancelling, setCancelling] = React.useState(false);
const [error, setError] = React.useState(null);
- const { blueprintId } = useSelector(selectShowPublishContext);
+ const { blueprintId: modId = null } =
+ useSelector(selectShowPublishContext) ?? {};
const { data: modDefinition, refetch: refetchModDefinitions } =
- useOptionalModDefinition(blueprintId);
+ useOptionalModDefinition(modId);
const { data: editablePackages, isFetching: isFetchingEditablePackages } =
useGetEditablePackagesQuery();
@@ -53,14 +55,23 @@ const CancelPublishContent: React.FunctionComponent = () => {
setError(null);
try {
+ assertNotNullish(
+ modDefinition,
+ `modDefinition for modId: ${modId} is nullish`,
+ );
const newModDefinition = produce(modDefinition, (draft) => {
draft.sharing.public = false;
});
+ assertNotNullish(editablePackages, "editablePackages is nullish");
const packageId = editablePackages.find(
(x) => x.name === newModDefinition.metadata.id,
)?.id;
+ assertNotNullish(
+ packageId,
+ `packageId for metadata id ${newModDefinition.metadata.id} is nullish`,
+ );
await updateModDefinition({
packageId,
modDefinition: newModDefinition,
@@ -73,9 +84,10 @@ const CancelPublishContent: React.FunctionComponent = () => {
} catch (error) {
if (
isSingleObjectBadRequestError(error) &&
- error.response.data.config?.length > 0
+ Number(error.response?.data.config?.length) > 0
) {
- setError(error.response.data.config.join(" "));
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-type-assertion -- See if block above
+ setError(error.response!.data.config!.join(" "));
} else {
const message = getErrorMessage(error);
setError(message);
diff --git a/src/extensionConsole/pages/mods/modals/shareModals/EditPublishContent.tsx b/src/extensionConsole/pages/mods/modals/shareModals/EditPublishContent.tsx
index a49cd9a00..1d4277658 100644
--- a/src/extensionConsole/pages/mods/modals/shareModals/EditPublishContent.tsx
+++ b/src/extensionConsole/pages/mods/modals/shareModals/EditPublishContent.tsx
@@ -24,6 +24,7 @@ import ActivationLink from "@/activation/ActivationLink";
import PublishContentLayout from "./PublishContentLayout";
import { MARKETPLACE_URL } from "@/urlConstants";
+import { assertNotNullish } from "@/utils/nullishUtils";
const EditPublishContent: React.FunctionComponent = () => {
const dispatch = useDispatch();
@@ -36,7 +37,9 @@ const EditPublishContent: React.FunctionComponent = () => {
dispatch(modModalsSlice.actions.setCancelingPublish());
};
- const { blueprintId } = useSelector(selectShowPublishContext);
+ const { blueprintId: modId } = useSelector(selectShowPublishContext) ?? {};
+
+ assertNotNullish(modId, "modId from publish context is nullish");
return (
@@ -57,7 +60,7 @@ const EditPublishContent: React.FunctionComponent = () => {
Public link to share:
-
+