- goLang follows the highlighter's chroma lookup, so golang and main.go need src= - a src= fence in a module README is refused and never captioned - shell fences inside callouts and lists are command-checked - a fence with four or more leading spaces is an indented code block
116 lines
3.5 KiB
Go
116 lines
3.5 KiB
Go
package docsite
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
gast "github.com/yuin/goldmark/ast"
|
|
)
|
|
|
|
// calloutTypes are the only `> [!TYPE]` callouts the theme renders.
|
|
var calloutTypes = []string{"NOTE", "TIP", "WARNING"}
|
|
|
|
// checkPolicy enforces the page content rules:
|
|
// - every Go-lexer fence in a docs/ page carries src=, at any depth
|
|
// (module READMEs are rendered as written, D-18);
|
|
// - callouts are NOTE, TIP or WARNING;
|
|
// - docs/ headings are plain ASCII text without links or code spans.
|
|
func (s *site) checkPolicy(docs []parsedDoc) ([]Problem, error) {
|
|
var problems []Problem
|
|
for _, d := range docs {
|
|
if d.page == nil {
|
|
continue
|
|
}
|
|
guide := d.page.Module == ""
|
|
if guide {
|
|
fences, err := collectFences(d.doc, d.body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, f := range fences {
|
|
if _, hasSrc := ParseSrc(f.info); goLang(f.lang) && !hasSrc {
|
|
problems = append(problems, Problem{File: d.file, Line: d.line + f.line, Rule: "snippet",
|
|
Message: f.lang + " code block has no src= reference"})
|
|
}
|
|
}
|
|
}
|
|
problems = append(problems, calloutProblems(d)...)
|
|
if guide {
|
|
problems = append(problems, headingProblems(d)...)
|
|
}
|
|
}
|
|
return problems, nil
|
|
}
|
|
|
|
// calloutProblems reports blockquotes whose marker is not NOTE, TIP or
|
|
// WARNING. calloutTransformer has already turned the known types into
|
|
// callout nodes, so a remaining blockquote with a marker is unknown.
|
|
// The marker is read from the AST, so a copy of it inside a code fence
|
|
// is not a callout.
|
|
func calloutProblems(d parsedDoc) []Problem {
|
|
var problems []Problem
|
|
_ = gast.Walk(d.doc, func(n gast.Node, entering bool) (gast.WalkStatus, error) {
|
|
bq, ok := n.(*gast.Blockquote)
|
|
if !entering || !ok {
|
|
return gast.WalkContinue, nil
|
|
}
|
|
para, ok := bq.FirstChild().(*gast.Paragraph)
|
|
if !ok || para.Lines().Len() == 0 {
|
|
return gast.WalkContinue, nil
|
|
}
|
|
first := para.Lines().At(0)
|
|
m := calloutLine.FindStringSubmatch(strings.TrimSpace(string(first.Value(d.body))))
|
|
if m == nil || slices.Contains(calloutTypes, m[1]) {
|
|
return gast.WalkContinue, nil
|
|
}
|
|
problems = append(problems, Problem{File: d.file, Line: lineOf(d.body, first.Start, d.line), Rule: "callout",
|
|
Message: fmt.Sprintf("unknown type %s (use NOTE, TIP or WARNING)", m[1])})
|
|
return gast.WalkContinue, nil
|
|
})
|
|
return problems
|
|
}
|
|
|
|
const headingMessage = "headings must be plain ASCII text without links or code"
|
|
|
|
// headingProblems reports headings with a link, image, code span or raw
|
|
// HTML, or with non-ASCII text: their IDs would not be stable across the
|
|
// site, Gitea and GitHub.
|
|
func headingProblems(d parsedDoc) []Problem {
|
|
var problems []Problem
|
|
_ = gast.Walk(d.doc, func(n gast.Node, entering bool) (gast.WalkStatus, error) {
|
|
h, ok := n.(*gast.Heading)
|
|
if !entering || !ok {
|
|
return gast.WalkContinue, nil
|
|
}
|
|
bad := false
|
|
_ = gast.Walk(h, func(c gast.Node, entering bool) (gast.WalkStatus, error) {
|
|
if !entering {
|
|
return gast.WalkContinue, nil
|
|
}
|
|
switch t := c.(type) {
|
|
case *gast.Link, *gast.Image, *gast.CodeSpan, *gast.RawHTML, *gast.AutoLink:
|
|
bad = true
|
|
return gast.WalkStop, nil
|
|
case *gast.Text:
|
|
for _, b := range t.Segment.Value(d.body) {
|
|
if b >= 0x80 {
|
|
bad = true
|
|
return gast.WalkStop, nil
|
|
}
|
|
}
|
|
}
|
|
return gast.WalkContinue, nil
|
|
})
|
|
if bad {
|
|
off := -1
|
|
if h.Lines().Len() > 0 {
|
|
off = h.Lines().At(0).Start
|
|
}
|
|
problems = append(problems, Problem{File: d.file, Line: lineOf(d.body, off, d.line), Rule: "heading", Message: headingMessage})
|
|
}
|
|
return gast.WalkSkipChildren, nil
|
|
})
|
|
return problems
|
|
}
|