refactor: route exhaustiveness guards through assertNever

- sync mode/scope, conflict mode, setup-requirement kind, credential type 5개 throw 가드 → assertNever(x, label) (에러 메시지 보존)
- migration.ts: 가짜 `type as never` → 진짜 `credType satisfies never` 가드로 업그레이드 (컴파일타임 exhaustiveness 확보, return undefined 동작 유지)
- settings-tab filterMode는 non-throw fallback이라 satisfies never 유지
This commit is contained in:
uppinote 2026-06-20 20:03:34 +09:00
parent 2181997e27
commit 9c4c7b4f13
4 changed files with 20 additions and 26 deletions

View file

@ -7,7 +7,7 @@
*/
import { Notice } from "obsidian";
import { areValuesEqual } from '../utils';
import { areValuesEqual, assertNever } from '../utils';
import type { LegacySettings, ConflictInfo, SyncResult, DatabaseProvider } from '../types';
/**
@ -80,10 +80,8 @@ export class ConflictResolver {
case 'manual':
return this.resolveManual(conflicts, recordId);
default: {
const _exhaustive: never = this.settings.conflictResolution;
throw new Error(`Unknown conflict resolution mode: ${_exhaustive}`);
}
default:
return assertNever(this.settings.conflictResolution, 'Unknown conflict resolution mode');
}
}

View file

@ -22,7 +22,7 @@ import { FieldCache } from '../services';
import { ConflictResolver } from './conflict-resolver';
import { FrontmatterParser, FileWatcher } from '../file-operations';
import { parseTemplate, buildMarkdownContent, generateBasesContent, resolveBasesFilePath, collectFieldNames } from '../builders';
import { sanitizeFileName, sanitizeSubfolderValue, validateAndSanitizeFilename } from '../utils';
import { sanitizeFileName, sanitizeSubfolderValue, validateAndSanitizeFilename, assertNever } from '../utils';
export interface StatusBarHandle {
setText(text: string): void;
@ -126,10 +126,8 @@ export class SyncOrchestrator {
break;
}
default: {
const _exhaustive: never = mode;
throw new Error(`Unknown sync mode: ${_exhaustive}`);
}
default:
assertNever(mode, 'Unknown sync mode');
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
@ -166,10 +164,8 @@ export class SyncOrchestrator {
return this.collectMarkdownFiles(folder);
}
default: {
const _exhaustive: never = scope;
throw new Error(`Unknown sync scope: ${_exhaustive}`);
}
default:
return assertNever(scope, 'Unknown sync scope');
}
}

View file

@ -34,6 +34,7 @@ import { FolderSuggest, FileSuggest } from './suggest';
import { generateId } from '../utils/object-utils';
import { validateFolderPath } from '../utils/validation';
import { debounce } from '../utils/debounce';
import { assertNever } from '../utils/assert';
/**
* Interface for the plugin that the settings tab needs.
@ -1410,10 +1411,8 @@ export class AutoNoteImporterSettingTab extends PluginSettingTab {
}
return;
}
default: {
const _exhaustive: never = needsSetup.kind;
throw new Error(`Unhandled setup requirement kind: ${String(_exhaustive)}`);
}
default:
return assertNever(needsSetup.kind, 'Unhandled setup requirement kind');
}
}
@ -1531,10 +1530,8 @@ export class AutoNoteImporterSettingTab extends PluginSettingTab {
return [credential.type, credential.integrationToken];
case 'custom-api':
return [credential.type, credential.baseUrl, credential.authHeader, credential.authValue];
default: {
const _exhaustive: never = credential;
throw new Error(`Unknown credential type: ${String(_exhaustive)}`);
}
default:
return assertNever(credential, 'Unknown credential type');
}
})();
return AutoNoteImporterSettingTab.hashCredentialFingerprint(parts.map(p => p.trim()).join('\0'));

View file

@ -155,7 +155,8 @@ function buildCredentialFromRecord(raw: Record<string, unknown>): Credential | u
}
const base = { id, name: readString(raw, 'name', '') };
switch (type as CredentialType) {
const credType = type as CredentialType;
switch (credType) {
case 'airtable':
return { ...base, type: 'airtable', apiKey: readString(raw, 'apiKey', '') };
case 'seatable':
@ -180,9 +181,11 @@ function buildCredentialFromRecord(raw: Record<string, unknown>): Credential | u
authValue: readString(raw, 'authValue', ''),
};
default: {
// Compile-time exhaustiveness guard — unreachable at runtime (CREDENTIAL_TYPES.includes guards above).
const _exhaustive: never = type as never;
void _exhaustive;
// Real compile-time exhaustiveness guard: credType narrows to never once
// every CredentialType has a case. Returns undefined (not throw) so a
// malformed legacy setting is skipped rather than fatal — the
// CREDENTIAL_TYPES.includes() check above already gates the runtime path.
credType satisfies never;
return undefined;
}
}