internal/frontend: remove use of goldmark for readme rendering
delete the code that uses goldmark and clean up the variants of the
code that use the markdown parser
For golang/go#61399
Change-Id: I03e8c303086110278dd0f3994ba97729e0cbf7c1
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/550039
Reviewed-by: Jonathan Amsterdam <jba@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
TryBot-Bypass: Michael Matloob <matloob@golang.org>
diff --git a/go.mod b/go.mod
index 4c7736b..b486205 100644
--- a/go.mod
+++ b/go.mod
@@ -27,8 +27,6 @@
github.com/jba/templatecheck v0.6.0
github.com/lib/pq v1.10.9
github.com/russross/blackfriday/v2 v2.1.0
- github.com/yuin/goldmark v1.6.0
- github.com/yuin/goldmark-emoji v1.0.1
go.opencensus.io v0.24.0
golang.org/x/mod v0.14.0
golang.org/x/net v0.19.0
diff --git a/go.sum b/go.sum
index 533ec47..47b4431 100644
--- a/go.sum
+++ b/go.sum
@@ -1039,9 +1039,6 @@
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
-github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
-github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
diff --git a/internal/frontend/goldmark.go b/internal/frontend/goldmark.go
deleted file mode 100644
index 22f6e2c..0000000
--- a/internal/frontend/goldmark.go
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * Copyright 2020 The Go Authors. All rights reserved.
- * Use of this source code is governed by a BSD-style
- * license that can be found in the LICENSE file.
- */
-
-package frontend
-
-import (
- "bytes"
- "context"
- "fmt"
- "strings"
-
- "github.com/yuin/goldmark/ast"
- "github.com/yuin/goldmark/parser"
- "github.com/yuin/goldmark/renderer"
- "github.com/yuin/goldmark/renderer/html"
- "github.com/yuin/goldmark/text"
- "github.com/yuin/goldmark/util"
- "golang.org/x/pkgsite/internal"
- "golang.org/x/pkgsite/internal/log"
- "golang.org/x/pkgsite/internal/source"
- "rsc.io/markdown"
-)
-
-// astTransformer is a default transformer of the goldmark tree. We pass in
-// readme information to use for the link transformations.
-type astTransformer struct {
- info *source.Info
- readme *internal.Readme
-}
-
-// Transform transforms the given AST tree.
-func (g *astTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
- _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
- if !entering {
- return ast.WalkContinue, nil
- }
- switch v := n.(type) {
- case *ast.Image:
- if d := translateLink(string(v.Destination), g.info, true, g.readme); d != "" {
- v.Destination = []byte(d)
- }
- case *ast.Link:
- if d := translateLink(string(v.Destination), g.info, false, g.readme); d != "" {
- v.Destination = []byte(d)
- }
- }
- return ast.WalkContinue, nil
- })
-}
-
-// htmlRenderer is a renderer.NodeRenderer implementation that renders
-// pkg.go.dev readme features.
-type htmlRenderer struct {
- html.Config
- info *source.Info
- readme *internal.Readme
- // firstHeading and offset are used to calculate the first heading tag's level in a readme.
- firstHeading bool
- offset int
-}
-
-// newHTMLRenderer creates a new HTMLRenderer for a readme.
-func newHTMLRenderer(info *source.Info, readme *internal.Readme, opts ...html.Option) renderer.NodeRenderer {
- r := &htmlRenderer{
- info: info,
- readme: readme,
- Config: html.NewConfig(),
- firstHeading: true,
- offset: 0,
- }
- for _, opt := range opts {
- opt.SetHTMLOption(&r.Config)
- }
- return r
-}
-
-// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
-func (r *htmlRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
- reg.Register(ast.KindHeading, r.renderHeading)
- reg.Register(ast.KindHTMLBlock, r.renderHTMLBlock)
- reg.Register(ast.KindRawHTML, r.renderRawHTML)
-}
-
-func (r *htmlRenderer) renderHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
- n := node.(*ast.Heading)
- if r.firstHeading {
- // The offset ensures the first heading is always an <h3>.
- r.offset = 3 - n.Level
- r.firstHeading = false
- }
- newLevel := n.Level + r.offset
- if entering {
- // TODO(matloob): Do we want the div and h elements to have analogous classes?
- // Currently we're using newLevel for the div's class but n.Level for the h element's
- // class.
- if newLevel > 6 {
- _, _ = w.WriteString(fmt.Sprintf(`<div class="h%d" role="heading" aria-level="%d"`, newLevel, n.Level))
- } else {
- _, _ = w.WriteString(fmt.Sprintf(`<h%d class="h%d"`, newLevel, n.Level))
- }
- if n.Attributes() != nil {
- html.RenderAttributes(w, node, html.HeadingAttributeFilter)
- }
- _ = w.WriteByte('>')
- } else {
- if newLevel > 6 {
- _, _ = w.WriteString("</div>\n")
- } else {
- _, _ = w.WriteString(fmt.Sprintf("</h%d>\n", newLevel))
- }
- }
- return ast.WalkContinue, nil
-}
-
-// renderHTMLBlock is copied directly from the goldmark source code and
-// modified to call translateHTML in every block
-func (r *htmlRenderer) renderHTMLBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
- n := node.(*ast.HTMLBlock)
- if entering {
- if r.Unsafe {
- l := n.Lines().Len()
- for i := 0; i < l; i++ {
- line := n.Lines().At(i)
- d, err := translateHTML(line.Value(source), r.info, r.readme)
- if err != nil {
- return ast.WalkStop, err
- }
- _, _ = w.Write(d)
- }
- } else {
- _, _ = w.WriteString("<!-- raw HTML omitted -->\n")
- }
- } else {
- if n.HasClosure() {
- if r.Unsafe {
- closure := n.ClosureLine
- _, _ = w.Write(closure.Value(source))
- } else {
- _, _ = w.WriteString("<!-- raw HTML omitted -->\n")
- }
- }
- }
- return ast.WalkContinue, nil
-}
-
-func (r *htmlRenderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
- if !entering {
- return ast.WalkSkipChildren, nil
- }
- if r.Unsafe {
- n := node.(*ast.RawHTML)
- for i := 0; i < n.Segments.Len(); i++ {
- segment := n.Segments.At(i)
- d, err := translateHTML(segment.Value(source), r.info, r.readme)
- if err != nil {
- return ast.WalkStop, err
- }
- _, _ = w.Write(d)
- }
- return ast.WalkSkipChildren, nil
- }
- _, _ = w.WriteString("<!-- raw HTML omitted -->")
- return ast.WalkSkipChildren, nil
-}
-
-// ids is a collection of element ids in document.
-type ids struct {
- values map[string]bool
-}
-
-// newIDs creates a collection of element ids in a document.
-func newIDs() parser.IDs {
- return &ids{
- values: map[string]bool{},
- }
-}
-
-// Generate turns heading content from a markdown document into a heading id.
-// First HTML markup and markdown images are stripped then ASCII letters
-// and numbers are used to generate the final result. Finally, all heading ids
-// are prefixed with "readme-" to avoid name collisions with other ids on the
-// unit page. Duplicated heading ids are given an incremental suffix. See
-// readme_test.go for examples.
-func (s *ids) Generate(value []byte, kind ast.NodeKind) []byte {
- var defaultID string
- if kind == ast.KindHeading {
- defaultID = "heading"
- } else {
- defaultID = "id"
- }
-
- parser := &markdown.Parser{}
- doc := parser.Parse("# " + string(value))
- return []byte(s.generateID(doc, defaultID))
-}
-
-func (s *ids) generateID(block markdown.Block, defaultID string) string {
- var buf bytes.Buffer
- walkBlocks([]markdown.Block{block}, func(b markdown.Block) error {
- if t, ok := b.(*markdown.Text); ok {
- for _, inl := range t.Inline {
- inl.PrintText(&buf)
- }
- }
- return nil
- })
- f := func(c rune) bool {
- return !('a' <= c && c <= 'z') && !('A' <= c && c <= 'Z') && !('0' <= c && c <= '9')
- }
- str := strings.Join(strings.FieldsFunc(buf.String(), f), "-")
- str = strings.ToLower(str)
- if len(str) == 0 {
- str = defaultID
- }
- key := str
- for i := 1; ; i++ {
- if _, ok := s.values[key]; !ok {
- s.values[key] = true
- break
- }
- key = fmt.Sprintf("%s-%d", str, i)
- }
- return "readme-" + key
-}
-
-// Put implements Put from the goldmark parser IDs interface.
-func (s *ids) Put(value []byte) {
- s.values[string(value)] = true
-}
-
-type extractLinks struct {
- ctx context.Context
- inLinksHeading bool
- links []link
-}
-
-// The name of the heading from which we extract links.
-const linkHeadingText = "Links"
-
-var linkHeadingBytes = []byte(linkHeadingText) // for faster comparison to node contents
-
-// Transform extracts links from the "Links" section of a README.
-func (e *extractLinks) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
- err := ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
- if !entering {
- return ast.WalkContinue, nil
- }
- switch n := n.(type) {
-
- case *ast.Heading:
- // We are in the links heading from the point we see a heading with
- // linkHeadingText until the point we see the next heading.
- if e.inLinksHeading {
- return ast.WalkStop, nil
- }
- if bytes.Equal(n.Text(reader.Source()), linkHeadingBytes) {
- e.inLinksHeading = true
- }
-
- case *ast.ListItem:
- // When in the links heading, extract links from list items.
- if !e.inLinksHeading {
- return ast.WalkSkipChildren, nil
- }
- // We expect the pattern: ListItem -> TextBlock -> Link, with no
- // other children.
- if tb, ok := n.FirstChild().(*ast.TextBlock); ok {
- if l, ok := tb.FirstChild().(*ast.Link); ok && l.NextSibling() == nil {
- // Record the link.
- e.links = append(e.links, link{
- Href: string(l.Destination),
- Body: string(l.Text(reader.Source())),
- })
- }
- }
- return ast.WalkSkipChildren, nil
- }
-
- return ast.WalkContinue, nil
- })
- if err != nil {
- log.Errorf(e.ctx, "extractLinks.Transform: %v", err)
- }
-}
-
-type extractTOC struct {
- ctx context.Context
- Headings []*Heading
- removeTitle bool // omit title from TOC
-}
-
-// Transform collects the headings from a readme into an outline
-// of the document. It nests the headings based on the h-level hierarchy.
-// See tests for heading levels in TestReadme for behavior.
-func (e *extractTOC) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
- var headings []*Heading
- err := ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
- if n.Kind() == ast.KindHeading && entering {
- heading := n.(*ast.Heading)
- section := &Heading{
- Level: heading.Level,
- Text: string(n.Text(reader.Source())),
- }
- if id, ok := heading.AttributeString("id"); ok {
- section.ID = string(id.([]byte))
- }
- headings = append(headings, section)
- return ast.WalkSkipChildren, nil
- }
- return ast.WalkContinue, nil
- })
- if err != nil {
- log.Errorf(e.ctx, "extractTOC.Transform: %v", err)
- }
-
- // We nest the headings by walking through the list we extracted and
- // establishing parent child relationships based on heading levels.
- var nested []*Heading
- for i, h := range headings {
- if i == 0 {
- nested = append(nested, h)
- continue
- }
- parent := headings[i-1]
- for parent != nil && parent.Level >= h.Level {
- parent = parent.parent
- }
- if parent == nil {
- nested = append(nested, h)
- } else {
- h.parent = parent
- parent.Children = append(parent.Children, h)
- }
- }
- if e.removeTitle {
- // If there is only one top tevel heading with 1 or more children we
- // assume it is the title of the document and remove it from the TOC.
- if len(nested) == 1 && len(nested[0].Children) > 0 {
- nested = nested[0].Children
- }
- }
- e.Headings = nested
-}
diff --git a/internal/frontend/main.go b/internal/frontend/main.go
index ae8f03d..32a4dc8 100644
--- a/internal/frontend/main.go
+++ b/internal/frontend/main.go
@@ -259,7 +259,7 @@
if !u.IsRedistributable {
return &Readme{}, nil
}
- return ProcessReadmeMarkdown(ctx, u)
+ return ProcessReadme(ctx, u)
}
const missingDocReplacement = `<p>Documentation is missing.</p>`
diff --git a/internal/frontend/markdown.go b/internal/frontend/markdown.go
index fcb5d98..9427447 100644
--- a/internal/frontend/markdown.go
+++ b/internal/frontend/markdown.go
@@ -19,7 +19,7 @@
"rsc.io/markdown"
)
-// ProcessReadmeMarkdown processes the README of unit u, if it has one.
+// ProcessReadme processes the README of unit u, if it has one.
// Processing includes rendering and sanitizing the HTML or Markdown,
// and extracting headings and links.
//
@@ -31,12 +31,12 @@
// The extracted links are for display outside of the readme contents.
//
// This function is exported for use by external tools.
-func ProcessReadmeMarkdown(ctx context.Context, u *internal.Unit) (_ *Readme, err error) {
- defer derrors.WrapAndReport(&err, "ProcessReadmeMarkdown(%q, %q, %q)", u.Path, u.ModulePath, u.Version)
- return processReadmeMarkdown(ctx, u.Readme, u.SourceInfo)
+func ProcessReadme(ctx context.Context, u *internal.Unit) (_ *Readme, err error) {
+ defer derrors.WrapAndReport(&err, "ProcessReadme(%q, %q, %q)", u.Path, u.ModulePath, u.Version)
+ return processReadme(ctx, u.Readme, u.SourceInfo)
}
-func processReadmeMarkdown(ctx context.Context, readme *internal.Readme, info *source.Info) (frontendReadme *Readme, err error) {
+func processReadme(ctx context.Context, readme *internal.Readme, info *source.Info) (frontendReadme *Readme, err error) {
if readme == nil || readme.Contents == "" {
return &Readme{}, nil
}
@@ -160,6 +160,15 @@
return nil
}
+type extractTOC struct {
+ ctx context.Context
+ Headings []*Heading
+ removeTitle bool // omit title from TOC
+}
+
+// extract collects the headings from a readme into an outline
+// of the document. It nests the headings based on the h-level hierarchy.
+// See tests for heading levels in TestReadme for behavior.
func (e *extractTOC) extract(doc *markdown.Document) {
var headings []*Heading
err := walkBlocks(doc.Blocks, func(b markdown.Block) error {
@@ -211,6 +220,18 @@
e.Headings = nested
}
+type extractLinks struct {
+ ctx context.Context
+ inLinksHeading bool
+ links []link
+}
+
+// The name of the heading from which we extract links.
+const linkHeadingText = "Links"
+
+var linkHeadingBytes = []byte(linkHeadingText) // for faster comparison to node contents
+
+// extract extracts links from the "Links" section of a README.
func (e *extractLinks) extract(doc *markdown.Document) {
var seenLinksHeading bool
err := walkBlocks(doc.Blocks, func(b markdown.Block) error {
@@ -371,16 +392,41 @@
)
// rewriteHeadingIDs generates ids based on the body of the heading.
-// The original code uses the raw markdown as the input to the ids.Generate
-// function, but we don't have the raw markdown anymore, so we use the
-// text instead.
+// The ASCII letters and numbers from the text are used to generate
+// each of the ids. Finally, all heading ids
+// are prefixed with "readme-" to avoid name collisions with other ids on the
+// unit page. Duplicated heading ids are given an incremental suffix. See
+// readme_test.go for examples.
func rewriteHeadingIDs(doc *markdown.Document) {
- ids := &ids{
- values: map[string]bool{},
+ ids := map[string]bool{}
+
+ generateID := func(heading *markdown.Heading) string {
+ var buf bytes.Buffer
+ for _, inl := range heading.Text.Inline {
+ inl.PrintText(&buf)
+ }
+ f := func(c rune) bool {
+ return !('a' <= c && c <= 'z') && !('A' <= c && c <= 'Z') && !('0' <= c && c <= '9')
+ }
+ str := strings.Join(strings.FieldsFunc(buf.String(), f), "-")
+ str = strings.ToLower(str)
+ if len(str) == 0 {
+ str = "heading"
+ }
+ key := str
+ for i := 1; ; i++ {
+ if _, ok := ids[key]; !ok {
+ ids[key] = true
+ break
+ }
+ key = fmt.Sprintf("%s-%d", str, i)
+ }
+ return "readme-" + key
}
+
walkBlocks(doc.Blocks, func(b markdown.Block) error {
if heading, ok := b.(*markdown.Heading); ok {
- id := ids.generateID(heading, "heading")
+ id := generateID(heading)
heading.ID = string(id)
}
return nil
diff --git a/internal/frontend/readme.go b/internal/frontend/readme.go
index 6709855..7cd159f 100644
--- a/internal/frontend/readme.go
+++ b/internal/frontend/readme.go
@@ -6,24 +6,10 @@
import (
"bytes"
- "context"
"github.com/google/safehtml"
- "github.com/google/safehtml/template"
"github.com/google/safehtml/uncheckedconversions"
- "github.com/yuin/goldmark"
- emoji "github.com/yuin/goldmark-emoji"
- "github.com/yuin/goldmark/extension"
- "github.com/yuin/goldmark/parser"
- "github.com/yuin/goldmark/renderer"
- goldmarkHtml "github.com/yuin/goldmark/renderer/html"
- gmtext "github.com/yuin/goldmark/text"
- "github.com/yuin/goldmark/util"
- "golang.org/x/pkgsite/internal"
- "golang.org/x/pkgsite/internal/derrors"
- "golang.org/x/pkgsite/internal/log"
"golang.org/x/pkgsite/internal/sanitizer"
- "golang.org/x/pkgsite/internal/source"
)
// Heading holds data about a heading and nested headings within a readme.
@@ -52,111 +38,6 @@
Links []link // links from the "Links" section
}
-// ProcessReadme processes the README of unit u, if it has one.
-// Processing includes rendering and sanitizing the HTML or Markdown,
-// and extracting headings and links.
-//
-// Headings are prefixed with "readme-" and heading levels are adjusted to start
-// at h3 in order to nest them properly within the rest of the page. The
-// readme's original styling is preserved in the html by giving headings a css
-// class styled identical to their original heading level.
-//
-// The extracted links are for display outside of the readme contents.
-//
-// This function is exported for use by external tools.
-func ProcessReadme(ctx context.Context, u *internal.Unit) (_ *Readme, err error) {
- defer derrors.WrapAndReport(&err, "ProcessReadme(%q, %q, %q)", u.Path, u.ModulePath, u.Version)
- return processReadme(ctx, u.Readme, u.SourceInfo)
-}
-
-func processReadme(ctx context.Context, readme *internal.Readme, sourceInfo *source.Info) (frontendReadme *Readme, err error) {
- if readme == nil || readme.Contents == "" {
- return &Readme{}, nil
- }
- if !isMarkdown(readme.Filepath) {
- t := template.Must(template.New("").Parse(`<pre class="readme">{{.}}</pre>`))
- h, err := t.ExecuteToHTML(readme.Contents)
- if err != nil {
- return nil, err
- }
- return &Readme{HTML: h}, nil
- }
-
- // Sets priority value so that we always use our custom transformer
- // instead of the default ones. The default values are in:
- // https://github.com/yuin/goldmark/blob/7b90f04af43131db79ec320be0bd4744079b346f/parser/parser.go#L567
- const astTransformerPriority = 10000
- el := &extractLinks{ctx: ctx}
- et := &extractTOC{ctx: ctx, removeTitle: true}
- gdMarkdown := goldmark.New(
- goldmark.WithParserOptions(
- // WithHeadingAttribute allows us to include other attributes in
- // heading tags. This is useful for our aria-level implementation of
- // increasing heading rankings.
- parser.WithHeadingAttribute(),
- // Generates an id in every heading tag. This is used in github in
- // order to generate a link with a hash that a user would scroll to
- // <h1 id="goldmark">goldmark</h1> => github.com/yuin/goldmark#goldmark
- parser.WithAutoHeadingID(),
- // Include custom ASTTransformer using the readme and module info to
- // use translateRelativeLink and translateHTML to modify the AST
- // before it is rendered.
- parser.WithASTTransformers(
- util.Prioritized(&astTransformer{
- info: sourceInfo,
- readme: readme,
- }, astTransformerPriority),
- // Extract links after we have transformed the URLs.
- util.Prioritized(el, astTransformerPriority+1),
- util.Prioritized(et, astTransformerPriority+1),
- ),
- ),
- // These extensions lets users write HTML code in the README. This is
- // fine since we process the contents using the sanitizer after.
- goldmark.WithRendererOptions(goldmarkHtml.WithUnsafe(), goldmarkHtml.WithXHTML()),
- goldmark.WithExtensions(
- extension.GFM, // Support Github Flavored Markdown.
- emoji.Emoji, // Support Github markdown emoji markup.
- ),
- )
- gdMarkdown.Renderer().AddOptions(
- renderer.WithNodeRenderers(
- util.Prioritized(newHTMLRenderer(sourceInfo, readme), 100),
- ),
- )
- contents := []byte(readme.Contents)
- gdParser := gdMarkdown.Parser()
- reader := gmtext.NewReader(contents)
- pctx := parser.NewContext(parser.WithIDs(newIDs()))
- doc := gdParser.Parse(reader, parser.WithContext(pctx))
- gdRenderer := gdMarkdown.Renderer()
-
- var b bytes.Buffer
- defer func() {
- // It's possible for gdRenderer.Render to panic. For example,
- // https://pkg.go.dev/github.com/jinghzhu/k8scrd/pkg/crd/jinghzhu/v1
- // results in a panic because gdRenderer.Render tries to index a slice
- // out of bounds.
- //
- // In case of a panic from gdRenderer.Render, treat this as a normal
- // error from that function.
- if p := recover(); p != nil {
- log.Debugf(ctx, "gdRenderer.Render: %v", p)
- frontendReadme = &Readme{}
- err = nil
- }
- }()
- if err := gdRenderer.Render(&b, contents, doc); err != nil {
- log.Debugf(ctx, "gdRenderer.Render: %v", err)
- return &Readme{}, nil
- }
- return &Readme{
- HTML: sanitizeHTML(&b),
- Outline: et.Headings,
- Links: el.links,
- }, nil
-}
-
// sanitizeHTML sanitizes HTML from a bytes.Buffer so that it is safe.
func sanitizeHTML(b *bytes.Buffer) safehtml.HTML {
s := string(sanitizer.SanitizeBytes(b.Bytes()))
diff --git a/internal/frontend/readme_test.go b/internal/frontend/readme_test.go
index 91b4b3e..60591ed 100644
--- a/internal/frontend/readme_test.go
+++ b/internal/frontend/readme_test.go
@@ -161,9 +161,9 @@
Filepath: sample.ReadmeFilePath,
Contents: "# :zap: Zap \n\n :joy:",
},
- wantHTML: "<h3 class=\"h1\" id=\"readme-zap-zap\">⚡ Zap</h3>\n<p>😂</p>",
+ wantHTML: "<h3 class=\"h1\" id=\"readme-zap\">⚡ Zap</h3>\n<p>😂</p>",
wantOutline: []*Heading{
- {Level: 1, Text: " Zap", ID: "readme-zap-zap"},
+ {Level: 1, Text: "⚡ Zap", ID: "readme-zap"},
},
},
{
@@ -379,7 +379,7 @@
Contents: `<p><img src="./foo.png"></p><p><img src="../bar.png"</p>` + "\n",
},
wantHTML: `<p><img src="https://github.com/valid/module_name/raw/v1.0.0/dir/sub/foo.png"/></p>` +
- `<p><img src="https://github.com/valid/module_name/raw/v1.0.0/dir/bar.png"/>` + "\n</p>",
+ `<p><img src="https://github.com/valid/module_name/raw/v1.0.0/dir/bar.png"/></p>`,
wantOutline: nil,
},
{
@@ -481,41 +481,20 @@
},
} {
t.Run(test.name, func(t *testing.T) {
- processReadmes := map[string]func(ctx context.Context, u *internal.Unit) (frontendReadme *Readme, err error){
- "goldmark": ProcessReadme,
- "markdown": ProcessReadmeMarkdown,
+ test.unit.Readme = test.readme
+ readme, err := ProcessReadme(ctx, test.unit)
+ if err != nil {
+ t.Fatal(err)
}
- for processFuncName, processFunc := range processReadmes {
- t.Run(processFuncName, func(t *testing.T) {
- wantHTML := test.wantHTML
- if processFuncName == "markdown" {
- if test.name == "Github markdown emoji markup is properly rendered" {
- t.Skip("github markdown emoji is not yet supported with the markdown package")
- }
- if test.name == "body has more than one child" {
- // The markdown package treats the newline differently when there's an incomplete tag.
- wantHTML = `<p><img src="https://github.com/valid/module_name/raw/v1.0.0/dir/sub/foo.png"/></p>` +
- `<p><img src="https://github.com/valid/module_name/raw/v1.0.0/dir/bar.png"/></p>`
- }
- }
-
- test.unit.Readme = test.readme
- readme, err := processFunc(ctx, test.unit)
- if err != nil {
- t.Fatal(err)
- }
- gotHTML := strings.TrimSpace(readme.HTML.String())
- if diff := cmp.Diff(wantHTML, gotHTML); diff != "" {
- t.Errorf("Readme(%v) html mismatch (-want +got):\n%s", test.unit.UnitMeta, diff)
- }
- if diff := cmp.Diff(test.wantOutline, readme.Outline, cmp.Options{
- cmpopts.IgnoreUnexported(Heading{}),
- }); diff != "" {
- t.Errorf("Readme(%v) outline mismatch (-want +got):\n%s", test.unit.UnitMeta, diff)
- }
- })
+ gotHTML := strings.TrimSpace(readme.HTML.String())
+ if diff := cmp.Diff(test.wantHTML, gotHTML); diff != "" {
+ t.Errorf("Readme(%v) html mismatch (-want +got):\n%s", test.unit.UnitMeta, diff)
}
-
+ if diff := cmp.Diff(test.wantOutline, readme.Outline, cmp.Options{
+ cmpopts.IgnoreUnexported(Heading{}),
+ }); diff != "" {
+ t.Errorf("Readme(%v) outline mismatch (-want +got):\n%s", test.unit.UnitMeta, diff)
+ }
})
}
}
@@ -617,24 +596,17 @@
},
} {
t.Run(test.name, func(t *testing.T) {
- processReadmes := map[string]func(ctx context.Context, u *internal.Unit) (frontendReadme *Readme, err error){
- "goldmark": ProcessReadme,
- "markdown": ProcessReadmeMarkdown,
+
+ unit.Readme = &internal.Readme{
+ Filepath: "README.md",
+ Contents: unindent(test.contents),
}
- for name, processFunc := range processReadmes {
- t.Run(name, func(t *testing.T) {
- unit.Readme = &internal.Readme{
- Filepath: "README.md",
- Contents: unindent(test.contents),
- }
- got, err := processFunc(ctx, unit)
- if err != nil {
- t.Fatal(err)
- }
- if diff := cmp.Diff(test.want, got.Links); diff != "" {
- t.Errorf("mismatch (-want +got):\n%s", diff)
- }
- })
+ got, err := ProcessReadme(ctx, unit)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if diff := cmp.Diff(test.want, got.Links); diff != "" {
+ t.Errorf("mismatch (-want +got):\n%s", diff)
}
})
}