Add ESLint config and unused-imports cleanup script
Recommend dbaeumer.vscode-eslint in .vscode/extensions.json
This commit is contained in:
5
.vscode/extensions.json
vendored
Normal file
5
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
||||
119
eslint.config.js
Normal file
119
eslint.config.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import js from '@eslint/js';
|
||||
import tsParser from '@typescript-eslint/parser';
|
||||
import tsPlugin from '@typescript-eslint/eslint-plugin';
|
||||
import unusedImports from 'eslint-plugin-unused-imports';
|
||||
import reactHooks from 'eslint-plugin-react-hooks';
|
||||
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
{
|
||||
ignores: [
|
||||
'node_modules/**',
|
||||
'dist/**',
|
||||
'electron/**',
|
||||
'scripts/**',
|
||||
],
|
||||
},
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
globals: {
|
||||
// Browser globals
|
||||
window: 'readonly',
|
||||
document: 'readonly',
|
||||
navigator: 'readonly',
|
||||
console: 'readonly',
|
||||
setTimeout: 'readonly',
|
||||
clearTimeout: 'readonly',
|
||||
setInterval: 'readonly',
|
||||
clearInterval: 'readonly',
|
||||
requestAnimationFrame: 'readonly',
|
||||
cancelAnimationFrame: 'readonly',
|
||||
fetch: 'readonly',
|
||||
URL: 'readonly',
|
||||
URLSearchParams: 'readonly',
|
||||
FormData: 'readonly',
|
||||
Blob: 'readonly',
|
||||
File: 'readonly',
|
||||
FileReader: 'readonly',
|
||||
AbortController: 'readonly',
|
||||
AbortSignal: 'readonly',
|
||||
Event: 'readonly',
|
||||
EventTarget: 'readonly',
|
||||
CustomEvent: 'readonly',
|
||||
MouseEvent: 'readonly',
|
||||
KeyboardEvent: 'readonly',
|
||||
DragEvent: 'readonly',
|
||||
ClipboardEvent: 'readonly',
|
||||
HTMLElement: 'readonly',
|
||||
HTMLInputElement: 'readonly',
|
||||
HTMLTextAreaElement: 'readonly',
|
||||
HTMLButtonElement: 'readonly',
|
||||
HTMLDivElement: 'readonly',
|
||||
HTMLFormElement: 'readonly',
|
||||
Element: 'readonly',
|
||||
Node: 'readonly',
|
||||
NodeList: 'readonly',
|
||||
MutationObserver: 'readonly',
|
||||
ResizeObserver: 'readonly',
|
||||
IntersectionObserver: 'readonly',
|
||||
localStorage: 'readonly',
|
||||
sessionStorage: 'readonly',
|
||||
crypto: 'readonly',
|
||||
performance: 'readonly',
|
||||
confirm: 'readonly',
|
||||
alert: 'readonly',
|
||||
prompt: 'readonly',
|
||||
getComputedStyle: 'readonly',
|
||||
atob: 'readonly',
|
||||
btoa: 'readonly',
|
||||
// Node.js globals (for Electron)
|
||||
process: 'readonly',
|
||||
global: 'readonly',
|
||||
Buffer: 'readonly',
|
||||
__dirname: 'readonly',
|
||||
__filename: 'readonly',
|
||||
module: 'readonly',
|
||||
require: 'readonly',
|
||||
exports: 'readonly',
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
'@typescript-eslint': tsPlugin,
|
||||
'unused-imports': unusedImports,
|
||||
'react-hooks': reactHooks,
|
||||
},
|
||||
rules: {
|
||||
// Disable base rules that conflict with TypeScript
|
||||
'no-unused-vars': 'off',
|
||||
'no-undef': 'off', // TypeScript handles this
|
||||
|
||||
// TypeScript unused vars (disabled in favor of unused-imports)
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
|
||||
// Unused imports plugin - this is the main feature you want
|
||||
'unused-imports/no-unused-imports': 'warn',
|
||||
'unused-imports/no-unused-vars': [
|
||||
'warn',
|
||||
{
|
||||
vars: 'all',
|
||||
varsIgnorePattern: '^_',
|
||||
args: 'after-used',
|
||||
argsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
|
||||
// React Hooks rules
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
},
|
||||
},
|
||||
];
|
||||
118
scripts/remove-unused-imports.mjs
Normal file
118
scripts/remove-unused-imports.mjs
Normal file
@@ -0,0 +1,118 @@
|
||||
import ts from 'typescript';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const rootDir = path.resolve(__dirname, '..');
|
||||
|
||||
// Find all TSX files recursively
|
||||
function findTsxFiles(dir, files = []) {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== 'dist') {
|
||||
findTsxFiles(fullPath, files);
|
||||
} else if (entry.isFile() && (entry.name.endsWith('.tsx') || entry.name.endsWith('.ts'))) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
// Organize imports using TypeScript Language Service
|
||||
function organizeImports(filePath) {
|
||||
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
||||
|
||||
// Create a simple in-memory host
|
||||
const servicesHost = {
|
||||
getScriptFileNames: () => [filePath],
|
||||
getScriptVersion: () => '1',
|
||||
getScriptSnapshot: (fileName) => {
|
||||
if (fileName === filePath) {
|
||||
return ts.ScriptSnapshot.fromString(fileContent);
|
||||
}
|
||||
if (!fs.existsSync(fileName)) {
|
||||
return undefined;
|
||||
}
|
||||
return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName, 'utf-8'));
|
||||
},
|
||||
getCurrentDirectory: () => rootDir,
|
||||
getCompilationSettings: () => ({
|
||||
target: ts.ScriptTarget.ES2022,
|
||||
module: ts.ModuleKind.ESNext,
|
||||
jsx: ts.JsxEmit.ReactJSX,
|
||||
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
||||
allowJs: true,
|
||||
skipLibCheck: true,
|
||||
noEmit: true,
|
||||
}),
|
||||
getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options),
|
||||
fileExists: ts.sys.fileExists,
|
||||
readFile: ts.sys.readFile,
|
||||
readDirectory: ts.sys.readDirectory,
|
||||
directoryExists: ts.sys.directoryExists,
|
||||
getDirectories: ts.sys.getDirectories,
|
||||
};
|
||||
|
||||
const services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
|
||||
|
||||
// Get organize imports edits
|
||||
const edits = services.organizeImports(
|
||||
{ type: 'file', fileName: filePath },
|
||||
{},
|
||||
{}
|
||||
);
|
||||
|
||||
if (edits.length === 0) {
|
||||
return { changed: false, content: fileContent };
|
||||
}
|
||||
|
||||
// Apply edits
|
||||
let newContent = fileContent;
|
||||
// Apply edits in reverse order to preserve positions
|
||||
for (const fileEdit of edits) {
|
||||
const textChanges = [...fileEdit.textChanges].sort((a, b) => b.span.start - a.span.start);
|
||||
for (const change of textChanges) {
|
||||
newContent =
|
||||
newContent.slice(0, change.span.start) +
|
||||
change.newText +
|
||||
newContent.slice(change.span.start + change.span.length);
|
||||
}
|
||||
}
|
||||
|
||||
return { changed: newContent !== fileContent, content: newContent };
|
||||
}
|
||||
|
||||
// Main
|
||||
console.log('🔍 Searching for TSX/TS files...\n');
|
||||
const files = findTsxFiles(rootDir);
|
||||
console.log(`Found ${files.length} files to process.\n`);
|
||||
|
||||
let changedCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const relativePath = path.relative(rootDir, file);
|
||||
try {
|
||||
const result = organizeImports(file);
|
||||
if (result.changed) {
|
||||
fs.writeFileSync(file, result.content, 'utf-8');
|
||||
console.log(`✅ Fixed: ${relativePath}`);
|
||||
changedCount++;
|
||||
} else {
|
||||
console.log(`⏭️ Skipped (no changes): ${relativePath}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Error processing ${relativePath}: ${error.message}`);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(50));
|
||||
console.log(`📊 Summary:`);
|
||||
console.log(` Files processed: ${files.length}`);
|
||||
console.log(` Files modified: ${changedCount}`);
|
||||
console.log(` Errors: ${errorCount}`);
|
||||
console.log('='.repeat(50));
|
||||
Reference in New Issue
Block a user