Files
summercms/internal/docsite/check_forbidden.go
Jakub Zych 5d4c1e3046 feat(11.1-02): check links, commands, forbidden names and fence policy
- relative links and anchors resolve against the renderer's heading IDs
- summer and ./bin/<app> command names come from the real command
  constructors through docsite.Options.Commands; a nil set is a problem
- consuming-application names fail in page sources and built outputs
- go fences in docs/ pages need src=, callouts are NOTE, TIP or WARNING,
  docs/ headings are plain ASCII
- gate gains --claude and self-test plants for each new rule
2026-09-30 21:40:46 +02:00

67 lines
2.0 KiB
Go

package docsite
import (
"fmt"
"os"
"path"
"regexp"
"slices"
"strings"
)
// forbiddenName matches the consuming-application spellings framework docs
// must never contain (D-11), the accented variant included.
var forbiddenName = regexp.MustCompile(`(?i)fonoteka|p[lł][yý]tarium`)
const forbiddenMessage = "consuming-application name in output"
// forbiddenLines returns the 1-based line numbers of text that name a
// consuming application. The match itself is never reported.
func forbiddenLines(text []byte) []int {
var lines []int
for i, line := range strings.Split(string(text), "\n") {
if forbiddenName.MatchString(line) {
lines = append(lines, i+1)
}
}
return lines
}
// checkForbiddenSources scans the full source file of every page (the
// frontmatter and a README's summary line included).
func (s *site) checkForbiddenSources() ([]Problem, error) {
var problems []Problem
for _, p := range s.pages {
raw, err := os.ReadFile(p.abs)
if err != nil {
return nil, fmt.Errorf("docsite: read %s: %w", p.Source, err)
}
for _, line := range forbiddenLines(raw) {
problems = append(problems, Problem{File: p.Source, Line: line, Rule: "forbidden", Message: forbiddenMessage})
}
}
return problems, nil
}
// forbiddenOutputExts are the text outputs scanned after rendering.
var forbiddenOutputExts = []string{".html", ".md", ".txt", ".json", ".js", ".css"}
// checkForbiddenOutputs scans every rendered text output, which covers
// text pulled in through src= snippet copies and templates.
func (s *site) checkForbiddenOutputs() []Problem {
names := make([]string, 0, len(s.outputs))
for name := range s.outputs {
if slices.Contains(forbiddenOutputExts, path.Ext(name)) {
names = append(names, name)
}
}
slices.Sort(names)
var problems []Problem
for _, name := range names {
for _, line := range forbiddenLines(s.outputs[name]) {
problems = append(problems, Problem{File: name, Line: line, Rule: "forbidden", Message: forbiddenMessage})
}
}
return problems
}