From 91ab34ddff893485654a9f1fa3a4e7bc1875fa62 Mon Sep 17 00:00:00 2001 From: vickydotbat Date: Sun, 24 May 2026 12:06:47 +0200 Subject: [PATCH] Fix validator screaming about new columns --- internal/topdata/topdata.go | 75 ++++++++++++++++++++++---------- internal/topdata/topdata_test.go | 45 +++++++++++++++++++ 2 files changed, 97 insertions(+), 23 deletions(-) diff --git a/internal/topdata/topdata.go b/internal/topdata/topdata.go index 54aaf08..239ea0e 100644 --- a/internal/topdata/topdata.go +++ b/internal/topdata/topdata.go @@ -432,10 +432,14 @@ func validateDataObject(path string, obj map[string]any, report *ValidationRepor validateRowsFile(path, obj, report, false) return } + if _, hasColumns := obj["columns"]; hasColumns { + validateColumnsFile(path, obj, report) + return + } report.Diagnostics = append(report.Diagnostics, Diagnostic{ Severity: SeverityWarning, Path: path, - Message: "module file does not use a recognized canonical shape yet; expected one of entries, overrides, or rows", + Message: "module file does not use a recognized canonical shape yet; expected one of columns, entries, overrides, or rows", }) return } @@ -832,28 +836,8 @@ func validateRowsFile(path string, obj map[string]any, report *ValidationReport, if rowsList, ok := rows.([]any); ok { validateRowCollection(path, obj, rowsList, report) } - if columns, ok := obj["columns"]; ok { - if list, ok := columns.([]any); !ok { - report.Diagnostics = append(report.Diagnostics, Diagnostic{ - Severity: SeverityError, - Path: path, - Message: "columns must be a JSON array when present", - }) - } else { - for _, raw := range list { - column, ok := raw.(string) - if !ok { - continue - } - if isAuthoringOnlyField(column) && !preserveLegacyWikiColumnForPath(path, column) { - report.Diagnostics = append(report.Diagnostics, Diagnostic{ - Severity: SeverityError, - Path: path, - Message: fmt.Sprintf("deprecated wiki column %q must be moved into meta.wiki", column), - }) - } - } - } + if _, ok := obj["columns"]; ok { + validateColumnsFile(path, obj, report) } else if requireColumns { report.Diagnostics = append(report.Diagnostics, Diagnostic{ Severity: SeverityError, @@ -863,6 +847,45 @@ func validateRowsFile(path string, obj map[string]any, report *ValidationReport, } } +func validateColumnsFile(path string, obj map[string]any, report *ValidationReport) { + columns, ok := obj["columns"] + if !ok { + report.Diagnostics = append(report.Diagnostics, Diagnostic{ + Severity: SeverityError, + Path: path, + Message: "columns file must contain a columns array", + }) + return + } + list, ok := columns.([]any) + if !ok { + report.Diagnostics = append(report.Diagnostics, Diagnostic{ + Severity: SeverityError, + Path: path, + Message: "columns must be a JSON array when present", + }) + return + } + for _, raw := range list { + column, ok := raw.(string) + if !ok { + report.Diagnostics = append(report.Diagnostics, Diagnostic{ + Severity: SeverityError, + Path: path, + Message: "columns must contain only strings", + }) + continue + } + if isAuthoringOnlyField(column) && !preserveLegacyWikiColumnForPath(path, column) { + report.Diagnostics = append(report.Diagnostics, Diagnostic{ + Severity: SeverityError, + Path: path, + Message: fmt.Sprintf("deprecated wiki column %q must be moved into meta.wiki", column), + }) + } + } +} + func validateEntriesFile(path string, obj map[string]any, report *ValidationReport) { entries, ok := obj["entries"] if !ok { @@ -899,6 +922,9 @@ func validateEntriesFile(path string, obj map[string]any, report *ValidationRepo validateInlineTextUsage(path, key, entry, report) } } + if _, ok := obj["columns"]; ok { + validateColumnsFile(path, obj, report) + } } func validateOverridesFile(path string, obj map[string]any, report *ValidationReport) { @@ -922,6 +948,9 @@ func validateOverridesFile(path string, obj map[string]any, report *ValidationRe container := map[string]any{"rows": overrideList} validateRowCollection(path, container, overrideList, report) } + if _, ok := obj["columns"]; ok { + validateColumnsFile(path, obj, report) + } } func validateStateObject(path string, obj map[string]any, report *ValidationReport) { diff --git a/internal/topdata/topdata_test.go b/internal/topdata/topdata_test.go index 0aef30b..08f584c 100644 --- a/internal/topdata/topdata_test.go +++ b/internal/topdata/topdata_test.go @@ -134,6 +134,51 @@ func TestValidateProjectRejectsDuplicateJSONKeys(t *testing.T) { } } +func TestValidateProjectAcceptsModuleColumnExpansionFile(t *testing.T) { + root := testProjectRoot(t) + mkdirAll(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "modules")) + writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") + writeFile(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "base.json"), `{ + "output": "racialtypes.2da", + "columns": ["Label"], + "rows": [] +}`+"\n") + writeFile(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "modules", "00_newcolumns.json"), `{ + "columns": ["ECL", "AvailableHeadsMale", "AvailableSkinColors"] +}`+"\n") + + report := ValidateProject(testProject(root)) + text := diagnosticsText(report.Diagnostics) + if strings.Contains(text, "module file does not use a recognized canonical shape") { + t.Fatalf("expected module column expansion file to validate as canonical, got:\n%s", text) + } + if report.HasErrors() { + t.Fatalf("expected module column expansion file to avoid validation errors, got:\n%s", text) + } +} + +func TestValidateProjectRejectsInvalidModuleColumnExpansionFile(t *testing.T) { + root := testProjectRoot(t) + mkdirAll(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "modules")) + writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n") + writeFile(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "base.json"), `{ + "output": "racialtypes.2da", + "columns": ["Label"], + "rows": [] +}`+"\n") + writeFile(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "modules", "00_badcolumns.json"), `{ + "columns": "ECL" +}`+"\n") + + report := ValidateProject(testProject(root)) + if !report.HasErrors() { + t.Fatalf("expected invalid module columns file to fail validation, got:\n%s", diagnosticsText(report.Diagnostics)) + } + if !diagnosticsContain(report.Diagnostics, "columns must be a JSON array when present") { + t.Fatalf("expected columns array diagnostic, got:\n%s", diagnosticsText(report.Diagnostics)) + } +} + func TestValidateProjectRejectsDuplicateEntryKeysAcrossModules(t *testing.T) { root := testProjectRoot(t) mkdirAll(t, filepath.Join(root, "topdata", "data", "baseitems", "modules"))