fix(11.1-07): check go fences, README src= and shell fences from the AST
- 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
This commit is contained in:
@@ -2,7 +2,6 @@ package docsite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -12,52 +11,63 @@ import (
|
||||
// calloutTypes are the only `> [!TYPE]` callouts the theme renders.
|
||||
var calloutTypes = []string{"NOTE", "TIP", "WARNING"}
|
||||
|
||||
// calloutMarker matches the first line of a callout blockquote.
|
||||
var calloutMarker = regexp.MustCompile(`^\s{0,3}>\s?\[!([A-Za-z]+)\]\s*$`)
|
||||
|
||||
// checkPolicy enforces the page content rules:
|
||||
// - every go fence in a docs/ page carries src= (module READMEs are
|
||||
// rendered as written, D-18);
|
||||
// - 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 {
|
||||
func (s *site) checkPolicy(docs []parsedDoc) ([]Problem, error) {
|
||||
var problems []Problem
|
||||
for _, d := range docs {
|
||||
if d.page == nil {
|
||||
continue
|
||||
}
|
||||
guide := d.page.Module == ""
|
||||
lines := strings.Split(string(d.body), "\n")
|
||||
fences := scanFences(lines)
|
||||
inFence := make([]bool, len(lines))
|
||||
for _, f := range fences {
|
||||
end := f.close
|
||||
if end < 0 {
|
||||
end = len(lines) - 1
|
||||
if guide {
|
||||
fences, err := collectFences(d.doc, d.body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := f.open; i <= end; i++ {
|
||||
inFence[i] = true
|
||||
}
|
||||
fields := strings.Fields(f.info)
|
||||
if guide && len(fields) > 0 && fields[0] == "go" &&
|
||||
!slices.ContainsFunc(fields[1:], func(f string) bool { return strings.HasPrefix(f, "src=") }) {
|
||||
problems = append(problems, Problem{File: d.file, Line: d.line + f.open, Rule: "snippet",
|
||||
Message: "go code block has no src= reference"})
|
||||
}
|
||||
}
|
||||
for i, line := range lines {
|
||||
if inFence[i] {
|
||||
continue
|
||||
}
|
||||
if m := calloutMarker.FindStringSubmatch(line); m != nil && !slices.Contains(calloutTypes, m[1]) {
|
||||
problems = append(problems, Problem{File: d.file, Line: d.line + i, Rule: "callout",
|
||||
Message: fmt.Sprintf("unknown type %s (use NOTE, TIP or WARNING)", m[1])})
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user