From 4a5283543d4001c2f192a1f4daf48ca1f9e2c276 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Wed, 17 Jun 2026 06:10:30 +0100 Subject: [PATCH] for automatic review --- src/apps/webapp/main.ts | 4 +- .../FSAPIStorageEventManagerAdapter.ts | 6 +- src/apps/webapp/vaultSelector.ts | 2 +- src/lib | 2 +- .../coreFeatures/ModuleConflictResolver.ts | 4 +- utilsdeno/README.md | 102 +++++++++++++ utilsdeno/detect-any.ts | 49 +++++++ utilsdeno/refactor-assertions.ts | 96 ++++++++++++ utilsdeno/refactor-globals.ts | 7 +- utilsdeno/refactor-styles.ts | 7 +- utilsdeno/refactor-unused.ts | 138 ++++++++++++++++++ 11 files changed, 406 insertions(+), 11 deletions(-) create mode 100644 utilsdeno/README.md create mode 100644 utilsdeno/detect-any.ts create mode 100644 utilsdeno/refactor-assertions.ts create mode 100644 utilsdeno/refactor-unused.ts diff --git a/src/apps/webapp/main.ts b/src/apps/webapp/main.ts index 8b3dd4b..eb67e04 100644 --- a/src/apps/webapp/main.ts +++ b/src/apps/webapp/main.ts @@ -92,7 +92,7 @@ class LiveSyncWebApp { console.log("[Settings] Loaded from .livesync/settings.json"); return { ...DEFAULT_SETTINGS, ...data } as ObsidianLiveSyncSettings; } - } catch (error) { + } catch { console.log("[Settings] Failed to load, using defaults"); } return DEFAULT_SETTINGS as ObsidianLiveSyncSettings; @@ -170,7 +170,7 @@ class LiveSyncWebApp { const file = await fileHandle.getFile(); const text = await file.text(); return JSON.parse(text); - } catch (error) { + } catch { // File doesn't exist yet return null; } diff --git a/src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts b/src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts index 7c7285d..762903f 100644 --- a/src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts +++ b/src/apps/webapp/managers/FSAPIStorageEventManagerAdapter.ts @@ -182,7 +182,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter { if (changedHandle && changedHandle.kind === "file") { const file = await changedHandle.getFile(); const fileInfo = { - path: relativePath as any, + path: relativePath, stat: { size: file.size, mtime: file.lastModified, @@ -200,7 +200,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter { } } else if (type === "disappeared") { const fileInfo = { - path: relativePath as any, + path: relativePath, stat: { size: 0, mtime: Date.now(), @@ -217,7 +217,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter { if (changedHandle && changedHandle.kind === "file") { const file = await changedHandle.getFile(); const fileInfo = { - path: relativePath as any, + path: relativePath, stat: { size: file.size, mtime: file.lastModified, diff --git a/src/apps/webapp/vaultSelector.ts b/src/apps/webapp/vaultSelector.ts index fac24ca..37b7573 100644 --- a/src/apps/webapp/vaultSelector.ts +++ b/src/apps/webapp/vaultSelector.ts @@ -91,7 +91,7 @@ export class VaultHistoryStore { async getVaultHistory(): Promise { return this.withStore("readonly", async (store) => { - const keys = (await this.requestAsPromise(store.getAllKeys())) as IDBValidKey[]; + const keys = (await this.requestAsPromise(store.getAllKeys())); const values = (await this.requestAsPromise(store.getAll())) as unknown[]; const items: VaultHistoryItem[] = []; for (let i = 0; i < keys.length; i++) { diff --git a/src/lib b/src/lib index 7157dbc..c926417 160000 --- a/src/lib +++ b/src/lib @@ -1 +1 @@ -Subproject commit 7157dbc62cbdd551e6a5f9ff417bf05a833e9099 +Subproject commit c926417f82a4bf90e2e6fbc1ad3071a49077c52e diff --git a/src/modules/coreFeatures/ModuleConflictResolver.ts b/src/modules/coreFeatures/ModuleConflictResolver.ts index 80362a9..28544ad 100644 --- a/src/modules/coreFeatures/ModuleConflictResolver.ts +++ b/src/modules/coreFeatures/ModuleConflictResolver.ts @@ -182,9 +182,9 @@ export class ModuleConflictResolver extends AbstractModule { revs.map(async (rev) => { const leaf = await this.core.databaseFileAccess.fetchEntryMeta(filename, rev); if (leaf == false) { - return [0, rev] as [number, string]; + return [0, rev]; } - return [leaf.mtime, rev] as [number, string]; + return [leaf.mtime, rev]; }) )), ] as [number, string][] diff --git a/utilsdeno/README.md b/utilsdeno/README.md new file mode 100644 index 0000000..70d3a83 --- /dev/null +++ b/utilsdeno/README.md @@ -0,0 +1,102 @@ +# Refactoring and Code Quality Utilities + +This directory contains Deno-based scripts that utilise `ts-morph` to perform codebase-wide refactoring, code quality clean-up, and static analysis. + +These utilities are designed to help maintain code quality, resolve compiler warnings, and ensure popout window compatibility in the Obsidian plug-in environment. + +--- + +## Prerequisites + +To execute these scripts, you must have Deno installed on your system. + +--- + +## General Usage + +By default, all refactoring scripts run in **dry-run mode**. They will output the proposed changes to the console without modifying any files. + +To apply the changes to the files, append the `'--run'` flag: + +```bash +deno run --allow-read --allow-write --allow-env .ts --run +``` + +--- + +## Utilities Reference + +### 1. Global Wrapper Refactoring (`refactor-globals.ts`) +Converts standard global variable usages to compatibility wrappers to ensure safe operation when running in Obsidian popout windows (which run in separate window contexts). + +* **Targets**: `setTimeout`, `clearTimeout`, `setInterval`, `clearInterval`, `requestAnimationFrame`, `cancelAnimationFrame`, `localStorage`, `navigator`, `location`, `window`, `globalThis`, and `document`. +* **Actions**: + * Replaces global namespace references (like `window` and `globalThis`) with `compatGlobal`. + * Replaces `document` with `_activeDocument` (from `@lib/common/coreEnvFunctions.ts`). + * Injects or updates the necessary imports in modified files. +* **Command**: + ```bash + deno run --allow-read --allow-write --allow-env refactor-globals.ts + ``` + +### 2. Element Style Normalisation (`refactor-styles.ts`) +Converts direct style assignments on HTML/SVG elements to use the plug-in's `setCssStyles` helper. + +* **Actions**: + * Replaces statements like `element.style.color = 'red';` with `element.setCssStyles({ color: 'red' });`. + * Groups multiple consecutive style assignments on the same element into a single call. + * Supports both static keys and computed bracket properties. +* **Command**: + ```bash + deno run --allow-read --allow-write --allow-env refactor-styles.ts + ``` + +### 3. Redundant Assertions Cleanup (`refactor-assertions.ts`) +Finds and removes type assertions that are redundant because the expression already evaluates to the asserted type. + +* **Actions**: + * Removes redundant `as Type` or `` assertions. + * Preserves critical literal assertions such as `as const` and ``. +* **Command**: + ```bash + deno run --allow-read --allow-write --allow-env refactor-assertions.ts + ``` + +### 4. Unused Code Refactoring (`refactor-unused.ts`) +Cleans up unused imports and catch variables to reduce bundle size and warnings. + +* **Actions**: + * Converts unused catch variables to simple catch statements (e.g. `catch (error)` -> `catch`). + * Removes unused items in named imports, handling alias bindings (e.g. `import { A as B }`) correctly. + * Deletes empty import declarations resulting from the named import clean-up. +* **Command**: + ```bash + deno run --allow-read --allow-write --allow-env refactor-unused.ts + ``` + +### 5. Explicit Any Detection (`detect-any.ts`) +Scans the codebase and logs all occurrences of explicit `any` types. + +* **Actions**: + * Identifies uses of the `any` keyword in TypeScript and Svelte files. + * Logs the filename, line number, and matching code line for audit purposes. +* **Command**: + ```bash + deno run --allow-read --allow-env detect-any.ts + ``` + +### 6. Import Normalisation (`normalise-imports.ts`) +Ensures that all import statements are standardised across the codebase, resolving paths to aliases such as `@lib/` and `@/` where applicable. + +* **Command**: + ```bash + deno run --allow-read --allow-write --allow-env normalise-imports.ts + ``` + +--- + +## Safety and Exclusions + +* **Tests Excluded**: All scripts automatically skip files located in `_test/` or `testdeno/` folders, as well as files ending with `.spec.ts` or `.test.ts`. +* **Submodule Caution**: Some tools will run against the `src/lib/` submodule. Ensure you verify changes inside the submodule prior to committing. +* **Verification**: Always run `npm run check` and `npm run test:unit` after performing refactoring tasks to verify that type safety and tests remain intact. diff --git a/utilsdeno/detect-any.ts b/utilsdeno/detect-any.ts new file mode 100644 index 0000000..f56e11a --- /dev/null +++ b/utilsdeno/detect-any.ts @@ -0,0 +1,49 @@ +// Detect explicit usage of 'any' type in the codebase. +// Use this script by running `deno run --allow-read --allow-env detect-any.ts` from the utilsdeno directory. +import { Project, SyntaxKind } from "npm:ts-morph"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const project = new Project({ tsConfigFilePath: "../tsconfig.json" }); +project.addSourceFilesAtPaths("../src/**/*.ts"); +project.addSourceFilesAtPaths("../src/**/*.svelte"); + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const projectRoot = path.resolve(__dirname, ".."); + +function toPosixPath(filePath: string): string { + return filePath.replace(/\\/g, "/"); +} + +const posixProjectRoot = toPosixPath(projectRoot); +const posixSrc = `${posixProjectRoot}/src`; + +let anyCount = 0; + +for (const sourceFile of project.getSourceFiles()) { + const filePath = sourceFile.getFilePath(); + const posixFilePath = toPosixPath(filePath); + + if (!posixFilePath.startsWith(posixSrc)) continue; + if ( + posixFilePath.includes("/_test/") || + posixFilePath.endsWith(".spec.ts") || + posixFilePath.endsWith(".test.ts") + ) { + continue; + } + + const anyNodes = sourceFile.getDescendantsOfKind(SyntaxKind.AnyKeyword); + if (anyNodes.length > 0) { + console.log(`File: ${posixFilePath.slice(posixProjectRoot.length + 1)}`); + for (const anyNode of anyNodes) { + const { line } = sourceFile.getLineAndColumnAtPos(anyNode.getStart()); + const lineText = sourceFile.getFullText().split(/\r?\n/)[line - 1]; + console.log(` Line ${line}: ${lineText.trim()}`); + anyCount++; + } + } +} + +console.log(`\nTotal explicit 'any' usages found: ${anyCount}`); diff --git a/utilsdeno/refactor-assertions.ts b/utilsdeno/refactor-assertions.ts new file mode 100644 index 0000000..24d8dc4 --- /dev/null +++ b/utilsdeno/refactor-assertions.ts @@ -0,0 +1,96 @@ +// Refactor unnecessary type assertions (e.g. `expr as Type` where type of `expr` is already `Type`). +// Use this script by running `deno run --allow-read --allow-write --allow-env refactor-assertions.ts` from the utilsdeno directory. +// Run with --run flag to apply changes. +import { Project, SyntaxKind, Node } from "npm:ts-morph"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const isDryRun = !Deno.args.includes("--run"); + +if (isDryRun) { + console.log("=== DRY RUN MODE ==="); + console.log( + "To apply changes, run with: deno run --allow-read --allow-write --allow-env refactor-assertions.ts --run\n" + ); +} else { + console.log("=== RUN MODE: WILL MODIFY FILES ==="); +} + +const project = new Project({ tsConfigFilePath: "../tsconfig.json" }); +project.addSourceFilesAtPaths("../src/**/*.ts"); + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const projectRoot = path.resolve(__dirname, ".."); + +function toPosixPath(filePath: string): string { + return filePath.replace(/\\/g, "/"); +} + +const posixProjectRoot = toPosixPath(projectRoot); +const posixSrc = `${posixProjectRoot}/src`; + +let modifiedFilesCount = 0; + +for (const sourceFile of project.getSourceFiles()) { + const filePath = sourceFile.getFilePath(); + const posixFilePath = toPosixPath(filePath); + + if (!posixFilePath.startsWith(posixSrc)) continue; + if (posixFilePath.includes("/_test/") || posixFilePath.endsWith(".spec.ts") || posixFilePath.endsWith(".test.ts")) continue; + + // Find AsExpression (expr as Type) and TypeAssertion (expr) + const asExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.AsExpression); + const typeAssertions = sourceFile.getDescendantsOfKind(SyntaxKind.TypeAssertion); + const allAssertions = [...asExpressions, ...typeAssertions]; + + const nodesToRemove: Node[] = []; + + for (const node of allAssertions) { + const expr = node.getExpression(); + const exprType = expr.getType(); + const assertType = node.getType(); + + // Skip `as const` or `` assertions + const typeNode = (node as any).getTypeNode?.(); + if (typeNode && typeNode.getText() === "const") { + continue; + } + + // Compare type texts to find redundant assertions + const exprTypeText = exprType.getText(); + const assertTypeText = assertType.getText(); + + if (exprTypeText === assertTypeText) { + nodesToRemove.push(node); + } + } + + if (nodesToRemove.length > 0) { + console.log(`File: ${posixFilePath.slice(posixProjectRoot.length + 1)}`); + + // Reverse nodes order to keep indices/references valid when modifying + const sortedNodes = [...nodesToRemove].sort((a, b) => b.getStart() - a.getStart()); + + for (const node of sortedNodes) { + const { line } = sourceFile.getLineAndColumnAtPos(node.getStart()); + const exprText = node.getExpression().getText(); + console.log(` Line ${line}: "${node.getText()}" -> "${exprText}"`); + + if (!isDryRun) { + node.replaceWithText(exprText); + } + } + + modifiedFilesCount++; + } +} + +console.log(`\nTotal files to modify: ${modifiedFilesCount}`); + +if (!isDryRun) { + project.saveSync(); + console.log("All changes successfully saved."); +} else { + console.log("Dry run complete. No changes were written to files."); +} diff --git a/utilsdeno/refactor-globals.ts b/utilsdeno/refactor-globals.ts index 36b75f3..bd368df 100644 --- a/utilsdeno/refactor-globals.ts +++ b/utilsdeno/refactor-globals.ts @@ -65,7 +65,12 @@ for (const sourceFile of project.getSourceFiles()) { } // Exclude unit and integration test files - if (posixFilePath.endsWith(".spec.ts") || posixFilePath.endsWith(".test.ts") || posixFilePath.includes("/_test/")) { + if ( + posixFilePath.endsWith(".spec.ts") || + posixFilePath.endsWith(".test.ts") || + posixFilePath.includes("/_test/") || + posixFilePath.includes("/testdeno/") + ) { continue; } diff --git a/utilsdeno/refactor-styles.ts b/utilsdeno/refactor-styles.ts index 4bdb4c6..c9546d7 100644 --- a/utilsdeno/refactor-styles.ts +++ b/utilsdeno/refactor-styles.ts @@ -100,7 +100,12 @@ for (const sourceFile of project.getSourceFiles()) { } // Exclude unit and integration test files - if (posixFilePath.endsWith(".spec.ts") || posixFilePath.endsWith(".test.ts") || posixFilePath.includes("/_test/")) { + if ( + posixFilePath.endsWith(".spec.ts") || + posixFilePath.endsWith(".test.ts") || + posixFilePath.includes("/_test/") || + posixFilePath.includes("/testdeno/") + ) { continue; } diff --git a/utilsdeno/refactor-unused.ts b/utilsdeno/refactor-unused.ts new file mode 100644 index 0000000..d805c32 --- /dev/null +++ b/utilsdeno/refactor-unused.ts @@ -0,0 +1,138 @@ +// Refactor unused catch variables and unused imports in the codebase. +// Use this script by running `deno run --allow-read --allow-write --allow-env refactor-unused.ts` from the utilsdeno directory. +// Run with --run flag to apply changes. +import { Project, SyntaxKind, Node } from "npm:ts-morph"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const isDryRun = !Deno.args.includes("--run"); + +if (isDryRun) { + console.log("=== DRY RUN MODE ==="); + console.log( + "To apply changes, run with: deno run --allow-read --allow-write --allow-env refactor-unused.ts --run\n" + ); +} else { + console.log("=== RUN MODE: WILL MODIFY FILES ==="); +} + +const project = new Project({ tsConfigFilePath: "../tsconfig.json" }); +// Only add .ts files to avoid Svelte-markup-blindness references +project.addSourceFilesAtPaths("../src/**/*.ts"); + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const projectRoot = path.resolve(__dirname, ".."); + +function toPosixPath(filePath: string): string { + return filePath.replace(/\\/g, "/"); +} + +const posixProjectRoot = toPosixPath(projectRoot); +const posixSrc = `${posixProjectRoot}/src`; + +let modifiedFilesCount = 0; + +for (const sourceFile of project.getSourceFiles()) { + const filePath = sourceFile.getFilePath(); + const posixFilePath = toPosixPath(filePath); + + if (!posixFilePath.startsWith(posixSrc)) continue; + if (posixFilePath.includes("/_test/") || posixFilePath.endsWith(".spec.ts") || posixFilePath.endsWith(".test.ts")) continue; + + let fileModified = false; + + // 1. Find unused catch variables: catch (error) -> catch + const catchClauses = sourceFile.getDescendantsOfKind(SyntaxKind.CatchClause); + const catchVarsToRemove: Node[] = []; + + for (const catchClause of catchClauses) { + const varDec = catchClause.getVariableDeclaration(); + if (varDec) { + const varName = varDec.getName(); + // Count references within the catch clause itself + const count = catchClause.getDescendantsOfKind(SyntaxKind.Identifier) + .filter((id) => id.getText() === varName) + .length; + if (count === 1) { // Only the declaration itself + catchVarsToRemove.push(varDec); + } + } + } + + if (catchVarsToRemove.length > 0) { + if (!fileModified) { + console.log(`File: ${posixFilePath.slice(posixProjectRoot.length + 1)}`); + fileModified = true; + } + for (const varDec of catchVarsToRemove) { + const { line } = sourceFile.getLineAndColumnAtPos(varDec.getStart()); + console.log(` Line ${line}: Unused catch variable "${varDec.getText()}" -> Remove it`); + if (!isDryRun) { + varDec.remove(); + } + } + } + + // 2. Find unused named imports + const importDeclarations = sourceFile.getImportDeclarations(); + const importsToRemove: { namedImport: any; impDecl: any }[] = []; + const modifiedDecls = new Set(); + + for (const impDecl of importDeclarations) { + const namedImports = impDecl.getNamedImports(); + if (namedImports.length === 0) continue; + + for (const namedImport of namedImports) { + const importName = namedImport.getAliasNode()?.getText() ?? namedImport.getName(); + // Count references in the entire file + const count = sourceFile.getDescendantsOfKind(SyntaxKind.Identifier) + .filter((id) => id.getText() === importName) + .length; + if (count === 1) { // Only the import specifier itself + importsToRemove.push({ namedImport, impDecl }); + } + } + } + + if (importsToRemove.length > 0) { + if (!fileModified) { + console.log(`File: ${posixFilePath.slice(posixProjectRoot.length + 1)}`); + fileModified = true; + } + for (const { namedImport, impDecl } of importsToRemove) { + const { line } = sourceFile.getLineAndColumnAtPos(namedImport.getStart()); + console.log(` Line ${line}: Unused named import "${namedImport.getText()}" -> Remove it`); + if (!isDryRun) { + namedImport.remove(); + modifiedDecls.add(impDecl); + } + } + } + + // 3. Clean up empty import declarations (only those we actually modified) + if (!isDryRun && fileModified && modifiedDecls.size > 0) { + for (const impDecl of modifiedDecls) { + if ( + impDecl.getNamedImports().length === 0 && + !impDecl.getDefaultImport() && + !impDecl.getNamespaceImport() + ) { + impDecl.remove(); + } + } + } + + if (fileModified) { + modifiedFilesCount++; + } +} + +console.log(`\nTotal files to modify: ${modifiedFilesCount}`); + +if (!isDryRun) { + project.saveSync(); + console.log("All changes successfully saved."); +} else { + console.log("Dry run complete. No changes were written to files."); +}