# TSX Refactoring with ts-morph 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 ✨ **Automated Code Extraction** - 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 - Adds parameter type annotations - Replaces `any` types with specific types - Reports type coverage metrics 🎨 **Lint Auto-Fixing** - Integrates with ESLint and Prettier - Organizes imports automatically - Fixes common linting issues - Ensures code style consistency ## Project Structure ``` tsmorph/ ├── src/ │ └── components/ │ ├── UserManagementDashboard.tsx # Main component (532 LOC) │ ├── UserManagementDashboard.types.ts # Extracted types (22 LOC) │ └── UserManagementDashboard.utils.ts # Extracted utilities (50 LOC) ├── scripts/ │ ├── refactor-tsx.ts # Main refactoring script (pass 1) │ ├── refactor-tsx-pass2.ts # Extract inner functions (pass 2) │ ├── analyze-types.ts # Type analysis and auto-fixing │ └── fix-lint.ts # Lint auto-fixer ├── package.json ├── tsconfig.json ├── .eslintrc.js └── .prettierrc ``` ## Installation ```bash npm install ``` ## Usage ### Quick Start - Full Refactoring Pipeline ```bash # Step 1: Extract types and interfaces npm run refactor # Step 2: Extract utility functions npm run refactor:pass2 # Step 3: Add missing types and improve type annotations npm run analyze-types # Step 4: Fix linting issues and format code npm run fix-lint # Step 5: Verify everything compiles npm run type-check ``` ### Individual Commands | Command | Description | |---------|-------------| | `npm run refactor` | Extract types/interfaces from component | | `npm run refactor:pass2` | Extract utility functions from component | | `npm run analyze-types` | Analyze and fix type annotations | | `npm run fix-lint` | Auto-fix lint issues and format code | | `npm run type-check` | Verify TypeScript compilation | | `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 ```tsx // UserManagementDashboard.tsx - Everything in one file import React, { useState } from 'react'; interface User { id: string; name: string; // ... more fields } interface FormData { // ... } export const UserManagementDashboard: React.FC = () => { const validateForm = (data: FormData) => { // validation logic... }; const getRoleBadgeColor = (role: string) => { // helper logic... }; // ... 500+ more lines }; ``` ### After: Refactored & Organized **UserManagementDashboard.types.ts** (22 LOC) ```typescript export interface User { id: string; name: string; email: string; role: 'admin' | 'user' | 'guest'; status: 'active' | 'inactive'; createdAt: string; } export interface FormData { name: string; email: string; role: 'admin' | 'user' | 'guest'; } export interface ValidationErrors { name?: string; email?: string; role?: string; } ``` **UserManagementDashboard.utils.ts** (50 LOC) ```typescript import type { FormData, ValidationErrors } from './UserManagementDashboard.types'; export const validateForm = (data: FormData): ValidationErrors => { const errors: ValidationErrors = {}; // validation logic... return errors; }; export const getRoleBadgeColor = (role: string): "#ff6b6b" | "#4ecdc4" | "#95a5a6" | "#7f8c8d" => { switch (role) { case 'admin': return '#ff6b6b'; case 'user': return '#4ecdc4'; case 'guest': return '#95a5a6'; default: return '#7f8c8d'; } }; export const getStatusBadgeColor = (status: string): "#2ecc71" | "#e74c3c" => { return status === 'active' ? '#2ecc71' : '#e74c3c'; }; export const formatDate = (dateString: string): string => { return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', }); }; ``` **UserManagementDashboard.tsx** (size varies) ```tsx import React, { useState, useEffect, useCallback } from 'react'; import { User, FormData, ValidationErrors } from './UserManagementDashboard.types'; import { validateForm, getRoleBadgeColor, getStatusBadgeColor, formatDate } from './UserManagementDashboard.utils'; export const UserManagementDashboard: React.FC = (): React.JSX.Element => { // Component logic... }; ``` ## How It Works ### 1. Type Extraction (`refactor-tsx.ts`) The script analyzes the AST using ts-morph to: - Find all interface and type alias declarations - Extract them to a separate `.types.ts` file - Add `export` keywords automatically - Remove them from the original file - Add import statement to the original file ```typescript // Analyzes file structure const interfaces = sourceFile.getInterfaces(); const typeAliases = sourceFile.getTypeAliases(); // Extracts to new file with exports typesFile.replaceWithText(typesContent); // Adds import to original sourceFile.addImportDeclaration({ moduleSpecifier: './Component.types', namedImports: typeNames, }); ``` ### 2. Utility Function Extraction (`refactor-tsx-pass2.ts`) Identifies and extracts helper functions: - Finds const declarations with arrow functions inside components - Filters by naming patterns (`validate*`, `get*`, `format*`, `handle*`) - Exports them with proper type imports - Updates original component with imports ```typescript // Find helper functions in component body const helperFunctions = statements .filter(stmt => stmt.isKind(SyntaxKind.VariableStatement)) .filter(decl => decl.name.match(/^(validate|get|format|handle)/)); // Extract and make exported const exportedText = text.replace(/^(const|let|var)/, 'export const'); ``` ### 3. Type Analysis (`analyze-types.ts`) Automatically improves type safety: - **Adds return types**: Infers from function body - **Adds parameter types**: Uses TypeScript's type inference - **Fixes 'any' types**: Replaces with specific inferred types - **Ensures exports**: Adds missing `export` keywords to types - **Type coverage report**: Shows typed vs untyped declarations ```typescript // Infer and add return type const returnType = func.getReturnType(); const typeText = returnType.getText(func); func.setReturnType(typeText); // Example output: // ✓ Added return type to getRoleBadgeColor: "#ff6b6b" | "#4ecdc4" | "#95a5a6" | "#7f8c8d" ``` ### 4. Lint Auto-Fixing (`fix-lint.ts`) Ensures code quality: - **Organize imports**: Sorts React first, then external, then local - **Format with Prettier**: Consistent code style - **Fix with ESLint**: Auto-fixes common issues - **Non-destructive**: Only applies safe transformations ## Type Analysis Output Example ``` 📊 Type Analysis: UserManagementDashboard.utils.ts ✓ Added return type to validateForm: ValidationErrors ✓ Added return type to getRoleBadgeColor: "#ff6b6b" | "#4ecdc4" | "#95a5a6" | "#7f8c8d" ✓ Added return type to getStatusBadgeColor: "#2ecc71" | "#e74c3c" ✓ Added return type to formatDate: string 💾 Saved 4 type improvements 📈 Type Coverage Report ======================== UserManagementDashboard.utils.ts: Type Coverage: 100% (4/4 declarations typed) ✅ No 'any' types found ``` ## Configuration ### TypeScript Configuration (`tsconfig.json`) ```json { "compilerOptions": { "strict": true, "noImplicitAny": true, "esModuleInterop": true, "jsx": "preserve", "moduleResolution": "bundler" } } ``` ### ESLint Configuration (`.eslintrc.js`) ```javascript module.exports = { parser: '@typescript-eslint/parser', extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier', ], rules: { '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], '@typescript-eslint/no-explicit-any': 'warn', }, }; ``` ### Prettier Configuration (`.prettierrc`) ```json { "semi": true, "singleQuote": true, "printWidth": 100, "trailingComma": "es5" } ``` ## Benefits ### ✅ Maintainability - **Smaller files**: Each file has a single responsibility - **Easy to locate**: Types and utilities are in predictable locations - **Reduced complexity**: Component focuses on UI logic ### ✅ Reusability - **Shared types**: Can be imported across multiple components - **Utility functions**: Easily tested and reused - **DRY principle**: Eliminates code duplication ### ✅ Type Safety - **100% type coverage**: All functions have proper type annotations - **Precise types**: Union types for string literals (e.g., color values) - **No 'any' types**: Strong typing throughout ### ✅ Code Quality - **Lint-compliant**: All code passes ESLint checks - **Consistent style**: Prettier ensures uniform formatting - **Auto-formatted**: No manual formatting needed ## Metrics | Metric | Before | After | Improvement | |--------|--------|-------|-------------| | Main component LOC | 603 | 532 | -12% | | Files | 1 | 3 | Organized | | Type coverage | ~60% | 100% | +40% | | 'any' types | Several | 0 | Eliminated | | Imports managed | Manual | Automatic | Automated | ## Advanced Usage ### Customizing Extraction Patterns Edit `scripts/refactor-tsx-pass2.ts` to change which functions get extracted: ```typescript // Current pattern if (name.match(/^(validate|getRoleBadgeColor|getStatusBadgeColor|formatDate)/)) { helperFunctions.push({ name, text }); } // Custom pattern - extract all 'use*' hooks if (name.match(/^use[A-Z]/)) { helperFunctions.push({ name, text }); } ``` ### Running on Different Files Modify the file paths in each script: ```typescript const targetFile = path.join( __dirname, '..', 'src', 'components', 'YourComponent.tsx' // Change this ); ``` ## Best Practices 1. **Run in sequence**: Execute refactor → refactor:pass2 → analyze-types → fix-lint 2. **Review changes**: Always review extracted code before committing 3. **Test thoroughly**: Run type-check after each refactoring step 4. **Commit incrementally**: Commit after each successful extraction 5. **Keep naming consistent**: Use predictable naming patterns for utilities ## Troubleshooting ### "Cannot find name" errors after refactoring **Problem**: Imports were not added correctly. **Solution**: ```bash # Re-run the refactoring scripts npm run refactor npm run refactor:pass2 npm run type-check ``` ### Type inference produces complex types **Problem**: Auto-generated types are too long or complex. **Solution**: Manually simplify the type or add explicit annotations before refactoring. ### Lint issues remain after fix-lint **Problem**: Some lint rules require manual fixes. **Solution**: ```bash npm run lint # Review remaining issues npm run lint:fix # Try auto-fix again ``` ## Contributing To add new refactoring patterns: 1. Create a new script in `scripts/` 2. Use ts-morph to analyze and transform the AST 3. Add a corresponding npm script in `package.json` 4. Update this README with usage instructions ## Dependencies - **ts-morph** (^21.0.0): TypeScript compiler API wrapper - **typescript** (^5.0.0): TypeScript compiler - **eslint** (^8.0.0): JavaScript/TypeScript linter - **prettier** (^3.0.0): Code formatter - **ts-node** (^10.9.0): Execute TypeScript directly ## License MIT ## Resources - [ts-morph Documentation](https://ts-morph.com/) - [TypeScript Compiler API](https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API) - [ESLint Rules](https://eslint.org/docs/rules/) - [Prettier Options](https://prettier.io/docs/en/options.html)