fix(core): resolve lint issues in services and view

This commit is contained in:
mssoftjp 2025-11-10 00:27:43 +09:00
parent 9cb8378f44
commit 7ddd851713
9 changed files with 70 additions and 32 deletions

View file

@ -106,7 +106,7 @@ export class ErrorHandler implements IDisposable {
// i18n service might not be available yet during initialization
try {
this.i18n = getI18n();
} catch (_error) {
} catch {
// i18n service will be available later
}
@ -130,22 +130,21 @@ export class ErrorHandler implements IDisposable {
// Unhandled promise rejections
this.unhandledRejectionHandler = (event: PromiseRejectionEvent) => {
// Filter out errors that are not from our plugin
const error = event.reason;
const errorString = error?.toString?.() || String(error);
const reasonSummary = this.describeReason(event.reason);
// Ignore errors from other plugins/sources
if (errorString.includes('inputEl') ||
errorString.includes('Cannot read properties of undefined') ||
!errorString.includes('voice-input')) {
if (reasonSummary.includes('inputEl') ||
reasonSummary.includes('Cannot read properties of undefined') ||
!reasonSummary.includes('voice-input')) {
// Log in development mode only
if (this.options.isDevelopment) {
this.logger.debug('[VoiceInput] Ignored external error', error);
this.logger.debug('[VoiceInput] Ignored external error', { reason: reasonSummary });
}
return;
}
this.handleError(
new Error(`Unhandled Promise Rejection: ${event.reason}`),
new Error(`Unhandled Promise Rejection: ${reasonSummary}`),
{
component: 'Global',
operation: 'UnhandledRejection',
@ -159,8 +158,9 @@ export class ErrorHandler implements IDisposable {
// Global error events
this.errorHandler = (event: ErrorEvent) => {
const eventError = event.error instanceof Error ? event.error : new Error(event.message);
this.handleError(
event.error || new Error(event.message),
eventError,
{
component: 'Global',
operation: 'UncaughtError',
@ -247,7 +247,7 @@ export class ErrorHandler implements IDisposable {
let contextSummary: string;
try {
contextSummary = JSON.stringify(context);
} catch (_error) {
} catch {
contextSummary = '[Unserializable context]';
}
throw lastError || new Error(`Retry operation failed after ${this.options.maxRetries} attempts in context: ${contextSummary}`);
@ -319,6 +319,20 @@ export class ErrorHandler implements IDisposable {
}
}
private describeReason(reason: unknown): string {
if (typeof reason === 'string') {
return reason;
}
if (reason instanceof Error) {
return reason.message || reason.name;
}
try {
return JSON.stringify(reason);
} catch {
return String(reason);
}
}
/**
*
*/
@ -343,7 +357,7 @@ export class ErrorHandler implements IDisposable {
if (!this.i18n) {
try {
this.i18n = getI18n();
} catch (_error) {
} catch {
// Fallback to English messages
switch (severity) {
case ErrorSeverity.FATAL:

View file

@ -37,7 +37,7 @@ export class TranscriptionError extends Error {
try {
i18n = getI18n();
} catch (_error) {
} catch {
// Fallback to original message if i18n is not available
return this.message;
}

View file

@ -1,2 +1,2 @@
// Main entry point for Voice Memo Plugin
export { default } from './plugin';
export { default } from './plugin';

View file

@ -21,7 +21,7 @@ export class ViewManager extends Disposable {
// ServiceLocatorからLoggerを取得
try {
this.logger = createServiceLogger('ViewManager');
} catch (_error) {
} catch {
// フォールバック: ServiceLocatorが初期化されていない場合
// 基本のLoggerインスタンスを作成
this.logger = Logger.getLogger('ViewManager');

View file

@ -11,6 +11,14 @@ import { serviceLocator, ServiceKeys, getI18nService } from '../services';
import { getObsidianLocale } from '../types';
import { SafeStorageService } from '../security';
type LegacyVoiceInputSettings = Partial<VoiceInputSettings> & {
language?: string;
interfaceLanguage?: string;
recordingMode?: string;
autoStopSilenceDuration?: number;
minSpeechDuration?: number;
};
export default class VoiceInputPlugin extends Plugin {
settings: VoiceInputSettings;
private viewManager: ViewManager;
@ -110,7 +118,7 @@ export default class VoiceInputPlugin extends Plugin {
// Add ribbon icon
this.addRibbonIcon('microphone', i18n.t('ui.titles.main'), () => {
this.logger.debug('Ribbon icon clicked');
this.viewManager.activateVoiceInputView();
void this.viewManager.activateVoiceInputView();
});
this.logger.debug('Ribbon icon added');
@ -120,7 +128,7 @@ export default class VoiceInputPlugin extends Plugin {
name: i18n.t('ui.commands.openView'),
callback: () => {
this.logger.debug('Command executed: open-view');
this.viewManager.activateVoiceInputView();
void this.viewManager.activateVoiceInputView();
}
});
this.logger.debug('Commands registered');
@ -212,7 +220,7 @@ export default class VoiceInputPlugin extends Plugin {
}
async loadSettings() {
const data = await this.loadData();
const data = this.parseStoredSettings(await this.loadData());
// まずデフォルト設定から開始
this.settings = { ...DEFAULT_SETTINGS };
@ -475,4 +483,11 @@ export default class VoiceInputPlugin extends Plugin {
getLogger(): Logger | null {
return this.logger;
}
private parseStoredSettings(data: unknown): LegacyVoiceInputSettings | null {
if (typeof data === 'object' && data !== null) {
return data as LegacyVoiceInputSettings;
}
return null;
}
}

View file

@ -34,6 +34,16 @@ const isElectronWindow = (win: Window): win is ElectronWindow => {
return 'require' in win && typeof (win as ElectronWindow).require === 'function';
};
const resolveElectronRenderer = (): ElectronRenderer | null => {
if (isElectronWindow(window) && typeof (window as ElectronWindow).require === 'function') {
return (window as ElectronWindow).require('electron') ?? null;
}
if ((window as ElectronWindow).electron) {
return (window as ElectronWindow).electron ?? null;
}
return (globalThis as ElectronGlobal).electron ?? null;
};
export class SafeStorageService {
private static safeStorage: ElectronSafeStorage | null = null;
private static logger: Logger = getLogger('SafeStorageService');
@ -53,15 +63,10 @@ export class SafeStorageService {
this.logger.debug(`window.require exists: ${'require' in window}`);
this.logger.debug(`window.require is function: ${typeof (window as ElectronWindow).require === 'function'}`);
const electron = isElectronWindow(window) ? window.require?.('electron') : null;
const electron = resolveElectronRenderer();
if (!electron) {
// Alternative access method
const globalElectron = (window as ElectronWindow).electron || (global as ElectronGlobal).electron;
if (globalElectron) {
this.logger.debug('Found electron via global access');
this.safeStorage = globalElectron.remote?.safeStorage || globalElectron.safeStorage || null;
}
this.logger.debug('Electron renderer not available');
} else {
this.logger.debug(`Electron object exists: ${!!electron}`);
this.logger.debug(`Electron.remote exists: ${!!electron?.remote}`);

View file

@ -127,8 +127,10 @@ export class I18nServiceImpl implements I18nService {
* Supports {paramName} syntax
*/
private substituteParams(translation: string, params: Record<string, string | number>): string {
return translation.replace(/{(\w+)}/g, (match, paramName) => {
return params[paramName]?.toString() || match;
return translation.replace(/{(\w+)}/g, (match, captured) => {
const paramKey = captured as keyof typeof params;
const value = params[paramKey];
return value !== undefined ? value.toString() : match;
});
}

View file

@ -20,7 +20,8 @@ export class ObsidianHttpClient {
}): Promise<{ status: number; json: unknown; text: string }> {
const { url, method = 'GET', headers = {}, body } = opts;
const res = await requestUrl({ url, method, headers, body, throw: false });
return { status: res.status, json: res.json, text: res.text };
const jsonBody: unknown = res.json;
return { status: res.status, json: jsonBody, text: res.text };
}
/**
@ -43,7 +44,8 @@ export class ObsidianHttpClient {
body: JSON.stringify(data),
throw: false
});
return { status: res.status, json: res.json };
const jsonBody: unknown = res.json;
return { status: res.status, json: jsonBody };
}
/**
@ -63,8 +65,8 @@ export class ObsidianHttpClient {
body,
throw: false
});
return { status: response.status, json: response.json };
const jsonBody: unknown = response.json;
return { status: response.status, json: jsonBody };
}
/**

View file

@ -33,8 +33,8 @@ export class VoiceInputViewUI {
secondRowContainer: HTMLElement;
// Settings controls
correctionToggle: ToggleComponent;
transcriptionModelDropdown: DropdownComponent;
correctionToggle: ToggleComponent | null = null;
transcriptionModelDropdown: DropdownComponent | null = null;
// Event handlers
private clickHandler: ((e: MouseEvent) => void) | null = null;