Publish grouped warning and diagnostic files

This commit is contained in:
2026-08-27 16:43:09 +00:00
parent 4dbbf68051
commit 54de2b816a
5 changed files with 193 additions and 20 deletions

View File

@@ -25,6 +25,11 @@ const chunkMapFileName = "chunk-map.json"
const evidenceContextFileName = "evidence-context.json"
const (
warningsSchemaVersion = "notarius.warnings.v2"
diagnosticsSchemaVersion = "notarius.diagnostics.v1"
)
var safeOutputFileChar = regexp.MustCompile(`[^A-Za-z0-9._-]`)
var _ contracts.OutputEncoder = (*Encoder)(nil)
@@ -231,6 +236,7 @@ type indexFile struct {
OutputFiles []outputFileIndex `json:"output_files"`
RejectedFile string `json:"rejected_file"`
WarningsFile string `json:"warnings_file"`
DiagnosticsFile string `json:"diagnostics_file"`
ChunkMap *artifactIndex `json:"chunk_map,omitempty"`
EvidenceContext *artifactIndex `json:"evidence_context,omitempty"`
}
@@ -259,7 +265,19 @@ type rejectedFile struct {
}
type warningsFile struct {
Groups []contracts.DiagnosticGroup `json:"groups"`
SchemaVersion string `json:"schema_version"`
GroupCount int `json:"group_count"`
OccurrenceCount int `json:"occurrence_count"`
Groups []contracts.DiagnosticGroup `json:"groups"`
}
type diagnosticsFile struct {
SchemaVersion string `json:"schema_version"`
GroupCount int `json:"group_count"`
OccurrenceCount int `json:"occurrence_count"`
Truncated bool `json:"truncated"`
UnrepresentedOccurrenceCount int `json:"unrepresented_occurrence_count"`
Groups []contracts.DiagnosticGroup `json:"groups"`
}
func logicalFiles(req contracts.OutputRequest, options Options) ([]contracts.OutputFile, error) {
@@ -269,7 +287,7 @@ func logicalFiles(req contracts.OutputRequest, options Options) ([]contracts.Out
})
outputIndexes := make([]outputFileIndex, 0, len(outputs))
files := make([]contracts.OutputFile, 0, len(outputs)+5)
files := make([]contracts.OutputFile, 0, len(outputs)+6)
manifestFile, err := jsonFile("manifest.json", req.Manifest)
if err != nil {
return nil, err
@@ -303,10 +321,11 @@ func logicalFiles(req contracts.OutputRequest, options Options) ([]contracts.Out
}
index := indexFile{
ManifestFile: "manifest.json",
OutputFiles: outputIndexes,
RejectedFile: "rejected.json",
WarningsFile: "warnings.json",
ManifestFile: "manifest.json",
OutputFiles: outputIndexes,
RejectedFile: "rejected.json",
WarningsFile: "warnings.json",
DiagnosticsFile: "diagnostics.json",
}
if options.IncludeChunkMap && req.ChunkMap != nil {
chunkMapOutput, chunkMapDescriptor, err := serializedChunkMapFile(*req.ChunkMap)
@@ -332,11 +351,15 @@ func logicalFiles(req contracts.OutputRequest, options Options) ([]contracts.Out
if err != nil {
return nil, err
}
warningsOutput, err := jsonFile("warnings.json", warningsFile{Groups: warningGroups(req.Diagnostics)})
warningsOutput, err := jsonFile("warnings.json", newWarningsFile(req.Diagnostics))
if err != nil {
return nil, err
}
files = append(files, indexOutput, rejectedOutput, warningsOutput)
diagnosticsOutput, err := jsonFile("diagnostics.json", newDiagnosticsFile(req.Diagnostics))
if err != nil {
return nil, err
}
files = append(files, indexOutput, rejectedOutput, warningsOutput, diagnosticsOutput)
sort.Slice(files, func(i, j int) bool {
return files[i].Name < files[j].Name
})
@@ -487,16 +510,56 @@ func cloneRejected(rejected []contracts.RejectedOutput) []contracts.RejectedOutp
return append([]contracts.RejectedOutput(nil), rejected...)
}
func warningGroups(collection contracts.DiagnosticCollection) []contracts.DiagnosticGroup {
func newWarningsFile(collection contracts.DiagnosticCollection) warningsFile {
groups := diagnosticGroupsByDisposition(collection, contracts.DiagnosticDispositionWarning)
return warningsFile{
SchemaVersion: warningsSchemaVersion,
GroupCount: len(groups),
OccurrenceCount: diagnosticOccurrenceCount(groups),
Groups: groups,
}
}
func newDiagnosticsFile(collection contracts.DiagnosticCollection) diagnosticsFile {
groups := diagnosticGroupsExceptDisposition(collection, contracts.DiagnosticDispositionWarning)
return diagnosticsFile{
SchemaVersion: diagnosticsSchemaVersion,
GroupCount: len(groups),
OccurrenceCount: diagnosticOccurrenceCount(groups) + collection.UnrepresentedOccurrenceCount,
Truncated: collection.Truncated,
UnrepresentedOccurrenceCount: collection.UnrepresentedOccurrenceCount,
Groups: groups,
}
}
func diagnosticGroupsByDisposition(collection contracts.DiagnosticCollection, disposition contracts.DiagnosticDisposition) []contracts.DiagnosticGroup {
groups := make([]contracts.DiagnosticGroup, 0)
for _, group := range collection.Groups {
if group.Disposition == contracts.DiagnosticDispositionWarning {
if group.Disposition == disposition {
groups = append(groups, group)
}
}
return contracts.CloneDiagnosticCollection(contracts.DiagnosticCollection{Groups: groups}).Groups
}
func diagnosticGroupsExceptDisposition(collection contracts.DiagnosticCollection, disposition contracts.DiagnosticDisposition) []contracts.DiagnosticGroup {
groups := make([]contracts.DiagnosticGroup, 0)
for _, group := range collection.Groups {
if group.Disposition != disposition {
groups = append(groups, group)
}
}
return contracts.CloneDiagnosticCollection(contracts.DiagnosticCollection{Groups: groups}).Groups
}
func diagnosticOccurrenceCount(groups []contracts.DiagnosticGroup) int {
count := 0
for _, group := range groups {
count += group.OccurrenceCount
}
return count
}
func encoderErrorf(format string, args ...any) error {
return fmt.Errorf("json output encoder: "+format, args...)
}