sarif-explorer
Version:
A modern SARIF report viewer that converts SARIF files into interactive, shareable HTML reports with file explorer, collapsible issue lists, and code snippets.
233 lines • 7.66 kB
JavaScript
/**
* Analyze code quality and provide insights based on SARIF violations
*/
export class CodeAnalyzer {
constructor(violations) {
this.violations = violations;
}
/**
* Get code quality score (0-100)
*/
getQualityScore() {
if (this.violations.length === 0) {
return 100;
}
const weights = {
error: 10,
warning: 3,
info: 1,
note: 0.5,
none: 0.1
};
let totalPenalty = 0;
for (const violation of this.violations) {
totalPenalty += weights[violation.level];
}
// Normalize to 0-100 scale
const maxPenalty = this.violations.length * weights.error;
const score = Math.max(0, 100 - (totalPenalty / maxPenalty) * 100);
return Math.round(score);
}
/**
* Get severity distribution
*/
getSeverityDistribution() {
const distribution = {
error: 0,
warning: 0,
info: 0,
note: 0,
none: 0
};
for (const violation of this.violations) {
distribution[violation.level]++;
}
return distribution;
}
/**
* Get most problematic rules
*/
getProblematicRules(limit = 5) {
const ruleCounts = new Map();
for (const violation of this.violations) {
const existing = ruleCounts.get(violation.ruleId);
if (existing) {
existing.count++;
// Keep the highest severity
if (this.getSeverityWeight(violation.level) > this.getSeverityWeight(existing.severity)) {
existing.severity = violation.level;
}
}
else {
ruleCounts.set(violation.ruleId, { count: 1, severity: violation.level });
}
}
return Array.from(ruleCounts.entries())
.map(([ruleId, data]) => ({ ruleId, ...data }))
.sort((a, b) => b.count - a.count)
.slice(0, limit);
}
/**
* Get code hotspots (files with most violations)
*/
getCodeHotspots(fileViolations, limit = 10) {
const hotspots = [];
for (const [file, violations] of Object.entries(fileViolations)) {
const analyzer = new CodeAnalyzer(violations);
hotspots.push({
file,
violations: violations.length,
score: analyzer.getQualityScore()
});
}
return hotspots
.sort((a, b) => b.violations - a.violations)
.slice(0, limit);
}
/**
* Get improvement recommendations
*/
getRecommendations() {
const recommendations = [];
const severityDist = this.getSeverityDistribution();
const problematicRules = this.getProblematicRules(3);
// High priority recommendations
if (severityDist.error > 0) {
recommendations.push({
type: 'critical',
message: `Fix ${severityDist.error} critical error(s) first as they may cause runtime failures`,
priority: 'high'
});
}
if (problematicRules.length > 0) {
const topRule = problematicRules[0];
recommendations.push({
type: 'rule-focus',
message: `Focus on fixing "${topRule.ruleId}" rule violations (${topRule.count} occurrences)`,
priority: 'high'
});
}
// Medium priority recommendations
if (severityDist.warning > 10) {
recommendations.push({
type: 'warnings',
message: `Address ${severityDist.warning} warnings to improve code quality`,
priority: 'medium'
});
}
// Low priority recommendations
if (severityDist.info > 0) {
recommendations.push({
type: 'info',
message: `Review ${severityDist.info} informational issues for potential improvements`,
priority: 'low'
});
}
return recommendations;
}
/**
* Get trend analysis (if historical data is available)
*/
getTrendAnalysis(previousViolations) {
if (!previousViolations) {
return { trend: 'stable', change: 0 };
}
const currentCount = this.violations.length;
const previousCount = previousViolations.length;
const change = currentCount - previousCount;
if (change < -5) {
return { trend: 'improving', change };
}
else if (change > 5) {
return { trend: 'declining', change };
}
else {
return { trend: 'stable', change };
}
}
/**
* Get compliance status
*/
getComplianceStatus() {
const issues = [];
const severityDist = this.getSeverityDistribution();
if (severityDist.error > 0) {
issues.push(`${severityDist.error} critical errors found`);
}
if (severityDist.warning > 20) {
issues.push(`${severityDist.warning} warnings exceed threshold`);
}
const qualityScore = this.getQualityScore();
if (qualityScore < 70) {
issues.push(`Code quality score is low (${qualityScore}/100)`);
}
return {
compliant: issues.length === 0,
issues
};
}
/**
* Get file type analysis
*/
getFileTypeAnalysis(fileViolations) {
const fileTypes = new Map();
for (const [file, violations] of Object.entries(fileViolations)) {
const extension = this.getFileExtension(file);
const existing = fileTypes.get(extension);
if (existing) {
existing.count++;
existing.violations += violations.length;
existing.files.push(file);
}
else {
fileTypes.set(extension, { count: 1, violations: violations.length, files: [file] });
}
}
const result = new Map();
for (const [ext, data] of fileTypes.entries()) {
result.set(ext, {
count: data.count,
violations: data.violations,
avgViolations: Math.round((data.violations / data.count) * 10) / 10
});
}
return result;
}
/**
* Get line density analysis
*/
getLineDensityAnalysis() {
const lineViolations = new Map();
for (const violation of this.violations) {
const line = violation.line;
lineViolations.set(line, (lineViolations.get(line) || 0) + 1);
}
const highDensityLines = [];
const densityMap = new Map();
for (const [line, count] of lineViolations.entries()) {
densityMap.set(line, count);
if (count >= 3) {
highDensityLines.push(line);
}
}
return {
highDensityLines: highDensityLines.sort((a, b) => a - b),
densityMap
};
}
/**
* Helper method to get severity weight
*/
getSeverityWeight(level) {
const weights = { error: 5, warning: 4, info: 3, note: 2, none: 1 };
return weights[level] || 0;
}
/**
* Helper method to get file extension
*/
getFileExtension(file) {
const parts = file.split('.');
return parts.length > 1 ? parts[parts.length - 1] : 'unknown';
}
}
//# sourceMappingURL=code-analyzer.js.map