UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

946 lines (940 loc) 36 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CodeQualityAnalyzer = void 0; exports.createCustomRule = createCustomRule; exports.analyzeCodeQuality = analyzeCodeQuality; const events_1 = require("events"); const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const child_process_1 = require("child_process"); const fast_glob_1 = __importDefault(require("fast-glob")); class CodeQualityAnalyzer extends events_1.EventEmitter { constructor(config) { super(); this.config = { generateReport: true, outputPath: './quality-reports', includePatterns: ['src/**/*.{ts,tsx,js,jsx}'], excludePatterns: ['**/node_modules/**', '**/dist/**', '**/*.test.{ts,tsx,js,jsx}'], thresholds: { bugs: 0, vulnerabilities: 0, codeSmells: 10, coverage: 80, duplicatedLines: 3, maintainabilityRating: 'A', reliabilityRating: 'A', securityRating: 'A' }, ...config }; } async analyze(projectPath) { this.emit('analysis:start', { project: projectPath }); const result = { project: path.basename(projectPath), timestamp: new Date(), summary: this.createEmptySummary(), files: [], metrics: this.createEmptyMetrics(), issues: [], recommendations: [] }; try { // Find and analyze files const files = await this.findSourceFiles(projectPath); this.emit('analysis:files', { count: files.length }); for (const file of files) { const analysis = await this.analyzeFile(file); result.files.push(analysis); result.issues.push(...analysis.issues); } // Calculate metrics result.metrics = this.calculateMetrics(result.files); result.summary = this.generateSummary(result.files, result.issues); // Run ESLint analysis if (this.config.eslint) { await this.runESLintAnalysis(projectPath, result); } // Run SonarQube analysis if (this.config.sonarQube) { result.sonarQube = await this.runSonarQubeAnalysis(projectPath); } // Apply custom rules if (this.config.customRules) { await this.applyCustomRules(result); } // Generate recommendations result.recommendations = this.generateRecommendations(result); // Calculate trends if historical data exists result.trends = await this.calculateTrends(result); this.emit('analysis:complete', result); return result; } catch (error) { this.emit('analysis:error', error); throw error; } } async findSourceFiles(projectPath) { return (0, fast_glob_1.default)(this.config.includePatterns, { cwd: projectPath, absolute: true, ignore: this.config.excludePatterns }); } async analyzeFile(filePath) { const content = await fs.readFile(filePath, 'utf-8'); const lines = content.split('\n').length; const analysis = { path: filePath, content, lines, complexity: this.calculateComplexity(content), maintainabilityIndex: this.calculateMaintainabilityIndex(content), dependencies: this.extractDependencies(content), exports: this.extractExports(content), imports: this.extractImports(content), functions: this.extractFunctions(content), classes: this.extractClasses(content), issues: [] }; // Analyze for common issues analysis.issues = this.analyzeForIssues(analysis); return analysis; } calculateComplexity(content) { // Simplified cyclomatic complexity calculation const complexityKeywords = [ 'if', 'else', 'while', 'for', 'switch', 'case', 'catch', 'try', '&&', '||', '?', ':', 'break', 'continue', 'return' ]; let complexity = 1; // Base complexity for (const keyword of complexityKeywords) { const regex = new RegExp(`\\b${keyword}\\b`, 'g'); const matches = content.match(regex); if (matches) { complexity += matches.length; } } return complexity; } calculateMaintainabilityIndex(content) { const lines = content.split('\n').length; const complexity = this.calculateComplexity(content); const volume = content.length; // Simplified maintainability index calculation // Real implementation would use Halstead metrics const maintainabilityIndex = Math.max(0, 171 - 5.2 * Math.log(volume) - 0.23 * complexity - 16.2 * Math.log(lines)); return Math.round(maintainabilityIndex); } extractDependencies(content) { const dependencies = []; const importRegex = /(?:import|require)\s*\(?['"`]([^'"`]+)['"`]\)?/g; let match; while ((match = importRegex.exec(content)) !== null) { dependencies.push(match[1]); } return [...new Set(dependencies)]; } extractExports(content) { const exports = []; const exportRegex = /export\s+(?:default\s+)?(?:class|function|const|let|var|interface|type)\s+(\w+)/g; let match; while ((match = exportRegex.exec(content)) !== null) { exports.push(match[1]); } return exports; } extractImports(content) { const imports = []; const importRegex = /import\s+(?:{([^}]+)}|\*\s+as\s+(\w+)|(\w+))\s+from/g; let match; while ((match = importRegex.exec(content)) !== null) { if (match[1]) { // Named imports const namedImports = match[1].split(',').map(s => s.trim()); imports.push(...namedImports); } else if (match[2]) { // Namespace import imports.push(match[2]); } else if (match[3]) { // Default import imports.push(match[3]); } } return imports; } extractFunctions(content) { const functions = []; const lines = content.split('\n'); // Function declarations const funcRegex = /(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\([^)]*\)/g; let match; while ((match = funcRegex.exec(content)) !== null) { const startLine = content.substring(0, match.index).split('\n').length; const endLine = this.findFunctionEnd(lines, startLine - 1); functions.push({ name: match[1], startLine, endLine, complexity: this.calculateFunctionComplexity(lines.slice(startLine - 1, endLine)), parameters: this.countParameters(match[0]), returns: 'unknown', documented: this.isFunctionDocumented(lines, startLine - 1) }); } // Arrow functions const arrowRegex = /(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/g; while ((match = arrowRegex.exec(content)) !== null) { const startLine = content.substring(0, match.index).split('\n').length; functions.push({ name: match[1], startLine, endLine: startLine, complexity: 1, parameters: this.countParameters(match[0]), returns: 'unknown', documented: this.isFunctionDocumented(lines, startLine - 1) }); } return functions; } extractClasses(content) { const classes = []; const lines = content.split('\n'); const classRegex = /(?:export\s+)?class\s+(\w+)(?:\s+extends\s+(\w+))?(?:\s+implements\s+([^{]+))?/g; let match; while ((match = classRegex.exec(content)) !== null) { const startLine = content.substring(0, match.index).split('\n').length; const endLine = this.findClassEnd(lines, startLine - 1); classes.push({ name: match[1], startLine, endLine, methods: this.countMethods(lines.slice(startLine - 1, endLine)), properties: this.countProperties(lines.slice(startLine - 1, endLine)), extends: match[2], implements: match[3] ? match[3].split(',').map(s => s.trim()) : undefined, documented: this.isClassDocumented(lines, startLine - 1) }); } return classes; } findFunctionEnd(lines, startLine) { let braceCount = 0; let inFunction = false; for (let i = startLine; i < lines.length; i++) { const line = lines[i]; for (const char of line) { if (char === '{') { braceCount++; inFunction = true; } else if (char === '}') { braceCount--; if (inFunction && braceCount === 0) { return i + 1; } } } } return startLine + 1; } findClassEnd(lines, startLine) { return this.findFunctionEnd(lines, startLine); } calculateFunctionComplexity(lines) { const content = lines.join('\n'); return this.calculateComplexity(content); } countParameters(funcDeclaration) { const paramMatch = funcDeclaration.match(/\(([^)]*)\)/); if (!paramMatch || !paramMatch[1].trim()) return 0; return paramMatch[1].split(',').filter(p => p.trim()).length; } countMethods(lines) { const content = lines.join('\n'); const methodRegex = /(?:async\s+)?(\w+)\s*\([^)]*\)\s*[:{]/g; const matches = content.match(methodRegex); return matches ? matches.length : 0; } countProperties(lines) { const content = lines.join('\n'); const propRegex = /(?:private|protected|public)?\s*(\w+)\s*[:\?]?\s*[^=]*=/g; const matches = content.match(propRegex); return matches ? matches.length : 0; } isFunctionDocumented(lines, lineIndex) { if (lineIndex === 0) return false; const prevLine = lines[lineIndex - 1].trim(); return prevLine.includes('/**') || prevLine.includes('//'); } isClassDocumented(lines, lineIndex) { return this.isFunctionDocumented(lines, lineIndex); } analyzeForIssues(analysis) { const issues = []; // Check complexity if (analysis.complexity > 10) { issues.push({ rule: 'complexity', severity: analysis.complexity > 20 ? 'major' : 'minor', type: 'code_smell', message: `High complexity: ${analysis.complexity}`, line: 1, effort: '30min', tags: ['brain-overload'] }); } // Check maintainability if (analysis.maintainabilityIndex < 50) { issues.push({ rule: 'maintainability', severity: 'major', type: 'code_smell', message: `Low maintainability index: ${analysis.maintainabilityIndex}`, line: 1, effort: '1h', tags: ['maintainability'] }); } // Check for long functions for (const func of analysis.functions) { if (func.complexity > 15) { issues.push({ rule: 'function-complexity', severity: 'major', type: 'code_smell', message: `Function '${func.name}' has high complexity: ${func.complexity}`, line: func.startLine, effort: '45min', tags: ['complexity'] }); } if (!func.documented && func.complexity > 5) { issues.push({ rule: 'missing-docs', severity: 'minor', type: 'code_smell', message: `Function '${func.name}' should be documented`, line: func.startLine, effort: '5min', tags: ['documentation'] }); } } // Check for undocumented classes for (const cls of analysis.classes) { if (!cls.documented) { issues.push({ rule: 'missing-class-docs', severity: 'minor', type: 'code_smell', message: `Class '${cls.name}' should be documented`, line: cls.startLine, effort: '10min', tags: ['documentation'] }); } } return issues; } async runESLintAnalysis(projectPath, result) { try { // Generate ESLint config if needed const configPath = await this.generateESLintConfig(projectPath); // Run ESLint const command = `npx eslint --format json ${this.config.includePatterns?.join(' ')}`; const output = (0, child_process_1.execSync)(command, { cwd: projectPath, encoding: 'utf-8', stdio: 'pipe' }); const eslintResults = JSON.parse(output); // Convert ESLint results to our format for (const fileResult of eslintResults) { for (const message of fileResult.messages) { result.issues.push({ rule: message.ruleId || 'eslint', severity: this.mapESLintSeverity(message.severity), type: 'code_smell', message: message.message, line: message.line, column: message.column, tags: ['eslint'] }); } } } catch (error) { this.emit('eslint:error', error); } } async generateESLintConfig(projectPath) { const configPath = path.join(projectPath, '.eslintrc.json'); if (!await fs.pathExists(configPath) && this.config.eslint) { const config = { extends: this.config.eslint.extends || ['@typescript-eslint/recommended'], plugins: this.config.eslint.plugins || ['@typescript-eslint'], parser: this.config.eslint.parser || '@typescript-eslint/parser', parserOptions: this.config.eslint.parserOptions || { ecmaVersion: 2020, sourceType: 'module' }, env: this.config.eslint.env || { node: true, es6: true }, rules: this.config.eslint.rules || {} }; await fs.writeJson(configPath, config, { spaces: 2 }); } return configPath; } mapESLintSeverity(eslintSeverity) { switch (eslintSeverity) { case 1: return 'minor'; case 2: return 'major'; default: return 'info'; } } async runSonarQubeAnalysis(projectPath) { const sonarConfig = this.config.sonarQube; try { // Generate sonar-project.properties await this.generateSonarProperties(projectPath, sonarConfig); // Run SonarQube scanner const scannerCommand = `npx sonar-scanner`; (0, child_process_1.execSync)(scannerCommand, { cwd: projectPath, stdio: 'pipe' }); // Fetch results from SonarQube API const results = await this.fetchSonarQubeResults(sonarConfig); return results; } catch (error) { this.emit('sonarqube:error', error); throw error; } } async generateSonarProperties(projectPath, config) { const properties = [ `sonar.projectKey=${config.projectKey}`, `sonar.projectName=${config.projectName || config.projectKey}`, `sonar.sources=${config.sources?.join(',') || 'src'}`, `sonar.host.url=${config.serverUrl}` ]; if (config.token) { properties.push(`sonar.login=${config.token}`); } if (config.exclusions) { properties.push(`sonar.exclusions=${config.exclusions.join(',')}`); } if (config.qualityGate) { properties.push(`sonar.qualitygate=${config.qualityGate}`); } // Add custom properties if (config.properties) { for (const [key, value] of Object.entries(config.properties)) { properties.push(`${key}=${value}`); } } const propertiesContent = properties.join('\n'); await fs.writeFile(path.join(projectPath, 'sonar-project.properties'), propertiesContent); } async fetchSonarQubeResults(config) { // Simplified SonarQube API interaction // In practice, would use proper HTTP client with authentication return { qualityGate: { status: 'OK', conditions: [] }, measures: [], issues: [], hotspots: [] }; } async applyCustomRules(result) { if (!this.config.customRules) return; for (const rule of this.config.customRules) { for (const fileAnalysis of result.files) { const violations = rule.check(fileAnalysis); for (const violation of violations) { result.issues.push({ rule: rule.id, severity: violation.severity, type: rule.type, message: violation.message, line: violation.line, column: violation.column, tags: ['custom-rule'] }); } } } } calculateMetrics(files) { const complexities = files.map(f => f.complexity); const maintainabilityIndexes = files.map(f => f.maintainabilityIndex); return { complexity: { average: complexities.reduce((a, b) => a + b, 0) / complexities.length, maximum: Math.max(...complexities), distribution: this.calculateComplexityDistribution(complexities), hotspots: this.identifyComplexityHotspots(files) }, maintainability: { index: maintainabilityIndexes.reduce((a, b) => a + b, 0) / maintainabilityIndexes.length, rating: this.calculateMaintainabilityRating(maintainabilityIndexes), factors: this.identifyMaintainabilityFactors(files), debt: this.calculateTechnicalDebt(files) }, reliability: { rating: 'A', bugs: 0, effort: '0min', issues: [] }, security: { rating: 'A', vulnerabilities: 0, hotspots: 0, effort: '0min', issues: [] }, coverage: { lines: 0, branches: 0, functions: 0, overall: 0 }, duplication: { lines: 0, blocks: 0, files: 0, percentage: 0, duplicatedFiles: [] } }; } calculateComplexityDistribution(complexities) { const distribution = { 'low (1-5)': 0, 'medium (6-10)': 0, 'high (11-15)': 0, 'very-high (16+)': 0 }; for (const complexity of complexities) { if (complexity <= 5) distribution['low (1-5)']++; else if (complexity <= 10) distribution['medium (6-10)']++; else if (complexity <= 15) distribution['high (11-15)']++; else distribution['very-high (16+)']++; } return distribution; } identifyComplexityHotspots(files) { const hotspots = []; for (const file of files) { for (const func of file.functions) { if (func.complexity > 10) { hotspots.push({ file: file.path, function: func.name, complexity: func.complexity, line: func.startLine, suggestion: func.complexity > 15 ? 'Consider breaking down this function' : 'Consider simplifying this function' }); } } } return hotspots.sort((a, b) => b.complexity - a.complexity).slice(0, 10); } calculateMaintainabilityRating(indexes) { const average = indexes.reduce((a, b) => a + b, 0) / indexes.length; if (average >= 85) return 'A'; if (average >= 70) return 'B'; if (average >= 50) return 'C'; if (average >= 25) return 'D'; return 'E'; } identifyMaintainabilityFactors(files) { const factors = []; const avgComplexity = files.reduce((sum, f) => sum + f.complexity, 0) / files.length; if (avgComplexity > 10) { factors.push({ factor: 'High Complexity', impact: avgComplexity, description: 'Complex code is harder to maintain and debug' }); } const undocumentedFunctions = files.reduce((sum, f) => sum + f.functions.filter(fn => !fn.documented).length, 0); if (undocumentedFunctions > 0) { factors.push({ factor: 'Missing Documentation', impact: undocumentedFunctions, description: 'Undocumented code increases maintenance effort' }); } return factors; } calculateTechnicalDebt(files) { const totalIssues = files.reduce((sum, f) => sum + f.issues.length, 0); const estimatedMinutes = totalIssues * 15; // Rough estimate if (estimatedMinutes < 60) return `${estimatedMinutes}min`; if (estimatedMinutes < 480) return `${Math.round(estimatedMinutes / 60)}h`; return `${Math.round(estimatedMinutes / 480)}d`; } generateSummary(files, issues) { const totalLines = files.reduce((sum, f) => sum + f.lines, 0); return { totalFiles: files.length, totalLines, totalIssues: issues.length, bugCount: issues.filter(i => i.type === 'bug').length, vulnerabilityCount: issues.filter(i => i.type === 'vulnerability').length, codeSmellCount: issues.filter(i => i.type === 'code_smell').length, duplicatedLines: 0, technicalDebt: this.calculateTechnicalDebt(files), maintainabilityRating: 'A', reliabilityRating: 'A', securityRating: 'A' }; } generateRecommendations(result) { const recommendations = []; // High complexity recommendations const complexityHotspots = result.metrics.complexity.hotspots; if (complexityHotspots.length > 0) { recommendations.push(`Address ${complexityHotspots.length} complexity hotspots to improve maintainability`); } // Documentation recommendations const undocumentedFunctions = result.files.reduce((sum, f) => sum + f.functions.filter(fn => !fn.documented).length, 0); if (undocumentedFunctions > 5) { recommendations.push(`Add documentation to ${undocumentedFunctions} functions`); } // Issue-based recommendations const criticalIssues = result.issues.filter(i => i.severity === 'critical'); if (criticalIssues.length > 0) { recommendations.push(`Fix ${criticalIssues.length} critical issues immediately`); } const majorIssues = result.issues.filter(i => i.severity === 'major'); if (majorIssues.length > 10) { recommendations.push(`Plan to address ${majorIssues.length} major issues in upcoming sprints`); } return recommendations; } async calculateTrends(result) { // Load historical data and calculate trends // This would typically involve comparing with previous analysis results return []; } async generateReport(analysisResult) { const actionPlan = this.generateActionPlan(analysisResult); const summary = { overallRating: analysisResult.summary.maintainabilityRating, totalIssues: analysisResult.summary.totalIssues, criticalIssues: analysisResult.issues.filter(i => i.severity === 'critical').length, technicalDebt: analysisResult.summary.technicalDebt, coverage: analysisResult.metrics.coverage.overall, maintainabilityTrend: 'stable' }; const report = { summary, analysis: analysisResult, actionPlan, timestamp: new Date() }; if (this.config.generateReport) { await this.saveReport(report); } return report; } generateActionPlan(result) { const actionItems = []; // Critical issues first const criticalIssues = result.issues.filter(i => i.severity === 'critical'); if (criticalIssues.length > 0) { actionItems.push({ priority: 'high', category: 'bug', title: 'Fix Critical Issues', description: `Address ${criticalIssues.length} critical issues`, effort: '1-2d', impact: 'High', files: [...new Set(criticalIssues.map(i => result.files.find(f => f.issues.includes(i))?.path))].filter(Boolean) }); } // Complexity hotspots const complexityHotspots = result.metrics.complexity.hotspots; if (complexityHotspots.length > 0) { actionItems.push({ priority: 'medium', category: 'maintainability', title: 'Reduce Complexity', description: `Refactor ${complexityHotspots.length} complex functions`, effort: '3-5d', impact: 'Medium', files: complexityHotspots.map(h => h.file) }); } // Documentation gaps const undocumentedCount = result.files.reduce((sum, f) => sum + f.functions.filter(fn => !fn.documented).length, 0); if (undocumentedCount > 10) { actionItems.push({ priority: 'low', category: 'maintainability', title: 'Add Documentation', description: `Document ${undocumentedCount} functions`, effort: '1-2d', impact: 'Low', files: result.files .filter(f => f.functions.some(fn => !fn.documented)) .map(f => f.path) }); } return actionItems; } async saveReport(report) { await fs.ensureDir(this.config.outputPath); // Save JSON report const jsonPath = path.join(this.config.outputPath, 'quality-report.json'); await fs.writeJson(jsonPath, report, { spaces: 2 }); // Save HTML report const htmlPath = path.join(this.config.outputPath, 'quality-report.html'); const html = this.generateHtmlReport(report); await fs.writeFile(htmlPath, html); this.emit('report:saved', { json: jsonPath, html: htmlPath }); } generateHtmlReport(report) { return `<!DOCTYPE html> <html> <head> <title>Code Quality Report</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } .summary { background: #f5f5f5; padding: 15px; border-radius: 5px; margin-bottom: 20px; } .metric { display: inline-block; margin: 10px; padding: 10px; background: white; border-radius: 3px; } .rating-A { color: #4caf50; } .rating-B { color: #8bc34a; } .rating-C { color: #ff9800; } .rating-D { color: #f44336; } .rating-E { color: #d32f2f; } table { width: 100%; border-collapse: collapse; margin-top: 20px; } th, td { text-align: left; padding: 8px; border-bottom: 1px solid #ddd; } th { background-color: #f2f2f2; } .critical { color: #d32f2f; font-weight: bold; } .major { color: #ff9800; } .minor { color: #2196f3; } </style> </head> <body> <h1>Code Quality Report</h1> <div class="summary"> <h2>Summary</h2> <div class="metric"> <strong>Overall Rating:</strong> <span class="rating-${report.summary.overallRating}">${report.summary.overallRating}</span> </div> <div class="metric"> <strong>Total Issues:</strong> ${report.summary.totalIssues} </div> <div class="metric"> <strong>Critical Issues:</strong> <span class="critical">${report.summary.criticalIssues}</span> </div> <div class="metric"> <strong>Technical Debt:</strong> ${report.summary.technicalDebt} </div> <div class="metric"> <strong>Coverage:</strong> ${report.summary.coverage.toFixed(1)}% </div> </div> <h2>Metrics</h2> <table> <tr> <th>Metric</th> <th>Value</th> <th>Rating</th> </tr> <tr> <td>Average Complexity</td> <td>${report.analysis.metrics.complexity.average.toFixed(1)}</td> <td>${report.analysis.metrics.complexity.average > 10 ? 'Poor' : 'Good'}</td> </tr> <tr> <td>Maintainability Index</td> <td>${report.analysis.metrics.maintainability.index.toFixed(1)}</td> <td class="rating-${report.analysis.metrics.maintainability.rating}"> ${report.analysis.metrics.maintainability.rating} </td> </tr> </table> <h2>Top Issues</h2> <table> <thead> <tr> <th>Severity</th> <th>Rule</th> <th>Message</th> <th>Line</th> </tr> </thead> <tbody> ${report.analysis.issues.slice(0, 20).map(issue => ` <tr> <td><span class="${issue.severity}">${issue.severity}</span></td> <td>${issue.rule}</td> <td>${issue.message}</td> <td>${issue.line}</td> </tr> `).join('')} </tbody> </table> <h2>Action Plan</h2> <div> ${report.actionPlan.map(item => ` <div style="margin-bottom: 15px; padding: 10px; border-left: 4px solid ${item.priority === 'high' ? '#f44336' : item.priority === 'medium' ? '#ff9800' : '#2196f3'};"> <h4>${item.title} (${item.priority} priority)</h4> <p>${item.description}</p> <p><strong>Effort:</strong> ${item.effort} | <strong>Impact:</strong> ${item.impact}</p> </div> `).join('')} </div> <footer> <p>Generated on ${report.timestamp.toISOString()}</p> </footer> </body> </html>`; } createEmptySummary() { return { totalFiles: 0, totalLines: 0, totalIssues: 0, bugCount: 0, vulnerabilityCount: 0, codeSmellCount: 0, duplicatedLines: 0, technicalDebt: '0min', maintainabilityRating: 'A', reliabilityRating: 'A', securityRating: 'A' }; } createEmptyMetrics() { return { complexity: { average: 0, maximum: 0, distribution: {}, hotspots: [] }, maintainability: { index: 100, rating: 'A', factors: [], debt: '0min' }, reliability: { rating: 'A', bugs: 0, effort: '0min', issues: [] }, security: { rating: 'A', vulnerabilities: 0, hotspots: 0, effort: '0min', issues: [] }, coverage: { lines: 0, branches: 0, functions: 0, overall: 0 }, duplication: { lines: 0, blocks: 0, files: 0, percentage: 0, duplicatedFiles: [] } }; } } exports.CodeQualityAnalyzer = CodeQualityAnalyzer; // Export utility functions function createCustomRule(id, name, check) { return { id, name, description: name, severity: 'minor', type: 'code_smell', check }; } async function analyzeCodeQuality(projectPath, config) { const analyzer = new CodeQualityAnalyzer(config || {}); const analysis = await analyzer.analyze(projectPath); return analyzer.generateReport(analysis); }