for automatic review

This commit is contained in:
vorotamoroz
2026-06-17 06:10:30 +01:00
parent 7895336189
commit 4a5283543d
11 changed files with 406 additions and 11 deletions
+102
View File
@@ -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 <script_name>.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 `<Type>` assertions.
* Preserves critical literal assertions such as `as const` and `<const>`.
* **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.
+49
View File
@@ -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}`);
+96
View File
@@ -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 (<Type>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 `<const>` 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.");
}
+6 -1
View File
@@ -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;
}
+6 -1
View File
@@ -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;
}
+138
View File
@@ -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<any>();
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.");
}