fix(11.1-07): find src= fences in the goldmark AST and refuse nested ones
- collectFences walks every fenced code block the renderer parses - a nested src= fence is refused and is not extracted, synced or captioned - checkSnippets runs on the same parse as the other checkers - Sync rewrites drifted top-level fences and keeps the frontmatter Deviation: TestSyncRewritesDrift's drifted src= fence sat inside a list item, which is now refused. The drift case is a two-space top-level fence so Sync still rewrites it (problem line 11).
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package docsite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
@@ -446,21 +447,31 @@ type drift struct {
|
||||
want string
|
||||
}
|
||||
|
||||
// checkFences verifies every src= fence in lines. lineBase is the 1-based
|
||||
// file line of lines[0]; file is the display path for problems.
|
||||
func checkFences(root, file string, lines []string, lineBase int) ([]drift, []Problem, error) {
|
||||
// nestedSrcMessage is the problem for a src= fence that is not a
|
||||
// top-level block. docs:sync cannot rewrite a line that still carries
|
||||
// its container marker, so the fence is refused instead of extracted.
|
||||
const nestedSrcMessage = "src= code block must be a top-level block of the page, not inside a callout, blockquote or list item"
|
||||
|
||||
// checkFences verifies every src= fence in fences. lines are the body
|
||||
// lines the fences index into. lineBase is the 1-based file line of
|
||||
// lines[0]; file is the display path for problems.
|
||||
func checkFences(root, file string, lines []string, lineBase int, fences []mdFence) ([]drift, []Problem, error) {
|
||||
var drifts []drift
|
||||
var problems []Problem
|
||||
for _, f := range scanFences(lines) {
|
||||
for _, f := range fences {
|
||||
ref, ok := ParseSrc(f.info)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
line := lineBase + f.open
|
||||
line := lineBase + f.line
|
||||
fail := func(msg string) {
|
||||
problems = append(problems, Problem{File: file, Line: line, Rule: "snippet", Message: msg})
|
||||
}
|
||||
if f.close < 0 {
|
||||
if !f.topLevel {
|
||||
fail(ref.String() + ": " + nestedSrcMessage)
|
||||
continue
|
||||
}
|
||||
if f.top.close < 0 {
|
||||
fail("code block has no closing fence")
|
||||
continue
|
||||
}
|
||||
@@ -476,9 +487,9 @@ func checkFences(root, file string, lines []string, lineBase int) ([]drift, []Pr
|
||||
case err != nil:
|
||||
return nil, nil, err
|
||||
}
|
||||
if fenceBody(lines, f) != strings.TrimRight(want, "\n") {
|
||||
if fenceBody(lines, f.top) != strings.TrimRight(want, "\n") {
|
||||
fail(fmt.Sprintf("body differs from %s (run: summer docs:sync)", ref))
|
||||
drifts = append(drifts, drift{f: f, want: want})
|
||||
drifts = append(drifts, drift{f: f.top, want: want})
|
||||
}
|
||||
}
|
||||
return drifts, problems, nil
|
||||
@@ -502,14 +513,20 @@ func unindent(l string, n int) string {
|
||||
return l[i:]
|
||||
}
|
||||
|
||||
// checkSnippets verifies the src= fences of every docs page.
|
||||
func (s *site) checkSnippets() ([]Problem, error) {
|
||||
// checkSnippets verifies the src= fences of every docs page. Module
|
||||
// READMEs are skipped here; a later check refuses src= on those pages
|
||||
// because their fences are rendered as written.
|
||||
func (s *site) checkSnippets(docs []parsedDoc) ([]Problem, error) {
|
||||
var problems []Problem
|
||||
for _, p := range s.pages {
|
||||
if p.Module != "" {
|
||||
for _, d := range docs {
|
||||
if d.page == nil || d.page.Module != "" {
|
||||
continue
|
||||
}
|
||||
_, ps, err := checkFences(s.opts.Root, p.Source, strings.Split(string(p.Body), "\n"), p.BodyLine)
|
||||
fences, err := collectFences(d.doc, d.body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, ps, err := checkFences(s.opts.Root, d.file, strings.Split(string(d.body), "\n"), d.line, fences)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -544,8 +561,20 @@ func Sync(opts Options) (SyncResult, []Problem, error) {
|
||||
if err != nil {
|
||||
return SyncResult{}, nil, fmt.Errorf("docs:sync: read %s: %w", abs, err)
|
||||
}
|
||||
lines := strings.Split(string(raw), "\n")
|
||||
drifts, ps, err := checkFences(opts.Root, s.rel(abs), lines, 1)
|
||||
body, bodyLine := raw, 1
|
||||
if _, b, bl, ok := splitFrontmatter(raw); ok {
|
||||
body, bodyLine = b, bl
|
||||
}
|
||||
if !bytes.HasSuffix(raw, body) {
|
||||
return SyncResult{}, nil, fmt.Errorf("docs:sync: %s: body is not a suffix of the file", abs)
|
||||
}
|
||||
doc := s.parseRaw(newMarkdown(), body)
|
||||
fences, err := collectFences(doc, body)
|
||||
if err != nil {
|
||||
return SyncResult{}, nil, err
|
||||
}
|
||||
lines := strings.Split(string(body), "\n")
|
||||
drifts, ps, err := checkFences(opts.Root, s.rel(abs), lines, bodyLine, fences)
|
||||
if err != nil {
|
||||
return SyncResult{}, nil, err
|
||||
}
|
||||
@@ -560,21 +589,22 @@ func Sync(opts Options) (SyncResult, []Problem, error) {
|
||||
for i := len(drifts) - 1; i >= 0; i-- {
|
||||
d := drifts[i]
|
||||
pad := strings.Repeat(" ", d.f.indent)
|
||||
var body []string
|
||||
var rewritten []string
|
||||
for _, l := range strings.Split(strings.TrimRight(d.want, "\n"), "\n") {
|
||||
if l == "" {
|
||||
body = append(body, "")
|
||||
rewritten = append(rewritten, "")
|
||||
} else {
|
||||
body = append(body, pad+l)
|
||||
rewritten = append(rewritten, pad+l)
|
||||
}
|
||||
}
|
||||
lines = slices.Concat(lines[:d.f.open+1], body, lines[d.f.close:])
|
||||
lines = slices.Concat(lines[:d.f.open+1], rewritten, lines[d.f.close:])
|
||||
}
|
||||
st, err := os.Stat(abs)
|
||||
if err != nil {
|
||||
return SyncResult{}, nil, fmt.Errorf("docs:sync: %w", err)
|
||||
}
|
||||
rewrites = append(rewrites, rewrite{path: abs, mode: st.Mode().Perm(), data: []byte(strings.Join(lines, "\n"))})
|
||||
rewritten := append(append([]byte{}, raw[:len(raw)-len(body)]...), []byte(strings.Join(lines, "\n"))...)
|
||||
rewrites = append(rewrites, rewrite{path: abs, mode: st.Mode().Perm(), data: rewritten})
|
||||
result.Snippets += len(drifts)
|
||||
result.Files++
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user