diff --git a/SQLLinter/Infrastructure/Reporters/HtmlReportFormatter.cs b/SQLLinter/Infrastructure/Reporters/HtmlReportFormatter.cs index 6ee9c69..7774dee 100644 --- a/SQLLinter/Infrastructure/Reporters/HtmlReportFormatter.cs +++ b/SQLLinter/Infrastructure/Reporters/HtmlReportFormatter.cs @@ -1,5 +1,7 @@ using SQLLinter.Common; using SQLLinter.Infrastructure.Diagram; +using SQLLinter.Infrastructure.Rules.RuleViolations; +using System.Data; using System.Reflection; using System.Text; using System.Text.Json; @@ -66,38 +68,76 @@ public class HtmlReportFormatter : IReportFormatter foreach (var fileGroup in groupedByFile) { - var fileData = new FileReportData + var fileData = new FileReport { - FileName = fileGroup.Key + Name = fileGroup.Key, }; + // Группировка по severity var severityGroups = fileGroup .GroupBy(v => v.Severity) .OrderByDescending(g => g.Key) .ToList(); - foreach (var severityGroup in severityGroups) + foreach (var violation in fileGroup.Select(t => t).OrderBy(v => v.Line).ThenBy(v => v.Column)) { - var violationsList = severityGroup - .OrderBy(v => v.Line) - .ThenBy(v => v.Column) - .Select(v => new ViolationData - { - Line = v.Line, - Column = v.Column, - RuleName = v.RuleName, - Text = EscapeHtml(v.Text), - Index = severityGroup.ToList().IndexOf(v) + 1 - }) - .ToList(); + int ruleId; + List args = new(); - if (severityGroup.Key == RuleViolationSeverity.Critical) - fileData.CriticalViolations = violationsList; - else if (severityGroup.Key == RuleViolationSeverity.Warning) - fileData.WarningViolations = violationsList; - else if (severityGroup.Key == RuleViolationSeverity.Info) - fileData.InfoViolations = violationsList; + if (violation is RuleTemplateViolation templateRule) + { + args = templateRule.Params.Select(p => EscapeHtml(p)).ToList(); + + if (reportData.Rules.Any(t => t.Value.Name == templateRule.RuleName)) + { + ruleId = reportData.Rules.First(t => t.Value.Name == templateRule.RuleName).Key; + } + else + { + ruleId = reportData.Rules.Count + 1; + reportData.Rules.Add(ruleId, new Rule + { + Name = templateRule.RuleName, + Template = templateRule.RuleTemplate + }); + } + } + else + { + ruleId = reportData.Rules.Count + 1; + reportData.Rules.Add(ruleId, new Rule + { + Name = violation.RuleName, + Template = violation.Text, + }); + } + + var v = new Violation() + { + RuleId = ruleId, + Args = args, + Column = violation.Column, + Line = violation.Line, + }; + + if (violation.Severity == RuleViolationSeverity.Critical) + { + v.Index = fileData.Violations.Critical.Count + 1; + fileData.Violations.Critical.Add(v); + } + + else if (violation.Severity == RuleViolationSeverity.Warning) + { + v.Index = fileData.Violations.Warning.Count + 1; + fileData.Violations.Warning.Add(v); + } + + else if (violation.Severity == RuleViolationSeverity.Info) + { + v.Index = fileData.Violations.Info.Count + 1; + fileData.Violations.Info.Add(v); + } } reportData.Files.Add(fileData); @@ -106,9 +146,9 @@ public class HtmlReportFormatter : IReportFormatter // Добавление диаграммы, если есть if (diagram != null) { - reportData.Diagram = new DiagramData + reportData.Diagram = new Diagram { - MermaidContent = MermaidRenderer.ToMermaidContent(diagram), + Content = MermaidRenderer.ToMermaidContent(diagram), HasDiagram = true }; } @@ -173,36 +213,70 @@ public class HtmlReportFormatter : IReportFormatter // Классы для сериализации private class ReportData { - public List Files { get; set; } = new(); - public DiagramData? Diagram { get; set; } + [JsonPropertyName("f")] // files + public List Files { get; set; } = new(); + + [JsonPropertyName("r")] // rules + public Dictionary Rules { get; set; } = new(); + + [JsonPropertyName("d")] // diagram + public Diagram Diagram { get; set; } = new(); } - private class FileReportData + private class FileReport { - public string FileName { get; set; } = string.Empty; - public List? CriticalViolations { get; set; } - public List? WarningViolations { get; set; } - public List? InfoViolations { get; set; } + [JsonPropertyName("n")] // name + public string Name { get; set; } = string.Empty; - [JsonIgnore] - public int TotalViolations => - (CriticalViolations?.Count ?? 0) + - (WarningViolations?.Count ?? 0) + - (InfoViolations?.Count ?? 0); + [JsonPropertyName("v")] // violations + public Violations Violations { get; set; } = new(); } - private class ViolationData + private class Violations { + [JsonPropertyName("c")] // critical + public List Critical { get; set; } = new(); + + [JsonPropertyName("w")] // warning + public List Warning { get; set; } = new(); + + [JsonPropertyName("i")] // info + public List Info { get; set; } = new(); + } + + private class Violation + { + [JsonPropertyName("i")] // index public int Index { get; set; } + + [JsonPropertyName("l")] // line public int Line { get; set; } + + [JsonPropertyName("c")] // column public int Column { get; set; } - public string RuleName { get; set; } = string.Empty; - public string Text { get; set; } = string.Empty; + + [JsonPropertyName("r")] // ruleId + public int RuleId { get; set; } + + [JsonPropertyName("a")] // args (optional) + public List? Args { get; set; } } - private class DiagramData + private class Rule { - public string MermaidContent { get; set; } = string.Empty; + [JsonPropertyName("n")] // name + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("t")] // template + public string Template { get; set; } = string.Empty; + } + + private class Diagram + { + [JsonPropertyName("c")] // content + public string Content { get; set; } = string.Empty; + + [JsonPropertyName("h")] // hasDiagram public bool HasDiagram { get; set; } } } \ No newline at end of file diff --git a/SQLLinter/Infrastructure/Reporters/Static/HtmlFormatter.css b/SQLLinter/Infrastructure/Reporters/Static/HtmlFormatter.css index c6dc1a1..6a70bfb 100644 --- a/SQLLinter/Infrastructure/Reporters/Static/HtmlFormatter.css +++ b/SQLLinter/Infrastructure/Reporters/Static/HtmlFormatter.css @@ -393,7 +393,7 @@ td.index { td.rule { font-weight: 600; color: var(--color-text-primary); - min-width: 200px; + width: 250px; } td.description { diff --git a/SQLLinter/Infrastructure/Reporters/Static/HtmlFormatter.js b/SQLLinter/Infrastructure/Reporters/Static/HtmlFormatter.js index e5f818b..9dccf8a 100644 --- a/SQLLinter/Infrastructure/Reporters/Static/HtmlFormatter.js +++ b/SQLLinter/Infrastructure/Reporters/Static/HtmlFormatter.js @@ -7,8 +7,9 @@ import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.mi class ReportRenderer { constructor(data) { this.data = data; - this.files = data.files || []; - this.diagram = data.diagram; + this.files = data.files || data.f || []; + this.rules = data.rules || data.r || {}; + this.diagram = data.diagram || data.d || {}; this.currentFileIndex = 0; this.reportsContainer = document.getElementById('reports-container'); @@ -24,12 +25,13 @@ class ReportRenderer { this.renderTabs(); this.renderFileReports(); this.setupTabNavigation(); - - // Активируем первый таб this.activateTab(0); } renderTabs() { + const tabsList = document.getElementById('tabs-list'); + if (!tabsList) return; + this.tabsList.innerHTML = ''; // Табы для файлов @@ -39,25 +41,30 @@ class ReportRenderer { tab.dataset.target = `file_${index}`; tab.dataset.index = index; + const total = (file.v.c?.length || 0) + (file.v.w?.length || 0) + (file.v.i?.length || 0); + tab.innerHTML = ` - ${this.escapeHtml(file.fileName)} - ${file.totalViolations || this.calculateTotalViolations(file)} + ${this.escapeHtml(file.n)} + ${total} `; this.tabsList.appendChild(tab); }); // Таб для диаграммы (если есть) - if (this.diagram?.hasDiagram) { + if (this.diagram.h || this.diagram.hasDiagram) { const diagramTab = document.createElement('button'); diagramTab.className = 'tab'; diagramTab.dataset.target = 'mermaid'; diagramTab.textContent = 'Диаграмма'; - this.tabsList.appendChild(diagramTab); + tabsList.appendChild(diagramTab); } } renderFileReports() { + const container = document.getElementById('reports-container'); + if (!container) return; + this.reportsContainer.innerHTML = ''; // Рендеринг отчетов по файлам @@ -67,6 +74,9 @@ class ReportRenderer { reportDiv.className = 'file-report'; reportDiv.style.display = 'none'; + const fileName = file.n || file.fileName || ''; + const violations = file.v || file.violations || {}; + // Заголовок файла reportDiv.innerHTML = `
@@ -75,27 +85,28 @@ class ReportRenderer { - ${this.escapeHtml(file.fileName)} + ${this.escapeHtml(fileName)}
`; - // Секции по severity - if (file.criticalViolations?.length > 0) { - reportDiv.appendChild(this.createSeveritySection('critical', file.criticalViolations)); + // Добавляем секции нарушений + if (file.v.c?.length > 0) { + reportDiv.appendChild(this.createViolationSection('critical', file.v.c)); } - if (file.warningViolations?.length > 0) { - reportDiv.appendChild(this.createSeveritySection('warning', file.warningViolations)); + if (file.v.w?.length > 0) { + reportDiv.appendChild(this.createViolationSection('warning', file.v.w)); } - if (file.infoViolations?.length > 0) { - reportDiv.appendChild(this.createSeveritySection('info', file.infoViolations)); + if (file.v.i?.length > 0) { + reportDiv.appendChild(this.createViolationSection('info', file.v.i)); } this.reportsContainer.appendChild(reportDiv); }); // Контейнер для диаграммы (если есть) - if (this.diagram?.hasDiagram) { + if (this.diagram.h || this.diagram.hasDiagram) { + const diagramContent = this.diagram.c || this.diagram.Content || ''; const diagramDiv = document.createElement('div'); diagramDiv.id = 'mermaid'; diagramDiv.className = 'file-report'; @@ -104,7 +115,7 @@ class ReportRenderer { diagramDiv.innerHTML = `
@@ -131,7 +142,7 @@ class ReportRenderer {
`; @@ -139,7 +150,7 @@ class ReportRenderer { } } - createSeveritySection(severity, violations) { + createViolationSection(severity, violations) { const severityTitle = { critical: 'Critical', warning: 'Warning', @@ -149,15 +160,31 @@ class ReportRenderer { const section = document.createElement('div'); section.className = `severity-section ${severity}`; - const tableRows = violations.map(v => ` - - ${v.index} - ${v.line} - ${v.column} - ${this.escapeHtml(v.ruleName)} - ${this.escapeHtml(v.text)} - - `).join(''); + const tableRows = violations.map(violation => { + // Получаем правило по ID + const ruleId = violation.r || violation.ruleId; + const rule = this.rules[ruleId] || { n: 'Unknown', t: 'Описание отсутствует' }; + + // Формируем текст с подстановкой параметров + let text = rule.t || ''; + const args = violation.a || violation.args || []; + + if (args.length > 0 && text.includes('{')) { + args.forEach((arg, index) => { + text = text.replace(new RegExp(`\\{${index}\\}`, 'g'), arg); + }); + } + + return ` + + ${violation.i || violation.index} + ${violation.l || violation.line} + ${violation.c || violation.column} + ${this.escapeHtml(rule.n || rule.name)} + ${this.escapeHtml(text)} + + `; + }).join(''); section.innerHTML = `