From 08ec59bdb9582b626dc0d6a96b9276537ec8444d Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 00:12:33 +0000 Subject: [PATCH] Clarify variable LOC in docs --- DOCUMENTATION.md | 18 +++- README.md | 19 +++- scripts/analyze-types.ts | 86 +++++++++++---- scripts/refactor-tsx-pass2.ts | 161 ++++++++++++++++++++++++---- scripts/refactor-tsx.ts | 196 +++++++++++++++++++++++++++++----- 5 files changed, 406 insertions(+), 74 deletions(-) diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 64c1d1f..96ab0f7 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -1,6 +1,6 @@ # TSX Refactoring with ts-morph -Automated TSX/TypeScript refactoring tools using [ts-morph](https://ts-morph.com/) to extract large code blocks (>150 LOC) into separate, well-typed, lint-compliant files with automatic import management. +Automated TSX/TypeScript refactoring tools using [ts-morph](https://ts-morph.com/) to extract code blocks into separate, well-typed, lint-compliant files with automatic import management. The intent is to make smaller components practical (e.g., 50/100/150 LOC thresholds) by pushing logic into hooks/utilities and iterating via a feedback loop. ## Features @@ -8,6 +8,8 @@ Automated TSX/TypeScript refactoring tools using [ts-morph](https://ts-morph.com - Extracts types and interfaces to separate `.types.ts` files - Extracts utility functions to separate `.utils.ts` files - Automatically generates and fixes imports +- Supports smaller component targets (e.g., 50/100/150 LOC) by pushing logic into hooks and helpers +- Designed for iterative runs so each pass can shrink the component further šŸ” **Type Analysis & Auto-Fixing** - Infers and adds missing return types @@ -80,9 +82,19 @@ npm run type-check | `npm run lint` | Check for lint issues | | `npm run format` | Format all files with Prettier | +### CLI Options (Target Any App) + +Use the scripts directly for non-demo components: + +```bash +ts-node scripts/refactor-tsx.ts --file path/to/Dashboard.tsx --min-function-lines 50 --min-variable-lines 25 +ts-node scripts/refactor-tsx-pass2.ts --file path/to/Dashboard.tsx --helper-pattern "^(validate|get|format|handle)" +ts-node scripts/analyze-types.ts --files path/to/Dashboard.types.ts,path/to/Dashboard.utils.ts,path/to/Dashboard.tsx +``` + ## Refactoring Examples -### Before: Monolithic Component (603 LOC) +### Before: Monolithic Component ```tsx // UserManagementDashboard.tsx - Everything in one file @@ -169,7 +181,7 @@ export const formatDate = (dateString: string): string => { }; ``` -**UserManagementDashboard.tsx** (532 LOC) +**UserManagementDashboard.tsx** (size varies) ```tsx import React, { useState, useEffect, useCallback } from 'react'; import { User, FormData, ValidationErrors } from './UserManagementDashboard.types'; diff --git a/README.md b/README.md index 29fccfa..6d9bd2f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # tsmorph -Automated TSX/TypeScript refactoring toolkit using [ts-morph](https://ts-morph.com/) to intelligently extract large code blocks (>150 LOC) into separate, well-typed, lint-compliant files with automatic import management. +Automated TSX/TypeScript refactoring toolkit using [ts-morph](https://ts-morph.com/) to intelligently extract code blocks into separate, well-typed, lint-compliant files with automatic import management. The goal is to support smaller components (e.g., 50/100/150 LOC thresholds) and push more logic into hooks/utilities via a feedback loop. ## šŸš€ Quick Start @@ -19,6 +19,8 @@ npm run type-check # Verify everything compiles ## ✨ Features - **Automated Code Extraction**: Extracts types, interfaces, and utility functions to separate files +- **Smaller Component Targets**: Supports tighter LOC targets (e.g., 50/100/150) by encouraging extraction into hooks and utilities +- **Feedback Loop Friendly**: Re-run the pipeline to keep shrinking components after each extraction pass - **Smart Import Management**: Automatically adds, removes, and organizes imports - **Type Analysis**: Infers and adds missing return types, parameter types, and fixes `any` types - **Lint Auto-Fixing**: Integrates with ESLint and Prettier for consistent code quality @@ -26,11 +28,11 @@ npm run type-check # Verify everything compiles ## šŸ“Š Results -Starting with a monolithic **603-line** TSX component, the tools automatically refactor it into: +Starting with a monolithic TSX component, the tools automatically refactor it into: | File | LOC | Purpose | |------|-----|---------| -| `UserManagementDashboard.tsx` | 532 | Main component logic | +| `UserManagementDashboard.tsx` | Varies | Main component logic | | `UserManagementDashboard.types.ts` | 22 | Type definitions & interfaces | | `UserManagementDashboard.utils.ts` | 50 | Utility functions with precise types | @@ -61,6 +63,16 @@ See [DOCUMENTATION.md](./DOCUMENTATION.md) for comprehensive guides on: | `npm run lint` | Check for lint issues | | `npm run format` | Format all files with Prettier | +## 🧰 CLI Usage for Another App + +Target any TSX file by passing file paths and thresholds directly to the scripts: + +```bash +ts-node scripts/refactor-tsx.ts --file path/to/Dashboard.tsx --min-function-lines 50 --min-variable-lines 25 +ts-node scripts/refactor-tsx-pass2.ts --file path/to/Dashboard.tsx --helper-pattern "^(validate|get|format|handle)" +ts-node scripts/analyze-types.ts --files path/to/Dashboard.types.ts,path/to/Dashboard.utils.ts,path/to/Dashboard.tsx +``` + ## šŸ“¦ Example Output ### Type Analysis Report @@ -86,4 +98,3 @@ See [DOCUMENTATION.md](./DOCUMENTATION.md) for comprehensive guides on: ## šŸ“ License MIT - diff --git a/scripts/analyze-types.ts b/scripts/analyze-types.ts index c3c50fe..6314e0f 100644 --- a/scripts/analyze-types.ts +++ b/scripts/analyze-types.ts @@ -1,6 +1,6 @@ #!/usr/bin/env ts-node -import { Project, SourceFile, SyntaxKind, Type, Node } from 'ts-morph'; +import { Project, SourceFile, Node } from 'ts-morph'; import * as path from 'path'; /** @@ -14,6 +14,51 @@ import * as path from 'path'; * 5. Makes types more specific where possible */ +interface CliArgs { + files: string[]; + help?: boolean; +} + +const parseArgs = (args: string[]): CliArgs => { + const result: CliArgs = { files: [] }; + + for (let i = 0; i < args.length; i += 1) { + const arg = args[i]; + if (!arg) continue; + + switch (arg) { + case '--files': { + const next = args[i + 1]; + if (next) { + result.files.push(...next.split(',').map(entry => entry.trim()).filter(Boolean)); + i += 1; + } + break; + } + case '--file': { + const next = args[i + 1]; + if (next) { + result.files.push(next); + i += 1; + } + break; + } + case '--help': + case '-h': + result.help = true; + break; + default: + break; + } + } + + return result; +}; + +const printUsage = (): void => { + console.log(`\nUsage:\n ts-node scripts/analyze-types.ts [options]\n\nOptions:\n --file Single file to analyze (repeatable)\n --files Comma-separated list of files to analyze\n --help Show this help message\n`); +}; + class TypeAnalyzer { private project: Project; @@ -304,30 +349,19 @@ class TypeAnalyzer { /** * Process all refactored files */ - processAllFiles(): void { - const componentsDir = path.join(__dirname, '..', 'src', 'components'); - const files = [ - path.join(componentsDir, 'UserManagementDashboard.types.ts'), - path.join(componentsDir, 'UserManagementDashboard.utils.ts'), - path.join(componentsDir, 'UserManagementDashboard.tsx'), - ]; - + processAllFiles(filePaths?: string[]): void { + const files = filePaths && filePaths.length > 0 ? filePaths : this.getDefaultFiles(); files.forEach(file => this.processFile(file)); } /** * Generate type coverage report */ - generateTypeCoverageReport(): void { + generateTypeCoverageReport(filePaths?: string[]): void { console.log('\nšŸ“ˆ Type Coverage Report'); console.log('========================\n'); - const componentsDir = path.join(__dirname, '..', 'src', 'components'); - const files = [ - path.join(componentsDir, 'UserManagementDashboard.types.ts'), - path.join(componentsDir, 'UserManagementDashboard.utils.ts'), - path.join(componentsDir, 'UserManagementDashboard.tsx'), - ]; + const files = filePaths && filePaths.length > 0 ? filePaths : this.getDefaultFiles(); files.forEach(filePath => { if (!require('fs').existsSync(filePath)) return; @@ -368,18 +402,34 @@ class TypeAnalyzer { } }); } + + private getDefaultFiles(): string[] { + const componentsDir = path.join(__dirname, '..', 'src', 'components'); + return [ + path.join(componentsDir, 'UserManagementDashboard.types.ts'), + path.join(componentsDir, 'UserManagementDashboard.utils.ts'), + path.join(componentsDir, 'UserManagementDashboard.tsx'), + ]; + } } async function main() { console.log('šŸš€ TypeScript Type Analysis and Auto-Fixing Tool\n'); + const args = parseArgs(process.argv.slice(2)); + if (args.help) { + printUsage(); + return; + } + const analyzer = new TypeAnalyzer(); // Process all files - analyzer.processAllFiles(); + const files = args.files.map(file => path.resolve(file)); + analyzer.processAllFiles(files); // Generate report - analyzer.generateTypeCoverageReport(); + analyzer.generateTypeCoverageReport(files); console.log('\nāœ… Type analysis complete!'); console.log('\nšŸ’” Next steps:'); diff --git a/scripts/refactor-tsx-pass2.ts b/scripts/refactor-tsx-pass2.ts index b46343c..f38718a 100644 --- a/scripts/refactor-tsx-pass2.ts +++ b/scripts/refactor-tsx-pass2.ts @@ -1,6 +1,6 @@ #!/usr/bin/env ts-node -import { Project, SyntaxKind, SourceFile, VariableDeclaration } from 'ts-morph'; +import { Project, SyntaxKind, SourceFile } from 'ts-morph'; import * as path from 'path'; /** @@ -8,16 +8,93 @@ import * as path from 'path'; * This extracts helper functions like validateForm, getRoleBadgeColor, etc. */ +interface Pass2Options { + targetFile: string; + utilsFile: string; + typesFile: string; + helperNamePattern: RegExp; +} + +interface CliArgs { + file?: string; + utils?: string; + types?: string; + helperPattern?: string; + help?: boolean; +} + +const DEFAULT_HELPER_PATTERN = '^(validate|get|format|handle)'; + +const toModuleSpecifier = (fromFile: string, toFile: string): string => { + const relativePath = path + .relative(path.dirname(fromFile), toFile) + .replace(/\\/g, '/') + .replace(/\.(tsx?|jsx?)$/, ''); + return relativePath.startsWith('.') ? relativePath : `./${relativePath}`; +}; + +const resolveOutputPath = (inputFile: string, suffix: string): string => { + if (inputFile.endsWith('.tsx')) { + return inputFile.replace(/\.tsx$/, suffix); + } + if (inputFile.endsWith('.ts')) { + return inputFile.replace(/\.ts$/, suffix); + } + return `${inputFile}${suffix}`; +}; + +const parseArgs = (args: string[]): CliArgs => { + const result: CliArgs = {}; + + for (let i = 0; i < args.length; i += 1) { + const arg = args[i]; + if (!arg) continue; + + switch (arg) { + case '--file': + result.file = args[i + 1]; + i += 1; + break; + case '--utils': + result.utils = args[i + 1]; + i += 1; + break; + case '--types': + result.types = args[i + 1]; + i += 1; + break; + case '--helper-pattern': + result.helperPattern = args[i + 1]; + i += 1; + break; + case '--help': + case '-h': + result.help = true; + break; + default: + break; + } + } + + return result; +}; + +const printUsage = (): void => { + console.log(`\nUsage:\n ts-node scripts/refactor-tsx-pass2.ts --file [options]\n\nOptions:\n --utils Output utils file path (default: .utils.ts)\n --types Types file path for type-only imports\n --helper-pattern Regex for helper function names (default: ${DEFAULT_HELPER_PATTERN})\n --help Show this help message\n`); +}; + class TSXRefactorer2 { private project: Project; private sourceFile: SourceFile; + private options: Pass2Options; - constructor(filePath: string) { + constructor(options: Pass2Options) { this.project = new Project({ tsConfigFilePath: path.join(__dirname, '..', 'tsconfig.json'), }); - this.sourceFile = this.project.addSourceFileAtPath(filePath); + this.sourceFile = this.project.addSourceFileAtPath(options.targetFile); + this.options = options; } /** @@ -26,10 +103,13 @@ class TSXRefactorer2 { extractHelperFunctions(): void { console.log('\nšŸ”„ Extracting helper functions (2nd pass)...'); - const utilsFilePath = this.sourceFile.getFilePath().replace('.tsx', '.utils.ts'); + const utilsFilePath = this.options.utilsFile; // Find the main component - const componentDecl = this.sourceFile.getVariableDeclaration('UserManagementDashboard'); + const componentDecl = this.sourceFile.getVariableDeclarations().find(decl => { + const initializer = decl.getInitializer(); + return initializer?.isKind(SyntaxKind.ArrowFunction); + }); if (!componentDecl) { console.log(' āš ļø Could not find component'); return; @@ -69,7 +149,7 @@ class TSXRefactorer2 { const text = stmt.getText(); // Extract these specific helper functions - if (name.match(/^(validate|getRoleBadgeColor|getStatusBadgeColor|formatDate)/)) { + if (this.options.helperNamePattern.test(name)) { helperFunctions.push({ name, text }); } } @@ -97,9 +177,9 @@ class TSXRefactorer2 { ' * Auto-generated by ts-morph refactoring script', ' */', '', - "import type { FormData, ValidationErrors, User } from './UserManagementDashboard.types';", + this.getTypeImportStatement(utilsFilePath), '', - ].join('\n'); + ].filter(Boolean).join('\n'); } // Add the helper functions @@ -132,20 +212,19 @@ class TSXRefactorer2 { }); // Add import - const existingImport = this.sourceFile.getImportDeclaration('./UserManagementDashboard.utils'); + const moduleSpecifier = toModuleSpecifier(this.options.targetFile, utilsFilePath); + const existingImport = this.sourceFile.getImportDeclaration(moduleSpecifier); if (existingImport) { - // Remove existing and recreate with combined imports const namedImports = existingImport.getNamedImports().map(ni => ni.getName()); const newImports = [...new Set([...namedImports, ...functionNames])]; existingImport.remove(); this.sourceFile.addImportDeclaration({ - moduleSpecifier: './UserManagementDashboard.utils', + moduleSpecifier, namedImports: newImports, }); } else { - // Create new import this.sourceFile.addImportDeclaration({ - moduleSpecifier: './UserManagementDashboard.utils', + moduleSpecifier, namedImports: functionNames, }); } @@ -158,20 +237,60 @@ class TSXRefactorer2 { this.sourceFile.saveSync(); console.log('\nāœ… Second pass refactoring complete!'); } + + private getExportedTypeNames(): string[] { + if (!this.options.typesFile) { + return []; + } + + const typesFile = this.project.getSourceFile(this.options.typesFile); + if (!typesFile) { + return []; + } + + const interfaces = typesFile.getInterfaces(); + const typeAliases = typesFile.getTypeAliases(); + + return [...interfaces, ...typeAliases] + .filter(type => type.isExported()) + .map(type => type.getName()); + } + + private getTypeImportStatement(utilsFilePath: string): string | null { + if (!this.options.typesFile) { + return null; + } + + const typeNames = this.getExportedTypeNames(); + if (typeNames.length === 0) { + return null; + } + + return `import type { ${typeNames.join(', ')} } from '${toModuleSpecifier(utilsFilePath, this.options.typesFile)}';`; + } } async function main() { console.log('šŸš€ TSX Refactoring Tool - Second Pass\n'); - const targetFile = path.join( - __dirname, - '..', - 'src', - 'components', - 'UserManagementDashboard.tsx' - ); + const args = parseArgs(process.argv.slice(2)); + if (args.help) { + printUsage(); + return; + } - const refactorer = new TSXRefactorer2(targetFile); + const targetFile = args.file + ? path.resolve(args.file) + : path.join(__dirname, '..', 'src', 'components', 'UserManagementDashboard.tsx'); + + const typesFile = args.types ? path.resolve(args.types) : resolveOutputPath(targetFile, '.types.ts'); + + const refactorer = new TSXRefactorer2({ + targetFile, + utilsFile: path.resolve(args.utils ?? resolveOutputPath(targetFile, '.utils.ts')), + typesFile, + helperNamePattern: new RegExp(args.helperPattern ?? DEFAULT_HELPER_PATTERN), + }); refactorer.extractHelperFunctions(); refactorer.save(); diff --git a/scripts/refactor-tsx.ts b/scripts/refactor-tsx.ts index ae7eab7..103c1fc 100644 --- a/scripts/refactor-tsx.ts +++ b/scripts/refactor-tsx.ts @@ -19,16 +19,107 @@ interface ExtractionCandidate { type: 'function' | 'component' | 'interface' | 'type' | 'variable'; } +interface RefactorOptions { + targetFile: string; + typesFile: string; + utilsFile: string; + minFunctionLines: number; + minVariableLines: number; + utilNamePattern: RegExp; +} + +interface CliArgs { + file?: string; + types?: string; + utils?: string; + minFunctionLines?: number; + minVariableLines?: number; + utilPattern?: string; + help?: boolean; +} + +const DEFAULT_MIN_FUNCTION_LINES = 20; +const DEFAULT_MIN_VARIABLE_LINES = 10; +const DEFAULT_UTIL_PATTERN = '^(validate|format|get|handle)'; + +const toModuleSpecifier = (fromFile: string, toFile: string): string => { + const relativePath = path + .relative(path.dirname(fromFile), toFile) + .replace(/\\/g, '/') + .replace(/\.(tsx?|jsx?)$/, ''); + return relativePath.startsWith('.') ? relativePath : `./${relativePath}`; +}; + +const parseArgs = (args: string[]): CliArgs => { + const result: CliArgs = {}; + + for (let i = 0; i < args.length; i += 1) { + const arg = args[i]; + if (!arg) continue; + + switch (arg) { + case '--file': + result.file = args[i + 1]; + i += 1; + break; + case '--types': + result.types = args[i + 1]; + i += 1; + break; + case '--utils': + result.utils = args[i + 1]; + i += 1; + break; + case '--min-function-lines': + result.minFunctionLines = Number(args[i + 1]); + i += 1; + break; + case '--min-variable-lines': + result.minVariableLines = Number(args[i + 1]); + i += 1; + break; + case '--util-pattern': + result.utilPattern = args[i + 1]; + i += 1; + break; + case '--help': + case '-h': + result.help = true; + break; + default: + break; + } + } + + return result; +}; + +const printUsage = (): void => { + console.log(`\nUsage:\n ts-node scripts/refactor-tsx.ts --file [options]\n\nOptions:\n --types Output types file path (default: .types.ts)\n --utils Output utils file path (default: .utils.ts)\n --min-function-lines Minimum lines before extracting functions (default: ${DEFAULT_MIN_FUNCTION_LINES})\n --min-variable-lines Minimum lines before extracting variables (default: ${DEFAULT_MIN_VARIABLE_LINES})\n --util-pattern Regex for utility names (default: ${DEFAULT_UTIL_PATTERN})\n --help Show this help message\n`); +}; + +const resolveOutputPath = (inputFile: string, suffix: string): string => { + if (inputFile.endsWith('.tsx')) { + return inputFile.replace(/\.tsx$/, suffix); + } + if (inputFile.endsWith('.ts')) { + return inputFile.replace(/\.ts$/, suffix); + } + return `${inputFile}${suffix}`; +}; + class TSXRefactorer { private project: Project; private sourceFile: SourceFile; + private options: RefactorOptions; - constructor(filePath: string) { + constructor(options: RefactorOptions) { this.project = new Project({ tsConfigFilePath: path.join(__dirname, '..', 'tsconfig.json'), }); - this.sourceFile = this.project.addSourceFileAtPath(filePath); + this.sourceFile = this.project.addSourceFileAtPath(options.targetFile); + this.options = options; } /** @@ -44,7 +135,7 @@ class TSXRefactorer { const functions = this.sourceFile.getFunctions(); functions.forEach(func => { const lineCount = this.getNodeLineCount(func); - if (lineCount > 20) { // Lower threshold for demonstration + if (lineCount > this.options.minFunctionLines) { candidates.push({ name: func.getName() || 'anonymous', node: func, @@ -62,7 +153,7 @@ class TSXRefactorer { const lineCount = this.getNodeLineCount(decl); const name = decl.getName(); - if (lineCount > 10) { + if (lineCount > this.options.minVariableLines) { candidates.push({ name, node: decl, @@ -113,7 +204,7 @@ class TSXRefactorer { console.log('\nšŸ”„ Extracting types to separate file...'); - const typesFilePath = this.sourceFile.getFilePath().replace('.tsx', '.types.ts'); + const typesFilePath = this.options.typesFile; const typesFile = this.project.createSourceFile(typesFilePath, '', { overwrite: true }); // Build the content for the types file @@ -145,13 +236,11 @@ class TSXRefactorer { }); // Add import to original file - const relativePath = './UserManagementDashboard.types'; const typeNames = typeCandidates.map(c => c.name); - - this.sourceFile.addImportDeclaration({ - moduleSpecifier: relativePath, - namedImports: typeNames, - }); + if (typeNames.length > 0) { + const moduleSpecifier = toModuleSpecifier(this.options.targetFile, typesFilePath); + this.addOrUpdateNamedImport(moduleSpecifier, typeNames); + } typesFile.saveSync(); console.log(` šŸ’¾ Saved: ${path.basename(typesFilePath)}`); @@ -162,7 +251,7 @@ class TSXRefactorer { */ extractUtilities(candidates: ExtractionCandidate[]): void { const utilCandidates = candidates.filter(c => - c.type === 'variable' && c.name.match(/^(validate|format|get|handle)/) + c.type === 'variable' && this.options.utilNamePattern.test(c.name) ); if (utilCandidates.length === 0) { @@ -172,7 +261,7 @@ class TSXRefactorer { console.log('\nšŸ”„ Extracting utility functions...'); - const utilsFilePath = this.sourceFile.getFilePath().replace('.tsx', '.utils.ts'); + const utilsFilePath = this.options.utilsFile; const utilsFile = this.project.createSourceFile(utilsFilePath, '', { overwrite: true }); const extractedNames: string[] = []; @@ -199,25 +288,28 @@ class TSXRefactorer { }); // Build the complete utils file content + const typeNames = this.getExportedTypeNames(); + const typeImport = typeNames.length > 0 + ? `import type { ${typeNames.join(', ')} } from '${toModuleSpecifier(utilsFilePath, this.options.typesFile)}';` + : null; + const utilsContent = [ '/**', ' * Extracted utility functions', ' * Auto-generated by ts-morph refactoring script', ' */', '', - "import type { FormData, ValidationErrors, User } from './UserManagementDashboard.types';", - '', + ...(typeImport ? [typeImport, ''] : []), ...utilStatements, ].join('\n'); utilsFile.replaceWithText(utilsContent); // Add import to original file - const relativePath = './UserManagementDashboard.utils'; - this.sourceFile.addImportDeclaration({ - moduleSpecifier: relativePath, - namedImports: extractedNames, - }); + if (extractedNames.length > 0) { + const moduleSpecifier = toModuleSpecifier(this.options.targetFile, utilsFilePath); + this.addOrUpdateNamedImport(moduleSpecifier, extractedNames); + } utilsFile.saveSync(); console.log(` šŸ’¾ Saved: ${path.basename(utilsFilePath)}`); @@ -267,21 +359,69 @@ class TSXRefactorer { const end = node.getEndLineNumber(); return end - start + 1; } + + private addOrUpdateNamedImport(moduleSpecifier: string, names: string[]): void { + const existingImport = this.sourceFile.getImportDeclaration(moduleSpecifier); + if (existingImport) { + const existingNames = existingImport.getNamedImports().map(imp => imp.getName()); + const mergedNames = Array.from(new Set([...existingNames, ...names])); + existingImport.remove(); + this.sourceFile.addImportDeclaration({ + moduleSpecifier, + namedImports: mergedNames, + }); + return; + } + + this.sourceFile.addImportDeclaration({ + moduleSpecifier, + namedImports: names, + }); + } + + private getExportedTypeNames(): string[] { + const typesFile = this.project.getSourceFile(this.options.typesFile); + if (!typesFile) { + return []; + } + + const interfaces = typesFile.getInterfaces(); + const typeAliases = typesFile.getTypeAliases(); + + return [...interfaces, ...typeAliases] + .filter(type => type.isExported()) + .map(type => type.getName()); + } } // Main execution async function main() { console.log('šŸš€ TSX Refactoring Tool using ts-morph\n'); - const targetFile = path.join( - __dirname, - '..', - 'src', - 'components', - 'UserManagementDashboard.tsx' - ); + const args = parseArgs(process.argv.slice(2)); + if (args.help) { + printUsage(); + return; + } - const refactorer = new TSXRefactorer(targetFile); + const targetFile = args.file + ? path.resolve(args.file) + : path.join(__dirname, '..', 'src', 'components', 'UserManagementDashboard.tsx'); + + const options: RefactorOptions = { + targetFile, + typesFile: path.resolve(args.types ?? resolveOutputPath(targetFile, '.types.ts')), + utilsFile: path.resolve(args.utils ?? resolveOutputPath(targetFile, '.utils.ts')), + minFunctionLines: Number.isFinite(args.minFunctionLines) + ? Number(args.minFunctionLines) + : DEFAULT_MIN_FUNCTION_LINES, + minVariableLines: Number.isFinite(args.minVariableLines) + ? Number(args.minVariableLines) + : DEFAULT_MIN_VARIABLE_LINES, + utilNamePattern: new RegExp(args.utilPattern ?? DEFAULT_UTIL_PATTERN), + }; + + const refactorer = new TSXRefactorer(options); // Step 1: Analyze const candidates = refactorer.analyzeFile();