@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
JavaScript
/**
* 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 };