UNPKG

aios-core

Version:

Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework

1,329 lines (1,135 loc) 40.5 kB
const fs = require('fs').promises; const path = require('path'); const chalk = require('chalk'); // INS-4.12: Optional dev-time deps — wrap in try-catch for brownfield installs let ESLint, prettier, jscodeshift; try { ({ ESLint } = require('eslint')); } catch { ESLint = null; } try { prettier = require('prettier'); } catch { prettier = null; } try { jscodeshift = require('jscodeshift'); } catch { jscodeshift = null; } /** * Automated code quality improvement system * Applies automatic fixes and improvements to enhance code quality */ class CodeQualityImprover { constructor(options = {}) { this.rootPath = options.rootPath || process.cwd(); this.improvements = []; this.metrics = new Map(); this.eslint = null; this.prettierConfig = null; this.improvementPatterns = new Map(); this.initializePatterns(); } /** * Initialize improvement patterns */ initializePatterns() { // Code formatting improvements this.improvementPatterns.set('formatting', { name: 'Code Formatting', description: 'Apply consistent code formatting', improver: this.improveFormatting.bind(this), priority: 'medium', automatic: true }); // Linting fixes this.improvementPatterns.set('linting', { name: 'Linting Fixes', description: 'Fix linting errors and warnings', improver: this.improveLinting.bind(this), priority: 'high', automatic: true }); // Modern syntax upgrades this.improvementPatterns.set('modern_syntax', { name: 'Modern Syntax', description: 'Upgrade to modern JavaScript syntax', improver: this.upgradeToModernSyntax.bind(this), priority: 'medium', automatic: true }); // Import optimization this.improvementPatterns.set('optimize_imports', { name: 'Optimize Imports', description: 'Organize and optimize import statements', improver: this.optimizeImports.bind(this), priority: 'low', automatic: true }); // Dead code elimination this.improvementPatterns.set('remove_unused', { name: 'Remove Unused Code', description: 'Remove unused variables and functions', improver: this.removeUnusedCode.bind(this), priority: 'high', automatic: false }); // Consistent naming this.improvementPatterns.set('naming_conventions', { name: 'Naming Conventions', description: 'Apply consistent naming conventions', improver: this.improveNaming.bind(this), priority: 'medium', automatic: false }); // Error handling improvements this.improvementPatterns.set('error_handling', { name: 'Error Handling', description: 'Improve error handling patterns', improver: this.improveErrorHandling.bind(this), priority: 'high', automatic: false }); // Async/await conversion this.improvementPatterns.set('async_await', { name: 'Async/Await Conversion', description: 'Convert promises to async/await', improver: this.convertToAsyncAwait.bind(this), priority: 'medium', automatic: true }); // Type safety improvements this.improvementPatterns.set('type_safety', { name: 'Type Safety', description: 'Add type checks and validations', improver: this.improveTypeSafety.bind(this), priority: 'high', automatic: false }); // Documentation generation this.improvementPatterns.set('documentation', { name: 'Documentation', description: 'Generate missing JSDoc comments', improver: this.generateDocumentation.bind(this), priority: 'medium', automatic: false }); } /** * Initialize tools */ async initialize() { // INS-4.12: Guard optional deps — may be null in brownfield installs if (ESLint) { this.eslint = new ESLint({ fix: true, baseConfig: await this.loadESLintConfig(), useEslintrc: true }); } else { console.warn('⚠️ eslint not available — linting improvements disabled'); } if (prettier) { this.prettierConfig = await this.loadPrettierConfig(); } else { console.warn('⚠️ prettier not available — formatting improvements disabled'); } } /** * Analyze and improve code quality */ async improveCode(_filePath, options = {}) { console.log(chalk.blue(`🎯 Improving: ${_filePath}`)); try { const content = await fs.readFile(_filePath, 'utf-8'); const fileType = path.extname(_filePath); if (!['.js', '.jsx', '.ts', '.tsx'].includes(fileType)) { return { _filePath, improvements: [], error: 'Unsupported file type' }; } // Calculate initial metrics const initialMetrics = await this.calculateMetrics(content, filePath); this.metrics.set(_filePath, { before: initialMetrics }); // Clear previous improvements this.improvements = []; let improvedContent = content; const appliedImprovements = []; // Apply improvement patterns for (const [patternId, pattern] of this.improvementPatterns) { if (options.patterns && !options.patterns.includes(patternId)) { continue; } if (!options.manual && !pattern.automatic) { continue; // Skip manual improvements in automatic mode } try { const result = await pattern.improver(improvedContent, _filePath, options); if (result.improved) { improvedContent = result.content; appliedImprovements.push({ patternId, pattern: pattern.name, priority: pattern.priority, changes: result.changes, impact: result.impact || 'medium' }); this.improvements.push(...result.improvements || []); } } catch (_error) { console.warn(chalk.yellow(`Failed to apply ${pattern.name}: ${error.message}`)); } } // Calculate final metrics const finalMetrics = await this.calculateMetrics(improvedContent, filePath); this.metrics.get(_filePath).after = finalMetrics; // Calculate improvement score const improvementScore = this.calculateImprovementScore(initialMetrics, finalMetrics); return { _filePath, originalContent: content, improvedContent, improvements: appliedImprovements, metrics: { before: initialMetrics, after: finalMetrics, improvementScore }, changed: content !== improvedContent }; } catch (_error) { return { _filePath, improvements: [], error: error.message }; } } /** * Calculate code metrics */ async calculateMetrics(content, filePath) { const metrics = { lines: content.split('\n').length, complexity: 0, maintainability: 0, issues: 0, coverage: 0 }; try { // Linting issues if (!this.eslint) return metrics; const lintResults = await this.eslint.lintText(content, { _filePath }); metrics.issues = lintResults[0]?.errorCount + lintResults[0]?.warningCount || 0; // Cyclomatic complexity (simplified) metrics.complexity = this.calculateCyclomaticComplexity(content); // Maintainability index (simplified) metrics.maintainability = this.calculateMaintainabilityIndex(content); // Documentation coverage metrics.coverage = this.calculateDocumentationCoverage(content); } catch (_error) { // Metrics calculation failed, use defaults } return metrics; } // Improvement functions async improveFormatting(content, _filePath, options) { try { if (!prettier) { return { improved: false, content, changes: [] }; } const formatted = await prettier.format(content, { ...this.prettierConfig, filepath: _filePath }); return { improved: formatted !== content, content: formatted, changes: formatted !== content ? ['Applied consistent formatting'] : [], improvements: [{ type: 'formatting', description: 'Applied Prettier formatting', line: 0 }] }; } catch (_error) { return { improved: false, content, error: error.message }; } } async improveLinting(content, _filePath, options) { try { if (!this.eslint) { return { improved: false, content, changes: [] }; } const results = await this.eslint.lintText(content, { _filePath }); if (results[0]?.output) { return { improved: true, content: results[0].output, changes: this.summarizeLintFixes(results[0]), improvements: results[0].messages.map(msg => ({ type: 'linting', description: msg.message, line: msg.line, severity: msg.severity === 2 ? 'error' : 'warning' })) }; } return { improved: false, content }; } catch (_error) { return { improved: false, content, error: error.message }; } } async upgradeToModernSyntax(content, _filePath, options) { const j = jscodeshift; let improved = false; const changes = []; try { // Convert var to let/const let ast = j(content); const _varToLetConst = ast .find(j.VariableDeclaration, { kind: 'var' }) .forEach(path => { const isReassigned = this.isVariableReassigned(j, path); path.node.kind = isReassigned ? 'let' : 'const'; improved = true; }); if (improved) { changes.push('Converted var to let/const'); } // Convert function expressions to arrow functions (where appropriate) ast.find(j.FunctionExpression) .filter(path => { // Don't convert if it uses 'this' or 'arguments' const usesThis = j(_path).find(j.ThisExpression).length > 0; const usesArguments = j(_path).find(j.Identifier, { name: 'arguments' }).length > 0; return !usesThis && !usesArguments && !path.node.id; }) .forEach(path => { const arrowFunction = j.arrowFunctionExpression( path.node.params, path.node.body, path.node.body.type !== 'BlockStatement' ); j(_path).replaceWith(arrowFunction); improved = true; }); if (changes.length > 0) { changes.push('Converted function expressions to arrow functions'); } // Template literals ast.find(j.BinaryExpression, { operator: '+' }) .filter(path => { // Check if it's string concatenation return path.node.left.type === 'Literal' || path.node.right.type === 'Literal'; }) .forEach(path => { // Convert to template literal (simplified) improved = true; }); const result = ast.toSource(); return { improved, content: improved ? result : content, changes, improvements: changes.map(change => ({ type: 'modern_syntax', description: change, line: 0 })) }; } catch (_error) { return { improved: false, content, error: error.message }; } } async optimizeImports(content, _filePath, options) { const j = jscodeshift; let improved = false; const changes = []; try { const ast = j(content); // Group imports by source const imports = new Map(); ast.find(j.ImportDeclaration) .forEach(path => { const source = path.node.source.value; if (!imports.has(source)) { imports.set(source, []); } imports.get(source).push(_path); }); // Merge imports from same source for (const [source, importPaths] of imports) { if (importPaths.length > 1) { // Merge specifiers const allSpecifiers = []; importPaths.forEach(path => { allSpecifiers.push(...path.node.specifiers); }); // Keep first import, remove others importPaths[0].node.specifiers = allSpecifiers; for (let i = 1; i < importPaths.length; i++) { j(importPaths[i]).remove(); } improved = true; changes.push(`Merged imports from ${source}`); } } // Sort imports const importNodes = []; ast.find(j.ImportDeclaration) .forEach(path => { importNodes.push(path.node); j(_path).remove(); }); if (importNodes.length > 0) { // Sort by: external packages, internal absolute, internal relative importNodes.sort((a, b) => { const aSource = a.source.value; const bSource = b.source.value; const aExternal = !aSource.startsWith('.') && !aSource.startsWith('/'); const bExternal = !bSource.startsWith('.') && !bSource.startsWith('/'); if (aExternal && !bExternal) return -1; if (!aExternal && bExternal) return 1; return aSource.localeCompare(bSource); }); // Re-insert sorted imports at the beginning const program = ast.find(j.Program); importNodes.reverse().forEach(node => { program.get('body').unshift(node); }); improved = true; changes.push('Sorted imports'); } const result = ast.toSource(); return { improved, content: improved ? result : content, changes, improvements: changes.map(change => ({ type: 'optimize_imports', description: change, line: 0 })) }; } catch (_error) { return { improved: false, content, error: error.message }; } } async removeUnusedCode(content, _filePath, options) { const j = jscodeshift; let improved = false; const changes = []; const improvements = []; try { const ast = j(content); // Find all variable declarations and their usage const declaredVars = new Map(); const usedVars = new Set(); // Collect declarations ast.find(j.VariableDeclarator) .forEach(path => { if (path.node.id.type === 'Identifier') { declaredVars.set(path.node.id.name, path); } }); // Collect usage ast.find(j.Identifier) .filter(path => { // Only count as used if it's not the declaration itself const parent = path.parent.node; return !(parent.type === 'VariableDeclarator' && parent.id === path.node); }) .forEach(path => { usedVars.add(path.node.name); }); // Remove unused variables for (const [varName, declaratorPath] of declaredVars) { if (!usedVars.has(varName) && !this.isExported(declaratorPath)) { const declaration = declaratorPath.parent; if (declaration.node.declarations.length === 1) { // Remove entire declaration j(declaration).remove(); } else { // Remove just this declarator j(declaratorPath).remove(); } improved = true; changes.push(`Removed unused variable: ${varName}`); improvements.push({ type: 'remove_unused', description: `Removed unused variable: ${varName}`, line: declaratorPath.node.loc?.start.line || 0 }); } } // Find unused functions const declaredFunctions = new Map(); const calledFunctions = new Set(); ast.find(j.FunctionDeclaration) .forEach(path => { if (path.node.id) { declaredFunctions.set(path.node.id.name, path); } }); ast.find(j.CallExpression) .forEach(path => { if (path.node.callee.type === 'Identifier') { calledFunctions.add(path.node.callee.name); } }); // Remove unused functions for (const [funcName, funcPath] of declaredFunctions) { if (!calledFunctions.has(funcName) && !this.isExported(funcPath)) { j(funcPath).remove(); improved = true; changes.push(`Removed unused function: ${funcName}`); improvements.push({ type: 'remove_unused', description: `Removed unused function: ${funcName}`, line: funcPath.node.loc?.start.line || 0 }); } } const result = ast.toSource(); return { improved, content: improved ? result : content, changes, improvements }; } catch (_error) { return { improved: false, content, error: error.message }; } } async improveNaming(content, _filePath, options) { const j = jscodeshift; let improved = false; const changes = []; const improvements = []; try { const ast = j(content); // Convert snake_case to camelCase for variables and functions ast.find(j.Identifier) .filter(path => { const name = path.node.name; return name.includes('_') && (path.parent.node.type === 'VariableDeclarator' || path.parent.node.type === 'FunctionDeclaration'); }) .forEach(path => { const oldName = path.node.name; const newName = this.snakeToCamel(oldName); // Rename all occurrences ast.find(j.Identifier, { name: oldName }) .forEach(p => { p.node.name = newName; }); improved = true; changes.push(`Renamed ${oldName} to ${newName}`); improvements.push({ type: 'naming_conventions', description: `Renamed ${oldName} to ${newName}`, line: path.node.loc?.start.line || 0 }); }); // Ensure classes start with uppercase ast.find(j.ClassDeclaration) .filter(path => { const name = path.node.id?.name; return name && name[0] !== name[0].toUpperCase(); }) .forEach(path => { const oldName = path.node.id.name; const newName = oldName[0].toUpperCase() + oldName.slice(1); // Rename all occurrences ast.find(j.Identifier, { name: oldName }) .forEach(p => { p.node.name = newName; }); improved = true; changes.push(`Renamed class ${oldName} to ${newName}`); improvements.push({ type: 'naming_conventions', description: `Renamed class ${oldName} to ${newName}`, line: path.node.loc?.start.line || 0 }); }); // Ensure constants are UPPER_SNAKE_CASE ast.find(j.VariableDeclaration, { kind: 'const' }) .forEach(path => { path.node.declarations.forEach(declarator => { if (declarator.id.type === 'Identifier') { const name = declarator.id.name; // Check if it looks like a constant (all caps or should be) if (this.shouldBeConstantCase(declarator) && !this.isConstantCase(name)) { const oldName = name; const newName = this.toConstantCase(name); // Rename all occurrences ast.find(j.Identifier, { name: oldName }) .forEach(p => { p.node.name = newName; }); improved = true; changes.push(`Renamed constant ${oldName} to ${newName}`); improvements.push({ type: 'naming_conventions', description: `Renamed constant ${oldName} to ${newName}`, line: declarator.loc?.start.line || 0 }); } } }); }); const result = ast.toSource(); return { improved, content: improved ? result : content, changes, improvements }; } catch (_error) { return { improved: false, content, error: error.message }; } } async improveErrorHandling(content, _filePath, options) { const j = jscodeshift; let improved = false; const changes = []; const improvements = []; try { const ast = j(content); // Add try-catch to async functions without error handling ast.find(j.FunctionDeclaration) .filter(path => path.node.async) .forEach(path => { const hasErrorHandling = j(_path).find(j.TryStatement).length > 0; if (!hasErrorHandling && path.node.body.body.length > 0) { // Wrap body in try-catch const originalBody = path.node.body.body; const tryStatement = j.tryStatement( j.blockStatement(originalBody), j.catchClause( j.identifier('error'), j.blockStatement([ j.expressionStatement( j.callExpression( j.memberExpression( j.identifier('console'), j.identifier('error') ), [j.identifier('error')] ) ), j.throwStatement(j.identifier('error')) ]) ) ); path.node.body.body = [tryStatement]; improved = true; const funcName = path.node.id?.name || 'anonymous'; changes.push(`Added error handling to ${funcName}`); improvements.push({ type: 'error_handling', description: `Added try-catch to async function ${funcName}`, line: path.node.loc?.start.line || 0 }); } }); // Improve catch blocks that swallow errors ast.find(j.CatchClause) .filter(path => { // Check if catch block is empty or only logs const body = path.node.body.body; return body.length === 0 || (body.length === 1 && this.isOnlyConsoleLog(body[0])); }) .forEach(path => { // Add proper error handling const errorParam = path.node.param || j.identifier('error'); path.node.body.body = [ j.expressionStatement( j.callExpression( j.memberExpression( j.identifier('console'), j.identifier('error') ), [j.literal('Error caught:'), errorParam] ) ), j.throwStatement(errorParam) ]; improved = true; changes.push('Improved catch block to properly handle errors'); improvements.push({ type: 'error_handling', description: 'Added proper error re-throwing in catch block', line: path.node.loc?.start.line || 0 }); }); const result = ast.toSource(); return { improved, content: improved ? result : content, changes, improvements }; } catch (_error) { return { improved: false, content, error: error.message }; } } async convertToAsyncAwait(content, _filePath, options) { const j = jscodeshift; let improved = false; const changes = []; const improvements = []; try { const ast = j(content); // Convert .then().catch() chains to async/await ast.find(j.CallExpression) .filter(path => { return path.node.callee.type === 'MemberExpression' && path.node.callee.property.name === 'then'; }) .forEach(path => { // Find the containing function const containingFunction = j(_path).closest(j.Function); if (containingFunction.length > 0) { const func = containingFunction.get(); // Make function async if not already if (!func.node.async) { func.node.async = true; } // Convert promise chain to await // This is simplified - real implementation would be more complex improved = true; changes.push('Converted promise chain to async/await'); improvements.push({ type: 'async_await', description: 'Converted .then() chain to async/await', line: path.node.loc?.start.line || 0 }); } }); // Convert Promise callbacks to async functions ast.find(j.NewExpression) .filter(path => { return path.node.callee.name === 'Promise' && path.node.arguments.length > 0 && path.node.arguments[0].type === 'FunctionExpression'; }) .forEach(path => { const promiseCallback = path.node.arguments[0]; // Convert to async function const asyncFunction = j.functionExpression( promiseCallback.id, promiseCallback.params, promiseCallback.body, promiseCallback.generator, true // async ); path.node.arguments[0] = asyncFunction; improved = true; changes.push('Converted Promise constructor to async function'); improvements.push({ type: 'async_await', description: 'Made Promise callback async', line: path.node.loc?.start.line || 0 }); }); const result = ast.toSource(); return { improved, content: improved ? result : content, changes, improvements }; } catch (_error) { return { improved: false, content, error: error.message }; } } async improveTypeSafety(content, _filePath, options) { const j = jscodeshift; let improved = false; const changes = []; const improvements = []; try { const ast = j(content); // Add parameter validation to functions ast.find(j.FunctionDeclaration) .forEach(path => { const params = path.node.params; if (params.length > 0) { const validationStatements = []; params.forEach(param => { if (param.type === 'Identifier') { // Add basic validation validationStatements.push( j.ifStatement( j.binaryExpression( '==', param, j.identifier('undefined') ), j.throwStatement( j.newExpression( j.identifier('Error'), [j.literal(`Parameter '${param.name}' is required`)] ) ) ) ); } }); if (validationStatements.length > 0) { // Insert at beginning of function body path.node.body.body.unshift(...validationStatements); improved = true; const funcName = path.node.id?.name || 'anonymous'; changes.push(`Added parameter validation to ${funcName}`); improvements.push({ type: 'type_safety', description: `Added parameter validation to function ${funcName}`, line: path.node.loc?.start.line || 0 }); } } }); // Add null checks before property access ast.find(j.MemberExpression) .filter(path => { // Check if it's a chain that could throw return path.node.object.type === 'MemberExpression' || (path.node.object.type === 'Identifier' && !this.isKnownSafeObject(path.node.object.name)); }) .forEach(path => { // Convert to optional chaining if not already if (!path.node.optional) { path.node.optional = true; improved = true; improvements.push({ type: 'type_safety', description: 'Added optional chaining for safer property access', line: path.node.loc?.start.line || 0 }); } }); if (improvements.length > 0) { changes.push('Added type safety improvements'); } const result = ast.toSource(); return { improved, content: improved ? result : content, changes, improvements }; } catch (_error) { return { improved: false, content, error: error.message }; } } async generateDocumentation(content, _filePath, options) { const j = jscodeshift; let improved = false; const changes = []; const improvements = []; try { const ast = j(content); // Add JSDoc to functions without documentation ast.find(j.FunctionDeclaration) .filter(path => { // Check if function already has JSDoc const comments = path.node.leadingComments || []; return !comments.some(c => c.type === 'CommentBlock' && c.value.includes('*')); }) .forEach(path => { const funcName = path.node.id?.name || 'anonymous'; const params = path.node.params; const isAsync = path.node.async; // Generate JSDoc let jsdoc = '/**\n'; jsdoc += ` * ${this.generateFunctionDescription(funcName)}\n`; params.forEach(param => { const paramName = param.type === 'Identifier' ? param.name : 'param'; jsdoc += ` * @param {*} ${paramName} - ${this.generateParamDescription(paramName)}\n`; }); jsdoc += ` * @returns {${isAsync ? 'Promise<*>' : '*'}} ${this.generateReturnDescription(funcName)}\n`; jsdoc += ' */'; // Add JSDoc comment path.node.leadingComments = [ j.commentBlock(jsdoc.replace('/**', '*').replace('*/', ''), true) ]; improved = true; changes.push(`Added JSDoc to ${funcName}`); improvements.push({ type: 'documentation', description: `Generated JSDoc for function ${funcName}`, line: path.node.loc?.start.line || 0 }); }); // Add JSDoc to classes ast.find(j.ClassDeclaration) .filter(path => { const comments = path.node.leadingComments || []; return !comments.some(c => c.type === 'CommentBlock' && c.value.includes('*')); }) .forEach(path => { const className = path.node.id?.name || 'Class'; let jsdoc = '/**\n'; jsdoc += ` * ${this.generateClassDescription(className)}\n`; jsdoc += ' */'; path.node.leadingComments = [ j.commentBlock(jsdoc.replace('/**', '*').replace('*/', ''), true) ]; improved = true; changes.push(`Added JSDoc to class ${className}`); improvements.push({ type: 'documentation', description: `Generated JSDoc for class ${className}`, line: path.node.loc?.start.line || 0 }); }); const result = ast.toSource(); return { improved, content: improved ? result : content, changes, improvements }; } catch (_error) { return { improved: false, content, error: error.message }; } } // Helper methods async loadESLintConfig() { try { const configPath = path.join(this.rootPath, '.eslintrc.json'); const content = await fs.readFile(configPath, 'utf-8'); return JSON.parse(content); } catch (_error) { // Return default config return { env: { es2021: true, node: true }, extends: ['eslint:recommended'], parserOptions: { ecmaVersion: 12, sourceType: 'module' }, rules: { 'no-unused-vars': 'error', 'no-console': 'warn', 'semi': ['error', 'always'], 'quotes': ['error', 'single'] } }; } } async loadPrettierConfig() { try { const configPath = path.join(this.rootPath, '.prettierrc'); const content = await fs.readFile(configPath, 'utf-8'); return JSON.parse(content); } catch (_error) { // Return default config return { semi: true, singleQuote: true, tabWidth: 2, trailingComma: 'es5', printWidth: 80 }; } } calculateCyclomaticComplexity(content) { // Simplified complexity calculation let complexity = 1; const complexityPatterns = [ /\bif\s*\(/g, /\belse\s+if\s*\(/g, /\bwhile\s*\(/g, /\bfor\s*\(/g, /\bcase\s+/g, /\bcatch\s*\(/g, /\?\s*[^:]+\s*:/g, // ternary /\|\|/g, /&&/g ]; complexityPatterns.forEach(pattern => { const matches = content.match(pattern); if (matches) { complexity += matches.length; } }); return complexity; } calculateMaintainabilityIndex(content) { // Simplified maintainability index (0-100) const lines = content.split('\n').length; const complexity = this.calculateCyclomaticComplexity(content); const comments = (content.match(/\/\//g) || []).length + (content.match(/\/\*/g) || []).length; // Simple formula const commentRatio = comments / lines; const complexityRatio = complexity / lines; const maintainability = Math.min(100, Math.max(0, 100 - (complexityRatio * 50) + (commentRatio * 20) )); return Math.round(maintainability); } calculateDocumentationCoverage(content) { // Calculate percentage of documented functions/classes const functionMatches = content.match(/function\s+\w+|class\s+\w+/g) || []; const jsdocMatches = content.match(/\/\*\*[\s\S]*?\*\//g) || []; if (functionMatches.length === 0) return 100; const coverage = (jsdocMatches.length / functionMatches.length) * 100; return Math.min(100, Math.round(coverage)); } calculateImprovementScore(before, after) { const improvements = { issues: Math.max(0, before.issues - after.issues), complexity: Math.max(0, before.complexity - after.complexity), maintainability: Math.max(0, after.maintainability - before.maintainability), coverage: Math.max(0, after.coverage - before.coverage) }; // Calculate weighted score const score = ( improvements.issues * 3 + improvements.complexity * 2 + improvements.maintainability + improvements.coverage * 0.5 ) / 6.5; return Math.round(score * 10) / 10; } summarizeLintFixes(lintResult) { const fixedRules = new Map(); lintResult.messages.forEach(message => { if (message.fix) { const count = fixedRules.get(message.ruleId) || 0; fixedRules.set(message.ruleId, count + 1); } }); return Array.from(fixedRules.entries()).map(([rule, count]) => `Fixed ${count} ${rule} issue${count > 1 ? 's' : ''}` ); } isVariableReassigned(j, variableDeclarator) { const varName = variableDeclarator.node.id.name; const scope = variableDeclarator.scope; let reassigned = false; j(scope.path).find(j.AssignmentExpression) .filter(path => { return path.node.left.type === 'Identifier' && path.node.left.name === varName; }) .forEach(() => { reassigned = true; }); return reassigned; } isExported(_path) { // Check if the declaration is exported const parent = path.parent; return parent.node.type === 'ExportNamedDeclaration' || parent.node.type === 'ExportDefaultDeclaration' || (parent.node.type === 'AssignmentExpression' && parent.node.left.type === 'MemberExpression' && parent.node.left.object.name === 'module' && parent.node.left.property.name === 'exports'); } isOnlyConsoleLog(statement) { return statement.type === 'ExpressionStatement' && statement.expression.type === 'CallExpression' && statement.expression.callee.type === 'MemberExpression' && statement.expression.callee.object.name === 'console'; } isKnownSafeObject(name) { const safeObjects = ['console', 'Math', 'JSON', 'Object', 'Array', 'String', 'Number']; return safeObjects.includes(name); } snakeToCamel(str) { return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); } shouldBeConstantCase(declarator) { // Check if the value is a literal or simple value const init = declarator.init; if (!init) return false; return init.type === 'Literal' || init.type === 'UnaryExpression' || (init.type === 'Identifier' && init.name === init.name.toUpperCase()); } isConstantCase(name) { return /^[A-Z_]+$/.test(name); } toConstantCase(name) { // Convert camelCase or snake_case to CONSTANT_CASE return name .replace(/([a-z])([A-Z])/g, '$1_$2') .replace(/[_-]+/g, '_') .toUpperCase(); } generateFunctionDescription(funcName) { // Generate meaningful description based on function name const words = funcName.split(/(?=[A-Z])/); return words.map(w => w.toLowerCase()).join(' ').replace(/^\w/, c => c.toUpperCase()); } generateParamDescription(paramName) { // Generate parameter description return `The ${paramName} parameter`; } generateReturnDescription(funcName) { // Generate return description if (funcName.startsWith('get')) return 'The requested value'; if (funcName.startsWith('is')) return 'True if condition is met, false otherwise'; if (funcName.startsWith('has')) return 'True if exists, false otherwise'; return 'The result of the operation'; } generateClassDescription(className) { // Generate class description const words = className.split(/(?=[A-Z])/); return `${words.join(' ')} class`; } /** * Apply improvements to file */ async applyImprovements(_filePath, improvedContent, backup = true) { try { if (backup) { // Create backup const backupPath = `${_filePath}.backup.${Date.now()}`; const originalContent = await fs.readFile(_filePath, 'utf-8'); await fs.writeFile(backupPath, originalContent); console.log(chalk.gray(`Backup created: ${backupPath}`)); } // Write improved content await fs.writeFile(_filePath, improvedContent); console.log(chalk.green(`✅ Improvements applied to: ${_filePath}`)); return { success: true }; } catch (_error) { console.error(chalk.red(`Failed to apply improvements: ${error.message}`)); return { success: false, error: error.message }; } } /** * Get improvement statistics */ getStatistics() { const stats = { filesAnalyzed: this.metrics.size, totalImprovements: 0, byType: {}, averageScore: 0 }; let totalScore = 0; for (const [file, metrics] of this.metrics) { if (metrics.after) { const score = this.calculateImprovementScore(metrics.before, metrics.after); totalScore += score; } } stats.averageScore = this.metrics.size > 0 ? (totalScore / this.metrics.size).toFixed(2) : 0; // Count improvements by type for (const improvement of this.improvements) { stats.byType[improvement.type] = (stats.byType[improvement.type] || 0) + 1; stats.totalImprovements++; } return stats; } } module.exports = CodeQualityImprover;