From 3e54226110a003eb60ab6dadf1341dad7e1915f3 Mon Sep 17 00:00:00 2001 From: Dariusz Koryto Date: Thu, 15 Oct 2026 15:54:59 +0200 Subject: [PATCH] chore(ts): enable noFallthroughCasesInSwitch and noImplicitOverride (#577) Turn on both compiler checks in the root TypeScript configuration, which apps/web, apps/api, apps/extension, packages/shared and packages/plugin-api extend, and in the separate Expo configuration of apps/mobile. No switch statement falls through today. The 19 members that override a base class member (React error boundaries, the local database recovery error and the vendored poster badge) now declare `override`; this only changes type checking and is erased from the emitted JavaScript. --- apps/mobile/tsconfig.json | 2 ++ .../src/components/DesktopRendererErrorBoundary.tsx | 6 +++--- apps/web/src/components/EditorPaneErrorBoundary.tsx | 8 ++++---- .../components/ai-sidebar/AiSidebarErrorBoundary.tsx | 6 +++--- apps/web/src/lib/local-database-recovery.ts | 2 +- apps/web/src/lib/poster-design/vendor/qiaomu/badge.ts | 10 +++++----- apps/web/src/mobile-edit.tsx | 6 +++--- tsconfig.json | 2 ++ 8 files changed, 23 insertions(+), 19 deletions(-) diff --git a/apps/mobile/tsconfig.json b/apps/mobile/tsconfig.json index d267c3a1..e4137fa9 100644 --- a/apps/mobile/tsconfig.json +++ b/apps/mobile/tsconfig.json @@ -3,6 +3,8 @@ "compilerOptions": { "ignoreDeprecations": "6.0", "strict": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, "baseUrl": ".", "paths": { "@/*": [ diff --git a/apps/web/src/components/DesktopRendererErrorBoundary.tsx b/apps/web/src/components/DesktopRendererErrorBoundary.tsx index 2fb98817..49d9ddb7 100644 --- a/apps/web/src/components/DesktopRendererErrorBoundary.tsx +++ b/apps/web/src/components/DesktopRendererErrorBoundary.tsx @@ -17,7 +17,7 @@ type State = { const isChineseInterface = () => navigator.language.toLowerCase().startsWith("zh"); export class DesktopRendererErrorBoundary extends React.Component<{ children: ReactNode }, State> { - state: State = { error: null, reporting: false }; + override state: State = { error: null, reporting: false }; static getDerivedStateFromError(error: unknown): Pick { const normalized = error instanceof Error ? error : new Error(String(error)); @@ -31,7 +31,7 @@ export class DesktopRendererErrorBoundary extends React.Component<{ children: Re }; } - componentDidCatch(error: Error, info: ErrorInfo) { + override componentDidCatch(error: Error, info: ErrorInfo) { markRendererRecoveryRequired(); const details: RendererErrorDetails = { kind: "react-error", @@ -56,7 +56,7 @@ export class DesktopRendererErrorBoundary extends React.Component<{ children: Re } }; - render() { + override render() { if (!this.state.error) return this.props.children; const zh = isChineseInterface(); const desktop = Boolean(window.edgeeverDesktop?.isAvailable); diff --git a/apps/web/src/components/EditorPaneErrorBoundary.tsx b/apps/web/src/components/EditorPaneErrorBoundary.tsx index f739f3e0..1a0b0c2e 100644 --- a/apps/web/src/components/EditorPaneErrorBoundary.tsx +++ b/apps/web/src/components/EditorPaneErrorBoundary.tsx @@ -14,13 +14,13 @@ type State = { const isChineseInterface = () => navigator.language.toLowerCase().startsWith("zh"); export class EditorPaneErrorBoundary extends React.Component { - state: State = { error: null }; + override state: State = { error: null }; static getDerivedStateFromError(error: unknown): State { return { error: error instanceof Error ? error : new Error(String(error)) }; } - componentDidCatch(error: Error, info: ErrorInfo) { + override componentDidCatch(error: Error, info: ErrorInfo) { markRendererRecoveryRequired(); const details = { kind: "react-error" as const, @@ -32,7 +32,7 @@ export class EditorPaneErrorBoundary extends React.Component { void window.edgeeverDesktop?.recordRendererError(details).catch(() => undefined); } - componentDidUpdate(previousProps: Props) { + override componentDidUpdate(previousProps: Props) { if (this.state.error && previousProps.resetKey !== this.props.resetKey) { clearRendererRecoveryRequired(); this.setState({ error: null }); @@ -44,7 +44,7 @@ export class EditorPaneErrorBoundary extends React.Component { this.setState({ error: null }); }; - render() { + override render() { if (!this.state.error) return this.props.children; const zh = isChineseInterface(); diff --git a/apps/web/src/components/ai-sidebar/AiSidebarErrorBoundary.tsx b/apps/web/src/components/ai-sidebar/AiSidebarErrorBoundary.tsx index a2d81329..d34ec8e1 100644 --- a/apps/web/src/components/ai-sidebar/AiSidebarErrorBoundary.tsx +++ b/apps/web/src/components/ai-sidebar/AiSidebarErrorBoundary.tsx @@ -9,17 +9,17 @@ type Props = { type State = { failed: boolean }; export class AiSidebarErrorBoundary extends React.Component { - state: State = { failed: false }; + override state: State = { failed: false }; static getDerivedStateFromError(): State { return { failed: true }; } - componentDidCatch(error: Error, info: ErrorInfo) { + override componentDidCatch(error: Error, info: ErrorInfo) { console.error("Caught AI sidebar error", error, info.componentStack); } - render() { + override render() { if (!this.state.failed) return this.props.children; if (!this.props.open) return null; const zh = navigator.language.toLowerCase().startsWith("zh"); diff --git a/apps/web/src/lib/local-database-recovery.ts b/apps/web/src/lib/local-database-recovery.ts index 91276916..02b2c491 100644 --- a/apps/web/src/lib/local-database-recovery.ts +++ b/apps/web/src/lib/local-database-recovery.ts @@ -9,7 +9,7 @@ class LocalDatabaseOperationTimeoutError extends Error { } export class LocalDatabaseUnavailableError extends Error { - readonly cause: unknown; + override readonly cause: unknown; constructor(cause: unknown) { super("Browser local storage is not responding"); diff --git a/apps/web/src/lib/poster-design/vendor/qiaomu/badge.ts b/apps/web/src/lib/poster-design/vendor/qiaomu/badge.ts index 8a402f80..f04fe1a3 100644 --- a/apps/web/src/lib/poster-design/vendor/qiaomu/badge.ts +++ b/apps/web/src/lib/poster-design/vendor/qiaomu/badge.ts @@ -21,20 +21,20 @@ export function measureLabel(text: string, size: number, weight: string, family: } export class BadgeBox extends Textbox { - static type = 'BadgeBox'; - static customProperties = ['badgeBg', 'padX', 'padY', 'radius']; + static override type = 'BadgeBox'; + static override customProperties = ['badgeBg', 'padX', 'padY', 'radius']; declare badgeBg: string; declare padX: number; declare padY: number; declare radius: number; constructor(text: string, options?: Partial & Partial) { super(text, { badgeBg: '#111111', padX: 0.6, padY: 0.34, radius: 0.5, editable: false, lineHeight: 1, ...options } as Partial); } private metrics(): Metrics { return measureLabel(this.text.trim(), this.fontSize, String(this.fontWeight), this.fontFamily); } /** Size comes from the measured label, not from Fabric's line layout. */ - initDimensions(): void { + override initDimensions(): void { const m = this.metrics(); const px = this.padX * this.fontSize, py = this.padY * this.fontSize; this.width = m.width + px * 2; this.height = m.ascent + m.descent + py * 2; this.textLines = [this.text]; this._textLines = [[...this.text]]; this.dirty = true; } /** The pill is the visible extent. Fabric's own line cache is never filled here, so its version would throw. */ - getLineWidth(): number { return this.width; } - _render(c: CanvasRenderingContext2D): void { + override getLineWidth(): number { return this.width; } + override _render(c: CanvasRenderingContext2D): void { const w = this.width, h = this.height; const m = this.metrics(); const r = Math.min(h / 2, this.radius * h); c.save(); c.fillStyle = this.badgeBg; c.beginPath(); c.moveTo(-w / 2 + r, -h / 2); c.arcTo(w / 2, -h / 2, w / 2, h / 2, r); c.arcTo(w / 2, h / 2, -w / 2, h / 2, r); c.arcTo(-w / 2, h / 2, -w / 2, -h / 2, r); c.arcTo(-w / 2, -h / 2, w / 2, -h / 2, r); c.closePath(); c.fill(); diff --git a/apps/web/src/mobile-edit.tsx b/apps/web/src/mobile-edit.tsx index 20482e2b..f8bd6d05 100644 --- a/apps/web/src/mobile-edit.tsx +++ b/apps/web/src/mobile-edit.tsx @@ -38,17 +38,17 @@ const returnToPreviousPage = () => { }; class MobileEditorErrorBoundary extends React.Component { - state: MobileEditorErrorBoundaryState = { failed: false }; + override state: MobileEditorErrorBoundaryState = { failed: false }; static getDerivedStateFromError(): MobileEditorErrorBoundaryState { return { failed: true }; } - componentDidCatch(error: Error) { + override componentDidCatch(error: Error) { console.error("Mobile editor crashed", error); } - render() { + override render() { if (!this.state.failed) return this.props.children; const english = (getBrowserLocale() ?? defaultLocale) === "en-US"; return ( diff --git a/tsconfig.json b/tsconfig.json index c760ad79..43b3e4e4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,8 @@ "isolatedModules": true, "jsx": "react-jsx", "strict": true, + "noFallthroughCasesInSwitch": true, + "noImplicitOverride": true, "noEmit": true, "skipLibCheck": true, "ignoreDeprecations": "6.0",