Доработан js и минимизация json
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
using SQLLinter.Common;
|
using SQLLinter.Common;
|
||||||
using SQLLinter.Infrastructure.Diagram;
|
using SQLLinter.Infrastructure.Diagram;
|
||||||
|
using SQLLinter.Infrastructure.Rules.RuleViolations;
|
||||||
|
using System.Data;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@@ -66,38 +68,76 @@ public class HtmlReportFormatter : IReportFormatter
|
|||||||
|
|
||||||
foreach (var fileGroup in groupedByFile)
|
foreach (var fileGroup in groupedByFile)
|
||||||
{
|
{
|
||||||
var fileData = new FileReportData
|
var fileData = new FileReport
|
||||||
{
|
{
|
||||||
FileName = fileGroup.Key
|
Name = fileGroup.Key,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Группировка по severity
|
// Группировка по severity
|
||||||
var severityGroups = fileGroup
|
var severityGroups = fileGroup
|
||||||
.GroupBy(v => v.Severity)
|
.GroupBy(v => v.Severity)
|
||||||
.OrderByDescending(g => g.Key)
|
.OrderByDescending(g => g.Key)
|
||||||
.ToList();
|
.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
|
int ruleId;
|
||||||
.OrderBy(v => v.Line)
|
List<string> args = new();
|
||||||
.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();
|
|
||||||
|
|
||||||
if (severityGroup.Key == RuleViolationSeverity.Critical)
|
if (violation is RuleTemplateViolation templateRule)
|
||||||
fileData.CriticalViolations = violationsList;
|
{
|
||||||
else if (severityGroup.Key == RuleViolationSeverity.Warning)
|
args = templateRule.Params.Select(p => EscapeHtml(p)).ToList();
|
||||||
fileData.WarningViolations = violationsList;
|
|
||||||
else if (severityGroup.Key == RuleViolationSeverity.Info)
|
if (reportData.Rules.Any(t => t.Value.Name == templateRule.RuleName))
|
||||||
fileData.InfoViolations = violationsList;
|
{
|
||||||
|
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);
|
reportData.Files.Add(fileData);
|
||||||
@@ -106,9 +146,9 @@ public class HtmlReportFormatter : IReportFormatter
|
|||||||
// Добавление диаграммы, если есть
|
// Добавление диаграммы, если есть
|
||||||
if (diagram != null)
|
if (diagram != null)
|
||||||
{
|
{
|
||||||
reportData.Diagram = new DiagramData
|
reportData.Diagram = new Diagram
|
||||||
{
|
{
|
||||||
MermaidContent = MermaidRenderer.ToMermaidContent(diagram),
|
Content = MermaidRenderer.ToMermaidContent(diagram),
|
||||||
HasDiagram = true
|
HasDiagram = true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -173,36 +213,70 @@ public class HtmlReportFormatter : IReportFormatter
|
|||||||
// Классы для сериализации
|
// Классы для сериализации
|
||||||
private class ReportData
|
private class ReportData
|
||||||
{
|
{
|
||||||
public List<FileReportData> Files { get; set; } = new();
|
[JsonPropertyName("f")] // files
|
||||||
public DiagramData? Diagram { get; set; }
|
public List<FileReport> Files { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonPropertyName("r")] // rules
|
||||||
|
public Dictionary<int, Rule> 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;
|
[JsonPropertyName("n")] // name
|
||||||
public List<ViolationData>? CriticalViolations { get; set; }
|
public string Name { get; set; } = string.Empty;
|
||||||
public List<ViolationData>? WarningViolations { get; set; }
|
|
||||||
public List<ViolationData>? InfoViolations { get; set; }
|
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonPropertyName("v")] // violations
|
||||||
public int TotalViolations =>
|
public Violations Violations { get; set; } = new();
|
||||||
(CriticalViolations?.Count ?? 0) +
|
|
||||||
(WarningViolations?.Count ?? 0) +
|
|
||||||
(InfoViolations?.Count ?? 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ViolationData
|
private class Violations
|
||||||
{
|
{
|
||||||
|
[JsonPropertyName("c")] // critical
|
||||||
|
public List<Violation> Critical { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonPropertyName("w")] // warning
|
||||||
|
public List<Violation> Warning { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonPropertyName("i")] // info
|
||||||
|
public List<Violation> Info { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Violation
|
||||||
|
{
|
||||||
|
[JsonPropertyName("i")] // index
|
||||||
public int Index { get; set; }
|
public int Index { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("l")] // line
|
||||||
public int Line { get; set; }
|
public int Line { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("c")] // column
|
||||||
public int Column { get; set; }
|
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<string>? 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; }
|
public bool HasDiagram { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -393,7 +393,7 @@ td.index {
|
|||||||
td.rule {
|
td.rule {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
min-width: 200px;
|
width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
td.description {
|
td.description {
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.mi
|
|||||||
class ReportRenderer {
|
class ReportRenderer {
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.files = data.files || [];
|
this.files = data.files || data.f || [];
|
||||||
this.diagram = data.diagram;
|
this.rules = data.rules || data.r || {};
|
||||||
|
this.diagram = data.diagram || data.d || {};
|
||||||
this.currentFileIndex = 0;
|
this.currentFileIndex = 0;
|
||||||
|
|
||||||
this.reportsContainer = document.getElementById('reports-container');
|
this.reportsContainer = document.getElementById('reports-container');
|
||||||
@@ -24,12 +25,13 @@ class ReportRenderer {
|
|||||||
this.renderTabs();
|
this.renderTabs();
|
||||||
this.renderFileReports();
|
this.renderFileReports();
|
||||||
this.setupTabNavigation();
|
this.setupTabNavigation();
|
||||||
|
|
||||||
// Активируем первый таб
|
|
||||||
this.activateTab(0);
|
this.activateTab(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTabs() {
|
renderTabs() {
|
||||||
|
const tabsList = document.getElementById('tabs-list');
|
||||||
|
if (!tabsList) return;
|
||||||
|
|
||||||
this.tabsList.innerHTML = '';
|
this.tabsList.innerHTML = '';
|
||||||
|
|
||||||
// Табы для файлов
|
// Табы для файлов
|
||||||
@@ -39,25 +41,30 @@ class ReportRenderer {
|
|||||||
tab.dataset.target = `file_${index}`;
|
tab.dataset.target = `file_${index}`;
|
||||||
tab.dataset.index = index;
|
tab.dataset.index = index;
|
||||||
|
|
||||||
|
const total = (file.v.c?.length || 0) + (file.v.w?.length || 0) + (file.v.i?.length || 0);
|
||||||
|
|
||||||
tab.innerHTML = `
|
tab.innerHTML = `
|
||||||
${this.escapeHtml(file.fileName)}
|
${this.escapeHtml(file.n)}
|
||||||
<span class="tab-badge">${file.totalViolations || this.calculateTotalViolations(file)}</span>
|
<span class="tab-badge">${total}</span>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
this.tabsList.appendChild(tab);
|
this.tabsList.appendChild(tab);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Таб для диаграммы (если есть)
|
// Таб для диаграммы (если есть)
|
||||||
if (this.diagram?.hasDiagram) {
|
if (this.diagram.h || this.diagram.hasDiagram) {
|
||||||
const diagramTab = document.createElement('button');
|
const diagramTab = document.createElement('button');
|
||||||
diagramTab.className = 'tab';
|
diagramTab.className = 'tab';
|
||||||
diagramTab.dataset.target = 'mermaid';
|
diagramTab.dataset.target = 'mermaid';
|
||||||
diagramTab.textContent = 'Диаграмма';
|
diagramTab.textContent = 'Диаграмма';
|
||||||
this.tabsList.appendChild(diagramTab);
|
tabsList.appendChild(diagramTab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFileReports() {
|
renderFileReports() {
|
||||||
|
const container = document.getElementById('reports-container');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
this.reportsContainer.innerHTML = '';
|
this.reportsContainer.innerHTML = '';
|
||||||
|
|
||||||
// Рендеринг отчетов по файлам
|
// Рендеринг отчетов по файлам
|
||||||
@@ -67,6 +74,9 @@ class ReportRenderer {
|
|||||||
reportDiv.className = 'file-report';
|
reportDiv.className = 'file-report';
|
||||||
reportDiv.style.display = 'none';
|
reportDiv.style.display = 'none';
|
||||||
|
|
||||||
|
const fileName = file.n || file.fileName || '';
|
||||||
|
const violations = file.v || file.violations || {};
|
||||||
|
|
||||||
// Заголовок файла
|
// Заголовок файла
|
||||||
reportDiv.innerHTML = `
|
reportDiv.innerHTML = `
|
||||||
<div class="file-title-container">
|
<div class="file-title-container">
|
||||||
@@ -75,27 +85,28 @@ class ReportRenderer {
|
|||||||
<path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
<path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="file-name">${this.escapeHtml(file.fileName)}</span>
|
<span class="file-name">${this.escapeHtml(fileName)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Секции по severity
|
// Добавляем секции нарушений
|
||||||
if (file.criticalViolations?.length > 0) {
|
if (file.v.c?.length > 0) {
|
||||||
reportDiv.appendChild(this.createSeveritySection('critical', file.criticalViolations));
|
reportDiv.appendChild(this.createViolationSection('critical', file.v.c));
|
||||||
}
|
}
|
||||||
if (file.warningViolations?.length > 0) {
|
if (file.v.w?.length > 0) {
|
||||||
reportDiv.appendChild(this.createSeveritySection('warning', file.warningViolations));
|
reportDiv.appendChild(this.createViolationSection('warning', file.v.w));
|
||||||
}
|
}
|
||||||
if (file.infoViolations?.length > 0) {
|
if (file.v.i?.length > 0) {
|
||||||
reportDiv.appendChild(this.createSeveritySection('info', file.infoViolations));
|
reportDiv.appendChild(this.createViolationSection('info', file.v.i));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.reportsContainer.appendChild(reportDiv);
|
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');
|
const diagramDiv = document.createElement('div');
|
||||||
diagramDiv.id = 'mermaid';
|
diagramDiv.id = 'mermaid';
|
||||||
diagramDiv.className = 'file-report';
|
diagramDiv.className = 'file-report';
|
||||||
@@ -104,7 +115,7 @@ class ReportRenderer {
|
|||||||
diagramDiv.innerHTML = `
|
diagramDiv.innerHTML = `
|
||||||
<div class="diagram-toolbar">
|
<div class="diagram-toolbar">
|
||||||
<div class="toolbar-search">
|
<div class="toolbar-search">
|
||||||
<input type="text" id="diagramSearch" placeholder="Поиск по узлам (нажмите Enter)" />
|
<input type="text" id="diagramSearch" placeholder="Поиск по узлам" />
|
||||||
<button class="search-clear" type="button">✕</button>
|
<button class="search-clear" type="button">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar-actions">
|
<div class="toolbar-actions">
|
||||||
@@ -131,7 +142,7 @@ class ReportRenderer {
|
|||||||
<div id="minimap"></div>
|
<div id="minimap"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="mermaidSource" style="display:none">
|
<div id="mermaidSource" style="display:none">
|
||||||
${this.escapeHtml(this.diagram.mermaidContent)}
|
${this.escapeHtml(diagramContent)}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -139,7 +150,7 @@ class ReportRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createSeveritySection(severity, violations) {
|
createViolationSection(severity, violations) {
|
||||||
const severityTitle = {
|
const severityTitle = {
|
||||||
critical: 'Critical',
|
critical: 'Critical',
|
||||||
warning: 'Warning',
|
warning: 'Warning',
|
||||||
@@ -149,15 +160,31 @@ class ReportRenderer {
|
|||||||
const section = document.createElement('div');
|
const section = document.createElement('div');
|
||||||
section.className = `severity-section ${severity}`;
|
section.className = `severity-section ${severity}`;
|
||||||
|
|
||||||
const tableRows = violations.map(v => `
|
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 `
|
||||||
<tr>
|
<tr>
|
||||||
<td class="index">${v.index}</td>
|
<td class="index">${violation.i || violation.index}</td>
|
||||||
<td class="line">${v.line}</td>
|
<td class="line">${violation.l || violation.line}</td>
|
||||||
<td class="column">${v.column}</td>
|
<td class="column">${violation.c || violation.column}</td>
|
||||||
<td class="rule">${this.escapeHtml(v.ruleName)}</td>
|
<td class="rule">${this.escapeHtml(rule.n || rule.name)}</td>
|
||||||
<td class="description">${this.escapeHtml(v.text)}</td>
|
<td class="description">${this.escapeHtml(text)}</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join('');
|
`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
section.innerHTML = `
|
section.innerHTML = `
|
||||||
<div class="severity-header">
|
<div class="severity-header">
|
||||||
|
|||||||
Reference in New Issue
Block a user