From f5ca74b63c96691a27d6c5993430f3b464e4ad0d Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Mon, 24 Nov 2025 21:22:38 +0100 Subject: [PATCH] refactor: Delete `isPlainObject` type guard and replace its usages with direct `typeof` checks. --- frontend/package.json | 1 + .../src/documents/DocumentSummarySection.tsx | 114 +++++++++--------- .../useDocumentCorrespondentActions.ts | 8 +- .../hooks/documents/useDocumentMutations.ts | 11 +- .../components/CapabilityDropdown.tsx | 12 +- .../sections/CapabilitySetsSection.tsx | 9 +- frontend/src/ui/QuickAddMenu.tsx | 20 +-- frontend/src/utils/typeGuards.ts | 6 - 8 files changed, 86 insertions(+), 95 deletions(-) delete mode 100644 frontend/src/utils/typeGuards.ts diff --git a/frontend/package.json b/frontend/package.json index 7f20a5f..629b87c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,6 +7,7 @@ "dev": "webpack serve --mode development --open", "build": "webpack --mode production", "lint": "eslint src --ext .js,.jsx,.ts,.tsx", + "check": "tsc --noEmit && npm run lint", "test:engine": "node --test tests/workspaceEngine.test.js" }, "dependencies": { diff --git a/frontend/src/documents/DocumentSummarySection.tsx b/frontend/src/documents/DocumentSummarySection.tsx index 62b30a7..03b1b0c 100644 --- a/frontend/src/documents/DocumentSummarySection.tsx +++ b/frontend/src/documents/DocumentSummarySection.tsx @@ -12,7 +12,7 @@ import { toIssuedTimestamp, } from '../utils/date'; import { describeDocumentSummary, type DocumentSummaryRow } from './documentSummary'; -import { isPlainObject } from '../utils/typeGuards'; + import { useFolderManager } from '../folders/FolderManagerContext'; type Identifier = string | number; @@ -128,11 +128,11 @@ const resolveOptionName = (source?: QuickAddOption | string | null): string => { if (!source) { return ''; } - if (isPlainObject(source)) { - const raw = source.name ?? source.label ?? ''; - return `${raw}`.trim(); + if (typeof source === 'string') { + return source.trim(); } - return `${source}`.trim(); + const raw = source.name ?? source.label ?? ''; + return `${raw}`.trim(); }; const normalizeQuickAddOption = (option?: QuickAddOption | string | null): QuickAddEntry | null => { @@ -140,17 +140,17 @@ const normalizeQuickAddOption = (option?: QuickAddOption | string | null): Quick return null; } const label = (() => { - if (isPlainObject(option)) { - const sourceLabel = option.label ?? option.name ?? ''; - return `${sourceLabel}`.trim(); + if (typeof option === 'string') { + return option.trim(); } - return `${option}`.trim(); + const sourceLabel = option.label ?? option.name ?? ''; + return `${sourceLabel}`.trim(); })(); if (!label) { return null; } return { - id: isPlainObject(option) && option.id ? option.id : label, + id: typeof option !== 'string' && option.id ? option.id : label, label, original: option, }; @@ -245,7 +245,7 @@ export const TagSection: React.FC = ({ return; } if (item.state === 'all' && onRemove) { - const payload = isPlainObject(item.payload) + const payload = item.payload && typeof item.payload === 'object' && 'id' in item.payload ? (item.payload as TagEntry) : tags.find((tag) => (tag.id ?? tag.label) === item.id) ?? { id: item.id, label: item.label }; onRemove(payload); @@ -379,7 +379,7 @@ export const CorrespondentSection: React.FC = ({ return; } if (item.state === 'all' && onRemove) { - const payload = isPlainObject(item.payload) + const payload = item.payload && typeof item.payload === 'object' && 'id' in item.payload ? (item.payload as CorrespondentEntry) : entries.find((entry) => (entry.id ?? entry.name) === item.id) ?? { id: item.id, name: item.label }; onRemove(payload); @@ -393,7 +393,7 @@ export const CorrespondentSection: React.FC = ({ if (!resolvedName) { return; } - const payload = isPlainObject(source) + const payload = typeof source !== 'string' ? { ...source, name: resolvedName } : { id: null, name: resolvedName }; onAdd({ name: resolvedName, option: payload, input: null }); @@ -405,26 +405,26 @@ export const CorrespondentSection: React.FC = ({
{hasEntries ? entries.map((entry) => { - const key = entry.id ?? entry.name; - return ( - - - {entry.name} - {showCount && entry.count ? ` (${entry.count})` : ''} - - {onRemove ? ( - - ) : null} + const key = entry.id ?? entry.name; + return ( + + + {entry.name} + {showCount && entry.count ? ` (${entry.count})` : ''} - ); - }) + {onRemove ? ( + + ) : null} + + ); + }) : !showQuickAdd && No correspondents yet.} {showQuickAdd ? ( = ({ if (active) { setFolderName(name); } - }).catch(() => {}); + }).catch(() => { }); } return () => { active = false; @@ -674,21 +674,21 @@ const DocumentSummarySection: React.FC = ({ const titleMetaDisplay = editableTitle && isTitleEditing ? renderTitleEditForm('doc-title-edit--inline') : ( - <> - {document?.title} - {editableTitle ? ( - - ) : null} - - ); + <> + {document?.title} + {editableTitle ? ( + + ) : null} + + ); const issuedDisplay = editableIssued && isIssuedEditing ? (
@@ -758,20 +758,20 @@ const DocumentSummarySection: React.FC = ({ onRemove={ onCorrespondentRemove ? (entry) => - onCorrespondentRemove({ - documentId: document.id, - correspondentId: entry.id, - }) + onCorrespondentRemove({ + documentId: document.id, + correspondentId: entry.id, + }) : undefined } onAdd={ onCorrespondentAdd ? ({ name, option }) => - onCorrespondentAdd({ - document, - name, - option, - }) + onCorrespondentAdd({ + document, + name, + option, + }) : undefined } showCount diff --git a/frontend/src/hooks/documents/useDocumentCorrespondentActions.ts b/frontend/src/hooks/documents/useDocumentCorrespondentActions.ts index 65c57a4..22267b3 100644 --- a/frontend/src/hooks/documents/useDocumentCorrespondentActions.ts +++ b/frontend/src/hooks/documents/useDocumentCorrespondentActions.ts @@ -1,5 +1,5 @@ import { useCallback, useMemo } from 'react'; -import { isPlainObject } from '../../utils/typeGuards'; + type ApiClient = { post: (path: string, body?: unknown) => Promise<{ data: unknown }>; @@ -124,16 +124,14 @@ const useDocumentCorrespondentActions = ({ if (!option) { return null; } - if (isPlainObject(option) && 'id' in option) { - return option as CorrespondentOption; - } if (typeof option === 'string') { const trimmed = option.trim(); if (trimmed) { return { id: null, name: trimmed }; } + return null; } - return null; + return option; }; const handleCorrespondentAdd = useCallback( diff --git a/frontend/src/hooks/documents/useDocumentMutations.ts b/frontend/src/hooks/documents/useDocumentMutations.ts index d3b85ac..4dd5477 100644 --- a/frontend/src/hooks/documents/useDocumentMutations.ts +++ b/frontend/src/hooks/documents/useDocumentMutations.ts @@ -1,5 +1,5 @@ import { useCallback } from 'react'; -import { isPlainObject } from '../../utils/typeGuards'; + import type { Dispatch, MutableRefObject, SetStateAction } from 'react'; import { DEFAULT_FOLDER_NAME, getRowId, isDocumentRowKey } from '../../app/appLayoutUtils'; import { @@ -66,7 +66,7 @@ interface DocumentLike { interface FolderContents { documents?: DocumentLike[]; - subfolders?: Array<{ id?: FolderId; [key: string]: unknown }>; + subfolders?: Array<{ id?: FolderId;[key: string]: unknown }>; [key: string]: unknown; } @@ -175,7 +175,7 @@ interface UseDocumentMutationsResult { const normalizeDocumentId = (value: unknown): DocumentId | null => { if (!value) return null; - if (isPlainObject(value) && 'id' in value && value.id != null) { + if (value && typeof value === 'object' && 'id' in value && value.id != null) { return value.id as DocumentId; } return value as DocumentId; @@ -446,7 +446,7 @@ const useDocumentMutations = ({ }, [ token, - documentLookup, + removeDocumentsFromCaches, previewDocumentId, closeDocumentPreview, @@ -495,7 +495,8 @@ const useDocumentMutations = ({ ); const handleDocumentIssuedUpdate = useCallback( - async (documentId: DocumentId, nextIssuedDate: number | null) => {const payload = { issued_at: nextIssuedDate || null }; + async (documentId: DocumentId, nextIssuedDate: number | null) => { + const payload = { issued_at: nextIssuedDate || null }; try { const data = await updateDocument(documentId, payload); const updatedDocument = extractDocumentFromResponse?.(data); diff --git a/frontend/src/settings/components/CapabilityDropdown.tsx b/frontend/src/settings/components/CapabilityDropdown.tsx index baf0d66..0bf8841 100644 --- a/frontend/src/settings/components/CapabilityDropdown.tsx +++ b/frontend/src/settings/components/CapabilityDropdown.tsx @@ -7,7 +7,7 @@ import { } from 'react'; import type { JSX } from 'react'; import { CheckIcon, ChevronDownIcon } from '../../ui/icons'; -import { isPlainObject } from '../../utils/typeGuards'; + type CapabilityValue = string | number; @@ -29,9 +29,7 @@ interface CapabilityDropdownProps { summaryLabel?: string; } -const isCapabilityOption = ( - option: CapabilityDropdownOption | CapabilityValue | null, -): option is CapabilityDropdownOption => isPlainObject(option); + const resolveCapabilityValue = ( option: CapabilityDropdownOption | CapabilityValue | null, @@ -39,8 +37,8 @@ const resolveCapabilityValue = ( if (option == null) { return null; } - if (!isCapabilityOption(option)) { - return option as CapabilityValue; + if (typeof option !== 'object') { + return option; } if (option.value != null) { return option.value; @@ -170,7 +168,7 @@ const CapabilityDropdown = ({ } const label = formatLabel ? formatLabel(value) - : (isCapabilityOption(option) && option?.label) || String(value); + : (typeof option === 'object' && option?.label) || String(value); const selected = selectedValues.includes(value); return (