UNPKG

@sun-asterisk/sunlint

Version:

☀️ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards

221 lines (193 loc) 6.9 kB
/** * Architecture Integration for SunLint * Wraps architecture-detection module for seamless integration * Following Rule C005: Single responsibility - handle architecture analysis integration */ const path = require('path'); const fs = require('fs'); const chalk = require('chalk'); class ArchitectureIntegration { constructor(options = {}) { this.options = options; this.archModule = null; } /** * Load architecture detection module * Tries bundled version first, then falls back to local development path */ async loadArchitectureModule() { if (this.archModule) { return this.archModule; } // Try bundled version first (engines/arch-detect) const bundledPath = path.join(__dirname, '..', 'engines', 'arch-detect', 'index.js'); if (fs.existsSync(bundledPath)) { try { this.archModule = require(bundledPath); if (this.options.verbose) { console.log(chalk.gray('📦 Loaded bundled architecture-detection')); } return this.archModule; } catch (error) { if (this.options.verbose) { console.log(chalk.yellow(`⚠️ Failed to load bundled module: ${error.message}`)); } } } // Fallback: Try local development path const devPaths = [ path.join(__dirname, '..', '..', '..', '..', 'architecture-detection', 'dist', 'index.js'), path.join(__dirname, '..', '..', '..', 'architecture-detection', 'dist', 'index.js'), ]; for (const devPath of devPaths) { if (fs.existsSync(devPath)) { try { this.archModule = require(devPath); if (this.options.verbose) { console.log(chalk.gray(`📦 Loaded architecture-detection from: ${devPath}`)); } return this.archModule; } catch (error) { if (this.options.verbose) { console.log(chalk.yellow(`⚠️ Failed to load from ${devPath}: ${error.message}`)); } } } } throw new Error( 'Architecture detection module not found. Run "npm run build" to bundle it, ' + 'or ensure architecture-detection is built in the parent directory.' ); } /** * Parse architecture patterns from CLI option */ parsePatterns() { if (!this.options.archPatterns) { return undefined; // Use default patterns } const patternMap = { 'layered': 'LAYERED', 'modular': 'MODULAR', 'mvvm': 'MVVM', 'viper': 'VIPER', 'presentation': 'PRESENTATION', 'clean': 'CLEAN_ARCHITECTURE', 'tdd': 'TDD_CLEAN_ARCHITECTURE', }; const patterns = this.options.archPatterns .split(',') .map(p => p.trim().toLowerCase()) .map(p => patternMap[p] || p.toUpperCase()) .filter(Boolean); return patterns.length > 0 ? patterns : undefined; } /** * Run architecture analysis on project * @param {string} projectPath - Path to analyze * @returns {Object} Architecture analysis results */ async analyze(projectPath) { const archModule = await this.loadArchitectureModule(); const { ArchitectureAnalyzer } = archModule; if (!ArchitectureAnalyzer) { throw new Error('ArchitectureAnalyzer not found in architecture-detection module'); } const analyzer = new ArchitectureAnalyzer({ patterns: this.parsePatterns(), respectGitignore: true, verbose: this.options.verbose, }); if (this.options.verbose) { console.log(chalk.blue(`🏛️ Analyzing architecture: ${projectPath}`)); } const result = await analyzer.analyze(projectPath); // Convert to SunLint-compatible format return this.convertToSunLintFormat(result, analyzer, projectPath); } /** * Convert architecture results to SunLint format */ convertToSunLintFormat(result, analyzer, projectPath) { const violations = []; // Convert architecture violations to SunLint violation format // Check violationAssessment.violations from the new format const violationAssessment = result.violationAssessment; if (violationAssessment && violationAssessment.violations && violationAssessment.violations.length > 0) { for (const violation of violationAssessment.violations) { violations.push({ ruleId: `ARCH-${violation.type || 'VIOLATION'}`, severity: this.mapSeverity(violation.impact), message: violation.description || violation.ruleName, file: violation.affectedFiles?.[0] || projectPath, line: 1, column: 1, category: 'architecture', source: 'architecture-detection', details: { ruleName: violation.ruleName, impactReason: violation.impactReason, suggestedFix: violation.suggestedFix, affectedFiles: violation.affectedFiles, }, }); } } // Generate markdown report if requested let markdownReport = null; if (this.options.archReport) { try { markdownReport = analyzer.formatAsMarkdown(result); } catch (error) { if (this.options.verbose) { console.log(chalk.yellow(`⚠️ Could not generate markdown report: ${error.message}`)); } } } // Map from hybridAnalysis structure const hybridAnalysis = result.hybridAnalysis || {}; const healthScore = violationAssessment?.healthScore || 100; return { summary: { primaryPattern: hybridAnalysis.primaryPattern || result.primaryPattern || 'UNKNOWN', primaryConfidence: hybridAnalysis.confidence || 0, secondaryPatterns: (hybridAnalysis.secondaryPatterns || []).map(p => ({ pattern: p, confidence: 0.5, // Default confidence for secondary patterns })), healthScore: healthScore, violationCount: violations.length, analysisTime: result.metadata?.analysisTimeMs || 0, isHybrid: hybridAnalysis.isHybrid || false, combination: hybridAnalysis.combination || null, }, violations, markdownReport, raw: result, }; } /** * Map architecture severity to SunLint severity */ mapSeverity(severity) { const severityMap = { 'critical': 'error', 'high': 'error', 'medium': 'warning', 'low': 'info', }; return severityMap[severity?.toLowerCase()] || 'warning'; } /** * Save markdown report to file */ async saveReport(markdownContent, projectPath) { const projectName = path.basename(projectPath); const date = new Date().toISOString().split('T')[0].replace(/-/g, '_'); const fileName = `sun_arch_report_${projectName}_${date}.md`; const outputPath = path.join(process.cwd(), fileName); fs.writeFileSync(outputPath, markdownContent, 'utf8'); return outputPath; } } module.exports = { ArchitectureIntegration };