docsite: require every fence to declare a language
An undeclared fence is invisible to the content gates that read fences, so it is the way a wrong command gets past review. The allowed list is short and extending it is deliberate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 file changed,
+55
−0
internal/docsite/fences_test.go+55 −0
diff --git a/internal/docsite/fences_test.go b/internal/docsite/fences_test.go| new file mode 100644 |
| index 000000000..c33d0253d |
| --- /dev/null |
| +++ b/internal/docsite/fences_test.go |
| @@ -0,0 +1,55 @@ |
| +package docsite |
| + |
| +import ( |
| + "os" |
| + "testing" |
| +) |
| + |
| +// knownFenceLangs is what a fence in this corpus may declare. Adding a |
| +// language here is a deliberate act: an undeclared or unknown fence is |
| +// invisible to every content gate that reads fences, so it is a way to |
| +// smuggle a wrong command past review. |
| +var knownFenceLangs = map[string]bool{ |
| + "sh": true, "bash": true, "console": true, "json": true, |
| + "go": true, "yaml": true, "toml": true, "ini": true, |
| + "text": true, "http": true, |
| +} |
| + |
| +func TestFenceLanguagesAreDeclared(t *testing.T) { |
| + if _, err := os.Stat(root + "/nav.json"); os.IsNotExist(err) { |
| + t.Skip("no corpus yet — Task 8 creates docs/site/nav.json") |
| + } |
| + site, err := Load(root) |
| + if err != nil { |
| + t.Fatalf("load: %v", err) |
| + } |
| + for _, p := range site.Pages() { |
| + for _, f := range p.Fences { |
| + if f.Lang == "" { |
| + t.Errorf("%s.md:%d: fence declares no language", p.Slug, f.Line) |
| + continue |
| + } |
| + if !knownFenceLangs[f.Lang] { |
| + t.Errorf("%s.md:%d: unknown fence language %q — add it to knownFenceLangs if it is real", |
| + p.Slug, f.Line, f.Lang) |
| + } |
| + } |
| + } |
| +} |
| + |
| +// TestTestdataFencesDeclareLanguages runs the same rule against the |
| +// testdata corpus, so the gate above is proven to work on a corpus that |
| +// exists before docs/site does. |
| +func TestTestdataFencesDeclareLanguages(t *testing.T) { |
| + site, err := Load(testCorpus) |
| + if err != nil { |
| + t.Fatalf("load: %v", err) |
| + } |
| + for _, p := range site.Pages() { |
| + for _, f := range p.Fences { |
| + if f.Lang == "" { |
| + t.Errorf("%s: testdata corpus has an undeclared fence at line %d", p.Slug, f.Line) |
| + } |
| + } |
| + } |
| +} |