Enable strict TypeScript, add errorMessage helper (#37292)

Enable full TypeScript `strict` mode and fix issues discovered during
this refactor. Introduced a `errorMessage` helper function to cleanly
extract a error messages from the `unknown` type.

Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (claude-opus-4-7) <noreply@anthropic.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
silverwind
2026-04-20 09:22:05 +02:00
committed by GitHub
parent 5a3d8d3224
commit f6960096f3
24 changed files with 74 additions and 51 deletions
-2
View File
@@ -35,8 +35,6 @@
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"strictPropertyInitialization": false,
"useUnknownInCatchVariables": false,
"stripInternal": true,
"verbatimModuleSyntax": true,
"types": [
+2 -1
View File
@@ -21,6 +21,7 @@ import {
type DayDataObject,
} from '../utils/time.ts';
import {chartJsColors} from '../utils/color.ts';
import {errorMessage} from '../modules/errors.ts';
import {sleep} from '../utils.ts';
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
import {onMounted, shallowRef} from 'vue';
@@ -78,7 +79,7 @@ async function fetchGraphData() {
errorText.value = response.statusText;
}
} catch (err) {
errorText.value = err.message;
errorText.value = errorMessage(err);
} finally {
isLoading.value = false;
}
+2 -1
View File
@@ -24,6 +24,7 @@ import {
fillEmptyStartDaysWithZeroes,
} from '../utils/time.ts';
import {chartJsColors} from '../utils/color.ts';
import {errorMessage} from '../modules/errors.ts';
import {sleep} from '../utils.ts';
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
import {fomanticQuery} from '../modules/fomantic/base.ts';
@@ -166,7 +167,7 @@ export default defineComponent({
this.errorText = response.statusText;
}
} catch (err) {
this.errorText = err.message;
this.errorText = errorMessage(err);
} finally {
this.isLoading = false;
}
+2 -1
View File
@@ -20,6 +20,7 @@ import {
type DayDataObject,
} from '../utils/time.ts';
import {chartJsColors} from '../utils/color.ts';
import {errorMessage} from '../modules/errors.ts';
import {sleep} from '../utils.ts';
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
import {onMounted, ref, shallowRef} from 'vue';
@@ -74,7 +75,7 @@ async function fetchGraphData() {
errorText.value = response.statusText;
}
} catch (err) {
errorText.value = err.message;
errorText.value = errorMessage(err);
} finally {
isLoading.value = false;
}
+2 -1
View File
@@ -2,6 +2,7 @@ import {showTemporaryTooltip} from '../../modules/tippy.ts';
import {POST} from '../../modules/fetch.ts';
import {registerGlobalInitFunc} from '../../modules/observer.ts';
import {queryElems} from '../../utils/dom.ts';
import {errorMessage} from '../../modules/errors.ts';
import {submitFormFetchAction} from '../common-fetch-action.ts';
const {appSubUrl} = window.config;
@@ -26,7 +27,7 @@ function initSystemConfigAutoCheckbox(el: HTMLInputElement) {
const json: Record<string, any> = await resp.json();
if (json.errorMessage) throw new Error(json.errorMessage);
} catch (ex) {
showTemporaryTooltip(el, ex.toString());
showTemporaryTooltip(el, errorMessage(ex));
el.checked = !el.checked;
}
});
+2 -1
View File
@@ -1,4 +1,5 @@
import {getCurrentLocale} from '../utils.ts';
import {errorMessage} from '../modules/errors.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
import {localUserSettings} from '../modules/user-settings.ts';
@@ -46,7 +47,7 @@ export async function initCitationFileCopyContent() {
try {
await initInputCitationValue(citationCopyApa, citationCopyBibtex);
} catch (e) {
console.error(`initCitationFileCopyContent error: ${e}`, e);
console.error(`initCitationFileCopyContent error: ${errorMessage(e)}`, e);
return;
}
updateUi();
+5 -4
View File
@@ -1,6 +1,7 @@
import {GET, request} from '../modules/fetch.ts';
import {hideToastsAll, showErrorToast} from '../modules/toast.ts';
import {addDelegatedEventListener, createElementFromHTML} from '../utils/dom.ts';
import {errorMessage} from '../modules/errors.ts';
import {confirmModal, createConfirmModal} from './comp/ConfirmModal.ts';
import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts';
import {registerGlobalSelectorFunc} from '../modules/observer.ts';
@@ -134,10 +135,10 @@ async function performActionRequest(el: HTMLElement, opt: FetchActionOpts) {
return;
}
await handleFetchActionError(resp);
} catch (e) {
if (e.name !== 'AbortError') {
console.error(`Fetch action request error:`, e);
showErrorToast(`Error: ${e.message ?? e}`);
} catch (err) {
if ((err as Error).name !== 'AbortError') {
console.error(`Fetch action request error:`, err);
showErrorToast(`Error: ${errorMessage(err)}`);
}
} finally {
toggleLoadingIndicator(el, opt, false);
+11 -11
View File
@@ -71,26 +71,26 @@ export class ComboMarkdownEditor {
options: ComboMarkdownEditorOptions;
tabEditor: HTMLElement;
tabPreviewer: HTMLElement;
tabEditor?: HTMLElement;
tabPreviewer?: HTMLElement;
supportEasyMDE: boolean;
supportEasyMDE!: boolean;
easyMDE: any;
easyMDEToolbarActions: any;
easyMDEToolbarDefault: any;
textarea: ComboMarkdownEditorTextarea;
textareaMarkdownToolbar: HTMLElement;
textarea!: ComboMarkdownEditorTextarea;
textareaMarkdownToolbar!: HTMLElement;
textareaAutosize: any;
buttonMonospace: HTMLButtonElement;
buttonMonospace!: HTMLButtonElement;
dropzone: HTMLElement | null;
dropzone: HTMLElement | null = null;
attachedDropzoneInst: any;
previewMode: string;
previewUrl: string;
previewContext: string;
previewMode!: string;
previewUrl!: string;
previewContext!: string;
constructor(container: ComboMarkdownEditorContainer, options:ComboMarkdownEditorOptions = {}) {
if (container._giteaComboMarkdownEditor) throw new Error('ComboMarkdownEditor already initialized');
@@ -291,7 +291,7 @@ export class ComboMarkdownEditor {
}
switchTabToEditor() {
this.tabEditor.click();
this.tabEditor!.click(); // when this function is called, the tab must exist
}
prepareEasyMDEToolbarActions() {
+2 -1
View File
@@ -5,6 +5,7 @@ import {showTemporaryTooltip} from '../modules/tippy.ts';
import {GET, POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {createElementFromHTML, createElementFromAttrs} from '../utils/dom.ts';
import {errorMessage} from '../modules/errors.ts';
import {isImageFile, isVideoFile} from '../utils.ts';
import type Dropzone from '@deltablot/dropzone';
@@ -149,7 +150,7 @@ export async function initDropzone(dropzoneEl: HTMLElement) {
} catch (error) {
// TODO: if listing the existing attachments failed, it should stop from operating the content or attachments,
// otherwise the attachments might be lost.
showErrorToast(`Failed to load attachments: ${error}`);
showErrorToast(`Failed to load attachments: ${errorMessage(error)}`);
console.error(error);
}
});
+2 -1
View File
@@ -2,6 +2,7 @@ import type {InplaceRenderPlugin} from '../render/plugin.ts';
import {newInplacePluginPdfViewer} from '../render/plugins/inplace-pdf-viewer.ts';
import {registerGlobalInitFunc} from '../modules/observer.ts';
import {createElementFromHTML} from '../utils/dom.ts';
import {errorMessage} from '../modules/errors.ts';
import {html} from '../utils/html.ts';
import {basename} from '../utils.ts';
@@ -30,7 +31,7 @@ async function renderRawFileToContainer(container: HTMLElement, rawFileLink: str
rendered = true;
}
} catch (e) {
errorMsg = `${e}`;
errorMsg = errorMessage(e);
} finally {
container.classList.remove('is-loading');
}
+2 -2
View File
@@ -94,8 +94,8 @@ function createContext(imageAfter: HTMLImageElement, imageBefore: HTMLImageEleme
}
class ImageDiff {
containerEl: HTMLElement;
diffContainerWidth: number;
containerEl!: HTMLElement;
diffContainerWidth!: number;
async init(containerEl: HTMLElement) {
this.containerEl = containerEl;
+2 -1
View File
@@ -1,4 +1,5 @@
import {queryElems} from '../utils/dom.ts';
import {errorMessage} from '../modules/errors.ts';
import {POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {sleep} from '../utils.ts';
@@ -26,7 +27,7 @@ async function onDownloadArchive(e: Event) {
window.location.href = el.href; // the archive is ready, start real downloading
} catch (e) {
console.error(e);
showErrorToast(`Failed to download the archive: ${e}`, {duration: 2500});
showErrorToast(`Failed to download the archive: ${errorMessage(e)}`, {duration: 2500});
} finally {
targetLoading.classList.remove('is-loading', 'loading-icon-2px');
}
+2 -1
View File
@@ -6,6 +6,7 @@ import {initViewedCheckboxListenerFor, initExpandAndCollapseFilesButton} from '.
import {initImageDiff} from './imagediff.ts';
import {showErrorToast} from '../modules/toast.ts';
import {queryElemSiblings, hideElem, showElem, animateOnce, addDelegatedEventListener, createElementFromHTML, queryElems} from '../utils/dom.ts';
import {errorMessage} from '../modules/errors.ts';
import {POST, GET} from '../modules/fetch.ts';
import {createTippy} from '../modules/tippy.ts';
import {invertFileFolding} from './file-fold.ts';
@@ -85,7 +86,7 @@ function initRepoDiffConversationForm() {
}
} catch (error) {
console.error('Error:', error);
showErrorToast(`Submit form failed: ${error}`);
showErrorToast(`Submit form failed: ${errorMessage(error)}`);
} finally {
form?.classList.remove('is-loading');
}
+2 -1
View File
@@ -3,6 +3,7 @@ import {getComboMarkdownEditor, initComboMarkdownEditor, ComboMarkdownEditor} fr
import {POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {hideElem, querySingleVisibleElem, showElem} from '../utils/dom.ts';
import {errorMessage} from '../modules/errors.ts';
import {triggerUploadStateChanged} from './comp/EditorUpload.ts';
import {convertHtmlToMarkdown} from '../markup/html2markdown.ts';
import {applyAreYouSure, reinitializeAreYouSure} from '../vendor/jquery.are-you-sure.ts';
@@ -73,7 +74,7 @@ async function tryOnEditContent(e: Event) {
}
comboMarkdownEditor.dropzoneSubmitReload();
} catch (error) {
showErrorToast(`Failed to save the content: ${error}`);
showErrorToast(`Failed to save the content: ${errorMessage(error)}`);
console.error(error);
} finally {
renderContent.classList.remove('is-loading');
+4 -1
View File
@@ -2,6 +2,7 @@ import {updateIssuesMeta} from './repo-common.ts';
import {toggleElem, queryElems, isElemVisible} from '../utils/dom.ts';
import {html, htmlRaw} from '../utils/html.ts';
import {confirmModal} from './comp/ConfirmModal.ts';
import {errorMessage} from '../modules/errors.ts';
import {showErrorToast} from '../modules/toast.ts';
import {createSortable} from '../modules/sortable.ts';
import {DELETE, POST} from '../modules/fetch.ts';
@@ -87,7 +88,9 @@ function initRepoIssueListCheckboxes() {
await updateIssuesMeta(url, action, issueIDs, elementId);
window.location.reload();
} catch (err) {
showErrorToast(err.responseJSON?.error ?? err.message);
// FIXME: this logic (including updateIssuesMeta) is not right, should refactor to our JSONError framework
const e = err as {responseJSON?: {error: string}};
showErrorToast(e.responseJSON?.error ?? errorMessage(err));
}
},
));
@@ -2,6 +2,7 @@ import {fomanticQuery} from '../modules/fomantic/base.ts';
import {GET, POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {addDelegatedEventListener, queryElemChildren, queryElems, toggleElem} from '../utils/dom.ts';
import {errorMessage} from '../modules/errors.ts';
import {parseDom} from '../utils.ts';
export function syncIssueMainContentTimelineItems(oldMainContent: Element, newMainContent: Element) {
@@ -42,7 +43,7 @@ export class IssueSidebarComboList {
elDropdown: HTMLElement;
elList: HTMLElement | null;
elComboValue: HTMLInputElement;
initialValues: string[];
initialValues: string[] = [];
container: HTMLElement;
elIssueMainContent: HTMLElement;
@@ -129,7 +130,7 @@ export class IssueSidebarComboList {
await this.reloadPagePartially();
} catch (e) {
console.error('Failed to update to backend', e);
showErrorToast(`Failed to update to backend: ${e}`);
showErrorToast(`Failed to update to backend: ${errorMessage(e)}`);
} finally {
this.elIssueSidebar.classList.remove('is-loading');
}
+2 -1
View File
@@ -1,3 +1,4 @@
import {errorMessage} from '../modules/errors.ts';
import {htmlEscape} from '../utils/html.ts';
import {createTippy} from '../modules/tippy.ts';
import {
@@ -425,7 +426,7 @@ export function initRepoIssueTitleEdit() {
window.location.reload();
} catch (error) {
console.error(error);
showErrorToast(error.message);
showErrorToast(errorMessage(error));
}
});
}
@@ -2,6 +2,7 @@ import {createSortable} from '../modules/sortable.ts';
import {POST} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {queryElemChildren} from '../utils/dom.ts';
import {errorMessage} from '../modules/errors.ts';
export function initRepoSettingsBranchesDrag() {
const protectedBranchesList = document.querySelector<HTMLElement>('#protected-branches-list');
@@ -23,8 +24,7 @@ export function initRepoSettingsBranchesDrag() {
},
});
} catch (err) {
const errorMessage = String(err);
showErrorToast(`Failed to update branch protection rule priority:, error: ${errorMessage}`);
showErrorToast(`Failed to update branch protection rule priority: ${errorMessage(err)}`);
}
})();
},
+5 -4
View File
@@ -1,5 +1,6 @@
import {encodeURLEncodedBase64, decodeURLEncodedBase64} from '../utils.ts';
import {hideElem, showElem} from '../utils/dom.ts';
import {errorMessage} from '../modules/errors.ts';
import {GET, POST} from '../modules/fetch.ts';
const {appSubUrl} = window.config;
@@ -80,7 +81,7 @@ async function loginPasskey() {
window.location.href = reply?.redirect ?? `${appSubUrl}/`;
} catch (err) {
webAuthnError('general', err.message);
webAuthnError('general', errorMessage(err));
}
}
@@ -104,7 +105,7 @@ async function login2FA() {
await verifyAssertion(credential);
} catch (err) {
if (!options.publicKey.extensions?.appid) {
webAuthnError('general', err.message);
webAuthnError('general', errorMessage(err));
return;
}
delete options.publicKey.extensions.appid;
@@ -114,7 +115,7 @@ async function login2FA() {
});
await verifyAssertion(credential);
} catch (err) {
webAuthnError('general', err.message);
webAuthnError('general', errorMessage(err));
}
}
}
@@ -262,6 +263,6 @@ async function webAuthnRegisterRequest() {
});
await webauthnRegistered(credential);
} catch (err) {
webAuthnError('unknown', err);
webAuthnError('unknown', errorMessage(err));
}
}
+4 -2
View File
@@ -1,8 +1,10 @@
export function displayError(el: Element, err: Error): void {
import {errorMessage} from '../modules/errors.ts';
export function displayError(el: Element, err: unknown): void {
el.classList.remove('is-loading');
const errorNode = document.createElement('pre');
errorNode.setAttribute('class', 'ui message error markup-block-error');
errorNode.textContent = err.message || String(err);
errorNode.textContent = errorMessage(err);
el.before(errorNode);
el.setAttribute('data-render-done', 'true');
}
+2 -1
View File
@@ -1,4 +1,5 @@
import {generateElemId} from '../utils/dom.ts';
import {errorMessage} from '../modules/errors.ts';
import {isDarkTheme} from '../utils.ts';
import {GET} from '../modules/fetch.ts';
@@ -11,7 +12,7 @@ function safeRenderIframeLink(link: any): string | null {
}
return url.href;
} catch (e) {
console.error(`Failed to parse link: ${link}, error: ${e}`);
console.error(`Failed to parse link: ${link}, error: ${errorMessage(e)}`);
return null;
}
}
+4
View File
@@ -2,6 +2,10 @@
import {html} from '../utils/html.ts';
import type {Intent} from '../types.ts';
export function errorMessage(err: unknown): string {
return (err as Error)?.message || String(err);
}
export function showGlobalErrorMessage(msg: string, msgType: Intent = 'error') {
const msgContainer = document.querySelector('.page-content') ?? document.body;
if (!msgContainer) {
+2 -1
View File
@@ -2,6 +2,7 @@ import emojis from '../../../assets/emoji.json' with {type: 'json'};
import {GET} from '../modules/fetch.ts';
import {showErrorToast} from '../modules/toast.ts';
import {parseIssuePageInfo} from '../utils.ts';
import {errorMessage} from '../modules/errors.ts';
import type {Issue, Mention} from '../types.ts';
const maxMatches = 6;
@@ -47,7 +48,7 @@ export function fetchMentions(mentionsUrl: string): Promise<Mention[]> {
if (!res.ok) throw new Error(res.statusText);
return await res.json() as Mention[];
} catch (e) {
showErrorToast(`Failed to load mentions: ${e}`);
showErrorToast(`Failed to load mentions: ${errorMessage(e)}`);
return [];
}
})();
+8 -8
View File
@@ -3,13 +3,13 @@ import {addDelegatedEventListener, generateElemId, isDocumentFragmentOrElementNo
import octiconKebabHorizontal from '../../../public/assets/img/svg/octicon-kebab-horizontal.svg';
window.customElements.define('overflow-menu', class extends HTMLElement {
popup: HTMLDivElement;
overflowItems: Array<HTMLElement>;
button: HTMLButtonElement | null;
menuItemsEl: HTMLElement;
resizeObserver: ResizeObserver;
mutationObserver: MutationObserver;
lastWidth: number;
popup!: HTMLDivElement;
overflowItems: Array<HTMLElement> = [];
button: HTMLButtonElement | null = null;
menuItemsEl!: HTMLElement;
resizeObserver!: ResizeObserver;
mutationObserver!: MutationObserver;
lastWidth!: number;
updateButtonActivationState() {
if (!this.button || !this.popup) return;
@@ -100,7 +100,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement {
const itemOverFlowMenuButton = this.querySelector<HTMLButtonElement>('.overflow-menu-button');
// move items in popup back into the menu items for subsequent measurement
for (const item of this.overflowItems || []) {
for (const item of this.overflowItems) {
if (!itemFlexSpace || item.getAttribute('data-after-flex-space')) {
this.menuItemsEl.append(item);
} else {