Enforce type checking in CI (#4126)

# Description of Changes
Currently, the `tsconfig.json` file enforces strict type checking, but
nothing in CI checks that the code is actually correctly typed. [Vite
only transpiles TypeScript
code](https://vite.dev/guide/features.html#transpile-only) so doesn't
ensure that the TS code we're running is correct.

This PR adds running of the type checker to CI and fixes the type errors
that have already crept into the codebase.

Note that many of the changes I've made to 'fix the types' are just
using `any` to disable the type checker because the code is under too
much churn to fix anything properly at the moment. I still think
enabling the type checker now is the best course of action though
because otherwise we'll never be able to fix all of them, and it should
at least help us not break things when adding new code.

Co-authored-by: James <[email protected]>
This commit is contained in:
James Brunton
2025-08-11 09:16:16 +01:00
committed by GitHub
co-authored by James
parent 507ad1dc61
commit af5a9d1ae1
52 changed files with 1141 additions and 919 deletions
+64 -61
View File
@@ -3,9 +3,9 @@
*/
import React, { createContext, useContext, useReducer, useCallback, useEffect, useRef } from 'react';
import {
FileContextValue,
FileContextState,
import {
FileContextValue,
FileContextState,
FileContextProviderProps,
ModeType,
ViewType,
@@ -53,7 +53,7 @@ const initialState: FileContextState = {
};
// Action types
type FileContextAction =
type FileContextAction =
| { type: 'SET_ACTIVE_FILES'; payload: File[] }
| { type: 'ADD_FILES'; payload: File[] }
| { type: 'REMOVE_FILES'; payload: string[] }
@@ -126,7 +126,7 @@ function fileContextReducer(state: FileContextState, action: FileContextAction):
case 'SET_CURRENT_MODE':
const coreViews = ['viewer', 'pageEditor', 'fileEditor'];
const isToolMode = !coreViews.includes(action.payload);
return {
...state,
currentMode: action.payload,
@@ -193,8 +193,8 @@ function fileContextReducer(state: FileContextState, action: FileContextAction):
const existing = newHistory.get(action.payload.fileId);
newHistory.set(action.payload.fileId, {
fileId: action.payload.fileId,
pageOperations: existing ?
[...existing.pageOperations, ...action.payload.operations] :
pageOperations: existing ?
[...existing.pageOperations, ...action.payload.operations] :
action.payload.operations,
lastModified: Date.now()
});
@@ -213,7 +213,7 @@ function fileContextReducer(state: FileContextState, action: FileContextAction):
const { fileId, operation } = action.payload;
const newOperationHistory = new Map(state.fileOperationHistory);
const existingHistory = newOperationHistory.get(fileId);
if (existingHistory) {
// Add operation to existing history
newOperationHistory.set(fileId, {
@@ -231,7 +231,7 @@ function fileContextReducer(state: FileContextState, action: FileContextAction):
lastModified: Date.now()
});
}
return {
...state,
fileOperationHistory: newOperationHistory
@@ -240,10 +240,10 @@ function fileContextReducer(state: FileContextState, action: FileContextAction):
case 'MARK_OPERATION_APPLIED':
const appliedHistory = new Map(state.fileOperationHistory);
const appliedFileHistory = appliedHistory.get(action.payload.fileId);
if (appliedFileHistory) {
const updatedOperations = appliedFileHistory.operations.map(op =>
op.id === action.payload.operationId
const updatedOperations = appliedFileHistory.operations.map(op =>
op.id === action.payload.operationId
? { ...op, status: 'applied' as const }
: op
);
@@ -253,7 +253,7 @@ function fileContextReducer(state: FileContextState, action: FileContextAction):
lastModified: Date.now()
});
}
return {
...state,
fileOperationHistory: appliedHistory
@@ -262,12 +262,12 @@ function fileContextReducer(state: FileContextState, action: FileContextAction):
case 'MARK_OPERATION_FAILED':
const failedHistory = new Map(state.fileOperationHistory);
const failedFileHistory = failedHistory.get(action.payload.fileId);
if (failedFileHistory) {
const updatedOperations = failedFileHistory.operations.map(op =>
op.id === action.payload.operationId
? {
...op,
const updatedOperations = failedFileHistory.operations.map(op =>
op.id === action.payload.operationId
? {
...op,
status: 'failed' as const,
metadata: { ...op.metadata, error: action.payload.error }
}
@@ -279,7 +279,7 @@ function fileContextReducer(state: FileContextState, action: FileContextAction):
lastModified: Date.now()
});
}
return {
...state,
fileOperationHistory: failedHistory
@@ -337,19 +337,19 @@ function fileContextReducer(state: FileContextState, action: FileContextAction):
const FileContext = createContext<FileContextValue | undefined>(undefined);
// Provider component
export function FileContextProvider({
children,
export function FileContextProvider({
children,
enableUrlSync = true,
enablePersistence = true,
maxCacheSize = 1024 * 1024 * 1024 // 1GB
}: FileContextProviderProps) {
const [state, dispatch] = useReducer(fileContextReducer, initialState);
// Cleanup timers and refs
const cleanupTimers = useRef<Map<string, NodeJS.Timeout>>(new Map());
const blobUrls = useRef<Set<string>>(new Set());
const pdfDocuments = useRef<Map<string, PDFDocument>>(new Map());
// Enhanced file processing hook
const {
processedFiles,
@@ -367,11 +367,11 @@ export function FileContextProvider({
// Update processed files when they change
useEffect(() => {
dispatch({ type: 'SET_PROCESSED_FILES', payload: processedFiles });
dispatch({
type: 'SET_PROCESSING',
payload: {
isProcessing: globalProcessing,
progress: processingProgress.overall
dispatch({
type: 'SET_PROCESSING',
payload: {
isProcessing: globalProcessing,
progress: processingProgress.overall
}
});
}, [processedFiles, globalProcessing, processingProgress.overall]);
@@ -397,7 +397,7 @@ export function FileContextProvider({
const cleanupFile = useCallback(async (fileId: string) => {
console.log('Cleaning up file:', fileId);
try {
// Cancel any pending cleanup timer
const timer = cleanupTimers.current.get(fileId);
@@ -425,7 +425,7 @@ export function FileContextProvider({
const cleanupAllFiles = useCallback(() => {
console.log('Cleaning up all files');
try {
// Clear all timers
cleanupTimers.current.forEach(timer => clearTimeout(timer));
@@ -461,7 +461,8 @@ export function FileContextProvider({
// Force garbage collection hint
if (typeof window !== 'undefined' && window.gc) {
setTimeout(() => window.gc(), 100);
let gc = window.gc
setTimeout(() => gc(), 100);
}
} catch (error) {
@@ -486,14 +487,14 @@ export function FileContextProvider({
const timer = setTimeout(() => {
cleanupFile(fileId);
}, delay);
cleanupTimers.current.set(fileId, timer);
}, [cleanupFile]);
// Action implementations
const addFiles = useCallback(async (files: File[]): Promise<File[]> => {
dispatch({ type: 'ADD_FILES', payload: files });
// Auto-save to IndexedDB if persistence enabled
if (enablePersistence) {
for (const file of files) {
@@ -504,7 +505,7 @@ export function FileContextProvider({
// File doesn't have explicit ID, store it with thumbnail
try {
// Generate thumbnail for better recent files experience
const thumbnail = await thumbnailGenerationService.generateThumbnail(file);
const thumbnail = await (thumbnailGenerationService as any /* FIX ME */).generateThumbnail(file);
const storedFile = await fileStorage.storeFile(file, thumbnail);
// Add the ID to the file object
Object.defineProperty(file, 'id', { value: storedFile.id, writable: false });
@@ -520,7 +521,7 @@ export function FileContextProvider({
}
}
}
// Return files with their IDs assigned
return files;
}, [enablePersistence]);
@@ -532,9 +533,9 @@ export function FileContextProvider({
enhancedPDFProcessingService.cancelProcessing(fileId);
cleanupFile(fileId);
});
dispatch({ type: 'REMOVE_FILES', payload: fileIds });
// Remove from IndexedDB only if requested
if (enablePersistence && deleteFromStorage) {
fileIds.forEach(async (fileId) => {
@@ -557,7 +558,7 @@ export function FileContextProvider({
const clearAllFiles = useCallback(() => {
// Cleanup all memory before clearing files
cleanupAllFiles();
dispatch({ type: 'SET_ACTIVE_FILES', payload: [] });
dispatch({ type: 'CLEAR_SELECTIONS' });
}, [cleanupAllFiles]);
@@ -594,11 +595,12 @@ export function FileContextProvider({
const setCurrentMode = useCallback((mode: ModeType) => {
requestNavigation(() => {
dispatch({ type: 'SET_CURRENT_MODE', payload: mode });
if (state.currentMode !== mode && state.activeFiles.length > 0) {
if (window.requestIdleCallback && typeof window !== 'undefined' && window.gc) {
let gc = window.gc;
window.requestIdleCallback(() => {
window.gc();
gc();
}, { timeout: 5000 });
}
}
@@ -608,11 +610,12 @@ export function FileContextProvider({
const setCurrentView = useCallback((view: ViewType) => {
requestNavigation(() => {
dispatch({ type: 'SET_CURRENT_VIEW', payload: view });
if (state.currentView !== view && state.activeFiles.length > 0) {
if (window.requestIdleCallback && typeof window !== 'undefined' && window.gc) {
let gc = window.gc;
window.requestIdleCallback(() => {
window.gc();
gc();
}, { timeout: 5000 });
}
}
@@ -642,8 +645,8 @@ export function FileContextProvider({
}, []);
const applyPageOperations = useCallback((fileId: string, operations: PageOperation[]) => {
dispatch({
type: 'ADD_PAGE_OPERATIONS',
dispatch({
type: 'ADD_PAGE_OPERATIONS',
payload: { fileId, operations }
});
}, []);
@@ -718,18 +721,18 @@ export function FileContextProvider({
// Context persistence
const saveContext = useCallback(async () => {
if (!enablePersistence) return;
try {
const contextData = {
currentView: state.currentView,
currentTool: state.currentTool,
selectedFileIds: state.selectedFileIds,
selectedPageIds: state.selectedPageIds,
selectedPageNumbers: state.selectedPageNumbers,
viewerConfig: state.viewerConfig,
lastExportConfig: state.lastExportConfig,
timestamp: Date.now()
};
localStorage.setItem('fileContext', JSON.stringify(contextData));
} catch (error) {
console.error('Failed to save context:', error);
@@ -738,7 +741,7 @@ export function FileContextProvider({
const loadContext = useCallback(async () => {
if (!enablePersistence) return;
try {
const saved = localStorage.getItem('fileContext');
if (saved) {
@@ -779,7 +782,7 @@ export function FileContextProvider({
const contextValue: FileContextValue = {
// State
...state,
// Actions
addFiles,
removeFiles,
@@ -804,7 +807,7 @@ export function FileContextProvider({
saveContext,
loadContext,
resetContext,
// Operation history management
recordOperation,
markOperationApplied,
@@ -812,13 +815,13 @@ export function FileContextProvider({
getFileHistory,
getAppliedOperations,
clearFileHistory,
// Navigation guard system
setHasUnsavedChanges,
requestNavigation,
confirmNavigation,
cancelNavigation,
// Memory management
trackBlobUrl,
trackPdfDocument,
@@ -852,17 +855,17 @@ export function useCurrentFile() {
}
export function useFileSelection() {
const {
selectedFileIds,
selectedPageIds,
setSelectedFiles,
setSelectedPages,
clearSelections
const {
selectedFileIds,
selectedPageNumbers,
setSelectedFiles,
setSelectedPages,
clearSelections
} = useFileContext();
return {
selectedFileIds,
selectedPageIds,
selectedPageNumbers,
setSelectedFiles,
setSelectedPages,
clearSelections
@@ -875,4 +878,4 @@ export function useViewerState() {
config: viewerConfig,
updateConfig: updateViewerConfig
};
}
}