Initial commit
This commit is contained in:
139
commands/refactor/.scripts/analyze-complexity.sh
Executable file
139
commands/refactor/.scripts/analyze-complexity.sh
Executable file
@@ -0,0 +1,139 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Purpose: Analyze code complexity using ESLint
|
||||
# Version: 1.0.0
|
||||
# Usage: ./analyze-complexity.sh <scope> [max-complexity]
|
||||
# Returns: 0 on success, 1 on error
|
||||
# Dependencies: npx, eslint
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration
|
||||
SCOPE="${1:-.}"
|
||||
MAX_COMPLEXITY="${2:-10}"
|
||||
MAX_DEPTH="${3:-3}"
|
||||
MAX_LINES="${4:-50}"
|
||||
MAX_PARAMS="${5:-4}"
|
||||
OUTPUT_FILE="complexity-report.json"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
GREEN='\033[0;32m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Check if scope exists
|
||||
if [ ! -e "$SCOPE" ]; then
|
||||
echo -e "${RED}Error: Scope does not exist: $SCOPE${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Analyzing complexity for: $SCOPE"
|
||||
echo "Max complexity: $MAX_COMPLEXITY"
|
||||
echo "Max depth: $MAX_DEPTH"
|
||||
echo "Max lines per function: $MAX_LINES"
|
||||
echo "Max parameters: $MAX_PARAMS"
|
||||
echo ""
|
||||
|
||||
# Check if eslint is available
|
||||
if ! command -v npx &> /dev/null; then
|
||||
echo -e "${RED}Error: npx not found. Please install Node.js and npm.${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create ESLint config for complexity analysis
|
||||
ESLINT_CONFIG=$(cat <<EOF
|
||||
{
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"rules": {
|
||||
"complexity": ["error", { "max": ${MAX_COMPLEXITY} }],
|
||||
"max-depth": ["error", ${MAX_DEPTH}],
|
||||
"max-lines-per-function": ["error", { "max": ${MAX_LINES}, "skipBlankLines": true, "skipComments": true }],
|
||||
"max-params": ["error", ${MAX_PARAMS}],
|
||||
"max-nested-callbacks": ["error", 3],
|
||||
"max-statements": ["error", 20]
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
# Write temp config
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
echo "$ESLINT_CONFIG" > "$TEMP_CONFIG"
|
||||
|
||||
# Run ESLint complexity analysis
|
||||
echo "Running complexity analysis..."
|
||||
|
||||
npx eslint "$SCOPE" \
|
||||
--ext .js,.jsx,.ts,.tsx \
|
||||
--config "$TEMP_CONFIG" \
|
||||
--format json \
|
||||
--output-file "$OUTPUT_FILE" \
|
||||
2>&1 || true
|
||||
|
||||
# Parse results
|
||||
if [ -f "$OUTPUT_FILE" ]; then
|
||||
TOTAL_FILES=$(jq 'length' "$OUTPUT_FILE")
|
||||
TOTAL_WARNINGS=$(jq '[.[].warningCount] | add // 0' "$OUTPUT_FILE")
|
||||
TOTAL_ERRORS=$(jq '[.[].errorCount] | add // 0' "$OUTPUT_FILE")
|
||||
|
||||
echo ""
|
||||
echo "=== Complexity Analysis Results ==="
|
||||
echo "Files analyzed: $TOTAL_FILES"
|
||||
echo "Warnings: $TOTAL_WARNINGS"
|
||||
echo "Errors: $TOTAL_ERRORS"
|
||||
echo ""
|
||||
|
||||
# Show critical issues (complexity > max)
|
||||
CRITICAL_ISSUES=$(jq -r '
|
||||
.[] |
|
||||
select(.messages | length > 0) |
|
||||
.filePath as $file |
|
||||
.messages[] |
|
||||
select(.ruleId == "complexity" and .severity == 2) |
|
||||
"\($file):\(.line):\(.column) - \(.message)"
|
||||
' "$OUTPUT_FILE" | head -20)
|
||||
|
||||
if [ -n "$CRITICAL_ISSUES" ]; then
|
||||
echo -e "${RED}Critical Complexity Issues:${NC}"
|
||||
echo "$CRITICAL_ISSUES"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Show files with most issues
|
||||
echo -e "${YELLOW}Files with Most Issues:${NC}"
|
||||
jq -r '
|
||||
sort_by(-.errorCount - .warningCount) |
|
||||
.[:5] |
|
||||
.[] |
|
||||
"\(.filePath): \(.errorCount) errors, \(.warningCount) warnings"
|
||||
' "$OUTPUT_FILE"
|
||||
|
||||
echo ""
|
||||
echo "Full report saved to: $OUTPUT_FILE"
|
||||
|
||||
# Summary
|
||||
if [ "$TOTAL_ERRORS" -gt 0 ]; then
|
||||
echo -e "${RED}Status: FAILED - $TOTAL_ERRORS functions exceed complexity threshold${NC}"
|
||||
exit 0 # Don't fail, just report
|
||||
elif [ "$TOTAL_WARNINGS" -gt 0 ]; then
|
||||
echo -e "${YELLOW}Status: WARNING - $TOTAL_WARNINGS potential complexity issues${NC}"
|
||||
else
|
||||
echo -e "${GREEN}Status: PASSED - All functions within complexity limits${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}Error: Failed to generate complexity report${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
rm -f "$TEMP_CONFIG"
|
||||
|
||||
exit 0
|
||||
128
commands/refactor/.scripts/detect-duplication.sh
Executable file
128
commands/refactor/.scripts/detect-duplication.sh
Executable file
@@ -0,0 +1,128 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Purpose: Detect code duplication using jsinspect
|
||||
# Version: 1.0.0
|
||||
# Usage: ./detect-duplication.sh <scope> [threshold]
|
||||
# Returns: 0 on success, 1 on error
|
||||
# Dependencies: npx, jsinspect
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration
|
||||
SCOPE="${1:-.}"
|
||||
THRESHOLD="${2:-80}"
|
||||
MIN_INSTANCES="${3:-2}"
|
||||
OUTPUT_FILE="duplication-report.json"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
GREEN='\033[0;32m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Check if scope exists
|
||||
if [ ! -e "$SCOPE" ]; then
|
||||
echo -e "${RED}Error: Scope does not exist: $SCOPE${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Detecting code duplication in: $SCOPE"
|
||||
echo "Similarity threshold: ${THRESHOLD}%"
|
||||
echo "Minimum instances: $MIN_INSTANCES"
|
||||
echo ""
|
||||
|
||||
# Check if npx is available
|
||||
if ! command -v npx &> /dev/null; then
|
||||
echo -e "${RED}Error: npx not found. Please install Node.js and npm.${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run jsinspect
|
||||
echo "Analyzing code for duplicates..."
|
||||
|
||||
npx jsinspect "$SCOPE" \
|
||||
--threshold "$THRESHOLD" \
|
||||
--min-instances "$MIN_INSTANCES" \
|
||||
--ignore "node_modules|dist|build|coverage|test|__tests__|*.spec.*|*.test.*" \
|
||||
--reporter json \
|
||||
> "$OUTPUT_FILE" 2>&1 || true
|
||||
|
||||
# Parse results
|
||||
if [ -f "$OUTPUT_FILE" ]; then
|
||||
# Check if output is valid JSON
|
||||
if ! jq empty "$OUTPUT_FILE" 2>/dev/null; then
|
||||
# Not JSON, probably text output or error
|
||||
if [ -s "$OUTPUT_FILE" ]; then
|
||||
echo -e "${YELLOW}Warning: Output is not JSON format${NC}"
|
||||
cat "$OUTPUT_FILE"
|
||||
else
|
||||
echo -e "${GREEN}No duplicates found!${NC}"
|
||||
echo "Duplication threshold: ${THRESHOLD}%"
|
||||
echo "Status: PASSED"
|
||||
rm -f "$OUTPUT_FILE"
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
# Valid JSON output
|
||||
DUPLICATE_COUNT=$(jq 'length' "$OUTPUT_FILE")
|
||||
|
||||
if [ "$DUPLICATE_COUNT" -eq 0 ]; then
|
||||
echo -e "${GREEN}No duplicates found!${NC}"
|
||||
echo "Duplication threshold: ${THRESHOLD}%"
|
||||
echo "Status: PASSED"
|
||||
rm -f "$OUTPUT_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Duplication Analysis Results ==="
|
||||
echo "Duplicate blocks found: $DUPLICATE_COUNT"
|
||||
echo ""
|
||||
|
||||
# Show duplicate details
|
||||
echo -e "${RED}Duplicate Code Blocks:${NC}"
|
||||
echo ""
|
||||
|
||||
jq -r '
|
||||
.[] |
|
||||
"Block \(.id // "N/A"):",
|
||||
" Lines: \(.lines)",
|
||||
" Instances: \(.instances | length)",
|
||||
" Locations:",
|
||||
(.instances[] | " - \(.path):\(.lines[0])-\(.lines[1])"),
|
||||
""
|
||||
' "$OUTPUT_FILE" | head -100
|
||||
|
||||
echo ""
|
||||
echo "Full report saved to: $OUTPUT_FILE"
|
||||
|
||||
# Calculate statistics
|
||||
TOTAL_INSTANCES=$(jq '[.[].instances | length] | add' "$OUTPUT_FILE")
|
||||
AVG_LINES=$(jq '[.[].lines] | add / length | floor' "$OUTPUT_FILE")
|
||||
|
||||
echo ""
|
||||
echo "=== Statistics ==="
|
||||
echo "Total duplicate instances: $TOTAL_INSTANCES"
|
||||
echo "Average duplicate size: $AVG_LINES lines"
|
||||
echo ""
|
||||
|
||||
if [ "$DUPLICATE_COUNT" -gt 10 ]; then
|
||||
echo -e "${RED}Status: HIGH DUPLICATION - $DUPLICATE_COUNT blocks found${NC}"
|
||||
elif [ "$DUPLICATE_COUNT" -gt 5 ]; then
|
||||
echo -e "${YELLOW}Status: MODERATE DUPLICATION - $DUPLICATE_COUNT blocks found${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}Status: LOW DUPLICATION - $DUPLICATE_COUNT blocks found${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Recommendations:"
|
||||
echo "1. Extract duplicate code to shared functions/components"
|
||||
echo "2. Use parameterization to reduce duplication"
|
||||
echo "3. Consider design patterns (Strategy, Template Method)"
|
||||
fi
|
||||
else
|
||||
echo -e "${RED}Error: Failed to generate duplication report${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
174
commands/refactor/.scripts/verify-tests.sh
Executable file
174
commands/refactor/.scripts/verify-tests.sh
Executable file
@@ -0,0 +1,174 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Purpose: Verify test coverage for code being refactored
|
||||
# Version: 1.0.0
|
||||
# Usage: ./verify-tests.sh <scope> [min-coverage]
|
||||
# Returns: 0 if coverage adequate, 1 if insufficient
|
||||
# Dependencies: npm, test runner (jest/mocha/etc)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration
|
||||
SCOPE="${1:-.}"
|
||||
MIN_COVERAGE="${2:-70}"
|
||||
OUTPUT_FILE="coverage-report.json"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
GREEN='\033[0;32m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Check if scope exists
|
||||
if [ ! -e "$SCOPE" ]; then
|
||||
echo -e "${RED}Error: Scope does not exist: $SCOPE${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Verifying test coverage for: $SCOPE"
|
||||
echo "Minimum coverage required: ${MIN_COVERAGE}%"
|
||||
echo ""
|
||||
|
||||
# Check if package.json exists
|
||||
if [ ! -f "package.json" ]; then
|
||||
echo -e "${YELLOW}Warning: No package.json found. Skipping test coverage check.${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if test script exists
|
||||
if ! grep -q '"test"' package.json; then
|
||||
echo -e "${YELLOW}Warning: No test script found in package.json. Skipping test coverage check.${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Run tests with coverage
|
||||
echo "Running tests with coverage..."
|
||||
echo ""
|
||||
|
||||
# Try different test runners
|
||||
if npm test -- --coverage --watchAll=false --json --outputFile="$OUTPUT_FILE" 2>&1; then
|
||||
TEST_RUNNER="jest"
|
||||
elif npm run test:coverage 2>&1; then
|
||||
TEST_RUNNER="npm"
|
||||
else
|
||||
echo -e "${YELLOW}Warning: Could not run tests with coverage${NC}"
|
||||
echo "Make sure your test runner supports coverage reporting"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Try to find coverage summary
|
||||
COVERAGE_SUMMARY=""
|
||||
|
||||
if [ -f "coverage/coverage-summary.json" ]; then
|
||||
COVERAGE_SUMMARY="coverage/coverage-summary.json"
|
||||
elif [ -f "coverage/lcov.info" ]; then
|
||||
echo "LCOV format detected, parsing..."
|
||||
# Convert lcov to summary (simplified)
|
||||
COVERAGE_SUMMARY="coverage/lcov.info"
|
||||
elif [ -f "$OUTPUT_FILE" ]; then
|
||||
COVERAGE_SUMMARY="$OUTPUT_FILE"
|
||||
fi
|
||||
|
||||
if [ -z "$COVERAGE_SUMMARY" ]; then
|
||||
echo -e "${YELLOW}Warning: Could not find coverage report${NC}"
|
||||
echo "Coverage report paths checked:"
|
||||
echo " - coverage/coverage-summary.json"
|
||||
echo " - coverage/lcov.info"
|
||||
echo " - $OUTPUT_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Parse coverage results
|
||||
echo "=== Test Coverage Results ==="
|
||||
echo ""
|
||||
|
||||
if [[ "$COVERAGE_SUMMARY" == *.json ]]; then
|
||||
# JSON format (Jest)
|
||||
if jq empty "$COVERAGE_SUMMARY" 2>/dev/null; then
|
||||
# Check if it's coverage-summary.json format
|
||||
if jq -e '.total' "$COVERAGE_SUMMARY" >/dev/null 2>&1; then
|
||||
STATEMENTS=$(jq -r '.total.statements.pct // 0' "$COVERAGE_SUMMARY")
|
||||
BRANCHES=$(jq -r '.total.branches.pct // 0' "$COVERAGE_SUMMARY")
|
||||
FUNCTIONS=$(jq -r '.total.functions.pct // 0' "$COVERAGE_SUMMARY")
|
||||
LINES=$(jq -r '.total.lines.pct // 0' "$COVERAGE_SUMMARY")
|
||||
|
||||
echo "Overall Coverage:"
|
||||
echo " Statements: ${STATEMENTS}%"
|
||||
echo " Branches: ${BRANCHES}%"
|
||||
echo " Functions: ${FUNCTIONS}%"
|
||||
echo " Lines: ${LINES}%"
|
||||
echo ""
|
||||
|
||||
# Check if coverage meets minimum
|
||||
COVERAGE_OK=true
|
||||
|
||||
if (( $(echo "$STATEMENTS < $MIN_COVERAGE" | bc -l) )); then
|
||||
COVERAGE_OK=false
|
||||
echo -e "${RED}✗ Statements coverage (${STATEMENTS}%) below minimum (${MIN_COVERAGE}%)${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✓ Statements coverage (${STATEMENTS}%) meets minimum${NC}"
|
||||
fi
|
||||
|
||||
if (( $(echo "$BRANCHES < $MIN_COVERAGE" | bc -l) )); then
|
||||
COVERAGE_OK=false
|
||||
echo -e "${RED}✗ Branches coverage (${BRANCHES}%) below minimum (${MIN_COVERAGE}%)${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✓ Branches coverage (${BRANCHES}%) meets minimum${NC}"
|
||||
fi
|
||||
|
||||
if (( $(echo "$FUNCTIONS < $MIN_COVERAGE" | bc -l) )); then
|
||||
COVERAGE_OK=false
|
||||
echo -e "${RED}✗ Functions coverage (${FUNCTIONS}%) below minimum (${MIN_COVERAGE}%)${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✓ Functions coverage (${FUNCTIONS}%) meets minimum${NC}"
|
||||
fi
|
||||
|
||||
if (( $(echo "$LINES < $MIN_COVERAGE" | bc -l) )); then
|
||||
COVERAGE_OK=false
|
||||
echo -e "${RED}✗ Lines coverage (${LINES}%) below minimum (${MIN_COVERAGE}%)${NC}"
|
||||
else
|
||||
echo -e "${GREEN}✓ Lines coverage (${LINES}%) meets minimum${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Find files with low coverage
|
||||
echo "Files with Coverage < ${MIN_COVERAGE}%:"
|
||||
jq -r --arg min "$MIN_COVERAGE" '
|
||||
. as $root |
|
||||
to_entries[] |
|
||||
select(.key != "total") |
|
||||
select(.value.lines.pct < ($min | tonumber)) |
|
||||
"\(.key): \(.value.lines.pct)%"
|
||||
' "$COVERAGE_SUMMARY" | head -20
|
||||
|
||||
echo ""
|
||||
|
||||
if [ "$COVERAGE_OK" = true ]; then
|
||||
echo -e "${GREEN}Status: PASSED - Test coverage is adequate${NC}"
|
||||
echo ""
|
||||
echo "✓ Safe to refactor - code is well tested"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}Status: FAILED - Test coverage insufficient${NC}"
|
||||
echo ""
|
||||
echo "⚠ Recommendations:"
|
||||
echo "1. Add tests before refactoring"
|
||||
echo "2. Focus refactoring on well-tested code"
|
||||
echo "3. Write tests for critical paths first"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# If we get here, couldn't parse coverage
|
||||
echo -e "${YELLOW}Warning: Could not parse coverage report${NC}"
|
||||
echo "Coverage file: $COVERAGE_SUMMARY"
|
||||
echo ""
|
||||
echo "Please verify test coverage manually before refactoring."
|
||||
|
||||
exit 0
|
||||
771
commands/refactor/README.md
Normal file
771
commands/refactor/README.md
Normal file
@@ -0,0 +1,771 @@
|
||||
# Code Refactoring Skill
|
||||
|
||||
Comprehensive code refactoring operations for improving code quality, maintainability, and architecture without changing external behavior.
|
||||
|
||||
## Overview
|
||||
|
||||
The refactor skill provides systematic, safety-first refactoring operations that follow industry best practices. It helps you identify code quality issues, eliminate technical debt, and modernize legacy code while maintaining test coverage and preserving external behavior.
|
||||
|
||||
**Key Principles:**
|
||||
- **Preserve Behavior**: External behavior must remain unchanged
|
||||
- **Safety First**: Verify test coverage before refactoring
|
||||
- **Small Steps**: Incremental changes with frequent testing
|
||||
- **Test-Driven**: Tests pass before, during, and after refactoring
|
||||
- **One Thing at a Time**: Don't mix refactoring with feature development
|
||||
- **Reversible**: Easy to revert if something goes wrong
|
||||
|
||||
## Available Operations
|
||||
|
||||
| Operation | Description | Use When |
|
||||
|-----------|-------------|----------|
|
||||
| **analyze** | Analyze code quality and identify opportunities | Starting refactoring session, need metrics |
|
||||
| **extract** | Extract methods, classes, modules, components | Functions too long, repeated code |
|
||||
| **patterns** | Introduce design patterns | Complex conditionals, tight coupling |
|
||||
| **types** | Improve TypeScript type safety | Using 'any', weak types, no types |
|
||||
| **duplicate** | Eliminate code duplication | Copy-paste code, DRY violations |
|
||||
| **modernize** | Update legacy code patterns | Callbacks, var, jQuery, class components |
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Syntax
|
||||
|
||||
```bash
|
||||
/10x-fullstack-engineer:refactor <operation> <parameters>
|
||||
```
|
||||
|
||||
### Parameter Format
|
||||
|
||||
All operations use key:value parameter format:
|
||||
|
||||
```bash
|
||||
/10x-fullstack-engineer:refactor analyze scope:"src/" metrics:"complexity,duplication" depth:"detailed"
|
||||
/10x-fullstack-engineer:refactor extract scope:"UserService.ts" type:"method" target:"validateEmail"
|
||||
/10x-fullstack-engineer:refactor patterns scope:"services/" pattern:"dependency-injection"
|
||||
```
|
||||
|
||||
## Operations Guide
|
||||
|
||||
### 1. Analyze - Code Quality Analysis
|
||||
|
||||
Identify refactoring opportunities through comprehensive code analysis.
|
||||
|
||||
**Parameters:**
|
||||
- `scope` (required): Path to analyze
|
||||
- `metrics` (optional): Comma-separated metrics (default: all)
|
||||
- `complexity` - Cyclomatic complexity
|
||||
- `duplication` - Code duplication detection
|
||||
- `coverage` - Test coverage analysis
|
||||
- `dependencies` - Circular dependency detection
|
||||
- `types` - TypeScript type coverage
|
||||
- `smells` - Code smells detection
|
||||
- `depth` (optional): `quick` | `standard` | `detailed` (default: standard)
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
/10x-fullstack-engineer:refactor analyze scope:"src/components" metrics:"complexity,duplication,coverage" depth:"detailed"
|
||||
```
|
||||
|
||||
**What it measures:**
|
||||
- **Complexity**: Functions with cyclomatic complexity > 10 (high risk)
|
||||
- **Duplication**: Duplicate code blocks (exact and near matches)
|
||||
- **Coverage**: Test coverage per file (target: >70%)
|
||||
- **Dependencies**: Circular dependencies and tight coupling
|
||||
- **Type Safety**: Usage of 'any' types in TypeScript
|
||||
- **Code Smells**: Long methods, large classes, switch statements
|
||||
|
||||
**Output:** Comprehensive report with prioritized refactoring opportunities, metrics, and estimated effort.
|
||||
|
||||
---
|
||||
|
||||
### 2. Extract - Method/Class/Module Extraction
|
||||
|
||||
Extract code into smaller, focused units to reduce complexity.
|
||||
|
||||
**Parameters:**
|
||||
- `scope` (required): File or module to refactor
|
||||
- `type` (required): What to extract
|
||||
- `method` - Extract method/function
|
||||
- `class` - Extract class from large class
|
||||
- `module` - Extract module from large file
|
||||
- `component` - Extract React/Vue component
|
||||
- `utility` - Extract utility function
|
||||
- `interface` - Extract TypeScript interface/type
|
||||
- `target` (required): What to extract (name or description)
|
||||
- `reason` (optional): Motivation for extraction
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Extract long method
|
||||
/10x-fullstack-engineer:refactor extract scope:"UserService.ts" type:"method" target:"validateAndCreateUser" reason:"reduce complexity"
|
||||
|
||||
# Extract reusable component
|
||||
/10x-fullstack-engineer:refactor extract scope:"UserProfile.tsx" type:"component" target:"ProfileHeader" reason:"reusability"
|
||||
|
||||
# Extract shared utility
|
||||
/10x-fullstack-engineer:refactor extract scope:"formatters.js" type:"utility" target:"formatDate" reason:"used in multiple places"
|
||||
```
|
||||
|
||||
**When to extract:**
|
||||
- **Method**: Function >50 lines, complexity >10, duplicated logic
|
||||
- **Class**: Class >300 lines, multiple responsibilities
|
||||
- **Module**: File >500 lines, unrelated functions
|
||||
- **Component**: Component >200 lines, reusable UI pattern
|
||||
- **Utility**: Pure function used in multiple places
|
||||
- **Interface**: Complex type used in multiple files
|
||||
|
||||
**Before/After Example:**
|
||||
|
||||
```typescript
|
||||
// Before: 73 lines, complexity 15
|
||||
async registerUser(userData: any) {
|
||||
// 20 lines of validation
|
||||
// 5 lines of existence check
|
||||
// 3 lines of password hashing
|
||||
// 10 lines of user creation
|
||||
// 15 lines of email sending
|
||||
// 10 lines of activity logging
|
||||
// 10 lines of result mapping
|
||||
}
|
||||
|
||||
// After: 12 lines, complexity 3
|
||||
async registerUser(userData: RegisterUserInput): Promise<UserDTO> {
|
||||
await this.validateRegistration(userData);
|
||||
await this.checkEmailAvailability(userData.email);
|
||||
|
||||
const hashedPassword = await this.hashPassword(userData.password);
|
||||
const user = await this.createUser({ ...userData, password: hashedPassword });
|
||||
|
||||
await this.sendRegistrationEmails(user);
|
||||
await this.logRegistrationActivity(user);
|
||||
|
||||
return this.mapToDTO(user);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Patterns - Design Pattern Introduction
|
||||
|
||||
Introduce proven design patterns to solve recurring design problems.
|
||||
|
||||
**Parameters:**
|
||||
- `scope` (required): Path to apply pattern
|
||||
- `pattern` (required): Pattern to introduce
|
||||
- `factory` - Create objects without specifying exact class
|
||||
- `strategy` - Encapsulate interchangeable algorithms
|
||||
- `observer` - Publish-subscribe event system
|
||||
- `decorator` - Add behavior dynamically
|
||||
- `adapter` - Make incompatible interfaces work together
|
||||
- `repository` - Abstract data access layer
|
||||
- `dependency-injection` - Invert control, improve testability
|
||||
- `singleton` - Ensure single instance (use sparingly)
|
||||
- `command` - Encapsulate requests as objects
|
||||
- `facade` - Simplified interface to complex subsystem
|
||||
- `reason` (optional): Why introducing this pattern
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Eliminate complex switch statement
|
||||
/10x-fullstack-engineer:refactor patterns scope:"PaymentProcessor.ts" pattern:"strategy" reason:"eliminate switch statement"
|
||||
|
||||
# Improve testability
|
||||
/10x-fullstack-engineer:refactor patterns scope:"services/" pattern:"dependency-injection" reason:"improve testability"
|
||||
|
||||
# Decouple event handling
|
||||
/10x-fullstack-engineer:refactor patterns scope:"UserService.ts" pattern:"observer" reason:"loose coupling"
|
||||
```
|
||||
|
||||
**Pattern Selection Guide:**
|
||||
|
||||
| Problem | Pattern | Benefit |
|
||||
|---------|---------|---------|
|
||||
| Complex switch/conditionals | Strategy, State | Eliminate conditionals |
|
||||
| Tight coupling | Dependency Injection, Observer | Loose coupling |
|
||||
| Complex object creation | Factory, Builder | Centralize creation |
|
||||
| Can't extend without modifying | Strategy, Decorator | Open/Closed Principle |
|
||||
| Complex subsystem interface | Facade, Adapter | Simplify interface |
|
||||
| Data access scattered | Repository | Abstract persistence |
|
||||
|
||||
**Before/After Example:**
|
||||
|
||||
```typescript
|
||||
// Before: 180 lines, switch statement with 5 cases
|
||||
async processPayment(order: Order, method: string) {
|
||||
switch (method) {
|
||||
case 'credit_card': /* 40 lines */ break;
|
||||
case 'paypal': /* 40 lines */ break;
|
||||
case 'bank_transfer': /* 40 lines */ break;
|
||||
case 'crypto': /* 40 lines */ break;
|
||||
}
|
||||
}
|
||||
|
||||
// After: Strategy Pattern, ~30 lines
|
||||
async processPayment(order: Order, method: string): Promise<PaymentResult> {
|
||||
const strategy = this.strategies.get(method);
|
||||
if (!strategy) throw new UnsupportedPaymentMethodError(method);
|
||||
|
||||
const result = await strategy.process(order);
|
||||
await this.transactionRepo.record(order.id, result);
|
||||
await this.notificationService.sendReceipt(order.customer, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Types - TypeScript Type Safety
|
||||
|
||||
Improve TypeScript type safety by eliminating 'any', adding types, and enabling strict mode.
|
||||
|
||||
**Parameters:**
|
||||
- `scope` (required): Path to improve
|
||||
- `strategy` (required): Type improvement strategy
|
||||
- `add-types` - Add missing type annotations
|
||||
- `strengthen-types` - Replace weak types with specific ones
|
||||
- `migrate-to-ts` - Convert JavaScript to TypeScript
|
||||
- `eliminate-any` - Remove 'any' types
|
||||
- `add-generics` - Add generic type parameters
|
||||
- `strict` (optional): Enable strict TypeScript mode (default: false)
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Add missing types
|
||||
/10x-fullstack-engineer:refactor types scope:"utils/helpers.js" strategy:"add-types"
|
||||
|
||||
# Eliminate all 'any' types
|
||||
/10x-fullstack-engineer:refactor types scope:"api/" strategy:"eliminate-any" strict:"true"
|
||||
|
||||
# Migrate JavaScript to TypeScript
|
||||
/10x-fullstack-engineer:refactor types scope:"src/legacy/" strategy:"migrate-to-ts"
|
||||
|
||||
# Add generics for reusability
|
||||
/10x-fullstack-engineer:refactor types scope:"Repository.ts" strategy:"add-generics"
|
||||
```
|
||||
|
||||
**Type Safety Improvements:**
|
||||
|
||||
| Strategy | Before | After | Benefit |
|
||||
|----------|--------|-------|---------|
|
||||
| add-types | `function process(data) { }` | `function process(data: Input): Output { }` | Compile-time checks |
|
||||
| eliminate-any | `async get(): Promise<any>` | `async get<T>(): Promise<T>` | Type safety |
|
||||
| migrate-to-ts | `.js` with no types | `.ts` with full types | Modern TypeScript |
|
||||
| add-generics | Separate class per type | `Repository<T>` | DRY, reusable |
|
||||
| strengthen-types | Weak 'any' types | Strong specific types | Catch errors early |
|
||||
|
||||
**Before/After Example:**
|
||||
|
||||
```typescript
|
||||
// Before: Weak 'any' types
|
||||
async get(endpoint: string): Promise<any> {
|
||||
return fetch(endpoint).then(r => r.json());
|
||||
}
|
||||
|
||||
// After: Strong generic types
|
||||
async get<T>(endpoint: string): Promise<T> {
|
||||
const response = await fetch(endpoint);
|
||||
if (!response.ok) throw await this.handleError(response);
|
||||
return response.json() as T;
|
||||
}
|
||||
|
||||
// Usage with full type safety
|
||||
const user = await client.get<User>('/users/1');
|
||||
console.log(user.name); // Autocomplete works!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Duplicate - Code Duplication Elimination
|
||||
|
||||
Detect and eliminate code duplication through extraction, parameterization, or templating.
|
||||
|
||||
**Parameters:**
|
||||
- `scope` (required): Path to analyze
|
||||
- `threshold` (optional): Similarity percentage (default: 80)
|
||||
- 100: Exact duplicates only
|
||||
- 80-99: Near duplicates (recommended)
|
||||
- 50-79: Similar patterns
|
||||
- `strategy` (optional): Consolidation strategy (default: auto-detect)
|
||||
- `extract-function` - Extract to shared function
|
||||
- `extract-class` - Extract to shared class
|
||||
- `parameterize` - Add parameters to reduce duplication
|
||||
- `template` - Use template/component pattern
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Find and eliminate duplicates
|
||||
/10x-fullstack-engineer:refactor duplicate scope:"src/validators" threshold:"80" strategy:"extract-function"
|
||||
|
||||
# Find exact duplicates only
|
||||
/10x-fullstack-engineer:refactor duplicate scope:"src/components" threshold:"100"
|
||||
|
||||
# Use parameterization
|
||||
/10x-fullstack-engineer:refactor duplicate scope:"formatters.ts" strategy:"parameterize"
|
||||
```
|
||||
|
||||
**Duplication Metrics:**
|
||||
- **Target**: < 3% code duplication
|
||||
- **Exact Duplicates**: 100% match (copy-paste code)
|
||||
- **Near Duplicates**: 80-99% similar (minor variations)
|
||||
- **Structural Duplicates**: 50-79% similar (same pattern)
|
||||
|
||||
**Before/After Example:**
|
||||
|
||||
```typescript
|
||||
// Before: 5 copies of validation (210 lines duplicated)
|
||||
// UserForm.tsx, ProfileForm.tsx, RegistrationForm.tsx, SettingsForm.tsx, AdminForm.tsx
|
||||
function validateForm() {
|
||||
const errors: Errors = {};
|
||||
// 42 lines of validation logic copied in each file
|
||||
return errors;
|
||||
}
|
||||
|
||||
// After: Single implementation (168 lines saved)
|
||||
// utils/validation.ts
|
||||
export function validateUserForm(data: FormData): ValidationResult {
|
||||
const errors: Record<string, string> = {};
|
||||
|
||||
const emailError = validateEmail(data.email);
|
||||
if (emailError) errors.email = emailError;
|
||||
|
||||
const passwordError = validatePassword(data.password);
|
||||
if (passwordError) errors.password = passwordError;
|
||||
|
||||
return { valid: Object.keys(errors).length === 0, errors };
|
||||
}
|
||||
|
||||
// All forms import and use shared validation
|
||||
import { validateUserForm } from '@/utils/validation';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. Modernize - Legacy Code Modernization
|
||||
|
||||
Update legacy code patterns to modern JavaScript/TypeScript standards.
|
||||
|
||||
**Parameters:**
|
||||
- `scope` (required): Path to modernize
|
||||
- `targets` (required): Comma-separated modernization targets
|
||||
- `callbacks-to-async` - Convert callbacks to async/await
|
||||
- `var-to-const` - Replace var with const/let
|
||||
- `prototypes-to-classes` - Convert prototypes to ES6 classes
|
||||
- `commonjs-to-esm` - Convert CommonJS to ES modules
|
||||
- `jquery-to-vanilla` - Replace jQuery with vanilla JS
|
||||
- `classes-to-hooks` - Convert React class components to hooks
|
||||
- `legacy-api` - Update deprecated API usage
|
||||
- `compatibility` (optional): Target environment (e.g., "node14+", "es2020")
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Modernize callback hell
|
||||
/10x-fullstack-engineer:refactor modernize scope:"legacy-api/" targets:"callbacks-to-async" compatibility:"node14+"
|
||||
|
||||
# Update all legacy patterns
|
||||
/10x-fullstack-engineer:refactor modernize scope:"src/old/" targets:"var-to-const,prototypes-to-classes,commonjs-to-esm"
|
||||
|
||||
# Remove jQuery dependency
|
||||
/10x-fullstack-engineer:refactor modernize scope:"public/js/" targets:"jquery-to-vanilla"
|
||||
|
||||
# Convert to React hooks
|
||||
/10x-fullstack-engineer:refactor modernize scope:"components/" targets:"classes-to-hooks"
|
||||
```
|
||||
|
||||
**Modernization Impact:**
|
||||
|
||||
| Target | Improvement | Benefit |
|
||||
|--------|-------------|---------|
|
||||
| callbacks-to-async | Flat code vs callback hell | Readability, error handling |
|
||||
| var-to-const | Block scope vs function scope | Prevent bugs, clarity |
|
||||
| prototypes-to-classes | ES6 class syntax | Modern, better IDE support |
|
||||
| commonjs-to-esm | import/export vs require() | Tree-shaking, standard |
|
||||
| jquery-to-vanilla | Native APIs vs jQuery | -30KB bundle, performance |
|
||||
| classes-to-hooks | Function components vs classes | Simpler, composable |
|
||||
|
||||
**Before/After Example:**
|
||||
|
||||
```javascript
|
||||
// Before: Callback hell (25+ lines, nested 4 levels)
|
||||
function getUser(userId, callback) {
|
||||
db.query('SELECT * FROM users WHERE id = ?', [userId], function(err, user) {
|
||||
if (err) return callback(err);
|
||||
db.query('SELECT * FROM posts WHERE author_id = ?', [userId], function(err, posts) {
|
||||
if (err) return callback(err);
|
||||
db.query('SELECT * FROM comments WHERE user_id = ?', [userId], function(err, comments) {
|
||||
if (err) return callback(err);
|
||||
callback(null, { user, posts, comments });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// After: Async/await (8 lines, flat structure)
|
||||
async function getUser(userId: number): Promise<UserWithContent> {
|
||||
const [user, posts, comments] = await Promise.all([
|
||||
query<User>('SELECT * FROM users WHERE id = ?', [userId]),
|
||||
query<Post[]>('SELECT * FROM posts WHERE author_id = ?', [userId]),
|
||||
query<Comment[]>('SELECT * FROM comments WHERE user_id = ?', [userId])
|
||||
]);
|
||||
|
||||
return { user, posts, comments };
|
||||
}
|
||||
```
|
||||
|
||||
## Pre-Refactoring Safety Checklist
|
||||
|
||||
**CRITICAL**: Before ANY refactoring operation, verify:
|
||||
|
||||
### ✓ Test Coverage
|
||||
- [ ] Existing test coverage is adequate (>70% for code being refactored)
|
||||
- [ ] All tests currently passing
|
||||
- [ ] Tests are meaningful (test behavior, not implementation)
|
||||
|
||||
### ✓ Version Control
|
||||
- [ ] All changes committed to version control
|
||||
- [ ] Working on a feature branch (not main/master)
|
||||
- [ ] Clean working directory (no uncommitted changes)
|
||||
|
||||
### ✓ Backup
|
||||
- [ ] Current state committed with clear message
|
||||
- [ ] Can easily revert if needed
|
||||
- [ ] Branch created specifically for this refactoring
|
||||
|
||||
### ✓ Scope Definition
|
||||
- [ ] Clearly defined boundaries of what to refactor
|
||||
- [ ] No mixing of refactoring with new features
|
||||
- [ ] Reasonable size for one refactoring session
|
||||
|
||||
### ✓ Risk Assessment
|
||||
- [ ] Understand dependencies and impact
|
||||
- [ ] Identify potential breaking changes
|
||||
- [ ] Have rollback plan ready
|
||||
|
||||
## Utility Scripts
|
||||
|
||||
The refactor skill includes three utility scripts for automated analysis:
|
||||
|
||||
### analyze-complexity.sh
|
||||
|
||||
Analyzes cyclomatic complexity using ESLint.
|
||||
|
||||
```bash
|
||||
./.scripts/analyze-complexity.sh <scope> [max-complexity]
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Detects functions with complexity > threshold
|
||||
- Identifies deep nesting (>3 levels)
|
||||
- Finds long functions (>50 lines)
|
||||
- Checks parameter counts (>4 parameters)
|
||||
- Generates JSON report with violations
|
||||
|
||||
**Output:** `complexity-report.json`
|
||||
|
||||
### detect-duplication.sh
|
||||
|
||||
Detects code duplication using jsinspect.
|
||||
|
||||
```bash
|
||||
./.scripts/detect-duplication.sh <scope> [threshold]
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Finds exact duplicates (100% match)
|
||||
- Detects near duplicates (>80% similar)
|
||||
- Identifies structural duplicates
|
||||
- Calculates duplication statistics
|
||||
- Provides remediation recommendations
|
||||
|
||||
**Output:** `duplication-report.json`
|
||||
|
||||
### verify-tests.sh
|
||||
|
||||
Verifies test coverage before refactoring.
|
||||
|
||||
```bash
|
||||
./.scripts/verify-tests.sh <scope> [min-coverage]
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Runs tests with coverage
|
||||
- Validates coverage meets minimum threshold
|
||||
- Identifies files with low coverage
|
||||
- Prevents unsafe refactoring
|
||||
- Supports Jest, Mocha, NYC
|
||||
|
||||
**Output:** `coverage-report.json`
|
||||
|
||||
**Exit codes:**
|
||||
- 0: Coverage adequate, safe to refactor
|
||||
- 1: Insufficient coverage, add tests first
|
||||
|
||||
## Refactoring Techniques
|
||||
|
||||
### Code Smells and Solutions
|
||||
|
||||
| Code Smell | Detection | Solution | Operation |
|
||||
|------------|-----------|----------|-----------|
|
||||
| **Long Method** | >50 lines | Extract smaller methods | extract |
|
||||
| **Long Parameter List** | >4 parameters | Introduce parameter object | extract |
|
||||
| **Duplicate Code** | >3% duplication | Extract to shared function | duplicate |
|
||||
| **Large Class** | >300 lines | Split into focused classes | extract |
|
||||
| **Switch Statements** | Complex conditionals | Use polymorphism/strategy | patterns |
|
||||
| **Feature Envy** | Method uses another class heavily | Move method to that class | extract |
|
||||
| **Data Clumps** | Same data grouped together | Introduce class/interface | extract |
|
||||
| **Primitive Obsession** | Primitives instead of objects | Introduce value objects | patterns |
|
||||
|
||||
### Refactoring Workflows
|
||||
|
||||
#### Workflow 1: High Complexity Function
|
||||
|
||||
```bash
|
||||
# 1. Analyze complexity
|
||||
/10x-fullstack-engineer:refactor analyze scope:"UserService.ts" metrics:"complexity"
|
||||
|
||||
# 2. Identify function with complexity >10
|
||||
# Result: validateAndCreateUser() has complexity 18
|
||||
|
||||
# 3. Extract methods
|
||||
/10x-fullstack-engineer:refactor extract scope:"UserService.ts" type:"method" target:"validateAndCreateUser"
|
||||
|
||||
# 4. Verify improvement
|
||||
/10x-fullstack-engineer:refactor analyze scope:"UserService.ts" metrics:"complexity"
|
||||
# Result: Complexity reduced from 18 to 3
|
||||
```
|
||||
|
||||
#### Workflow 2: Code Duplication
|
||||
|
||||
```bash
|
||||
# 1. Detect duplication
|
||||
/10x-fullstack-engineer:refactor duplicate scope:"src/components" threshold:"80"
|
||||
|
||||
# 2. Review duplicate blocks
|
||||
# Result: Validation logic duplicated in 5 files
|
||||
|
||||
# 3. Extract to shared utility
|
||||
/10x-fullstack-engineer:refactor duplicate scope:"src/components" strategy:"extract-function"
|
||||
|
||||
# 4. Verify elimination
|
||||
/10x-fullstack-engineer:refactor duplicate scope:"src/components" threshold:"80"
|
||||
# Result: Duplication reduced from 6.6% to 1.1%
|
||||
```
|
||||
|
||||
#### Workflow 3: Legacy Code Modernization
|
||||
|
||||
```bash
|
||||
# 1. Identify legacy patterns
|
||||
/10x-fullstack-engineer:refactor analyze scope:"src/legacy/" metrics:"all"
|
||||
|
||||
# 2. Modernize callbacks to async/await
|
||||
/10x-fullstack-engineer:refactor modernize scope:"src/legacy/" targets:"callbacks-to-async"
|
||||
|
||||
# 3. Update var to const/let
|
||||
/10x-fullstack-engineer:refactor modernize scope:"src/legacy/" targets:"var-to-const"
|
||||
|
||||
# 4. Convert to ES modules
|
||||
/10x-fullstack-engineer:refactor modernize scope:"src/legacy/" targets:"commonjs-to-esm"
|
||||
|
||||
# 5. Verify all tests pass
|
||||
npm test
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do's
|
||||
|
||||
✅ **Start Small**: Begin with low-risk, high-value refactorings
|
||||
✅ **Test Continuously**: Run tests after each change
|
||||
✅ **Commit Frequently**: Small commits with clear messages
|
||||
✅ **Pair Review**: Have someone review refactored code
|
||||
✅ **Measure Impact**: Track metrics before and after
|
||||
✅ **Document Why**: Explain reasoning in commits and comments
|
||||
✅ **Avoid Scope Creep**: Stay focused on defined scope
|
||||
✅ **Time Box**: Set time limits for refactoring sessions
|
||||
|
||||
### Don'ts
|
||||
|
||||
❌ **Mix with Features**: Don't add features while refactoring
|
||||
❌ **Skip Tests**: Never refactor code with <70% coverage
|
||||
❌ **Big Bang**: Avoid massive refactorings
|
||||
❌ **Change Behavior**: External behavior must stay the same
|
||||
❌ **Uncommitted Changes**: Always commit before refactoring
|
||||
❌ **Ignore Warnings**: Address all compiler/linter warnings
|
||||
❌ **Over-Engineer**: Apply patterns only when truly needed
|
||||
❌ **Rush**: Take time to refactor properly
|
||||
|
||||
## Metrics and Goals
|
||||
|
||||
### Code Quality Targets
|
||||
|
||||
| Metric | Target | Warning | Critical |
|
||||
|--------|--------|---------|----------|
|
||||
| Cyclomatic Complexity | <6 | 6-10 | >10 |
|
||||
| Function Length | <50 lines | 50-100 | >100 |
|
||||
| Class Length | <300 lines | 300-500 | >500 |
|
||||
| Parameter Count | <4 | 4-6 | >6 |
|
||||
| Code Duplication | <3% | 3-8% | >8% |
|
||||
| Test Coverage | >80% | 70-80% | <70% |
|
||||
| Type Coverage (TS) | >95% | 90-95% | <90% |
|
||||
|
||||
### Refactoring Impact
|
||||
|
||||
Track these metrics before and after refactoring:
|
||||
|
||||
- **Complexity Reduction**: Cyclomatic complexity decrease
|
||||
- **Lines of Code**: Reduction through extraction and DRY
|
||||
- **Test Coverage**: Improvement in coverage percentage
|
||||
- **Type Safety**: Reduction in 'any' usage
|
||||
- **Duplication**: Percentage of duplicate code eliminated
|
||||
- **Bundle Size**: Reduction (e.g., removing jQuery)
|
||||
|
||||
## Integration with 10x-fullstack-engineer Agent
|
||||
|
||||
All refactoring operations leverage the **10x-fullstack-engineer** agent for:
|
||||
|
||||
- Expert code quality analysis
|
||||
- Best practice application
|
||||
- Pattern recognition and recommendation
|
||||
- Consistency with project standards
|
||||
- Risk assessment and mitigation
|
||||
- Test-driven refactoring approach
|
||||
|
||||
The agent applies **SOLID principles**, **DRY**, **YAGNI**, and follows the **Boy Scout Rule** (leave code better than you found it).
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Issue: "Insufficient test coverage"
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# 1. Check current coverage
|
||||
/10x-fullstack-engineer:refactor analyze scope:"UserService.ts" metrics:"coverage"
|
||||
|
||||
# 2. Add tests before refactoring
|
||||
# Write tests for the code you're about to refactor
|
||||
|
||||
# 3. Verify coverage improved
|
||||
npm test -- --coverage
|
||||
|
||||
# 4. Retry refactoring
|
||||
/10x-fullstack-engineer:refactor extract scope:"UserService.ts" type:"method" target:"validateUser"
|
||||
```
|
||||
|
||||
### Issue: "Uncommitted changes detected"
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# 1. Check git status
|
||||
git status
|
||||
|
||||
# 2. Commit or stash changes
|
||||
git add .
|
||||
git commit -m "chore: prepare for refactoring"
|
||||
|
||||
# 3. Create refactoring branch
|
||||
git checkout -b refactor/improve-user-service
|
||||
|
||||
# 4. Retry refactoring
|
||||
/10x-fullstack-engineer:refactor extract scope:"UserService.ts" type:"method" target:"validateUser"
|
||||
```
|
||||
|
||||
### Issue: "Too many duplicates found"
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# 1. Increase threshold to focus on exact duplicates
|
||||
/10x-fullstack-engineer:refactor duplicate scope:"src/" threshold:"95"
|
||||
|
||||
# 2. Tackle highest impact duplicates first
|
||||
# Extract most duplicated code blocks
|
||||
|
||||
# 3. Gradually lower threshold
|
||||
/10x-fullstack-engineer:refactor duplicate scope:"src/" threshold:"85"
|
||||
|
||||
# 4. Continue until <3% duplication
|
||||
/10x-fullstack-engineer:refactor duplicate scope:"src/" threshold:"80"
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Complete Refactoring Session
|
||||
|
||||
```bash
|
||||
# Session: Refactor UserService for better maintainability
|
||||
|
||||
# Step 1: Analyze current state
|
||||
/10x-fullstack-engineer:refactor analyze scope:"src/services/UserService.ts" depth:"detailed"
|
||||
# Results:
|
||||
# - Complexity: 18 (CRITICAL)
|
||||
# - Duplication: 6.6% (HIGH)
|
||||
# - Coverage: 65% (INADEQUATE)
|
||||
|
||||
# Step 2: Add tests to reach >70% coverage
|
||||
# (Write tests for critical paths)
|
||||
|
||||
# Step 3: Verify coverage improved
|
||||
npm test -- --coverage
|
||||
# Coverage: 78% ✓
|
||||
|
||||
# Step 4: Extract complex method
|
||||
/10x-fullstack-engineer:refactor extract scope:"src/services/UserService.ts" type:"method" target:"validateAndCreateUser"
|
||||
# Complexity: 18 → 3 (83% improvement)
|
||||
|
||||
# Step 5: Introduce dependency injection pattern
|
||||
/10x-fullstack-engineer:refactor patterns scope:"src/services/UserService.ts" pattern:"dependency-injection"
|
||||
# Testability: Greatly improved
|
||||
|
||||
# Step 6: Eliminate duplicate validation
|
||||
/10x-fullstack-engineer:refactor duplicate scope:"src/services/" threshold:"80" strategy:"extract-function"
|
||||
# Duplication: 6.6% → 1.1% (87.5% improvement)
|
||||
|
||||
# Step 7: Strengthen types
|
||||
/10x-fullstack-engineer:refactor types scope:"src/services/UserService.ts" strategy:"eliminate-any"
|
||||
# Type safety: 100% (0 'any' types remaining)
|
||||
|
||||
# Step 8: Final analysis
|
||||
/10x-fullstack-engineer:refactor analyze scope:"src/services/UserService.ts" depth:"detailed"
|
||||
# Results:
|
||||
# - Complexity: 3 (EXCELLENT)
|
||||
# - Duplication: 1.1% (EXCELLENT)
|
||||
# - Coverage: 85% (GOOD)
|
||||
# - Type Safety: 100% (EXCELLENT)
|
||||
|
||||
# Step 9: Run all tests
|
||||
npm test
|
||||
# All tests passing ✓
|
||||
|
||||
# Step 10: Commit refactoring
|
||||
git add .
|
||||
git commit -m "refactor(UserService): improve maintainability and testability
|
||||
|
||||
- Reduced complexity from 18 to 3
|
||||
- Eliminated 87.5% of code duplication
|
||||
- Improved test coverage from 65% to 85%
|
||||
- Removed all 'any' types
|
||||
- Introduced dependency injection pattern"
|
||||
```
|
||||
|
||||
## Related Skills
|
||||
|
||||
- `/test` - Test generation and coverage improvement
|
||||
- `/review` - Code review and quality checks
|
||||
- `/debug` - Debugging and issue diagnosis
|
||||
- `/optimize` - Performance optimization
|
||||
|
||||
## Further Reading
|
||||
|
||||
- **Refactoring (Martin Fowler)**: Definitive guide to refactoring
|
||||
- **Clean Code (Robert C. Martin)**: Code quality principles
|
||||
- **Design Patterns (Gang of Four)**: Pattern catalog
|
||||
- **Working Effectively with Legacy Code (Michael Feathers)**: Legacy modernization
|
||||
- **Refactoring UI (Adam Wathan)**: Component extraction patterns
|
||||
|
||||
---
|
||||
|
||||
**Remember**: Refactoring is not about making code perfect—it's about making code better, more maintainable, and easier to change in the future. Refactor continuously, in small steps, with confidence provided by comprehensive test coverage.
|
||||
659
commands/refactor/analyze.md
Normal file
659
commands/refactor/analyze.md
Normal file
@@ -0,0 +1,659 @@
|
||||
# Code Quality Analysis Operation
|
||||
|
||||
Analyze code quality, identify code smells, calculate metrics, and prioritize refactoring opportunities.
|
||||
|
||||
## Parameters
|
||||
|
||||
**Received from $ARGUMENTS**: All arguments after "analyze"
|
||||
|
||||
**Expected format**:
|
||||
```
|
||||
scope:"<path-or-description>" [metrics:"<metric1,metric2>"] [depth:"quick|standard|detailed"]
|
||||
```
|
||||
|
||||
**Parameter definitions**:
|
||||
- `scope` (REQUIRED): Path to analyze or description (e.g., "user-service/", "authentication module", "src/components/UserProfile.tsx")
|
||||
- `metrics` (OPTIONAL): Comma-separated metrics to analyze (default: all)
|
||||
- `complexity` - Cyclomatic complexity
|
||||
- `duplication` - Code duplication detection
|
||||
- `coverage` - Test coverage analysis
|
||||
- `dependencies` - Dependency analysis and circular dependencies
|
||||
- `types` - Type coverage (TypeScript projects)
|
||||
- `smells` - Code smells detection
|
||||
- `depth` (OPTIONAL): Analysis depth (default: standard)
|
||||
- `quick` - Fast scan, high-level metrics only
|
||||
- `standard` - Balanced analysis with key metrics
|
||||
- `detailed` - Comprehensive deep analysis with recommendations
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Pre-Analysis Verification
|
||||
|
||||
Before analyzing, verify:
|
||||
|
||||
```bash
|
||||
# Check if scope exists
|
||||
test -e <scope> || echo "Error: Scope path does not exist"
|
||||
|
||||
# Check if project has package.json
|
||||
test -f package.json || echo "Warning: No package.json found"
|
||||
|
||||
# Verify analysis tools availability
|
||||
command -v npx >/dev/null 2>&1 || echo "Warning: npm/npx not available"
|
||||
```
|
||||
|
||||
### 2. Complexity Analysis
|
||||
|
||||
**Measure cyclomatic complexity** using ESLint:
|
||||
|
||||
```bash
|
||||
# Run complexity analysis
|
||||
npx eslint <scope> \
|
||||
--ext .js,.jsx,.ts,.tsx \
|
||||
--rule 'complexity: [error, { max: 10 }]' \
|
||||
--rule 'max-depth: [error, 3]' \
|
||||
--rule 'max-lines-per-function: [error, { max: 50 }]' \
|
||||
--rule 'max-params: [error, 4]' \
|
||||
--format json > complexity-report.json
|
||||
|
||||
# Or use script
|
||||
./.scripts/analyze-complexity.sh <scope>
|
||||
```
|
||||
|
||||
**Identify**:
|
||||
- Functions with complexity > 10 (high risk)
|
||||
- Functions with complexity 6-10 (moderate risk)
|
||||
- Deep nesting (>3 levels)
|
||||
- Long functions (>50 lines)
|
||||
- Long parameter lists (>4 parameters)
|
||||
|
||||
**Report format**:
|
||||
```markdown
|
||||
### Complexity Analysis
|
||||
|
||||
**Critical Issues** (Complexity > 10):
|
||||
1. `UserService.validateAndCreateUser()` - Complexity: 18 (45 lines)
|
||||
- Location: src/services/UserService.ts:127
|
||||
- Impact: High - Used in 8 places
|
||||
- Recommendation: Extract validation logic into separate functions
|
||||
|
||||
2. `OrderProcessor.processPayment()` - Complexity: 15 (38 lines)
|
||||
- Location: src/services/OrderProcessor.ts:89
|
||||
- Impact: Medium - Payment critical path
|
||||
- Recommendation: Use strategy pattern for payment methods
|
||||
|
||||
**Moderate Issues** (Complexity 6-10):
|
||||
- 12 functions identified
|
||||
- Average complexity: 7.3
|
||||
- Recommendation: Monitor, refactor opportunistically
|
||||
```
|
||||
|
||||
### 3. Duplication Detection
|
||||
|
||||
**Detect duplicate code** using jsinspect:
|
||||
|
||||
```bash
|
||||
# Find duplicated code blocks
|
||||
npx jsinspect <scope> \
|
||||
--threshold 30 \
|
||||
--min-instances 2 \
|
||||
--ignore "node_modules|dist|build" \
|
||||
--reporter json > duplication-report.json
|
||||
|
||||
# Or use script
|
||||
./.scripts/detect-duplication.sh <scope>
|
||||
```
|
||||
|
||||
**Identify**:
|
||||
- Exact duplicates (100% match)
|
||||
- Near duplicates (>80% similar)
|
||||
- Copy-paste patterns
|
||||
- Repeated logic across files
|
||||
|
||||
**Report format**:
|
||||
```markdown
|
||||
### Code Duplication
|
||||
|
||||
**Exact Duplicates** (100% match):
|
||||
1. Validation logic (42 lines) - 5 instances
|
||||
- src/components/UserForm.tsx:45-87
|
||||
- src/components/ProfileForm.tsx:32-74
|
||||
- src/components/RegistrationForm.tsx:56-98
|
||||
- src/components/SettingsForm.tsx:23-65
|
||||
- src/components/AdminForm.tsx:89-131
|
||||
- **Recommendation**: Extract to shared validator utility
|
||||
- **Estimated savings**: 168 lines (4 duplicates × 42 lines)
|
||||
|
||||
**Near Duplicates** (>80% similar):
|
||||
2. API error handling (18 lines) - 8 instances
|
||||
- Average similarity: 87%
|
||||
- **Recommendation**: Create centralized error handler
|
||||
- **Estimated savings**: 126 lines
|
||||
|
||||
**Total Duplication**:
|
||||
- Duplicate lines: 542 / 8,234 (6.6%)
|
||||
- Target: < 3%
|
||||
- **Priority**: HIGH - Significant duplication found
|
||||
```
|
||||
|
||||
### 4. Test Coverage Analysis
|
||||
|
||||
**Calculate test coverage**:
|
||||
|
||||
```bash
|
||||
# Run tests with coverage
|
||||
npm test -- --coverage --watchAll=false
|
||||
|
||||
# Generate coverage report
|
||||
npx nyc report --reporter=json > coverage-report.json
|
||||
|
||||
# Or use script
|
||||
./.scripts/verify-tests.sh <scope>
|
||||
```
|
||||
|
||||
**Identify**:
|
||||
- Files with < 70% coverage (inadequate)
|
||||
- Files with 70-80% coverage (acceptable)
|
||||
- Files with > 80% coverage (good)
|
||||
- Untested code paths
|
||||
- Missing edge case tests
|
||||
|
||||
**Report format**:
|
||||
```markdown
|
||||
### Test Coverage
|
||||
|
||||
**Overall Coverage**:
|
||||
- Statements: 78.5% (Target: 80%)
|
||||
- Branches: 72.3% (Target: 75%)
|
||||
- Functions: 81.2% (Target: 80%)
|
||||
- Lines: 77.8% (Target: 80%)
|
||||
|
||||
**Critical Gaps** (< 70% coverage):
|
||||
1. `src/services/PaymentService.ts` - 45% coverage
|
||||
- Missing: Error handling paths
|
||||
- Missing: Edge cases (negative amounts, invalid cards)
|
||||
- **Risk**: HIGH - Financial logic
|
||||
|
||||
2. `src/utils/validation.ts` - 62% coverage
|
||||
- Missing: Boundary conditions
|
||||
- Missing: Invalid input handling
|
||||
- **Risk**: MEDIUM - Used in 15 components
|
||||
|
||||
**Recommendation**: Add tests before refactoring these areas.
|
||||
```
|
||||
|
||||
### 5. Dependency Analysis
|
||||
|
||||
**Analyze module dependencies**:
|
||||
|
||||
```bash
|
||||
# Check for circular dependencies
|
||||
npx madge --circular --extensions ts,tsx,js,jsx <scope>
|
||||
|
||||
# Generate dependency graph
|
||||
npx madge --image deps.png <scope>
|
||||
|
||||
# Find orphaned files
|
||||
npx madge --orphans <scope>
|
||||
```
|
||||
|
||||
**Identify**:
|
||||
- Circular dependencies (breaks modularity)
|
||||
- Highly coupled modules
|
||||
- God objects (too many dependencies)
|
||||
- Orphaned files (unused)
|
||||
- Deep dependency chains
|
||||
|
||||
**Report format**:
|
||||
```markdown
|
||||
### Dependency Analysis
|
||||
|
||||
**Circular Dependencies** (CRITICAL):
|
||||
1. UserService ↔ AuthService ↔ SessionService
|
||||
- **Impact**: Cannot test in isolation
|
||||
- **Recommendation**: Introduce interface/abstraction layer
|
||||
|
||||
2. OrderModel ↔ PaymentModel ↔ CustomerModel
|
||||
- **Impact**: Tight coupling, difficult to change
|
||||
- **Recommendation**: Use repository pattern
|
||||
|
||||
**High Coupling**:
|
||||
- `UserService.ts` - 23 dependencies (Target: < 10)
|
||||
- `AppConfig.ts` - 18 dependencies (Target: < 10)
|
||||
- **Recommendation**: Split into smaller, focused modules
|
||||
|
||||
**Orphaned Files**: 5 files unused
|
||||
- src/utils/old-validator.ts (can be deleted)
|
||||
- src/helpers/deprecated.ts (can be deleted)
|
||||
```
|
||||
|
||||
### 6. Type Coverage Analysis (TypeScript)
|
||||
|
||||
**Analyze TypeScript type safety**:
|
||||
|
||||
```bash
|
||||
# Type check with strict mode
|
||||
npx tsc --noEmit --strict
|
||||
|
||||
# Count 'any' usage
|
||||
grep -r "any" <scope> --include="*.ts" --include="*.tsx" | wc -l
|
||||
|
||||
# Check for implicit any
|
||||
npx tsc --noEmit --noImplicitAny
|
||||
```
|
||||
|
||||
**Identify**:
|
||||
- Usage of `any` type
|
||||
- Implicit any declarations
|
||||
- Missing return type annotations
|
||||
- Weak type definitions
|
||||
- Type assertion overuse
|
||||
|
||||
**Report format**:
|
||||
```markdown
|
||||
### Type Safety Analysis (TypeScript)
|
||||
|
||||
**Type Coverage**:
|
||||
- Files with types: 145 / 167 (87%)
|
||||
- Any usage: 42 instances (Target: 0)
|
||||
- Implicit any: 18 instances
|
||||
- **Rating**: MODERATE - Room for improvement
|
||||
|
||||
**Critical Issues**:
|
||||
1. `src/api/client.ts` - 12 'any' types
|
||||
- Functions without return types
|
||||
- Untyped API responses
|
||||
- **Recommendation**: Add proper interfaces for API contracts
|
||||
|
||||
2. `src/utils/helpers.ts` - 8 'any' types
|
||||
- Generic utility functions
|
||||
- **Recommendation**: Use generics instead of 'any'
|
||||
|
||||
**Opportunity**: Eliminate 'any' types for 23% improvement
|
||||
```
|
||||
|
||||
### 7. Code Smells Detection
|
||||
|
||||
**Identify common code smells**:
|
||||
|
||||
**Long Method** (>50 lines):
|
||||
- Difficult to understand
|
||||
- Hard to test
|
||||
- Often doing too much
|
||||
- **Fix**: Extract smaller methods
|
||||
|
||||
**Long Parameter List** (>4 parameters):
|
||||
- Difficult to use
|
||||
- Hard to remember order
|
||||
- Often indicates missing abstraction
|
||||
- **Fix**: Introduce parameter object
|
||||
|
||||
**Duplicate Code**:
|
||||
- Maintenance nightmare
|
||||
- Bug multiplication
|
||||
- **Fix**: Extract to shared function/component
|
||||
|
||||
**Large Class** (>300 lines):
|
||||
- Too many responsibilities
|
||||
- Hard to understand
|
||||
- Difficult to test
|
||||
- **Fix**: Split into smaller classes
|
||||
|
||||
**Switch Statements** (complex conditionals):
|
||||
- Hard to extend
|
||||
- Violates Open/Closed Principle
|
||||
- **Fix**: Use polymorphism or strategy pattern
|
||||
|
||||
**Report format**:
|
||||
```markdown
|
||||
### Code Smells Detected
|
||||
|
||||
**Long Methods**: 23 functions > 50 lines
|
||||
- Worst: `OrderService.processOrder()` (247 lines)
|
||||
- **Impact**: Extremely difficult to understand and maintain
|
||||
- **Priority**: CRITICAL
|
||||
|
||||
**Long Parameter Lists**: 18 functions > 4 parameters
|
||||
- Worst: `createUser(name, email, age, address, phone, role, settings)` (7 params)
|
||||
- **Fix**: Use `CreateUserParams` object
|
||||
|
||||
**Large Classes**: 8 classes > 300 lines
|
||||
- Worst: `UserService.ts` (842 lines)
|
||||
- **Responsibilities**: Validation, CRUD, Auth, Notifications, Logging
|
||||
- **Fix**: Split into focused services
|
||||
|
||||
**Switch Statements**: 12 complex conditionals
|
||||
- `src/services/PaymentProcessor.ts` - Switch on payment method (5 cases, 180 lines)
|
||||
- **Fix**: Use strategy pattern for payment methods
|
||||
```
|
||||
|
||||
### 8. Generate Prioritized Report
|
||||
|
||||
**Priority calculation** based on:
|
||||
- **Severity**: Critical > High > Medium > Low
|
||||
- **Impact**: How many files/components affected
|
||||
- **Risk**: Test coverage, complexity, usage frequency
|
||||
- **Effort**: Estimated time to fix (hours)
|
||||
- **Value**: Improvement in maintainability
|
||||
|
||||
**Report format**:
|
||||
```markdown
|
||||
## Code Quality Analysis Report
|
||||
|
||||
### Executive Summary
|
||||
|
||||
**Scope Analyzed**: <scope>
|
||||
**Analysis Date**: <date>
|
||||
**Total Files**: <count>
|
||||
**Total Lines**: <count>
|
||||
|
||||
**Overall Health Score**: 6.5 / 10 (Needs Improvement)
|
||||
|
||||
**Top Priorities**:
|
||||
1. Eliminate critical code duplication (HIGH)
|
||||
2. Refactor high-complexity functions (HIGH)
|
||||
3. Improve test coverage for critical paths (HIGH)
|
||||
4. Remove circular dependencies (MEDIUM)
|
||||
5. Strengthen TypeScript type safety (MEDIUM)
|
||||
|
||||
---
|
||||
|
||||
### Metrics Summary
|
||||
|
||||
| Metric | Current | Target | Status |
|
||||
|--------|---------|--------|--------|
|
||||
| Cyclomatic Complexity (avg) | 8.3 | < 6 | ⚠️ Above target |
|
||||
| Code Duplication | 6.6% | < 3% | ⚠️ Above target |
|
||||
| Test Coverage | 78.5% | > 80% | ⚠️ Below target |
|
||||
| Type Coverage | 87% | > 95% | ⚠️ Below target |
|
||||
| Circular Dependencies | 3 | 0 | ❌ Critical |
|
||||
|
||||
---
|
||||
|
||||
### Priority 1: Critical Issues (Fix Immediately)
|
||||
|
||||
#### 1.1 Circular Dependencies
|
||||
**Severity**: CRITICAL
|
||||
**Impact**: Cannot test modules in isolation, tight coupling
|
||||
**Files Affected**: 8
|
||||
|
||||
**Dependencies**:
|
||||
- UserService ↔ AuthService ↔ SessionService
|
||||
- OrderModel ↔ PaymentModel ↔ CustomerModel
|
||||
- ComponentA ↔ ComponentB ↔ ComponentC
|
||||
|
||||
**Recommendation**: Introduce dependency injection and interface abstractions
|
||||
**Estimated Effort**: 8 hours
|
||||
**Value**: HIGH - Enables independent testing and deployment
|
||||
|
||||
#### 1.2 Extremely High Complexity Functions
|
||||
**Severity**: CRITICAL
|
||||
**Impact**: Very difficult to understand, test, maintain
|
||||
**Functions**: 4
|
||||
|
||||
**Functions**:
|
||||
1. `UserService.validateAndCreateUser()` - Complexity: 18
|
||||
2. `OrderProcessor.processPayment()` - Complexity: 15
|
||||
3. `ReportGenerator.generateQuarterly()` - Complexity: 14
|
||||
4. `DataTransformer.transform()` - Complexity: 13
|
||||
|
||||
**Recommendation**: Extract smaller functions, use early returns
|
||||
**Estimated Effort**: 6 hours
|
||||
**Value**: HIGH - Dramatic readability improvement
|
||||
|
||||
---
|
||||
|
||||
### Priority 2: High Issues (Fix Soon)
|
||||
|
||||
#### 2.1 Significant Code Duplication
|
||||
**Severity**: HIGH
|
||||
**Impact**: Maintenance burden, bug multiplication
|
||||
|
||||
**Duplicate Code**:
|
||||
- Validation logic: 5 exact copies (210 lines duplicated)
|
||||
- Error handling: 8 similar copies (144 lines duplicated)
|
||||
- Data formatting: 6 copies (96 lines duplicated)
|
||||
|
||||
**Recommendation**: Extract to shared utilities
|
||||
**Estimated Effort**: 4 hours
|
||||
**Value**: HIGH - 450 lines reduction, single source of truth
|
||||
|
||||
#### 2.2 Inadequate Test Coverage
|
||||
**Severity**: HIGH
|
||||
**Impact**: High risk of regressions during refactoring
|
||||
|
||||
**Critical Gaps**:
|
||||
- `PaymentService.ts` - 45% coverage (Financial logic!)
|
||||
- `AuthService.ts` - 58% coverage (Security logic!)
|
||||
- `validation.ts` - 62% coverage (Used everywhere!)
|
||||
|
||||
**Recommendation**: Add comprehensive tests before ANY refactoring
|
||||
**Estimated Effort**: 8 hours
|
||||
**Value**: CRITICAL - Enables safe refactoring
|
||||
|
||||
---
|
||||
|
||||
### Priority 3: Medium Issues (Plan to Fix)
|
||||
|
||||
#### 3.1 TypeScript Type Safety
|
||||
**Severity**: MEDIUM
|
||||
**Impact**: Runtime errors, poor IDE support
|
||||
|
||||
**Issues**:
|
||||
- 42 usages of 'any' type
|
||||
- 18 implicit any declarations
|
||||
- Missing return type annotations
|
||||
|
||||
**Recommendation**: Eliminate 'any', add proper types
|
||||
**Estimated Effort**: 6 hours
|
||||
**Value**: MEDIUM - Catch errors at compile time
|
||||
|
||||
#### 3.2 Long Methods and Large Classes
|
||||
**Severity**: MEDIUM
|
||||
**Impact**: Difficult to understand and maintain
|
||||
|
||||
**Issues**:
|
||||
- 23 long methods (>50 lines)
|
||||
- 8 large classes (>300 lines)
|
||||
- Single Responsibility Principle violations
|
||||
|
||||
**Recommendation**: Extract methods and split classes
|
||||
**Estimated Effort**: 12 hours
|
||||
**Value**: MEDIUM - Improved maintainability
|
||||
|
||||
---
|
||||
|
||||
### Priority 4: Low Issues (Opportunistic)
|
||||
|
||||
- Rename unclear variables (quick wins)
|
||||
- Add missing JSDoc comments
|
||||
- Consolidate similar utility functions
|
||||
- Remove unused imports and variables
|
||||
|
||||
---
|
||||
|
||||
### Recommended Refactoring Sequence
|
||||
|
||||
**Week 1**:
|
||||
1. Add missing tests for critical paths (8 hrs)
|
||||
2. Fix circular dependencies (8 hrs)
|
||||
|
||||
**Week 2**:
|
||||
3. Eliminate critical code duplication (4 hrs)
|
||||
4. Refactor highest complexity functions (6 hrs)
|
||||
|
||||
**Week 3**:
|
||||
5. Strengthen TypeScript types (6 hrs)
|
||||
6. Extract long methods (6 hrs)
|
||||
|
||||
**Week 4**:
|
||||
7. Split large classes (6 hrs)
|
||||
8. Address remaining medium priority issues
|
||||
|
||||
**Total Estimated Effort**: ~54 hours
|
||||
|
||||
---
|
||||
|
||||
### Code Examples
|
||||
|
||||
#### Example 1: High Complexity Function
|
||||
|
||||
**Before** (Complexity: 18):
|
||||
```typescript
|
||||
async validateAndCreateUser(userData: any) {
|
||||
if (!userData.email) {
|
||||
throw new Error("Email required");
|
||||
}
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(userData.email)) {
|
||||
throw new Error("Invalid email");
|
||||
}
|
||||
|
||||
if (!userData.password || userData.password.length < 8) {
|
||||
throw new Error("Password must be at least 8 characters");
|
||||
}
|
||||
|
||||
const hasUpper = /[A-Z]/.test(userData.password);
|
||||
const hasLower = /[a-z]/.test(userData.password);
|
||||
const hasNumber = /[0-9]/.test(userData.password);
|
||||
|
||||
if (!hasUpper || !hasLower || !hasNumber) {
|
||||
throw new Error("Password must contain uppercase, lowercase, and number");
|
||||
}
|
||||
|
||||
const existing = await this.db.users.findOne({ email: userData.email });
|
||||
if (existing) {
|
||||
throw new Error("Email already registered");
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(userData.password, 10);
|
||||
|
||||
const user = await this.db.users.create({
|
||||
email: userData.email,
|
||||
password: hashedPassword,
|
||||
name: userData.name,
|
||||
createdAt: new Date()
|
||||
});
|
||||
|
||||
await this.emailService.sendWelcomeEmail(user.email);
|
||||
|
||||
return user;
|
||||
}
|
||||
```
|
||||
|
||||
**After** (Complexity: 3):
|
||||
```typescript
|
||||
async validateAndCreateUser(userData: CreateUserInput): Promise<User> {
|
||||
this.validateUserInput(userData);
|
||||
await this.checkEmailAvailability(userData.email);
|
||||
|
||||
const hashedPassword = await this.hashPassword(userData.password);
|
||||
const user = await this.createUser({ ...userData, password: hashedPassword });
|
||||
|
||||
await this.sendWelcomeEmail(user);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private validateUserInput(userData: CreateUserInput): void {
|
||||
validateEmail(userData.email);
|
||||
validatePassword(userData.password);
|
||||
}
|
||||
|
||||
private async checkEmailAvailability(email: string): Promise<void> {
|
||||
const existing = await this.db.users.findOne({ email });
|
||||
if (existing) {
|
||||
throw new UserAlreadyExistsError(email);
|
||||
}
|
||||
}
|
||||
|
||||
private async hashPassword(password: string): Promise<string> {
|
||||
return bcrypt.hash(password, 10);
|
||||
}
|
||||
|
||||
private async createUser(data: CreateUserData): Promise<User> {
|
||||
return this.db.users.create({
|
||||
...data,
|
||||
createdAt: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
private async sendWelcomeEmail(user: User): Promise<void> {
|
||||
await this.emailService.sendWelcomeEmail(user.email);
|
||||
}
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Complexity: 18 → 3 (83% reduction)
|
||||
- Lines per function: 37 → 5 (86% reduction)
|
||||
- Testability: Each function can be tested independently
|
||||
- Readability: Clear intent, self-documenting code
|
||||
- Type safety: Proper interfaces instead of 'any'
|
||||
|
||||
---
|
||||
|
||||
### Next Steps
|
||||
|
||||
Based on this analysis, consider:
|
||||
|
||||
1. **Immediate Actions**:
|
||||
- Add tests for PaymentService, AuthService, validation.ts
|
||||
- Fix circular dependencies
|
||||
- Review and approve refactoring priorities
|
||||
|
||||
2. **Use Refactoring Operations**:
|
||||
- `/refactor extract` - For long methods
|
||||
- `/refactor duplicate` - For code duplication
|
||||
- `/refactor patterns` - For circular dependencies (DI pattern)
|
||||
- `/refactor types` - For TypeScript improvements
|
||||
|
||||
3. **Continuous Monitoring**:
|
||||
- Set up automated complexity checks in CI/CD
|
||||
- Track duplication metrics over time
|
||||
- Monitor test coverage trends
|
||||
- Review code quality in pull requests
|
||||
|
||||
---
|
||||
|
||||
**Analysis Complete**: Refactoring priorities identified and prioritized by impact and effort.
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
Provide a comprehensive analysis report with:
|
||||
- Executive summary with health score
|
||||
- Metrics table (current vs target)
|
||||
- Prioritized issues (Critical → High → Medium → Low)
|
||||
- Code examples showing before/after improvements
|
||||
- Estimated effort and value for each issue
|
||||
- Recommended refactoring sequence
|
||||
- Next steps and monitoring recommendations
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Scope not found**:
|
||||
```
|
||||
Error: Specified scope does not exist: <scope>
|
||||
|
||||
Please provide a valid path or description:
|
||||
- Relative path: "src/components/"
|
||||
- Absolute path: "/full/path/to/code"
|
||||
- Module description: "user authentication module"
|
||||
```
|
||||
|
||||
**No metrics requested**:
|
||||
```
|
||||
Using default metrics: complexity, duplication, coverage, dependencies
|
||||
|
||||
To specify metrics: metrics:"complexity,duplication"
|
||||
```
|
||||
|
||||
**Analysis tools not available**:
|
||||
```
|
||||
Warning: Some analysis tools not available:
|
||||
- eslint: Install with 'npm install -D eslint'
|
||||
- jsinspect: Install with 'npm install -g jsinspect'
|
||||
|
||||
Proceeding with available tools...
|
||||
```
|
||||
823
commands/refactor/duplicate.md
Normal file
823
commands/refactor/duplicate.md
Normal file
@@ -0,0 +1,823 @@
|
||||
# Code Duplication Elimination Operation
|
||||
|
||||
Detect and eliminate code duplication through extraction, parameterization, or templating.
|
||||
|
||||
## Parameters
|
||||
|
||||
**Received from $ARGUMENTS**: All arguments after "duplicate"
|
||||
|
||||
**Expected format**:
|
||||
```
|
||||
scope:"<path>" [threshold:"<percentage>"] [strategy:"<strategy-name>"]
|
||||
```
|
||||
|
||||
**Parameter definitions**:
|
||||
- `scope` (REQUIRED): Path to analyze (e.g., "src/", "src/components/")
|
||||
- `threshold` (OPTIONAL): Similarity threshold percentage (default: 80)
|
||||
- 100: Exact duplicates only
|
||||
- 80-99: Near duplicates (recommended)
|
||||
- 50-79: Similar patterns
|
||||
- `strategy` (OPTIONAL): Consolidation strategy (default: auto-detect)
|
||||
- `extract-function` - Extract to shared function
|
||||
- `extract-class` - Extract to shared class
|
||||
- `parameterize` - Add parameters to reduce duplication
|
||||
- `template` - Use template/component pattern
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Detect Duplication
|
||||
|
||||
Use jsinspect or similar tools:
|
||||
|
||||
```bash
|
||||
# Find duplicate code blocks
|
||||
npx jsinspect <scope> \
|
||||
--threshold <threshold> \
|
||||
--min-instances 2 \
|
||||
--ignore "node_modules|dist|build|test" \
|
||||
--reporter json
|
||||
|
||||
# Or use script
|
||||
./.scripts/detect-duplication.sh <scope> <threshold>
|
||||
```
|
||||
|
||||
### 2. Analyze Duplication Patterns
|
||||
|
||||
Categorize duplicates:
|
||||
- **Exact duplicates** (100% match): Copy-paste code
|
||||
- **Near duplicates** (80-99% match): Similar with minor differences
|
||||
- **Structural duplicates** (50-79% match): Same pattern, different data
|
||||
|
||||
### 3. Choose Consolidation Strategy
|
||||
|
||||
Based on duplication type:
|
||||
|
||||
## Strategy Examples
|
||||
|
||||
### Strategy 1: Extract Function
|
||||
|
||||
**When to use**:
|
||||
- Exact or near duplicate code blocks
|
||||
- Pure logic with clear inputs/outputs
|
||||
- Used in 2+ places
|
||||
- No complex state dependencies
|
||||
|
||||
**Before** (Duplicated validation):
|
||||
```typescript
|
||||
// UserForm.tsx
|
||||
function validateForm() {
|
||||
const errors: Errors = {};
|
||||
|
||||
if (!formData.email) {
|
||||
errors.email = "Email is required";
|
||||
} else {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(formData.email)) {
|
||||
errors.email = "Invalid email format";
|
||||
}
|
||||
}
|
||||
|
||||
if (!formData.password) {
|
||||
errors.password = "Password is required";
|
||||
} else if (formData.password.length < 8) {
|
||||
errors.password = "Password must be at least 8 characters";
|
||||
} else {
|
||||
const hasUpper = /[A-Z]/.test(formData.password);
|
||||
const hasLower = /[a-z]/.test(formData.password);
|
||||
const hasNumber = /[0-9]/.test(formData.password);
|
||||
if (!hasUpper || !hasLower || !hasNumber) {
|
||||
errors.password = "Password must contain uppercase, lowercase, and number";
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
// ProfileForm.tsx - Same validation copied
|
||||
function validateForm() {
|
||||
const errors: Errors = {};
|
||||
|
||||
if (!formData.email) {
|
||||
errors.email = "Email is required";
|
||||
} else {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(formData.email)) {
|
||||
errors.email = "Invalid email format";
|
||||
}
|
||||
}
|
||||
|
||||
if (!formData.password) {
|
||||
errors.password = "Password is required";
|
||||
} else if (formData.password.length < 8) {
|
||||
errors.password = "Password must be at least 8 characters";
|
||||
} else {
|
||||
const hasUpper = /[A-Z]/.test(formData.password);
|
||||
const hasLower = /[a-z]/.test(formData.password);
|
||||
const hasNumber = /[0-9]/.test(formData.password);
|
||||
if (!hasUpper || !hasLower || !hasNumber) {
|
||||
errors.password = "Password must contain uppercase, lowercase, and number";
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
// RegistrationForm.tsx - Same validation copied again
|
||||
// SettingsForm.tsx - Same validation copied again
|
||||
// AdminForm.tsx - Same validation copied again
|
||||
```
|
||||
|
||||
**After** (Extracted to shared utilities):
|
||||
```typescript
|
||||
// utils/validation.ts
|
||||
export interface ValidationResult {
|
||||
valid: boolean;
|
||||
errors: Record<string, string>;
|
||||
}
|
||||
|
||||
export function validateEmail(email: string): string | null {
|
||||
if (!email) {
|
||||
return "Email is required";
|
||||
}
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
return "Invalid email format";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function validatePassword(password: string): string | null {
|
||||
if (!password) {
|
||||
return "Password is required";
|
||||
}
|
||||
|
||||
if (password.length < 8) {
|
||||
return "Password must be at least 8 characters";
|
||||
}
|
||||
|
||||
const hasUpper = /[A-Z]/.test(password);
|
||||
const hasLower = /[a-z]/.test(password);
|
||||
const hasNumber = /[0-9]/.test(password);
|
||||
|
||||
if (!hasUpper || !hasLower || !hasNumber) {
|
||||
return "Password must contain uppercase, lowercase, and number";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function validateUserForm(data: {
|
||||
email: string;
|
||||
password: string;
|
||||
}): ValidationResult {
|
||||
const errors: Record<string, string> = {};
|
||||
|
||||
const emailError = validateEmail(data.email);
|
||||
if (emailError) errors.email = emailError;
|
||||
|
||||
const passwordError = validatePassword(data.password);
|
||||
if (passwordError) errors.password = passwordError;
|
||||
|
||||
return {
|
||||
valid: Object.keys(errors).length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
// All forms now use shared validation
|
||||
// UserForm.tsx
|
||||
import { validateUserForm } from '@/utils/validation';
|
||||
|
||||
function validateForm() {
|
||||
return validateUserForm(formData);
|
||||
}
|
||||
|
||||
// ProfileForm.tsx
|
||||
import { validateUserForm } from '@/utils/validation';
|
||||
|
||||
function validateForm() {
|
||||
return validateUserForm(formData);
|
||||
}
|
||||
|
||||
// Same for RegistrationForm, SettingsForm, AdminForm
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- DRY: 5 duplicates → 1 implementation
|
||||
- Lines saved: ~200 lines (40 lines × 5 copies)
|
||||
- Single source of truth: Fix bugs once
|
||||
- Testability: Test validation independently
|
||||
- Consistency: All forms use same validation
|
||||
|
||||
---
|
||||
|
||||
### Strategy 2: Extract Class
|
||||
|
||||
**When to use**:
|
||||
- Duplicated logic with state
|
||||
- Related methods copied together
|
||||
- Object-oriented patterns
|
||||
- Multiple functions working on same data
|
||||
|
||||
**Before** (Duplicated error handling across services):
|
||||
```typescript
|
||||
// UserService.ts
|
||||
class UserService {
|
||||
async createUser(data: any) {
|
||||
try {
|
||||
const user = await this.db.users.create(data);
|
||||
return { success: true, data: user };
|
||||
} catch (error) {
|
||||
if (error.code === '23505') {
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'DUPLICATE_EMAIL', message: 'Email already exists' }
|
||||
};
|
||||
}
|
||||
if (error.code === '23503') {
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'INVALID_REFERENCE', message: 'Invalid reference' }
|
||||
};
|
||||
}
|
||||
console.error('User creation error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'INTERNAL_ERROR', message: 'Internal server error' }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PostService.ts - Same error handling copied
|
||||
class PostService {
|
||||
async createPost(data: any) {
|
||||
try {
|
||||
const post = await this.db.posts.create(data);
|
||||
return { success: true, data: post };
|
||||
} catch (error) {
|
||||
if (error.code === '23505') {
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'DUPLICATE_TITLE', message: 'Title already exists' }
|
||||
};
|
||||
}
|
||||
if (error.code === '23503') {
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'INVALID_REFERENCE', message: 'Invalid reference' }
|
||||
};
|
||||
}
|
||||
console.error('Post creation error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'INTERNAL_ERROR', message: 'Internal server error' }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CommentService.ts - Same pattern copied
|
||||
// OrderService.ts - Same pattern copied
|
||||
```
|
||||
|
||||
**After** (Extracted error handler class):
|
||||
```typescript
|
||||
// errors/DatabaseErrorHandler.ts
|
||||
export interface ErrorResponse {
|
||||
code: string;
|
||||
message: string;
|
||||
details?: any;
|
||||
}
|
||||
|
||||
export interface Result<T> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
error?: ErrorResponse;
|
||||
}
|
||||
|
||||
export class DatabaseErrorHandler {
|
||||
private errorMappings: Map<string, (error: any) => ErrorResponse> = new Map([
|
||||
['23505', this.handleDuplicateKey],
|
||||
['23503', this.handleForeignKeyViolation],
|
||||
['23502', this.handleNotNullViolation],
|
||||
['23514', this.handleCheckViolation]
|
||||
]);
|
||||
|
||||
handleError(error: any, context: string = 'Database'): ErrorResponse {
|
||||
const handler = this.errorMappings.get(error.code);
|
||||
if (handler) {
|
||||
return handler.call(this, error);
|
||||
}
|
||||
|
||||
console.error(`${context} error:`, error);
|
||||
return {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: 'Internal server error'
|
||||
};
|
||||
}
|
||||
|
||||
private handleDuplicateKey(error: any): ErrorResponse {
|
||||
return {
|
||||
code: 'DUPLICATE_KEY',
|
||||
message: 'Resource with this identifier already exists',
|
||||
details: error.detail
|
||||
};
|
||||
}
|
||||
|
||||
private handleForeignKeyViolation(error: any): ErrorResponse {
|
||||
return {
|
||||
code: 'INVALID_REFERENCE',
|
||||
message: 'Referenced resource does not exist',
|
||||
details: error.detail
|
||||
};
|
||||
}
|
||||
|
||||
private handleNotNullViolation(error: any): ErrorResponse {
|
||||
return {
|
||||
code: 'MISSING_REQUIRED_FIELD',
|
||||
message: 'Required field is missing',
|
||||
details: error.column
|
||||
};
|
||||
}
|
||||
|
||||
private handleCheckViolation(error: any): ErrorResponse {
|
||||
return {
|
||||
code: 'CONSTRAINT_VIOLATION',
|
||||
message: 'Data violates constraint',
|
||||
details: error.constraint
|
||||
};
|
||||
}
|
||||
|
||||
async wrapOperation<T>(
|
||||
operation: () => Promise<T>,
|
||||
context?: string
|
||||
): Promise<Result<T>> {
|
||||
try {
|
||||
const data = await operation();
|
||||
return { success: true, data };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: this.handleError(error, context)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Services now use shared error handler
|
||||
// UserService.ts
|
||||
class UserService {
|
||||
constructor(
|
||||
private db: Database,
|
||||
private errorHandler: DatabaseErrorHandler
|
||||
) {}
|
||||
|
||||
async createUser(data: CreateUserInput): Promise<Result<User>> {
|
||||
return this.errorHandler.wrapOperation(
|
||||
() => this.db.users.create(data),
|
||||
'User creation'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// PostService.ts
|
||||
class PostService {
|
||||
constructor(
|
||||
private db: Database,
|
||||
private errorHandler: DatabaseErrorHandler
|
||||
) {}
|
||||
|
||||
async createPost(data: CreatePostInput): Promise<Result<Post>> {
|
||||
return this.errorHandler.wrapOperation(
|
||||
() => this.db.posts.create(data),
|
||||
'Post creation'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// All services now use shared error handling
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Centralized error handling
|
||||
- Consistent error responses
|
||||
- Easier to extend (add new error types)
|
||||
- Better logging and monitoring
|
||||
- DRY: One error handler for all services
|
||||
|
||||
---
|
||||
|
||||
### Strategy 3: Parameterize
|
||||
|
||||
**When to use**:
|
||||
- Functions differ only in values/configuration
|
||||
- Similar structure, different data
|
||||
- Can be unified with parameters
|
||||
- Limited number of variations
|
||||
|
||||
**Before** (Similar functions with hard-coded values):
|
||||
```typescript
|
||||
// formatters.ts
|
||||
function formatUserName(user: User): string {
|
||||
return `${user.firstName} ${user.lastName}`;
|
||||
}
|
||||
|
||||
function formatAdminName(admin: Admin): string {
|
||||
return `${admin.firstName} ${admin.lastName} (Admin)`;
|
||||
}
|
||||
|
||||
function formatModeratorName(moderator: Moderator): string {
|
||||
return `${moderator.firstName} ${moderator.lastName} (Moderator)`;
|
||||
}
|
||||
|
||||
function formatGuestName(guest: Guest): string {
|
||||
return `Guest: ${guest.firstName} ${guest.lastName}`;
|
||||
}
|
||||
|
||||
// Similar for emails
|
||||
function formatUserEmail(user: User): string {
|
||||
return user.email.toLowerCase();
|
||||
}
|
||||
|
||||
function formatAdminEmail(admin: Admin): string {
|
||||
return `admin-${admin.email.toLowerCase()}`;
|
||||
}
|
||||
|
||||
function formatModeratorEmail(moderator: Moderator): string {
|
||||
return `mod-${moderator.email.toLowerCase()}`;
|
||||
}
|
||||
```
|
||||
|
||||
**After** (Parameterized):
|
||||
```typescript
|
||||
// formatters.ts
|
||||
interface Person {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
type NameFormat = {
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
template?: (person: Person) => string;
|
||||
};
|
||||
|
||||
function formatName(person: Person, format: NameFormat = {}): string {
|
||||
if (format.template) {
|
||||
return format.template(person);
|
||||
}
|
||||
|
||||
const base = `${person.firstName} ${person.lastName}`;
|
||||
const prefix = format.prefix ? `${format.prefix}: ` : '';
|
||||
const suffix = format.suffix ? ` (${format.suffix})` : '';
|
||||
|
||||
return `${prefix}${base}${suffix}`;
|
||||
}
|
||||
|
||||
type EmailFormat = {
|
||||
prefix?: string;
|
||||
domain?: string;
|
||||
transform?: (email: string) => string;
|
||||
};
|
||||
|
||||
function formatEmail(person: Person, format: EmailFormat = {}): string {
|
||||
let email = person.email.toLowerCase();
|
||||
|
||||
if (format.transform) {
|
||||
email = format.transform(email);
|
||||
}
|
||||
|
||||
if (format.prefix) {
|
||||
const [local, domain] = email.split('@');
|
||||
email = `${format.prefix}-${local}@${domain}`;
|
||||
}
|
||||
|
||||
if (format.domain) {
|
||||
const [local] = email.split('@');
|
||||
email = `${local}@${format.domain}`;
|
||||
}
|
||||
|
||||
return email;
|
||||
}
|
||||
|
||||
// Usage - Much more flexible
|
||||
const userName = formatName(user);
|
||||
const adminName = formatName(admin, { suffix: 'Admin' });
|
||||
const modName = formatName(moderator, { suffix: 'Moderator' });
|
||||
const guestName = formatName(guest, { prefix: 'Guest' });
|
||||
|
||||
const userEmail = formatEmail(user);
|
||||
const adminEmail = formatEmail(admin, { prefix: 'admin' });
|
||||
const modEmail = formatEmail(moderator, { prefix: 'mod' });
|
||||
|
||||
// Easy to add new formats without new functions
|
||||
const vipName = formatName(vip, { suffix: 'VIP', prefix: 'Special' });
|
||||
const customEmail = formatEmail(user, {
|
||||
transform: (email) => email.toUpperCase()
|
||||
});
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- 7 functions → 2 parameterized functions
|
||||
- More flexible: Infinite combinations possible
|
||||
- Easier to maintain: One function to update
|
||||
- Easier to test: Test parameters instead of functions
|
||||
- Extensible: Add new formats without new code
|
||||
|
||||
---
|
||||
|
||||
### Strategy 4: Template/Component Pattern
|
||||
|
||||
**When to use**:
|
||||
- Repeated UI patterns
|
||||
- Similar component structures
|
||||
- Variations in content, not structure
|
||||
- React/Vue component duplication
|
||||
|
||||
**Before** (Duplicated card components):
|
||||
```typescript
|
||||
// UserCard.tsx
|
||||
function UserCard({ user }: { user: User }) {
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<img src={user.avatar} alt={user.name} />
|
||||
<h3>{user.name}</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<p>{user.email}</p>
|
||||
<p>{user.role}</p>
|
||||
</div>
|
||||
<div className="card-footer">
|
||||
<button onClick={() => viewUser(user.id)}>View</button>
|
||||
<button onClick={() => editUser(user.id)}>Edit</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// PostCard.tsx - Same structure copied
|
||||
function PostCard({ post }: { post: Post }) {
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<img src={post.thumbnail} alt={post.title} />
|
||||
<h3>{post.title}</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<p>{post.excerpt}</p>
|
||||
<p>By {post.author}</p>
|
||||
</div>
|
||||
<div className="card-footer">
|
||||
<button onClick={() => viewPost(post.id)}>View</button>
|
||||
<button onClick={() => editPost(post.id)}>Edit</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ProductCard.tsx - Same structure copied
|
||||
// CommentCard.tsx - Same structure copied
|
||||
```
|
||||
|
||||
**After** (Generic Card template):
|
||||
```typescript
|
||||
// components/Card.tsx
|
||||
interface CardProps {
|
||||
header: {
|
||||
image: string;
|
||||
title: string;
|
||||
imageAlt?: string;
|
||||
};
|
||||
body: React.ReactNode;
|
||||
footer?: {
|
||||
actions: Array<{
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
variant?: 'primary' | 'secondary';
|
||||
}>;
|
||||
};
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function Card({ header, body, footer, className = '' }: CardProps) {
|
||||
return (
|
||||
<div className={`card ${className}`}>
|
||||
<div className="card-header">
|
||||
<img
|
||||
src={header.image}
|
||||
alt={header.imageAlt || header.title}
|
||||
className="card-image"
|
||||
/>
|
||||
<h3 className="card-title">{header.title}</h3>
|
||||
</div>
|
||||
<div className="card-body">{body}</div>
|
||||
{footer && (
|
||||
<div className="card-footer">
|
||||
{footer.actions.map((action, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={action.onClick}
|
||||
className={`btn btn-${action.variant || 'primary'}`}
|
||||
>
|
||||
{action.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Usage - Much cleaner
|
||||
// UserCard.tsx
|
||||
function UserCard({ user }: { user: User }) {
|
||||
return (
|
||||
<Card
|
||||
header={{
|
||||
image: user.avatar,
|
||||
title: user.name,
|
||||
imageAlt: `${user.name}'s avatar`
|
||||
}}
|
||||
body={
|
||||
<>
|
||||
<p>{user.email}</p>
|
||||
<p className="user-role">{user.role}</p>
|
||||
</>
|
||||
}
|
||||
footer={{
|
||||
actions: [
|
||||
{ label: 'View', onClick: () => viewUser(user.id) },
|
||||
{ label: 'Edit', onClick: () => editUser(user.id), variant: 'secondary' }
|
||||
]
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// PostCard.tsx
|
||||
function PostCard({ post }: { post: Post }) {
|
||||
return (
|
||||
<Card
|
||||
header={{
|
||||
image: post.thumbnail,
|
||||
title: post.title
|
||||
}}
|
||||
body={
|
||||
<>
|
||||
<p>{post.excerpt}</p>
|
||||
<p className="post-author">By {post.author}</p>
|
||||
</>
|
||||
}
|
||||
footer={{
|
||||
actions: [
|
||||
{ label: 'Read More', onClick: () => viewPost(post.id) },
|
||||
{ label: 'Edit', onClick: () => editPost(post.id), variant: 'secondary' }
|
||||
]
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Reusable Card component
|
||||
- Consistent UI across cards
|
||||
- Easy to change card structure globally
|
||||
- Less code duplication
|
||||
- Compose with different content
|
||||
|
||||
---
|
||||
|
||||
## Measurement
|
||||
|
||||
Calculate duplication savings:
|
||||
|
||||
```bash
|
||||
# Before
|
||||
Total lines: 10,000
|
||||
Duplicate lines: 800 (8%)
|
||||
|
||||
# After
|
||||
Total lines: 9,200
|
||||
Duplicate lines: 100 (1.1%)
|
||||
|
||||
# Savings
|
||||
Lines removed: 800
|
||||
Duplication reduced: 8% → 1.1% (87.5% improvement)
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
```markdown
|
||||
# Code Duplication Elimination Report
|
||||
|
||||
## Analysis
|
||||
|
||||
**Scope**: <path>
|
||||
**Threshold**: <percentage>%
|
||||
|
||||
**Duplicates Found**:
|
||||
- Exact duplicates: <count> instances
|
||||
- Near duplicates: <count> instances
|
||||
- Total duplicate lines: <count> / <total> (<percentage>%)
|
||||
|
||||
## Duplication Examples
|
||||
|
||||
### Duplicate 1: <description>
|
||||
|
||||
**Instances**: <count> copies
|
||||
|
||||
**Locations**:
|
||||
1. <file1>:<line-range>
|
||||
2. <file2>:<line-range>
|
||||
3. <file3>:<line-range>
|
||||
|
||||
**Strategy**: <extract-function | extract-class | parameterize | template>
|
||||
|
||||
**Before** (<lines> lines duplicated):
|
||||
```typescript
|
||||
<duplicated-code>
|
||||
```
|
||||
|
||||
**After** (Single implementation):
|
||||
```typescript
|
||||
<consolidated-code>
|
||||
```
|
||||
|
||||
**Savings**: <lines> lines removed
|
||||
|
||||
## Total Impact
|
||||
|
||||
**Before**:
|
||||
- Total lines: <count>
|
||||
- Duplicate lines: <count> (<percentage>%)
|
||||
|
||||
**After**:
|
||||
- Total lines: <count>
|
||||
- Duplicate lines: <count> (<percentage>%)
|
||||
|
||||
**Improvement**:
|
||||
- Lines removed: <count>
|
||||
- Duplication reduced: <before>% → <after>% (<percentage>% improvement)
|
||||
- Maintainability: Significantly improved
|
||||
|
||||
## Files Changed
|
||||
|
||||
**Created**:
|
||||
- <new-shared-file-1>
|
||||
- <new-shared-file-2>
|
||||
|
||||
**Modified**:
|
||||
- <file-1>: Replaced with shared implementation
|
||||
- <file-2>: Replaced with shared implementation
|
||||
|
||||
## Testing
|
||||
|
||||
**Tests Updated**:
|
||||
- <test-file-1>: Updated to test shared code
|
||||
- <test-file-2>: Removed duplicate tests
|
||||
|
||||
**Coverage**:
|
||||
- Before: <percentage>%
|
||||
- After: <percentage>%
|
||||
|
||||
## Next Steps
|
||||
|
||||
**Remaining Duplication**:
|
||||
1. <duplicate-pattern-1>: <count> instances
|
||||
2. <duplicate-pattern-2>: <count> instances
|
||||
|
||||
**Recommendations**:
|
||||
- Continue eliminating duplicates
|
||||
- Set up automated duplication detection in CI/CD
|
||||
- Code review for new duplicates
|
||||
|
||||
---
|
||||
|
||||
**Duplication Eliminated**: Code is now DRY and maintainable.
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
**No duplicates found**:
|
||||
```
|
||||
Success: No significant code duplication found (threshold: <percentage>%)
|
||||
|
||||
**Duplication**: <percentage>% (Target: < 3%)
|
||||
|
||||
The codebase is already DRY. Great work!
|
||||
```
|
||||
|
||||
**Threshold too low**:
|
||||
```
|
||||
Warning: Threshold <percentage>% is very low. Found <count> potential duplicates.
|
||||
|
||||
Many may be false positives (similar structure but different purpose).
|
||||
|
||||
Recommendation: Use threshold 80-90% for meaningful duplicates.
|
||||
```
|
||||
1229
commands/refactor/extract.md
Normal file
1229
commands/refactor/extract.md
Normal file
File diff suppressed because it is too large
Load Diff
962
commands/refactor/modernize.md
Normal file
962
commands/refactor/modernize.md
Normal file
@@ -0,0 +1,962 @@
|
||||
# Legacy Code Modernization Operation
|
||||
|
||||
Update legacy code patterns to modern JavaScript/TypeScript standards and best practices.
|
||||
|
||||
## Parameters
|
||||
|
||||
**Received from $ARGUMENTS**: All arguments after "modernize"
|
||||
|
||||
**Expected format**:
|
||||
```
|
||||
scope:"<path>" targets:"<target1,target2>" [compatibility:"<version>"]
|
||||
```
|
||||
|
||||
**Parameter definitions**:
|
||||
- `scope` (REQUIRED): Path to modernize (e.g., "src/legacy/", "utils/old-helpers.js")
|
||||
- `targets` (REQUIRED): Comma-separated modernization targets
|
||||
- `callbacks-to-async` - Convert callbacks to async/await
|
||||
- `var-to-const` - Replace var with const/let
|
||||
- `prototypes-to-classes` - Convert prototypes to ES6 classes
|
||||
- `commonjs-to-esm` - Convert CommonJS to ES modules
|
||||
- `jquery-to-vanilla` - Replace jQuery with vanilla JS
|
||||
- `classes-to-hooks` - Convert React class components to hooks
|
||||
- `legacy-api` - Update deprecated API usage
|
||||
- `compatibility` (OPTIONAL): Target environment (e.g., "node14+", "es2020", "modern-browsers")
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Analyze Legacy Patterns
|
||||
|
||||
Identify legacy code to modernize:
|
||||
|
||||
```bash
|
||||
# Find var usage
|
||||
grep -r "var " <scope> --include="*.js" --include="*.ts"
|
||||
|
||||
# Find callback patterns
|
||||
grep -r "function.*callback" <scope>
|
||||
|
||||
# Find prototype usage
|
||||
grep -r ".prototype" <scope>
|
||||
|
||||
# Find require() usage
|
||||
grep -r "require\(" <scope>
|
||||
|
||||
# Find jQuery usage
|
||||
grep -r "\$\(" <scope>
|
||||
```
|
||||
|
||||
### 2. Target-Specific Modernization
|
||||
|
||||
## Modernization Examples
|
||||
|
||||
### Target 1: Callbacks to Async/Await
|
||||
|
||||
**When to modernize**:
|
||||
- Callback hell (deeply nested callbacks)
|
||||
- Error handling is scattered
|
||||
- Readability suffers
|
||||
- Modern runtime supports async/await
|
||||
|
||||
**Before** (Callback hell):
|
||||
```javascript
|
||||
// database.js
|
||||
function getUser(userId, callback) {
|
||||
db.query('SELECT * FROM users WHERE id = ?', [userId], function(err, user) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
db.query('SELECT * FROM posts WHERE author_id = ?', [userId], function(err, posts) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
db.query('SELECT * FROM comments WHERE user_id = ?', [userId], function(err, comments) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback(null, {
|
||||
user: user,
|
||||
posts: posts,
|
||||
comments: comments
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Usage
|
||||
getUser(123, function(err, data) {
|
||||
if (err) {
|
||||
console.error('Error:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('User:', data.user);
|
||||
console.log('Posts:', data.posts);
|
||||
console.log('Comments:', data.comments);
|
||||
});
|
||||
```
|
||||
|
||||
**After** (Async/await - Clean and readable):
|
||||
```typescript
|
||||
// database.ts
|
||||
import { query } from './db';
|
||||
|
||||
interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface Post {
|
||||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
authorId: number;
|
||||
}
|
||||
|
||||
interface Comment {
|
||||
id: number;
|
||||
content: string;
|
||||
userId: number;
|
||||
}
|
||||
|
||||
interface UserWithContent {
|
||||
user: User;
|
||||
posts: Post[];
|
||||
comments: Comment[];
|
||||
}
|
||||
|
||||
async function getUser(userId: number): Promise<UserWithContent> {
|
||||
// Parallel execution for better performance
|
||||
const [user, posts, comments] = await Promise.all([
|
||||
query<User>('SELECT * FROM users WHERE id = ?', [userId]),
|
||||
query<Post[]>('SELECT * FROM posts WHERE author_id = ?', [userId]),
|
||||
query<Comment[]>('SELECT * FROM comments WHERE user_id = ?', [userId])
|
||||
]);
|
||||
|
||||
return { user, posts, comments };
|
||||
}
|
||||
|
||||
// Usage - Much cleaner
|
||||
try {
|
||||
const data = await getUser(123);
|
||||
console.log('User:', data.user);
|
||||
console.log('Posts:', data.posts);
|
||||
console.log('Comments:', data.comments);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
```
|
||||
|
||||
**More callback conversions**:
|
||||
|
||||
```javascript
|
||||
// Before: fs callbacks
|
||||
const fs = require('fs');
|
||||
|
||||
fs.readFile('config.json', 'utf8', function(err, data) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const config = JSON.parse(data);
|
||||
fs.writeFile('output.json', JSON.stringify(config), function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
console.log('Done');
|
||||
});
|
||||
});
|
||||
|
||||
// After: fs promises
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
|
||||
try {
|
||||
const data = await readFile('config.json', 'utf8');
|
||||
const config = JSON.parse(data);
|
||||
await writeFile('output.json', JSON.stringify(config));
|
||||
console.log('Done');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- No callback hell: Flat, linear code
|
||||
- Better error handling: Single try/catch
|
||||
- Parallel execution: Promise.all() for performance
|
||||
- Type safety: Full TypeScript support
|
||||
- Readability: Much easier to understand
|
||||
|
||||
---
|
||||
|
||||
### Target 2: var to const/let
|
||||
|
||||
**When to modernize**:
|
||||
- Using old var declarations
|
||||
- Want block scoping
|
||||
- Prevent accidental reassignment
|
||||
- Modern ES6+ environment
|
||||
|
||||
**Before** (var - function scoped, hoisted):
|
||||
```javascript
|
||||
function processOrders() {
|
||||
var total = 0;
|
||||
var count = 0;
|
||||
|
||||
for (var i = 0; i < orders.length; i++) {
|
||||
var order = orders[i];
|
||||
var price = order.price;
|
||||
var quantity = order.quantity;
|
||||
|
||||
total += price * quantity;
|
||||
count++;
|
||||
}
|
||||
|
||||
// i is still accessible here (function scoped!)
|
||||
console.log(i); // orders.length
|
||||
|
||||
return { total: total, count: count };
|
||||
}
|
||||
|
||||
// Hoisting issues
|
||||
function example() {
|
||||
console.log(x); // undefined (not error)
|
||||
var x = 10;
|
||||
}
|
||||
|
||||
// Loop issues
|
||||
for (var i = 0; i < 3; i++) {
|
||||
setTimeout(function() {
|
||||
console.log(i); // Always prints 3!
|
||||
}, 100);
|
||||
}
|
||||
```
|
||||
|
||||
**After** (const/let - block scoped, not hoisted):
|
||||
```typescript
|
||||
function processOrders(): { total: number; count: number } {
|
||||
let total = 0;
|
||||
let count = 0;
|
||||
|
||||
for (let i = 0; i < orders.length; i++) {
|
||||
const order = orders[i];
|
||||
const price = order.price;
|
||||
const quantity = order.quantity;
|
||||
|
||||
total += price * quantity;
|
||||
count++;
|
||||
}
|
||||
|
||||
// i is NOT accessible here (block scoped)
|
||||
// console.log(i); // Error: i is not defined
|
||||
|
||||
return { total, count };
|
||||
}
|
||||
|
||||
// No hoisting issues
|
||||
function example() {
|
||||
console.log(x); // Error: Cannot access 'x' before initialization
|
||||
const x = 10;
|
||||
}
|
||||
|
||||
// Loop fixed
|
||||
for (let i = 0; i < 3; i++) {
|
||||
setTimeout(() => {
|
||||
console.log(i); // Prints 0, 1, 2 correctly
|
||||
}, 100);
|
||||
}
|
||||
```
|
||||
|
||||
**Guidelines**:
|
||||
- Use `const` by default (immutable binding)
|
||||
- Use `let` when reassignment needed
|
||||
- Never use `var` in modern code
|
||||
- Block scope prevents many bugs
|
||||
|
||||
---
|
||||
|
||||
### Target 3: Prototypes to ES6 Classes
|
||||
|
||||
**When to modernize**:
|
||||
- Using prototype-based inheritance
|
||||
- Want cleaner OOP syntax
|
||||
- Better IDE support needed
|
||||
- Modern JavaScript environment
|
||||
|
||||
**Before** (Prototype pattern):
|
||||
```javascript
|
||||
// Animal.js
|
||||
function Animal(name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
Animal.prototype.speak = function() {
|
||||
console.log(this.name + ' makes a sound');
|
||||
};
|
||||
|
||||
Animal.prototype.getInfo = function() {
|
||||
return this.name + ' is ' + this.age + ' years old';
|
||||
};
|
||||
|
||||
// Dog.js
|
||||
function Dog(name, age, breed) {
|
||||
Animal.call(this, name, age);
|
||||
this.breed = breed;
|
||||
}
|
||||
|
||||
Dog.prototype = Object.create(Animal.prototype);
|
||||
Dog.prototype.constructor = Dog;
|
||||
|
||||
Dog.prototype.speak = function() {
|
||||
console.log(this.name + ' barks');
|
||||
};
|
||||
|
||||
Dog.prototype.fetch = function() {
|
||||
console.log(this.name + ' fetches the ball');
|
||||
};
|
||||
|
||||
// Usage
|
||||
var dog = new Dog('Rex', 3, 'Labrador');
|
||||
dog.speak(); // Rex barks
|
||||
console.log(dog.getInfo()); // Rex is 3 years old
|
||||
```
|
||||
|
||||
**After** (ES6 Classes):
|
||||
```typescript
|
||||
// Animal.ts
|
||||
export class Animal {
|
||||
constructor(
|
||||
protected name: string,
|
||||
protected age: number
|
||||
) {}
|
||||
|
||||
speak(): void {
|
||||
console.log(`${this.name} makes a sound`);
|
||||
}
|
||||
|
||||
getInfo(): string {
|
||||
return `${this.name} is ${this.age} years old`;
|
||||
}
|
||||
}
|
||||
|
||||
// Dog.ts
|
||||
export class Dog extends Animal {
|
||||
constructor(
|
||||
name: string,
|
||||
age: number,
|
||||
private breed: string
|
||||
) {
|
||||
super(name, age);
|
||||
}
|
||||
|
||||
speak(): void {
|
||||
console.log(`${this.name} barks`);
|
||||
}
|
||||
|
||||
fetch(): void {
|
||||
console.log(`${this.name} fetches the ball`);
|
||||
}
|
||||
|
||||
getBreed(): string {
|
||||
return this.breed;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const dog = new Dog('Rex', 3, 'Labrador');
|
||||
dog.speak(); // Rex barks
|
||||
console.log(dog.getInfo()); // Rex is 3 years old
|
||||
console.log(dog.getBreed()); // Labrador
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Cleaner syntax: More readable
|
||||
- Better inheritance: extends keyword
|
||||
- Access modifiers: public, private, protected
|
||||
- Type safety: Full TypeScript support
|
||||
- IDE support: Better autocomplete
|
||||
|
||||
---
|
||||
|
||||
### Target 4: CommonJS to ES Modules
|
||||
|
||||
**When to modernize**:
|
||||
- Using require() and module.exports
|
||||
- Want tree-shaking benefits
|
||||
- Modern bundler support
|
||||
- Better static analysis
|
||||
|
||||
**Before** (CommonJS):
|
||||
```javascript
|
||||
// utils.js
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
|
||||
function generateId() {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
function readConfig() {
|
||||
return JSON.parse(fs.readFileSync('config.json', 'utf8'));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateId,
|
||||
readConfig
|
||||
};
|
||||
|
||||
// user-service.js
|
||||
const { generateId } = require('./utils');
|
||||
const db = require('./database');
|
||||
|
||||
class UserService {
|
||||
async createUser(data) {
|
||||
const id = generateId();
|
||||
return db.users.create({ ...data, id });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserService;
|
||||
|
||||
// index.js
|
||||
const express = require('express');
|
||||
const UserService = require('./user-service');
|
||||
|
||||
const app = express();
|
||||
const userService = new UserService();
|
||||
|
||||
app.post('/users', async (req, res) => {
|
||||
const user = await userService.createUser(req.body);
|
||||
res.json(user);
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
```
|
||||
|
||||
**After** (ES Modules):
|
||||
```typescript
|
||||
// utils.ts
|
||||
import { randomUUID } from 'crypto';
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
export function generateId(): string {
|
||||
return randomUUID();
|
||||
}
|
||||
|
||||
export function readConfig(): Config {
|
||||
return JSON.parse(readFileSync('config.json', 'utf8'));
|
||||
}
|
||||
|
||||
// user-service.ts
|
||||
import { generateId } from './utils.js';
|
||||
import { db } from './database.js';
|
||||
|
||||
export class UserService {
|
||||
async createUser(data: CreateUserInput): Promise<User> {
|
||||
const id = generateId();
|
||||
return db.users.create({ ...data, id });
|
||||
}
|
||||
}
|
||||
|
||||
// index.ts
|
||||
import express from 'express';
|
||||
import { UserService } from './user-service.js';
|
||||
|
||||
const app = express();
|
||||
const userService = new UserService();
|
||||
|
||||
app.post('/users', async (req, res) => {
|
||||
const user = await userService.createUser(req.body);
|
||||
res.json(user);
|
||||
});
|
||||
|
||||
export default app;
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Tree-shaking: Remove unused exports
|
||||
- Static imports: Better bundler optimization
|
||||
- Named exports: More explicit imports
|
||||
- Top-level await: Possible in ES modules
|
||||
- Standard: Modern JavaScript standard
|
||||
|
||||
---
|
||||
|
||||
### Target 5: jQuery to Vanilla JavaScript
|
||||
|
||||
**When to modernize**:
|
||||
- Remove jQuery dependency
|
||||
- Reduce bundle size
|
||||
- Modern browsers support native APIs
|
||||
- Better performance
|
||||
|
||||
**Before** (jQuery):
|
||||
```javascript
|
||||
// app.js - Heavy jQuery usage
|
||||
$(document).ready(function() {
|
||||
// DOM selection
|
||||
var $button = $('#submit-button');
|
||||
var $form = $('.user-form');
|
||||
var $inputs = $form.find('input');
|
||||
|
||||
// Event handling
|
||||
$button.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Get form data
|
||||
var formData = {};
|
||||
$inputs.each(function() {
|
||||
var $input = $(this);
|
||||
formData[$input.attr('name')] = $input.val();
|
||||
});
|
||||
|
||||
// AJAX request
|
||||
$.ajax({
|
||||
url: '/api/users',
|
||||
method: 'POST',
|
||||
data: JSON.stringify(formData),
|
||||
contentType: 'application/json',
|
||||
success: function(response) {
|
||||
// DOM manipulation
|
||||
var $message = $('<div>')
|
||||
.addClass('success-message')
|
||||
.text('User created successfully!');
|
||||
|
||||
$form.after($message);
|
||||
$message.fadeIn().delay(3000).fadeOut();
|
||||
|
||||
// Clear form
|
||||
$inputs.val('');
|
||||
},
|
||||
error: function(xhr) {
|
||||
$('.error-message').text(xhr.responseText).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Show/hide password
|
||||
$('.toggle-password').on('click', function() {
|
||||
var $input = $(this).siblings('input');
|
||||
var type = $input.attr('type');
|
||||
$input.attr('type', type === 'password' ? 'text' : 'password');
|
||||
$(this).toggleClass('active');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**After** (Vanilla JavaScript):
|
||||
```typescript
|
||||
// app.ts - Modern vanilla JavaScript
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// DOM selection - Native APIs
|
||||
const button = document.querySelector<HTMLButtonElement>('#submit-button');
|
||||
const form = document.querySelector<HTMLFormElement>('.user-form');
|
||||
const inputs = form?.querySelectorAll<HTMLInputElement>('input');
|
||||
|
||||
if (!button || !form || !inputs) return;
|
||||
|
||||
// Event handling - addEventListener
|
||||
button.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Get form data - FormData API
|
||||
const formData = new FormData(form);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
|
||||
try {
|
||||
// Fetch API instead of $.ajax
|
||||
const response = await fetch('/api/users', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
|
||||
const user = await response.json();
|
||||
|
||||
// DOM manipulation - Native APIs
|
||||
const message = document.createElement('div');
|
||||
message.className = 'success-message';
|
||||
message.textContent = 'User created successfully!';
|
||||
|
||||
form.insertAdjacentElement('afterend', message);
|
||||
|
||||
// CSS animations instead of jQuery animations
|
||||
message.style.opacity = '0';
|
||||
message.style.display = 'block';
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
message.style.transition = 'opacity 0.3s';
|
||||
message.style.opacity = '1';
|
||||
|
||||
setTimeout(() => {
|
||||
message.style.opacity = '0';
|
||||
setTimeout(() => message.remove(), 300);
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
// Clear form
|
||||
form.reset();
|
||||
|
||||
} catch (error) {
|
||||
const errorMessage = document.querySelector('.error-message');
|
||||
if (errorMessage) {
|
||||
errorMessage.textContent = error.message;
|
||||
errorMessage.style.display = 'block';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Show/hide password - Native APIs
|
||||
const toggleButtons = document.querySelectorAll<HTMLButtonElement>('.toggle-password');
|
||||
|
||||
toggleButtons.forEach(toggle => {
|
||||
toggle.addEventListener('click', () => {
|
||||
const input = toggle.previousElementSibling as HTMLInputElement;
|
||||
if (!input) return;
|
||||
|
||||
const type = input.type === 'password' ? 'text' : 'password';
|
||||
input.type = type;
|
||||
toggle.classList.toggle('active');
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Bundle size impact**:
|
||||
- Before: ~30KB (jQuery minified + gzipped)
|
||||
- After: ~0KB (native APIs)
|
||||
- **Savings**: 30KB, faster load time
|
||||
|
||||
**Improvements**:
|
||||
- No jQuery dependency
|
||||
- Modern native APIs
|
||||
- Better performance
|
||||
- TypeScript support
|
||||
- Smaller bundle size
|
||||
|
||||
---
|
||||
|
||||
### Target 6: React Class Components to Hooks
|
||||
|
||||
**When to modernize**:
|
||||
- Using class components
|
||||
- Want simpler code
|
||||
- Better functional composition
|
||||
- Modern React patterns
|
||||
|
||||
**Before** (Class component):
|
||||
```typescript
|
||||
// UserProfile.tsx
|
||||
import React, { Component } from 'react';
|
||||
|
||||
interface Props {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
user: User | null;
|
||||
loading: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
class UserProfile extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
user: null,
|
||||
loading: true,
|
||||
error: null
|
||||
};
|
||||
|
||||
this.handleRefresh = this.handleRefresh.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadUser();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
if (prevProps.userId !== this.props.userId) {
|
||||
this.loadUser();
|
||||
}
|
||||
}
|
||||
|
||||
async loadUser() {
|
||||
this.setState({ loading: true, error: null });
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/users/${this.props.userId}`);
|
||||
const user = await response.json();
|
||||
this.setState({ user, loading: false });
|
||||
} catch (error) {
|
||||
this.setState({ error, loading: false });
|
||||
}
|
||||
}
|
||||
|
||||
handleRefresh() {
|
||||
this.loadUser();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { user, loading, error } = this.state;
|
||||
|
||||
if (loading) return <div>Loading...</div>;
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
if (!user) return <div>User not found</div>;
|
||||
|
||||
return (
|
||||
<div className="user-profile">
|
||||
<h1>{user.name}</h1>
|
||||
<p>{user.email}</p>
|
||||
<button onClick={this.handleRefresh}>Refresh</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserProfile;
|
||||
```
|
||||
|
||||
**After** (Function component with hooks):
|
||||
```typescript
|
||||
// UserProfile.tsx
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
interface Props {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export function UserProfile({ userId }: Props) {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
// Extract to custom hook for reusability
|
||||
const loadUser = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/users/${userId}`);
|
||||
const userData = await response.json();
|
||||
setUser(userData);
|
||||
} catch (err) {
|
||||
setError(err as Error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Load user on mount and when userId changes
|
||||
useEffect(() => {
|
||||
loadUser();
|
||||
}, [userId]);
|
||||
|
||||
if (loading) return <div>Loading...</div>;
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
if (!user) return <div>User not found</div>;
|
||||
|
||||
return (
|
||||
<div className="user-profile">
|
||||
<h1>{user.name}</h1>
|
||||
<p>{user.email}</p>
|
||||
<button onClick={loadUser}>Refresh</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Even better with custom hook**:
|
||||
```typescript
|
||||
// hooks/useUser.ts
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export function useUser(userId: string) {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
const loadUser = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/users/${userId}`);
|
||||
const userData = await response.json();
|
||||
setUser(userData);
|
||||
} catch (err) {
|
||||
setError(err as Error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadUser();
|
||||
}, [userId]);
|
||||
|
||||
return { user, loading, error, refresh: loadUser };
|
||||
}
|
||||
|
||||
// UserProfile.tsx - Super clean now!
|
||||
export function UserProfile({ userId }: { userId: string }) {
|
||||
const { user, loading, error, refresh } = useUser(userId);
|
||||
|
||||
if (loading) return <div>Loading...</div>;
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
if (!user) return <div>User not found</div>;
|
||||
|
||||
return (
|
||||
<div className="user-profile">
|
||||
<h1>{user.name}</h1>
|
||||
<p>{user.email}</p>
|
||||
<button onClick={refresh}>Refresh</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Less boilerplate: No constructor, bind, etc.
|
||||
- Simpler: Function instead of class
|
||||
- Reusability: Custom hooks
|
||||
- Better composition: Hooks compose well
|
||||
- Modern: Current React best practice
|
||||
|
||||
---
|
||||
|
||||
## Output Format
|
||||
|
||||
```markdown
|
||||
# Legacy Code Modernization Report
|
||||
|
||||
## Targets Modernized: <target1, target2, ...>
|
||||
|
||||
**Scope**: <path>
|
||||
**Compatibility**: <version>
|
||||
|
||||
## Before Modernization
|
||||
|
||||
**Legacy Patterns Found**:
|
||||
- var declarations: <count>
|
||||
- Callback functions: <count>
|
||||
- Prototype usage: <count>
|
||||
- CommonJS modules: <count>
|
||||
- jQuery usage: <count>
|
||||
- Class components: <count>
|
||||
|
||||
**Issues**:
|
||||
- Callback hell in <count> files
|
||||
- Poor error handling
|
||||
- Large bundle size (jQuery dependency)
|
||||
- Outdated syntax
|
||||
|
||||
## Modernization Performed
|
||||
|
||||
### Target 1: <target-name>
|
||||
|
||||
**Files Modified**: <count>
|
||||
|
||||
**Before**:
|
||||
```javascript
|
||||
<legacy-code>
|
||||
```
|
||||
|
||||
**After**:
|
||||
```typescript
|
||||
<modern-code>
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- <improvement 1>
|
||||
- <improvement 2>
|
||||
|
||||
### Target 2: <target-name>
|
||||
|
||||
[Same structure...]
|
||||
|
||||
## After Modernization
|
||||
|
||||
**Modern Patterns**:
|
||||
- const/let: <count> conversions
|
||||
- Async/await: <count> conversions
|
||||
- ES6 classes: <count> conversions
|
||||
- ES modules: <count> conversions
|
||||
- Vanilla JS: <count> jQuery removals
|
||||
- Function components: <count> conversions
|
||||
|
||||
**Metrics**:
|
||||
- Bundle size: <before>KB → <after>KB (<percentage>% reduction)
|
||||
- Code quality: Significantly improved
|
||||
- Maintainability: Much easier
|
||||
- Performance: <improvement>
|
||||
|
||||
## Testing
|
||||
|
||||
**Tests Updated**: <count>
|
||||
**All tests passing**: YES
|
||||
**New tests added**: <count>
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
<list-breaking-changes-or-none>
|
||||
|
||||
## Migration Guide
|
||||
|
||||
**For Consumers**:
|
||||
```typescript
|
||||
// Old API
|
||||
<old-usage>
|
||||
|
||||
// New API
|
||||
<new-usage>
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
**Further Modernization**:
|
||||
1. <next-opportunity>
|
||||
2. <another-opportunity>
|
||||
|
||||
---
|
||||
|
||||
**Modernization Complete**: Codebase updated to modern standards.
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Incompatible environment**:
|
||||
```
|
||||
Error: Target environment does not support <feature>
|
||||
|
||||
Target: <compatibility>
|
||||
Required: <minimum-version>
|
||||
|
||||
Options:
|
||||
1. Use Babel/TypeScript to transpile
|
||||
2. Update target environment
|
||||
3. Choose different modernization target
|
||||
```
|
||||
|
||||
**Too many changes**:
|
||||
```
|
||||
Warning: Modernizing <count> files is a large change.
|
||||
|
||||
Recommendation: Gradual modernization
|
||||
1. Start with critical paths
|
||||
2. Modernize incrementally
|
||||
3. Test thoroughly between changes
|
||||
4. Review with team
|
||||
```
|
||||
845
commands/refactor/patterns.md
Normal file
845
commands/refactor/patterns.md
Normal file
@@ -0,0 +1,845 @@
|
||||
# Design Pattern Introduction Operation
|
||||
|
||||
Introduce proven design patterns to solve recurring design problems and improve code structure.
|
||||
|
||||
## Parameters
|
||||
|
||||
**Received from $ARGUMENTS**: All arguments after "patterns"
|
||||
|
||||
**Expected format**:
|
||||
```
|
||||
scope:"<path>" pattern:"<pattern-name>" [reason:"<motivation>"]
|
||||
```
|
||||
|
||||
**Parameter definitions**:
|
||||
- `scope` (REQUIRED): Path to apply pattern (e.g., "src/services/", "src/components/UserForm.tsx")
|
||||
- `pattern` (REQUIRED): Pattern to introduce
|
||||
- `factory` - Create objects without specifying exact class
|
||||
- `strategy` - Encapsulate interchangeable algorithms
|
||||
- `observer` - Publish-subscribe event system
|
||||
- `decorator` - Add behavior dynamically
|
||||
- `adapter` - Make incompatible interfaces work together
|
||||
- `repository` - Abstract data access layer
|
||||
- `dependency-injection` - Invert control, improve testability
|
||||
- `singleton` - Ensure single instance (use sparingly!)
|
||||
- `command` - Encapsulate requests as objects
|
||||
- `facade` - Simplified interface to complex subsystem
|
||||
- `reason` (OPTIONAL): Why introducing this pattern (e.g., "eliminate switch statement", "improve testability")
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Pattern Selection Validation
|
||||
|
||||
Verify pattern is appropriate for the problem:
|
||||
|
||||
**Anti-patterns to avoid**:
|
||||
- Using pattern for pattern's sake (over-engineering)
|
||||
- Singleton when not needed (global state)
|
||||
- Factory when simple constructor suffices
|
||||
- Strategy for only 2 simple options
|
||||
|
||||
**Good reasons to introduce pattern**:
|
||||
- Eliminate complex conditional logic (Strategy, State)
|
||||
- Improve testability (Dependency Injection, Repository)
|
||||
- Enable extensibility without modification (Strategy, Decorator, Observer)
|
||||
- Simplify complex interface (Facade, Adapter)
|
||||
- Separate concerns (Repository, Factory)
|
||||
|
||||
### 2. Analyze Current Code Structure
|
||||
|
||||
Understand what needs to change:
|
||||
|
||||
```bash
|
||||
# Find relevant files
|
||||
find <scope> -type f -name "*.ts" -o -name "*.tsx"
|
||||
|
||||
# Analyze complexity
|
||||
npx eslint <scope> --rule 'complexity: [error, { max: 10 }]'
|
||||
|
||||
# Check dependencies
|
||||
npx madge <scope>
|
||||
```
|
||||
|
||||
### 3. Pattern-Specific Implementation
|
||||
|
||||
## Pattern Examples
|
||||
|
||||
### Pattern 1: Factory Pattern
|
||||
|
||||
**Use when**:
|
||||
- Object creation is complex
|
||||
- Need to choose between multiple implementations
|
||||
- Want to hide creation logic
|
||||
- Need centralized object creation
|
||||
|
||||
**Before** (Direct instantiation everywhere):
|
||||
```typescript
|
||||
// Scattered throughout codebase
|
||||
const emailNotif = new EmailNotification(config);
|
||||
await emailNotif.send(user, message);
|
||||
|
||||
const smsNotif = new SMSNotification(twilioConfig);
|
||||
await smsNotif.send(user, message);
|
||||
|
||||
const pushNotif = new PushNotification(fcmConfig);
|
||||
await pushNotif.send(user, message);
|
||||
|
||||
// Different interfaces, hard to extend
|
||||
```
|
||||
|
||||
**After** (Factory Pattern):
|
||||
```typescript
|
||||
// notifications/NotificationFactory.ts
|
||||
interface Notification {
|
||||
send(user: User, message: string): Promise<void>;
|
||||
}
|
||||
|
||||
export class NotificationFactory {
|
||||
constructor(private config: NotificationConfig) {}
|
||||
|
||||
create(type: NotificationType): Notification {
|
||||
switch (type) {
|
||||
case 'email':
|
||||
return new EmailNotification(this.config.email);
|
||||
case 'sms':
|
||||
return new SMSNotification(this.config.sms);
|
||||
case 'push':
|
||||
return new PushNotification(this.config.push);
|
||||
default:
|
||||
throw new Error(`Unknown notification type: ${type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const factory = new NotificationFactory(config);
|
||||
const notification = factory.create(user.preferences.notificationType);
|
||||
await notification.send(user, message);
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Centralized creation logic
|
||||
- Consistent interface
|
||||
- Easy to add new notification types
|
||||
- Configuration hidden from consumers
|
||||
|
||||
---
|
||||
|
||||
### Pattern 2: Strategy Pattern
|
||||
|
||||
**Use when**:
|
||||
- Multiple algorithms for same task
|
||||
- Need to switch algorithms at runtime
|
||||
- Want to eliminate complex conditionals
|
||||
- Algorithms have different implementations but same interface
|
||||
|
||||
**Before** (Complex switch statement):
|
||||
```typescript
|
||||
// PaymentProcessor.ts - 180 lines, complexity: 15
|
||||
class PaymentProcessor {
|
||||
async processPayment(order: Order, method: string) {
|
||||
switch (method) {
|
||||
case 'credit_card':
|
||||
// 40 lines of credit card processing
|
||||
const ccGateway = new CreditCardGateway(this.config.stripe);
|
||||
const ccToken = await ccGateway.tokenize(order.paymentDetails);
|
||||
const ccCharge = await ccGateway.charge(ccToken, order.amount);
|
||||
await this.recordTransaction(order.id, ccCharge);
|
||||
await this.sendReceipt(order.customer, ccCharge);
|
||||
return ccCharge;
|
||||
|
||||
case 'paypal':
|
||||
// 40 lines of PayPal processing
|
||||
const ppGateway = new PayPalGateway(this.config.paypal);
|
||||
const ppAuth = await ppGateway.authenticate();
|
||||
const ppPayment = await ppGateway.createPayment(order);
|
||||
await this.recordTransaction(order.id, ppPayment);
|
||||
await this.sendReceipt(order.customer, ppPayment);
|
||||
return ppPayment;
|
||||
|
||||
case 'bank_transfer':
|
||||
// 40 lines of bank transfer processing
|
||||
const btGateway = new BankTransferGateway(this.config.bank);
|
||||
const btReference = await btGateway.generateReference(order);
|
||||
await this.sendInstructions(order.customer, btReference);
|
||||
await this.recordTransaction(order.id, btReference);
|
||||
return btReference;
|
||||
|
||||
case 'crypto':
|
||||
// 40 lines of crypto processing
|
||||
const cryptoGateway = new CryptoGateway(this.config.crypto);
|
||||
const wallet = await cryptoGateway.generateAddress();
|
||||
await this.sendInstructions(order.customer, wallet);
|
||||
await this.recordTransaction(order.id, wallet);
|
||||
return wallet;
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported payment method: ${method}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**After** (Strategy Pattern):
|
||||
```typescript
|
||||
// payment/PaymentStrategy.ts
|
||||
export interface PaymentStrategy {
|
||||
process(order: Order): Promise<PaymentResult>;
|
||||
}
|
||||
|
||||
// payment/strategies/CreditCardStrategy.ts
|
||||
export class CreditCardStrategy implements PaymentStrategy {
|
||||
constructor(private gateway: CreditCardGateway) {}
|
||||
|
||||
async process(order: Order): Promise<PaymentResult> {
|
||||
const token = await this.gateway.tokenize(order.paymentDetails);
|
||||
const charge = await this.gateway.charge(token, order.amount);
|
||||
return {
|
||||
success: true,
|
||||
transactionId: charge.id,
|
||||
method: 'credit_card'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// payment/strategies/PayPalStrategy.ts
|
||||
export class PayPalStrategy implements PaymentStrategy {
|
||||
constructor(private gateway: PayPalGateway) {}
|
||||
|
||||
async process(order: Order): Promise<PaymentResult> {
|
||||
await this.gateway.authenticate();
|
||||
const payment = await this.gateway.createPayment(order);
|
||||
return {
|
||||
success: true,
|
||||
transactionId: payment.id,
|
||||
method: 'paypal'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// payment/strategies/BankTransferStrategy.ts
|
||||
export class BankTransferStrategy implements PaymentStrategy {
|
||||
constructor(private gateway: BankTransferGateway) {}
|
||||
|
||||
async process(order: Order): Promise<PaymentResult> {
|
||||
const reference = await this.gateway.generateReference(order);
|
||||
return {
|
||||
success: true,
|
||||
transactionId: reference,
|
||||
method: 'bank_transfer',
|
||||
requiresManualConfirmation: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// payment/PaymentProcessor.ts - Now clean and extensible
|
||||
export class PaymentProcessor {
|
||||
private strategies: Map<string, PaymentStrategy>;
|
||||
|
||||
constructor(
|
||||
strategies: Map<string, PaymentStrategy>,
|
||||
private transactionRepo: TransactionRepository,
|
||||
private notificationService: NotificationService
|
||||
) {
|
||||
this.strategies = strategies;
|
||||
}
|
||||
|
||||
async processPayment(order: Order, method: string): Promise<PaymentResult> {
|
||||
const strategy = this.strategies.get(method);
|
||||
if (!strategy) {
|
||||
throw new UnsupportedPaymentMethodError(method);
|
||||
}
|
||||
|
||||
const result = await strategy.process(order);
|
||||
await this.transactionRepo.record(order.id, result);
|
||||
await this.notificationService.sendReceipt(order.customer, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup (dependency injection)
|
||||
const processor = new PaymentProcessor(
|
||||
new Map([
|
||||
['credit_card', new CreditCardStrategy(ccGateway)],
|
||||
['paypal', new PayPalStrategy(ppGateway)],
|
||||
['bank_transfer', new BankTransferStrategy(btGateway)],
|
||||
['crypto', new CryptoStrategy(cryptoGateway)]
|
||||
]),
|
||||
transactionRepo,
|
||||
notificationService
|
||||
);
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Complexity: 15 → 3 (80% reduction)
|
||||
- Open/Closed Principle: Add strategies without modifying processor
|
||||
- Testability: Each strategy tested independently
|
||||
- Maintainability: Clear separation of concerns
|
||||
- Extensibility: Add new payment methods easily
|
||||
|
||||
---
|
||||
|
||||
### Pattern 3: Observer Pattern (Pub-Sub)
|
||||
|
||||
**Use when**:
|
||||
- Multiple objects need to react to events
|
||||
- Want loose coupling between components
|
||||
- Need publish-subscribe event system
|
||||
- State changes should notify dependents
|
||||
|
||||
**Before** (Tight coupling, manual notification):
|
||||
```typescript
|
||||
class UserService {
|
||||
async createUser(data: CreateUserInput) {
|
||||
const user = await this.db.users.create(data);
|
||||
|
||||
// Tightly coupled to all consumers
|
||||
await this.emailService.sendWelcome(user);
|
||||
await this.analyticsService.trackSignup(user);
|
||||
await this.subscriptionService.createTrialSubscription(user);
|
||||
await this.notificationService.sendAdminAlert(user);
|
||||
await this.auditLogger.logUserCreated(user);
|
||||
|
||||
// Adding new consumer requires modifying this method
|
||||
return user;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**After** (Observer Pattern):
|
||||
```typescript
|
||||
// events/EventEmitter.ts
|
||||
type EventHandler<T = any> = (data: T) => Promise<void> | void;
|
||||
|
||||
export class EventEmitter {
|
||||
private handlers: Map<string, EventHandler[]> = new Map();
|
||||
|
||||
on(event: string, handler: EventHandler): void {
|
||||
if (!this.handlers.has(event)) {
|
||||
this.handlers.set(event, []);
|
||||
}
|
||||
this.handlers.get(event)!.push(handler);
|
||||
}
|
||||
|
||||
async emit(event: string, data: any): Promise<void> {
|
||||
const handlers = this.handlers.get(event) || [];
|
||||
await Promise.all(handlers.map(handler => handler(data)));
|
||||
}
|
||||
}
|
||||
|
||||
// events/UserEvents.ts
|
||||
export const UserEvents = {
|
||||
CREATED: 'user.created',
|
||||
UPDATED: 'user.updated',
|
||||
DELETED: 'user.deleted'
|
||||
} as const;
|
||||
|
||||
// services/UserService.ts - Now decoupled
|
||||
export class UserService {
|
||||
constructor(
|
||||
private db: Database,
|
||||
private events: EventEmitter
|
||||
) {}
|
||||
|
||||
async createUser(data: CreateUserInput): Promise<User> {
|
||||
const user = await this.db.users.create(data);
|
||||
|
||||
// Simply publish event
|
||||
await this.events.emit(UserEvents.CREATED, user);
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
// subscribers/WelcomeEmailSubscriber.ts
|
||||
export class WelcomeEmailSubscriber {
|
||||
constructor(
|
||||
private emailService: EmailService,
|
||||
private events: EventEmitter
|
||||
) {
|
||||
this.events.on(UserEvents.CREATED, this.handle.bind(this));
|
||||
}
|
||||
|
||||
private async handle(user: User): Promise<void> {
|
||||
await this.emailService.sendWelcome(user);
|
||||
}
|
||||
}
|
||||
|
||||
// subscribers/AnalyticsSubscriber.ts
|
||||
export class AnalyticsSubscriber {
|
||||
constructor(
|
||||
private analyticsService: AnalyticsService,
|
||||
private events: EventEmitter
|
||||
) {
|
||||
this.events.on(UserEvents.CREATED, this.handle.bind(this));
|
||||
}
|
||||
|
||||
private async handle(user: User): Promise<void> {
|
||||
await this.analyticsService.trackSignup(user);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup
|
||||
const events = new EventEmitter();
|
||||
new WelcomeEmailSubscriber(emailService, events);
|
||||
new AnalyticsSubscriber(analyticsService, events);
|
||||
new SubscriptionSubscriber(subscriptionService, events);
|
||||
new NotificationSubscriber(notificationService, events);
|
||||
new AuditSubscriber(auditLogger, events);
|
||||
|
||||
const userService = new UserService(db, events);
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Loose coupling: UserService doesn't know about consumers
|
||||
- Open/Closed: Add subscribers without modifying UserService
|
||||
- Testability: Test UserService without dependencies
|
||||
- Maintainability: Each subscriber isolated
|
||||
- Flexibility: Enable/disable subscribers easily
|
||||
|
||||
---
|
||||
|
||||
### Pattern 4: Dependency Injection
|
||||
|
||||
**Use when**:
|
||||
- Want to improve testability
|
||||
- Need to swap implementations
|
||||
- Want loose coupling
|
||||
- Multiple dependencies
|
||||
|
||||
**Before** (Tight coupling, hard to test):
|
||||
```typescript
|
||||
class UserService {
|
||||
private db = new Database(process.env.DB_URL!);
|
||||
private emailService = new EmailService(process.env.SMTP_CONFIG!);
|
||||
private logger = new Logger('UserService');
|
||||
|
||||
async createUser(data: CreateUserInput) {
|
||||
this.logger.info('Creating user', data);
|
||||
|
||||
const user = await this.db.users.create(data);
|
||||
|
||||
await this.emailService.sendWelcome(user);
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
// Testing is painful - can't mock dependencies
|
||||
test('createUser', async () => {
|
||||
// Cannot inject test database or mock email service!
|
||||
const service = new UserService();
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
**After** (Dependency Injection):
|
||||
```typescript
|
||||
// Define interfaces
|
||||
interface IDatabase {
|
||||
users: {
|
||||
create(data: CreateUserData): Promise<User>;
|
||||
findById(id: string): Promise<User | null>;
|
||||
};
|
||||
}
|
||||
|
||||
interface IEmailService {
|
||||
sendWelcome(user: User): Promise<void>;
|
||||
}
|
||||
|
||||
interface ILogger {
|
||||
info(message: string, data?: any): void;
|
||||
error(message: string, error?: Error): void;
|
||||
}
|
||||
|
||||
// Inject dependencies
|
||||
class UserService {
|
||||
constructor(
|
||||
private db: IDatabase,
|
||||
private emailService: IEmailService,
|
||||
private logger: ILogger
|
||||
) {}
|
||||
|
||||
async createUser(data: CreateUserInput): Promise<User> {
|
||||
this.logger.info('Creating user', data);
|
||||
|
||||
const user = await this.db.users.create(data);
|
||||
|
||||
await this.emailService.sendWelcome(user);
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
// Production setup
|
||||
const db = new PostgresDatabase(config.database);
|
||||
const emailService = new SMTPEmailService(config.smtp);
|
||||
const logger = new WinstonLogger('UserService');
|
||||
const userService = new UserService(db, emailService, logger);
|
||||
|
||||
// Test setup - Easy mocking!
|
||||
test('createUser sends welcome email', async () => {
|
||||
const mockDb = {
|
||||
users: {
|
||||
create: jest.fn().mockResolvedValue({ id: '1', email: 'test@example.com' })
|
||||
}
|
||||
};
|
||||
const mockEmail = {
|
||||
sendWelcome: jest.fn().mockResolvedValue(undefined)
|
||||
};
|
||||
const mockLogger = {
|
||||
info: jest.fn(),
|
||||
error: jest.fn()
|
||||
};
|
||||
|
||||
const service = new UserService(mockDb, mockEmail, mockLogger);
|
||||
await service.createUser({ email: 'test@example.com', name: 'Test' });
|
||||
|
||||
expect(mockEmail.sendWelcome).toHaveBeenCalledWith({ id: '1', email: 'test@example.com' });
|
||||
});
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Testability: Easy to inject mocks
|
||||
- Flexibility: Swap implementations (PostgreSQL → MongoDB)
|
||||
- Loose coupling: Depends on interfaces, not implementations
|
||||
- Clear dependencies: Constructor shows all dependencies
|
||||
|
||||
---
|
||||
|
||||
### Pattern 5: Repository Pattern
|
||||
|
||||
**Use when**:
|
||||
- Want to abstract data access
|
||||
- Need to swap data sources
|
||||
- Want consistent query interface
|
||||
- Separate domain from persistence
|
||||
|
||||
**Before** (Data access mixed with business logic):
|
||||
```typescript
|
||||
class UserService {
|
||||
async getUsersByRole(role: string) {
|
||||
// Direct database queries in service
|
||||
const users = await prisma.user.findMany({
|
||||
where: { role },
|
||||
include: {
|
||||
profile: true,
|
||||
posts: { where: { published: true } }
|
||||
}
|
||||
});
|
||||
return users;
|
||||
}
|
||||
|
||||
async getActiveUsers() {
|
||||
const users = await prisma.user.findMany({
|
||||
where: { status: 'active', deletedAt: null }
|
||||
});
|
||||
return users;
|
||||
}
|
||||
|
||||
// Many more methods with direct queries...
|
||||
}
|
||||
```
|
||||
|
||||
**After** (Repository Pattern):
|
||||
```typescript
|
||||
// repositories/UserRepository.ts
|
||||
export interface IUserRepository {
|
||||
findById(id: string): Promise<User | null>;
|
||||
findByEmail(email: string): Promise<User | null>;
|
||||
findByRole(role: string): Promise<User[]>;
|
||||
findActive(): Promise<User[]>;
|
||||
create(data: CreateUserData): Promise<User>;
|
||||
update(id: string, data: Partial<User>): Promise<User>;
|
||||
delete(id: string): Promise<void>;
|
||||
}
|
||||
|
||||
export class PrismaUserRepository implements IUserRepository {
|
||||
constructor(private prisma: PrismaClient) {}
|
||||
|
||||
async findById(id: string): Promise<User | null> {
|
||||
return this.prisma.user.findUnique({
|
||||
where: { id },
|
||||
include: { profile: true }
|
||||
});
|
||||
}
|
||||
|
||||
async findByEmail(email: string): Promise<User | null> {
|
||||
return this.prisma.user.findUnique({
|
||||
where: { email },
|
||||
include: { profile: true }
|
||||
});
|
||||
}
|
||||
|
||||
async findByRole(role: string): Promise<User[]> {
|
||||
return this.prisma.user.findMany({
|
||||
where: { role },
|
||||
include: {
|
||||
profile: true,
|
||||
posts: { where: { published: true } }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async findActive(): Promise<User[]> {
|
||||
return this.prisma.user.findMany({
|
||||
where: { status: 'active', deletedAt: null }
|
||||
});
|
||||
}
|
||||
|
||||
async create(data: CreateUserData): Promise<User> {
|
||||
return this.prisma.user.create({ data });
|
||||
}
|
||||
|
||||
async update(id: string, data: Partial<User>): Promise<User> {
|
||||
return this.prisma.user.update({ where: { id }, data });
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
await this.prisma.user.update({
|
||||
where: { id },
|
||||
data: { deletedAt: new Date() }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// services/UserService.ts - Clean business logic
|
||||
export class UserService {
|
||||
constructor(private userRepository: IUserRepository) {}
|
||||
|
||||
async getUsersByRole(role: string): Promise<User[]> {
|
||||
return this.userRepository.findByRole(role);
|
||||
}
|
||||
|
||||
async getActiveUsers(): Promise<User[]> {
|
||||
return this.userRepository.findActive();
|
||||
}
|
||||
|
||||
// Business logic, no persistence concerns
|
||||
}
|
||||
|
||||
// Easy to swap data sources
|
||||
class InMemoryUserRepository implements IUserRepository {
|
||||
private users: Map<string, User> = new Map();
|
||||
|
||||
async findById(id: string): Promise<User | null> {
|
||||
return this.users.get(id) || null;
|
||||
}
|
||||
|
||||
// ... other methods using in-memory Map
|
||||
}
|
||||
|
||||
// Testing with in-memory repository
|
||||
test('getUsersByRole', async () => {
|
||||
const repo = new InMemoryUserRepository();
|
||||
await repo.create({ id: '1', email: 'admin@example.com', role: 'admin' });
|
||||
|
||||
const service = new UserService(repo);
|
||||
const admins = await service.getUsersByRole('admin');
|
||||
|
||||
expect(admins).toHaveLength(1);
|
||||
});
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Separation of concerns: Business logic separate from persistence
|
||||
- Testability: Easy to use in-memory repository for tests
|
||||
- Flexibility: Swap Prisma → TypeORM → MongoDB without changing services
|
||||
- Consistency: Standardized query interface
|
||||
- Caching: Easy to add caching layer in repository
|
||||
|
||||
---
|
||||
|
||||
### Pattern 6: Decorator Pattern
|
||||
|
||||
**Use when**:
|
||||
- Want to add behavior dynamically
|
||||
- Need multiple combinations of features
|
||||
- Avoid subclass explosion
|
||||
- Wrap functionality around core
|
||||
|
||||
**Before** (Subclass explosion):
|
||||
```typescript
|
||||
class Logger { log(message: string) { /* ... */ } }
|
||||
class TimestampLogger extends Logger { /* adds timestamp */ }
|
||||
class ColorLogger extends Logger { /* adds colors */ }
|
||||
class FileLogger extends Logger { /* writes to file */ }
|
||||
class TimestampColorLogger extends TimestampLogger { /* both timestamp and color */ }
|
||||
class TimestampFileLogger extends TimestampLogger { /* both timestamp and file */ }
|
||||
// Need class for every combination!
|
||||
```
|
||||
|
||||
**After** (Decorator Pattern):
|
||||
```typescript
|
||||
// Core interface
|
||||
interface Logger {
|
||||
log(message: string): void;
|
||||
}
|
||||
|
||||
// Base implementation
|
||||
class ConsoleLogger implements Logger {
|
||||
log(message: string): void {
|
||||
console.log(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Decorators
|
||||
class TimestampDecorator implements Logger {
|
||||
constructor(private logger: Logger) {}
|
||||
|
||||
log(message: string): void {
|
||||
const timestamp = new Date().toISOString();
|
||||
this.logger.log(`[${timestamp}] ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
class ColorDecorator implements Logger {
|
||||
constructor(private logger: Logger, private color: string) {}
|
||||
|
||||
log(message: string): void {
|
||||
this.logger.log(`\x1b[${this.color}m${message}\x1b[0m`);
|
||||
}
|
||||
}
|
||||
|
||||
class FileDecorator implements Logger {
|
||||
constructor(private logger: Logger, private filePath: string) {}
|
||||
|
||||
log(message: string): void {
|
||||
this.logger.log(message);
|
||||
fs.appendFileSync(this.filePath, message + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
// Compose decorators
|
||||
let logger: Logger = new ConsoleLogger();
|
||||
logger = new TimestampDecorator(logger);
|
||||
logger = new ColorDecorator(logger, '32'); // green
|
||||
logger = new FileDecorator(logger, './app.log');
|
||||
|
||||
logger.log('Hello World');
|
||||
// Output: [2025-01-15T10:30:00.000Z] Hello World (in green, also in file)
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Flexibility: Mix and match decorators
|
||||
- No subclass explosion: N decorators instead of 2^N classes
|
||||
- Open/Closed: Add decorators without modifying logger
|
||||
- Composition over inheritance
|
||||
|
||||
---
|
||||
|
||||
## Output Format
|
||||
|
||||
```markdown
|
||||
# Design Pattern Introduction Report
|
||||
|
||||
## Pattern Applied: <pattern-name>
|
||||
|
||||
**Scope**: <path>
|
||||
**Reason**: <motivation>
|
||||
|
||||
## Problem Statement
|
||||
|
||||
**Before**:
|
||||
- <issue 1>
|
||||
- <issue 2>
|
||||
- <issue 3>
|
||||
|
||||
**Symptoms**:
|
||||
- Complex conditional logic
|
||||
- Tight coupling
|
||||
- Difficult to test
|
||||
- Hard to extend
|
||||
|
||||
## Solution: <Pattern Name>
|
||||
|
||||
**Benefits**:
|
||||
- <benefit 1>
|
||||
- <benefit 2>
|
||||
- <benefit 3>
|
||||
|
||||
**Trade-offs**:
|
||||
- <trade-off 1> (if any)
|
||||
|
||||
## Implementation
|
||||
|
||||
### Files Created
|
||||
- <new-file-1>
|
||||
- <new-file-2>
|
||||
|
||||
### Files Modified
|
||||
- <modified-file-1>
|
||||
- <modified-file-2>
|
||||
|
||||
### Code Changes
|
||||
|
||||
**Before**:
|
||||
```typescript
|
||||
<original-code>
|
||||
```
|
||||
|
||||
**After**:
|
||||
```typescript
|
||||
<refactored-code-with-pattern>
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
**Tests**:
|
||||
- All existing tests: PASS
|
||||
- New pattern tests: 12 added
|
||||
- Coverage: 78% → 85%
|
||||
|
||||
**Metrics**:
|
||||
- Complexity: 15 → 4 (73% improvement)
|
||||
- Coupling: High → Low
|
||||
- Extensibility: Improved
|
||||
|
||||
## Usage Guide
|
||||
|
||||
**How to use the new pattern**:
|
||||
```typescript
|
||||
<usage-example>
|
||||
```
|
||||
|
||||
**How to extend**:
|
||||
```typescript
|
||||
<extension-example>
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
**Additional Improvements**:
|
||||
1. <next-opportunity>
|
||||
2. <another-opportunity>
|
||||
|
||||
---
|
||||
|
||||
**Pattern Introduction Complete**: Code is now more flexible, testable, and maintainable.
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Pattern not appropriate**:
|
||||
```
|
||||
Warning: <pattern> may not be the best solution for this problem.
|
||||
|
||||
Current problem: <description>
|
||||
Suggested pattern: <alternative-pattern>
|
||||
|
||||
Reason: <explanation>
|
||||
```
|
||||
|
||||
**Over-engineering risk**:
|
||||
```
|
||||
Warning: Introducing <pattern> may be over-engineering for current needs.
|
||||
|
||||
Current complexity: LOW
|
||||
Pattern complexity: HIGH
|
||||
|
||||
Recommendation: Consider simpler solutions first:
|
||||
1. Extract method/function
|
||||
2. Use simple conditional
|
||||
3. Wait until pattern truly needed (YAGNI)
|
||||
```
|
||||
243
commands/refactor/skill.md
Normal file
243
commands/refactor/skill.md
Normal file
@@ -0,0 +1,243 @@
|
||||
---
|
||||
description: Comprehensive code refactoring operations for improving code quality and maintainability
|
||||
---
|
||||
|
||||
# Code Refactoring Skill
|
||||
|
||||
You are executing a **code refactoring operation** to improve code quality, maintainability, and architecture without changing external behavior.
|
||||
|
||||
## Operation Routing
|
||||
|
||||
Parse `$ARGUMENTS` to identify the requested operation and parameters:
|
||||
|
||||
**Available Operations**:
|
||||
- `analyze` → Analyze code quality and identify refactoring opportunities
|
||||
- `extract` → Extract methods, classes, modules, or components
|
||||
- `patterns` → Introduce design patterns (Factory, Strategy, Observer, etc.)
|
||||
- `types` → Improve type safety (TypeScript)
|
||||
- `duplicate` → Eliminate code duplication
|
||||
- `modernize` → Update legacy code patterns
|
||||
|
||||
**Base Directory**: `/home/danie/projects/plugins/architect/open-plugins/plugins/10x-fullstack-engineer/commands/refactor`
|
||||
|
||||
## Request Processing
|
||||
|
||||
**Received**: `$ARGUMENTS`
|
||||
|
||||
**Parse format**:
|
||||
```
|
||||
<operation> <parameters>
|
||||
```
|
||||
|
||||
Example arguments:
|
||||
- `analyze scope:"user authentication module" metrics:"complexity,duplication" depth:"detailed"`
|
||||
- `extract scope:"UserProfile.tsx" type:"method" target:"validateEmail" reason:"reduce complexity"`
|
||||
- `patterns scope:"services/" pattern:"dependency-injection" reason:"improve testability"`
|
||||
- `types scope:"api-client/" strategy:"eliminate-any" strict:"true"`
|
||||
- `duplicate scope:"src/validators" threshold:"80" strategy:"extract-function"`
|
||||
- `modernize scope:"legacy-api/" targets:"callbacks-to-async,classes-to-hooks"`
|
||||
|
||||
## Pre-Refactoring Safety Checklist
|
||||
|
||||
**CRITICAL**: Before ANY refactoring, verify:
|
||||
|
||||
1. **Test Coverage**:
|
||||
- Existing test coverage is adequate (>70% for code being refactored)
|
||||
- All tests currently passing
|
||||
- Tests are meaningful and test behavior, not implementation
|
||||
|
||||
2. **Version Control**:
|
||||
- All changes committed to version control
|
||||
- Working on a feature branch (not main/master)
|
||||
- Clean working directory (no uncommitted changes)
|
||||
|
||||
3. **Backup**:
|
||||
- Current state committed with clear message
|
||||
- Can easily revert if needed
|
||||
- Branch created specifically for this refactoring
|
||||
|
||||
4. **Scope Definition**:
|
||||
- Clearly defined boundaries of what to refactor
|
||||
- No mixing of refactoring with new features
|
||||
- Reasonable size for one refactoring session
|
||||
|
||||
5. **Risk Assessment**:
|
||||
- Understand dependencies and impact
|
||||
- Identify potential breaking changes
|
||||
- Have rollback plan ready
|
||||
|
||||
## Operation Execution
|
||||
|
||||
Based on the first word in `$ARGUMENTS`, execute the corresponding operation:
|
||||
|
||||
### If operation is "analyze":
|
||||
Read and execute: `.claude/commands/refactor/analyze.md`
|
||||
|
||||
**Purpose**: Analyze code quality, identify code smells, calculate metrics, prioritize refactoring opportunities.
|
||||
|
||||
### If operation is "extract":
|
||||
Read and execute: `.claude/commands/refactor/extract.md`
|
||||
|
||||
**Purpose**: Extract methods, classes, modules, components, utilities, or interfaces to improve code organization.
|
||||
|
||||
### If operation is "patterns":
|
||||
Read and execute: `.claude/commands/refactor/patterns.md`
|
||||
|
||||
**Purpose**: Introduce design patterns (Factory, Strategy, Observer, Dependency Injection, Repository, etc.) to solve recurring design problems.
|
||||
|
||||
### If operation is "types":
|
||||
Read and execute: `.claude/commands/refactor/types.md`
|
||||
|
||||
**Purpose**: Improve type safety by adding types, strengthening types, migrating to TypeScript, eliminating 'any', or adding generics.
|
||||
|
||||
### If operation is "duplicate":
|
||||
Read and execute: `.claude/commands/refactor/duplicate.md`
|
||||
|
||||
**Purpose**: Detect and eliminate code duplication through extraction, parameterization, or templating.
|
||||
|
||||
### If operation is "modernize":
|
||||
Read and execute: `.claude/commands/refactor/modernize.md`
|
||||
|
||||
**Purpose**: Update legacy code patterns (callbacks→async/await, var→const/let, prototypes→classes, CommonJS→ESM, jQuery→vanilla, classes→hooks).
|
||||
|
||||
### If operation is unknown or missing:
|
||||
Provide operation list and usage examples.
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Unknown Operation**:
|
||||
```
|
||||
Error: Unknown refactoring operation: <operation>
|
||||
|
||||
Available operations:
|
||||
- analyze - Analyze code quality and identify opportunities
|
||||
- extract - Extract methods, classes, modules, components
|
||||
- patterns - Introduce design patterns
|
||||
- types - Improve type safety (TypeScript)
|
||||
- duplicate - Eliminate code duplication
|
||||
- modernize - Update legacy code patterns
|
||||
|
||||
Usage: /refactor <operation> <parameters>
|
||||
|
||||
Examples:
|
||||
/refactor analyze scope:"user-service/" depth:"detailed"
|
||||
/refactor extract scope:"UserForm.tsx" type:"component" target:"EmailInput"
|
||||
/refactor patterns scope:"services/" pattern:"dependency-injection"
|
||||
```
|
||||
|
||||
**Missing Parameters**:
|
||||
```
|
||||
Error: Required parameters missing for <operation>
|
||||
|
||||
Expected format: /refactor <operation> scope:"..." [additional-params]
|
||||
|
||||
See: /refactor <operation> help
|
||||
```
|
||||
|
||||
**Insufficient Test Coverage**:
|
||||
```
|
||||
Warning: Test coverage is below recommended threshold (<70%).
|
||||
|
||||
Recommendations:
|
||||
1. Add tests for code being refactored
|
||||
2. Reduce refactoring scope to well-tested areas
|
||||
3. Write tests first, then refactor (Red-Green-Refactor)
|
||||
|
||||
Continue anyway? This increases risk of breaking changes.
|
||||
```
|
||||
|
||||
**Uncommitted Changes**:
|
||||
```
|
||||
Error: Working directory has uncommitted changes.
|
||||
|
||||
Refactoring requires clean version control state for safety.
|
||||
|
||||
Action required:
|
||||
1. Commit current changes: git add . && git commit -m "..."
|
||||
2. Or stash changes: git stash
|
||||
3. Create feature branch: git checkout -b refactor/<description>
|
||||
|
||||
Then retry refactoring operation.
|
||||
```
|
||||
|
||||
## Integration with 10x-fullstack-engineer Agent
|
||||
|
||||
All refactoring operations leverage the **10x-fullstack-engineer** agent for:
|
||||
- Expert code quality analysis
|
||||
- Best practice application
|
||||
- Pattern recognition and recommendation
|
||||
- Consistency with project standards
|
||||
- Risk assessment and mitigation
|
||||
- Test-driven refactoring approach
|
||||
|
||||
The agent applies SOLID principles, DRY, YAGNI, and follows the Boy Scout Rule (leave code better than found).
|
||||
|
||||
## Refactoring Principles
|
||||
|
||||
All operations adhere to:
|
||||
|
||||
1. **Preserve Behavior**: External behavior must remain unchanged
|
||||
2. **Small Steps**: Incremental changes with frequent testing
|
||||
3. **Test-Driven**: Tests pass before, during, and after refactoring
|
||||
4. **One Thing at a Time**: Don't mix refactoring with feature development
|
||||
5. **Frequent Commits**: Commit after each successful refactoring step
|
||||
6. **Clear Intent**: Each change has clear purpose and benefit
|
||||
7. **Reversibility**: Easy to revert if something goes wrong
|
||||
8. **Maintainability First**: Optimize for readability and maintainability
|
||||
|
||||
## Usage Examples
|
||||
|
||||
**Analyze codebase for refactoring opportunities**:
|
||||
```bash
|
||||
/refactor analyze scope:"src/components" metrics:"complexity,duplication,coverage" depth:"detailed"
|
||||
```
|
||||
|
||||
**Extract long method into smaller functions**:
|
||||
```bash
|
||||
/refactor extract scope:"UserService.ts" type:"method" target:"validateAndCreateUser" reason:"function is 150 lines, too complex"
|
||||
```
|
||||
|
||||
**Introduce dependency injection pattern**:
|
||||
```bash
|
||||
/refactor patterns scope:"services/" pattern:"dependency-injection" reason:"improve testability and flexibility"
|
||||
```
|
||||
|
||||
**Strengthen TypeScript type safety**:
|
||||
```bash
|
||||
/refactor types scope:"api/" strategy:"eliminate-any" strict:"true"
|
||||
```
|
||||
|
||||
**Eliminate duplicate validation logic**:
|
||||
```bash
|
||||
/refactor duplicate scope:"src/validators" threshold:"75" strategy:"extract-function"
|
||||
```
|
||||
|
||||
**Modernize legacy callback code to async/await**:
|
||||
```bash
|
||||
/refactor modernize scope:"legacy-api/" targets:"callbacks-to-async" compatibility:"node14+"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Start Small**: Begin with low-risk, high-value refactorings
|
||||
2. **Test Continuously**: Run tests after each change
|
||||
3. **Commit Frequently**: Small commits with clear messages
|
||||
4. **Pair Review**: Have someone review refactored code
|
||||
5. **Measure Impact**: Track metrics before and after
|
||||
6. **Document Why**: Explain reasoning in commits and comments
|
||||
7. **Avoid Scope Creep**: Stay focused on defined scope
|
||||
8. **Time Box**: Set time limits for refactoring sessions
|
||||
|
||||
## Output
|
||||
|
||||
All operations provide detailed reports including:
|
||||
- Before/after code examples
|
||||
- Metrics improvement (complexity, coverage, duplication)
|
||||
- Changes made and reasoning
|
||||
- Verification steps
|
||||
- Future refactoring opportunities
|
||||
- Risk assessment and mitigation
|
||||
|
||||
---
|
||||
|
||||
**Ready to refactor**: Specify operation and parameters to begin.
|
||||
896
commands/refactor/types.md
Normal file
896
commands/refactor/types.md
Normal file
@@ -0,0 +1,896 @@
|
||||
# Type Safety Improvement Operation
|
||||
|
||||
Improve TypeScript type safety by adding types, strengthening existing types, migrating to TypeScript, eliminating 'any', or adding generics.
|
||||
|
||||
## Parameters
|
||||
|
||||
**Received from $ARGUMENTS**: All arguments after "types"
|
||||
|
||||
**Expected format**:
|
||||
```
|
||||
scope:"<path>" strategy:"<strategy-name>" [strict:"true|false"]
|
||||
```
|
||||
|
||||
**Parameter definitions**:
|
||||
- `scope` (REQUIRED): Path to improve (e.g., "src/api/", "utils/helpers.ts")
|
||||
- `strategy` (REQUIRED): Type improvement strategy
|
||||
- `add-types` - Add missing type annotations
|
||||
- `strengthen-types` - Replace weak types with specific ones
|
||||
- `migrate-to-ts` - Convert JavaScript to TypeScript
|
||||
- `eliminate-any` - Remove 'any' types
|
||||
- `add-generics` - Add generic type parameters
|
||||
- `strict` (OPTIONAL): Enable strict TypeScript mode (default: false)
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. TypeScript Configuration Check
|
||||
|
||||
Verify TypeScript setup:
|
||||
|
||||
```bash
|
||||
# Check if TypeScript is configured
|
||||
test -f tsconfig.json || echo "No tsconfig.json found"
|
||||
|
||||
# Check current strictness
|
||||
cat tsconfig.json | grep -A5 "compilerOptions"
|
||||
|
||||
# Type check current state
|
||||
npx tsc --noEmit
|
||||
```
|
||||
|
||||
### 2. Analyze Type Coverage
|
||||
|
||||
Assess current type safety:
|
||||
|
||||
```bash
|
||||
# Count 'any' usage
|
||||
grep -r "any" <scope> --include="*.ts" --include="*.tsx" | wc -l
|
||||
|
||||
# Count implicit any
|
||||
npx tsc --noEmit --noImplicitAny 2>&1 | grep "implicitly has an 'any' type" | wc -l
|
||||
|
||||
# Check for type assertions
|
||||
grep -r "as any" <scope> --include="*.ts" --include="*.tsx"
|
||||
```
|
||||
|
||||
## Strategy Examples
|
||||
|
||||
### Strategy 1: Add Missing Types
|
||||
|
||||
**Before** (Missing types):
|
||||
```typescript
|
||||
// utils/helpers.ts
|
||||
export function formatDate(date) {
|
||||
return date.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
export function calculateTotal(items) {
|
||||
return items.reduce((sum, item) => sum + item.price, 0);
|
||||
}
|
||||
|
||||
export async function fetchUser(id) {
|
||||
const response = await fetch(`/api/users/${id}`);
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export function createUser(name, email, age) {
|
||||
return {
|
||||
id: generateId(),
|
||||
name,
|
||||
email,
|
||||
age,
|
||||
createdAt: new Date()
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**After** (Full type annotations):
|
||||
```typescript
|
||||
// utils/helpers.ts
|
||||
export function formatDate(date: Date): string {
|
||||
return date.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
interface Item {
|
||||
price: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function calculateTotal(items: Item[]): number {
|
||||
return items.reduce((sum, item) => sum + item.price, 0);
|
||||
}
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
age: number;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export async function fetchUser(id: string): Promise<User> {
|
||||
const response = await fetch(`/api/users/${id}`);
|
||||
return response.json() as User;
|
||||
}
|
||||
|
||||
export function createUser(
|
||||
name: string,
|
||||
email: string,
|
||||
age: number
|
||||
): User {
|
||||
return {
|
||||
id: generateId(),
|
||||
name,
|
||||
email,
|
||||
age,
|
||||
createdAt: new Date()
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Catch type errors at compile time
|
||||
- Better IDE autocomplete
|
||||
- Self-documenting code
|
||||
- Refactoring safety
|
||||
|
||||
---
|
||||
|
||||
### Strategy 2: Strengthen Types (Eliminate 'any')
|
||||
|
||||
**Before** (Weak 'any' types):
|
||||
```typescript
|
||||
// api/client.ts
|
||||
class APIClient {
|
||||
async get(endpoint: string): Promise<any> {
|
||||
const response = await fetch(endpoint);
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async post(endpoint: string, data: any): Promise<any> {
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
return response.json();
|
||||
}
|
||||
|
||||
handleError(error: any) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage - No type safety!
|
||||
const user = await client.get('/users/1');
|
||||
console.log(user.nameeee); // Typo not caught!
|
||||
```
|
||||
|
||||
**After** (Strong specific types):
|
||||
```typescript
|
||||
// types/api.ts
|
||||
export interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
role: 'admin' | 'user';
|
||||
}
|
||||
|
||||
export interface Post {
|
||||
id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
authorId: string;
|
||||
}
|
||||
|
||||
export interface APIError {
|
||||
code: string;
|
||||
message: string;
|
||||
details?: Record<string, string[]>;
|
||||
}
|
||||
|
||||
// api/client.ts
|
||||
class APIClient {
|
||||
async get<T>(endpoint: string): Promise<T> {
|
||||
const response = await fetch(endpoint);
|
||||
if (!response.ok) {
|
||||
throw await this.handleError(response);
|
||||
}
|
||||
return response.json() as T;
|
||||
}
|
||||
|
||||
async post<TRequest, TResponse>(
|
||||
endpoint: string,
|
||||
data: TRequest
|
||||
): Promise<TResponse> {
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw await this.handleError(response);
|
||||
}
|
||||
|
||||
return response.json() as TResponse;
|
||||
}
|
||||
|
||||
private async handleError(response: Response): Promise<APIError> {
|
||||
const error: APIError = await response.json();
|
||||
console.error('API Error:', error);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage - Full type safety!
|
||||
const user = await client.get<User>('/users/1');
|
||||
console.log(user.name); // Autocomplete works!
|
||||
console.log(user.nameeee); // Error: Property 'nameeee' does not exist
|
||||
|
||||
const newPost = await client.post<CreatePostInput, Post>('/posts', {
|
||||
title: 'Hello',
|
||||
content: 'World'
|
||||
});
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Eliminate all 'any' types
|
||||
- Generic type parameters for flexibility
|
||||
- Catch typos at compile time
|
||||
- Better developer experience
|
||||
|
||||
---
|
||||
|
||||
### Strategy 3: Add Generics
|
||||
|
||||
**Before** (Type repetition, limited reusability):
|
||||
```typescript
|
||||
// Without generics - Need separate class for each type
|
||||
class UserRepository {
|
||||
private users: User[] = [];
|
||||
|
||||
add(user: User): void {
|
||||
this.users.push(user);
|
||||
}
|
||||
|
||||
findById(id: string): User | undefined {
|
||||
return this.users.find(u => u.id === id);
|
||||
}
|
||||
|
||||
findAll(): User[] {
|
||||
return [...this.users];
|
||||
}
|
||||
|
||||
remove(id: string): boolean {
|
||||
const index = this.users.findIndex(u => u.id === id);
|
||||
if (index > -1) {
|
||||
this.users.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class PostRepository {
|
||||
private posts: Post[] = [];
|
||||
|
||||
add(post: Post): void {
|
||||
this.posts.push(post);
|
||||
}
|
||||
|
||||
findById(id: string): Post | undefined {
|
||||
return this.posts.find(p => p.id === id);
|
||||
}
|
||||
|
||||
findAll(): Post[] {
|
||||
return [...this.posts];
|
||||
}
|
||||
|
||||
remove(id: string): boolean {
|
||||
const index = this.posts.findIndex(p => p.id === id);
|
||||
if (index > -1) {
|
||||
this.posts.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Need duplicate class for each entity type!
|
||||
```
|
||||
|
||||
**After** (Generic repository - DRY):
|
||||
```typescript
|
||||
// Generic base repository
|
||||
interface Entity {
|
||||
id: string;
|
||||
}
|
||||
|
||||
class Repository<T extends Entity> {
|
||||
private items: Map<string, T> = new Map();
|
||||
|
||||
add(item: T): void {
|
||||
this.items.set(item.id, item);
|
||||
}
|
||||
|
||||
findById(id: string): T | undefined {
|
||||
return this.items.get(id);
|
||||
}
|
||||
|
||||
findAll(): T[] {
|
||||
return Array.from(this.items.values());
|
||||
}
|
||||
|
||||
findBy(predicate: (item: T) => boolean): T[] {
|
||||
return this.findAll().filter(predicate);
|
||||
}
|
||||
|
||||
update(id: string, updates: Partial<T>): T | undefined {
|
||||
const item = this.items.get(id);
|
||||
if (item) {
|
||||
const updated = { ...item, ...updates };
|
||||
this.items.set(id, updated);
|
||||
return updated;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
remove(id: string): boolean {
|
||||
return this.items.delete(id);
|
||||
}
|
||||
|
||||
count(): number {
|
||||
return this.items.size;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage with specific types
|
||||
interface User extends Entity {
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface Post extends Entity {
|
||||
title: string;
|
||||
content: string;
|
||||
authorId: string;
|
||||
}
|
||||
|
||||
const userRepo = new Repository<User>();
|
||||
const postRepo = new Repository<Post>();
|
||||
|
||||
// Full type safety
|
||||
userRepo.add({ id: '1', name: 'John', email: 'john@example.com' });
|
||||
const user = userRepo.findById('1'); // Type: User | undefined
|
||||
const admins = userRepo.findBy(u => u.email.endsWith('@admin.com')); // Type: User[]
|
||||
|
||||
postRepo.add({ id: '1', title: 'Hello', content: 'World', authorId: '1' });
|
||||
const post = postRepo.findById('1'); // Type: Post | undefined
|
||||
```
|
||||
|
||||
**More generic examples**:
|
||||
```typescript
|
||||
// Generic API response wrapper
|
||||
interface APIResponse<T> {
|
||||
data: T;
|
||||
status: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
async function fetchData<T>(url: string): Promise<APIResponse<T>> {
|
||||
const response = await fetch(url);
|
||||
return response.json();
|
||||
}
|
||||
|
||||
// Usage
|
||||
const userResponse = await fetchData<User>('/api/user');
|
||||
const users = userResponse.data; // Type: User
|
||||
|
||||
// Generic event emitter
|
||||
class EventEmitter<TEvents extends Record<string, any>> {
|
||||
private handlers: Partial<{
|
||||
[K in keyof TEvents]: Array<(data: TEvents[K]) => void>;
|
||||
}> = {};
|
||||
|
||||
on<K extends keyof TEvents>(
|
||||
event: K,
|
||||
handler: (data: TEvents[K]) => void
|
||||
): void {
|
||||
if (!this.handlers[event]) {
|
||||
this.handlers[event] = [];
|
||||
}
|
||||
this.handlers[event]!.push(handler);
|
||||
}
|
||||
|
||||
emit<K extends keyof TEvents>(event: K, data: TEvents[K]): void {
|
||||
const handlers = this.handlers[event] || [];
|
||||
handlers.forEach(handler => handler(data));
|
||||
}
|
||||
}
|
||||
|
||||
// Usage with typed events
|
||||
interface AppEvents {
|
||||
'user:login': { userId: string; timestamp: Date };
|
||||
'user:logout': { userId: string };
|
||||
'post:created': { postId: string; authorId: string };
|
||||
}
|
||||
|
||||
const emitter = new EventEmitter<AppEvents>();
|
||||
|
||||
emitter.on('user:login', (data) => {
|
||||
// data is typed as { userId: string; timestamp: Date }
|
||||
console.log(`User ${data.userId} logged in at ${data.timestamp}`);
|
||||
});
|
||||
|
||||
emitter.emit('user:login', {
|
||||
userId: '123',
|
||||
timestamp: new Date()
|
||||
}); // Type safe!
|
||||
|
||||
// This would error:
|
||||
// emitter.emit('user:login', { userId: 123 }); // Error: number not assignable to string
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- DRY: Single implementation for all types
|
||||
- Type safety: Generic constraints ensure correctness
|
||||
- Reusability: Works with any type that extends Entity
|
||||
- Maintainability: Fix bugs once, benefits all uses
|
||||
|
||||
---
|
||||
|
||||
### Strategy 4: Migrate JavaScript to TypeScript
|
||||
|
||||
**Before** (JavaScript with no types):
|
||||
```javascript
|
||||
// user-service.js
|
||||
const bcrypt = require('bcrypt');
|
||||
|
||||
class UserService {
|
||||
constructor(database, emailService) {
|
||||
this.db = database;
|
||||
this.emailService = emailService;
|
||||
}
|
||||
|
||||
async registerUser(userData) {
|
||||
// Validate email
|
||||
if (!userData.email || !userData.email.includes('@')) {
|
||||
throw new Error('Invalid email');
|
||||
}
|
||||
|
||||
// Check if user exists
|
||||
const existing = await this.db.users.findOne({ email: userData.email });
|
||||
if (existing) {
|
||||
throw new Error('User already exists');
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const hashedPassword = await bcrypt.hash(userData.password, 10);
|
||||
|
||||
// Create user
|
||||
const user = await this.db.users.create({
|
||||
email: userData.email,
|
||||
password: hashedPassword,
|
||||
name: userData.name,
|
||||
createdAt: new Date()
|
||||
});
|
||||
|
||||
// Send welcome email
|
||||
await this.emailService.sendWelcome(user.email);
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name
|
||||
};
|
||||
}
|
||||
|
||||
async login(email, password) {
|
||||
const user = await this.db.users.findOne({ email });
|
||||
if (!user) {
|
||||
throw new Error('Invalid credentials');
|
||||
}
|
||||
|
||||
const passwordMatch = await bcrypt.compare(password, user.password);
|
||||
if (!passwordMatch) {
|
||||
throw new Error('Invalid credentials');
|
||||
}
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserService;
|
||||
```
|
||||
|
||||
**After** (TypeScript with full types):
|
||||
```typescript
|
||||
// types/user.ts
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
password: string;
|
||||
name: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface CreateUserInput {
|
||||
email: string;
|
||||
password: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface UserDTO {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface LoginCredentials {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
// types/database.ts
|
||||
export interface IDatabase {
|
||||
users: {
|
||||
findOne(query: { email: string }): Promise<User | null>;
|
||||
create(data: Omit<User, 'id' | 'updatedAt'>): Promise<User>;
|
||||
};
|
||||
}
|
||||
|
||||
// types/email.ts
|
||||
export interface IEmailService {
|
||||
sendWelcome(email: string): Promise<void>;
|
||||
}
|
||||
|
||||
// user-service.ts
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import {
|
||||
User,
|
||||
CreateUserInput,
|
||||
UserDTO,
|
||||
LoginCredentials
|
||||
} from './types/user';
|
||||
import { IDatabase } from './types/database';
|
||||
import { IEmailService } from './types/email';
|
||||
|
||||
export class UserService {
|
||||
constructor(
|
||||
private readonly db: IDatabase,
|
||||
private readonly emailService: IEmailService
|
||||
) {}
|
||||
|
||||
async registerUser(userData: CreateUserInput): Promise<UserDTO> {
|
||||
// Validate email
|
||||
this.validateEmail(userData.email);
|
||||
|
||||
// Check if user exists
|
||||
const existing = await this.db.users.findOne({ email: userData.email });
|
||||
if (existing) {
|
||||
throw new UserAlreadyExistsError(userData.email);
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const hashedPassword = await bcrypt.hash(userData.password, 10);
|
||||
|
||||
// Create user
|
||||
const user = await this.db.users.create({
|
||||
email: userData.email,
|
||||
password: hashedPassword,
|
||||
name: userData.name,
|
||||
createdAt: new Date()
|
||||
});
|
||||
|
||||
// Send welcome email
|
||||
await this.emailService.sendWelcome(user.email);
|
||||
|
||||
return this.toDTO(user);
|
||||
}
|
||||
|
||||
async login(credentials: LoginCredentials): Promise<UserDTO> {
|
||||
const user = await this.db.users.findOne({ email: credentials.email });
|
||||
if (!user) {
|
||||
throw new InvalidCredentialsError();
|
||||
}
|
||||
|
||||
const passwordMatch = await bcrypt.compare(
|
||||
credentials.password,
|
||||
user.password
|
||||
);
|
||||
if (!passwordMatch) {
|
||||
throw new InvalidCredentialsError();
|
||||
}
|
||||
|
||||
return this.toDTO(user);
|
||||
}
|
||||
|
||||
private validateEmail(email: string): void {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
throw new InvalidEmailError(email);
|
||||
}
|
||||
}
|
||||
|
||||
private toDTO(user: User): UserDTO {
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Custom error classes with types
|
||||
export class UserAlreadyExistsError extends Error {
|
||||
constructor(email: string) {
|
||||
super(`User with email ${email} already exists`);
|
||||
this.name = 'UserAlreadyExistsError';
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidCredentialsError extends Error {
|
||||
constructor() {
|
||||
super('Invalid credentials');
|
||||
this.name = 'InvalidCredentialsError';
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidEmailError extends Error {
|
||||
constructor(email: string) {
|
||||
super(`Invalid email format: ${email}`);
|
||||
this.name = 'InvalidEmailError';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Migration steps**:
|
||||
1. Rename `.js` to `.ts`
|
||||
2. Add interface definitions
|
||||
3. Add type annotations to parameters and return types
|
||||
4. Replace `require()` with `import`
|
||||
5. Replace `module.exports` with `export`
|
||||
6. Add custom error classes with types
|
||||
7. Extract utility functions with proper types
|
||||
8. Fix all TypeScript errors
|
||||
9. Enable strict mode gradually
|
||||
|
||||
**Improvements**:
|
||||
- Full compile-time type checking
|
||||
- Better refactoring support
|
||||
- Self-documenting code
|
||||
- Catch errors before runtime
|
||||
- Modern ES6+ features
|
||||
|
||||
---
|
||||
|
||||
### Strategy 5: Enable Strict Mode
|
||||
|
||||
**Before** (tsconfig.json - Lenient):
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"strict": false,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**After** (tsconfig.json - Strict):
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
|
||||
/* Additional Checks */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
|
||||
/* Module Resolution */
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": false,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Impact of strict mode**:
|
||||
|
||||
```typescript
|
||||
// Before: Implicit any allowed
|
||||
function process(data) { // No error
|
||||
return data.value;
|
||||
}
|
||||
|
||||
// After: Must specify types
|
||||
function process(data: DataInput): number { // Required
|
||||
return data.value;
|
||||
}
|
||||
|
||||
// Before: Null not checked
|
||||
function getUser(id: string): User {
|
||||
return database.findById(id); // Could be null!
|
||||
}
|
||||
|
||||
// After: Must handle null
|
||||
function getUser(id: string): User | null {
|
||||
return database.findById(id);
|
||||
}
|
||||
|
||||
const user = getUser('123');
|
||||
console.log(user.name); // Error: Object is possibly 'null'
|
||||
|
||||
// Must check:
|
||||
if (user) {
|
||||
console.log(user.name); // OK
|
||||
}
|
||||
|
||||
// Or use optional chaining:
|
||||
console.log(user?.name); // OK
|
||||
|
||||
// Before: Array access unchecked
|
||||
const users: User[] = [];
|
||||
const first = users[0]; // Type: User (wrong! could be undefined)
|
||||
first.email; // Runtime error if array is empty
|
||||
|
||||
// After: Array access checked
|
||||
const users: User[] = [];
|
||||
const first = users[0]; // Type: User | undefined (correct!)
|
||||
first.email; // Error: Object is possibly 'undefined'
|
||||
|
||||
// Must check:
|
||||
if (first) {
|
||||
first.email; // OK
|
||||
}
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Catch more errors at compile time
|
||||
- Safer null/undefined handling
|
||||
- No implicit any types
|
||||
- More robust code
|
||||
- Better IDE support
|
||||
|
||||
---
|
||||
|
||||
## Output Format
|
||||
|
||||
```markdown
|
||||
# Type Safety Improvement Report
|
||||
|
||||
## Strategy Applied: <strategy-name>
|
||||
|
||||
**Scope**: <path>
|
||||
**Strict Mode**: <enabled/disabled>
|
||||
|
||||
## Before
|
||||
|
||||
**Type Coverage**:
|
||||
- Files with types: <count> / <total> (<percentage>%)
|
||||
- 'any' usage: <count> instances
|
||||
- Implicit any: <count> instances
|
||||
- Type errors: <count>
|
||||
|
||||
**Issues**:
|
||||
- <issue 1>
|
||||
- <issue 2>
|
||||
|
||||
## Changes Made
|
||||
|
||||
### Files Modified
|
||||
- <file-1>: Added type annotations
|
||||
- <file-2>: Eliminated 'any' types
|
||||
- <file-3>: Migrated JS to TS
|
||||
|
||||
### Type Definitions Added
|
||||
```typescript
|
||||
<new-interfaces-and-types>
|
||||
```
|
||||
|
||||
### Code Examples
|
||||
|
||||
**Before**:
|
||||
```typescript
|
||||
<code-without-types>
|
||||
```
|
||||
|
||||
**After**:
|
||||
```typescript
|
||||
<code-with-types>
|
||||
```
|
||||
|
||||
## After
|
||||
|
||||
**Type Coverage**:
|
||||
- Files with types: <count> / <total> (<percentage>%)
|
||||
- 'any' usage: <count> instances (<percentage>% reduction)
|
||||
- Implicit any: 0 (eliminated)
|
||||
- Type errors: 0 (all fixed)
|
||||
|
||||
**Improvements**:
|
||||
- Type safety: <before>% → <after>%
|
||||
- Compile-time error detection: +<count> errors caught
|
||||
- IDE autocomplete: Significantly improved
|
||||
- Refactoring safety: Enhanced
|
||||
|
||||
## Verification
|
||||
|
||||
**Type Check**:
|
||||
```bash
|
||||
npx tsc --noEmit
|
||||
# No errors
|
||||
```
|
||||
|
||||
**Tests**:
|
||||
- All tests passing: YES
|
||||
- Coverage: <before>% → <after>%
|
||||
|
||||
## Migration Guide
|
||||
|
||||
**For Consumers**:
|
||||
```typescript
|
||||
// Old usage (if breaking changes)
|
||||
<old-usage>
|
||||
|
||||
// New usage
|
||||
<new-usage>
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
**Additional Improvements**:
|
||||
1. Enable stricter compiler options
|
||||
2. Add runtime type validation (Zod, io-ts)
|
||||
3. Generate types from API schemas
|
||||
4. Add JSDoc for better documentation
|
||||
|
||||
---
|
||||
|
||||
**Type Safety Improved**: Code is now safer and more maintainable.
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
**No TypeScript configuration**:
|
||||
```
|
||||
Error: No tsconfig.json found in project
|
||||
|
||||
To use this operation, initialize TypeScript:
|
||||
1. npm install -D typescript
|
||||
2. npx tsc --init
|
||||
3. Configure tsconfig.json
|
||||
4. Retry operation
|
||||
```
|
||||
|
||||
**Too many type errors**:
|
||||
```
|
||||
Warning: Found <count> type errors. This is a large migration.
|
||||
|
||||
Recommendation: Gradual migration approach:
|
||||
1. Start with strict: false
|
||||
2. Fix implicit any errors first
|
||||
3. Enable strictNullChecks
|
||||
4. Enable other strict options one by one
|
||||
5. Fix errors incrementally
|
||||
```
|
||||
Reference in New Issue
Block a user