Improved Logging
This commit is contained in:
@@ -27,8 +27,9 @@ type Diagnostic struct {
|
||||
}
|
||||
|
||||
type Report struct {
|
||||
Diagnostics []Diagnostic
|
||||
SourceCount int
|
||||
Diagnostics []Diagnostic
|
||||
SourceCount int
|
||||
DecompiledModels []string
|
||||
}
|
||||
|
||||
type loadedDocument struct {
|
||||
@@ -121,6 +122,11 @@ func ValidateProject(p *project.Project) Report {
|
||||
}
|
||||
base := strings.ToLower(strings.TrimSuffix(filepath.Base(rel), filepath.Ext(rel)))
|
||||
extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(rel)), ".")
|
||||
if extension == "mdl" {
|
||||
if isTextMDL(filepath.Join(p.AssetsDir(), filepath.FromSlash(rel))) {
|
||||
report.DecompiledModels = append(report.DecompiledModels, rel)
|
||||
}
|
||||
}
|
||||
key := strings.ToLower(base) + "." + extension
|
||||
if _, exists := assetIndex[key]; !exists {
|
||||
assetIndex[key] = rel
|
||||
@@ -153,10 +159,22 @@ func ValidateProject(p *project.Project) Report {
|
||||
}
|
||||
return strings.Compare(a.Message, b.Message)
|
||||
})
|
||||
slices.Sort(report.DecompiledModels)
|
||||
|
||||
return report
|
||||
}
|
||||
|
||||
func (r Report) SummaryLines(maxPaths int) []string {
|
||||
return summarizeDiagnostics(r.Diagnostics, maxPaths)
|
||||
}
|
||||
|
||||
func (r Report) DecompiledSummaryLine(maxPaths int) string {
|
||||
if len(r.DecompiledModels) == 0 {
|
||||
return ""
|
||||
}
|
||||
return formatSummaryLine("info", "ASCII/decompiled .mdl files detected", r.DecompiledModels, maxPaths)
|
||||
}
|
||||
|
||||
func (r *Report) add(path, message string, severity Severity) {
|
||||
r.Diagnostics = append(r.Diagnostics, Diagnostic{Path: path, Message: message, Severity: severity})
|
||||
}
|
||||
@@ -185,6 +203,79 @@ func (r Report) WarningCount() int {
|
||||
return count
|
||||
}
|
||||
|
||||
func summarizeDiagnostics(diagnostics []Diagnostic, maxPaths int) []string {
|
||||
type key struct {
|
||||
severity Severity
|
||||
message string
|
||||
}
|
||||
groups := map[key][]string{}
|
||||
order := make([]key, 0)
|
||||
for _, diagnostic := range diagnostics {
|
||||
k := key{severity: diagnostic.Severity, message: diagnostic.Message}
|
||||
if _, ok := groups[k]; !ok {
|
||||
order = append(order, k)
|
||||
}
|
||||
groups[k] = append(groups[k], diagnostic.Path)
|
||||
}
|
||||
slices.SortFunc(order, func(a, b key) int {
|
||||
if a.severity != b.severity {
|
||||
if a.severity == SeverityError {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
return strings.Compare(a.message, b.message)
|
||||
})
|
||||
lines := make([]string, 0, len(order))
|
||||
for _, k := range order {
|
||||
paths := append([]string(nil), groups[k]...)
|
||||
slices.Sort(paths)
|
||||
paths = slices.Compact(paths)
|
||||
lines = append(lines, formatSummaryLine(string(k.severity), k.message, paths, maxPaths))
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func formatSummaryLine(prefix, message string, paths []string, maxPaths int) string {
|
||||
if len(paths) == 0 {
|
||||
return fmt.Sprintf("%s: %s", prefix, message)
|
||||
}
|
||||
if maxPaths <= 0 {
|
||||
maxPaths = 3
|
||||
}
|
||||
display := paths
|
||||
if len(display) > maxPaths {
|
||||
display = display[:maxPaths]
|
||||
}
|
||||
location := strings.Join(display, ", ")
|
||||
if len(paths) > len(display) {
|
||||
location += fmt.Sprintf(", and %d more", len(paths)-len(display))
|
||||
}
|
||||
return fmt.Sprintf("%s: %s [%s]", prefix, message, location)
|
||||
}
|
||||
|
||||
func isTextMDL(path string) bool {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil || len(data) == 0 {
|
||||
return false
|
||||
}
|
||||
if len(data) > 4096 {
|
||||
data = data[:4096]
|
||||
}
|
||||
if strings.IndexByte(string(data), 0) >= 0 {
|
||||
return false
|
||||
}
|
||||
printable := 0
|
||||
for _, b := range data {
|
||||
if b == '\n' || b == '\r' || b == '\t' || (b >= 32 && b <= 126) {
|
||||
printable++
|
||||
}
|
||||
}
|
||||
ratio := float64(printable) / float64(len(data))
|
||||
lower := strings.ToLower(string(data))
|
||||
return strings.Contains(lower, "newmodel ") || strings.Contains(lower, "donemodel") || ratio >= 0.9
|
||||
}
|
||||
|
||||
func loadDocument(path string) (gff.Document, string, string, error) {
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
|
||||
@@ -199,6 +199,52 @@ func TestValidateProjectWarnsForMissingScriptReferences(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateProjectReportsDecompiledModelsWithoutFailingValidation(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
mustMkdir(t, filepath.Join(root, "src"))
|
||||
mustMkdir(t, filepath.Join(root, "assets", "models"))
|
||||
mustMkdir(t, filepath.Join(root, "build"))
|
||||
|
||||
mustWriteFile(t, filepath.Join(root, "nwn-tool.json"), `{
|
||||
"module": {
|
||||
"name": "Test Module",
|
||||
"resref": "testmod"
|
||||
},
|
||||
"paths": {
|
||||
"source": "src",
|
||||
"assets": "assets",
|
||||
"build": "build"
|
||||
}
|
||||
}
|
||||
`)
|
||||
mustWriteFile(t, filepath.Join(root, "assets", "models", "plain_text.mdl"), "newmodel plain_text\nsetsupermodel plain_text NULL\nbeginmodelgeom plain_text\nendmodelgeom plain_text\ndonemodel plain_text\n")
|
||||
if err := os.WriteFile(filepath.Join(root, "assets", "models", "binary_ok.mdl"), []byte{0x00, 0x01, 0x02, 0x03}, 0o644); err != nil {
|
||||
t.Fatalf("write binary mdl: %v", err)
|
||||
}
|
||||
|
||||
p, err := project.Load(root)
|
||||
if err != nil {
|
||||
t.Fatalf("load project: %v", err)
|
||||
}
|
||||
if err := p.ValidateLayout(); err != nil {
|
||||
t.Fatalf("validate layout: %v", err)
|
||||
}
|
||||
if err := p.Scan(); err != nil {
|
||||
t.Fatalf("scan: %v", err)
|
||||
}
|
||||
|
||||
report := ValidateProject(p)
|
||||
if report.HasErrors() {
|
||||
t.Fatalf("expected no validation errors, got %#v", report.Diagnostics)
|
||||
}
|
||||
if len(report.DecompiledModels) != 1 {
|
||||
t.Fatalf("expected 1 decompiled model, got %#v", report.DecompiledModels)
|
||||
}
|
||||
if report.DecompiledModels[0] != "models/plain_text.mdl" {
|
||||
t.Fatalf("unexpected decompiled model path: %#v", report.DecompiledModels)
|
||||
}
|
||||
}
|
||||
|
||||
func mustMkdir(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
if err := os.MkdirAll(path, 0o755); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user