internal/godoc/dochtml/internal/render: use go/doc/comment
Parse doc comments with the new go/doc/comment package.
Change-Id: If79c1035086b2b70f1207cb84153f8c0bcbd6599
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/412676
Reviewed-by: Jamal Carvalho <jamal@golang.org>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/godoc/dochtml/internal/render/linkify.go b/internal/godoc/dochtml/internal/render/linkify.go
index fc1e6c8..542fd50 100644
--- a/internal/godoc/dochtml/internal/render/linkify.go
+++ b/internal/godoc/dochtml/internal/render/linkify.go
@@ -56,22 +56,6 @@
badAnchorRx = regexp.MustCompile(`[^a-zA-Z0-9]`)
)
-type docData struct {
- Elements []docElement
- Headings []docElement
- EnableCommandTOC bool
-}
-
-type docElement struct {
- IsHeading bool
- IsPreformat bool
- // for paragraph and preformat
- Body safehtml.HTML
- // for heading
- Title string
- ID safehtml.Identifier
-}
-
func (r *Renderer) declHTML(doc string, decl ast.Decl, extractLinks bool) (out struct{ Doc, Decl safehtml.HTML }) {
if doc != "" {
out.Doc = r.formatDocHTML(doc, extractLinks)
@@ -83,54 +67,6 @@
return out
}
-func (r *Renderer) formatDocHTML(doc string, extractLinks bool) safehtml.HTML {
- var els []docElement
- inLinks := false
- for _, blk := range docToBlocks(doc) {
- var el docElement
- switch blk := blk.(type) {
- case *paragraph:
- if inLinks {
- r.links = append(r.links, parseLinks(blk.lines)...)
- } else {
- el.Body = r.linesToHTML(blk.lines, false)
- els = append(els, el)
- }
- case *preformat:
- if inLinks {
- r.links = append(r.links, parseLinks(blk.lines)...)
- } else {
- el.IsPreformat = true
- el.Body = r.linesToHTML(blk.lines, true)
- els = append(els, el)
- }
- case *heading:
- if extractLinks && blk.title == "Links" {
- inLinks = true
- } else {
- inLinks = false
- el.IsHeading = true
- el.Title = blk.title
- id := badAnchorRx.ReplaceAllString(blk.title, "_")
- el.ID = safehtml.IdentifierFromConstantPrefix("hdr", id)
- els = append(els, el)
- }
- }
- }
-
- var headings []docElement
- for _, e := range els {
- if e.IsHeading {
- headings = append(headings, e)
- }
- }
- return ExecuteToHTML(r.docTmpl, docData{
- Elements: els,
- Headings: headings,
- EnableCommandTOC: r.enableCommandTOC,
- })
-}
-
// parseLinks extracts links from lines.
func parseLinks(lines []string) []Link {
var links []Link
@@ -159,16 +95,6 @@
}
}
-func (r *Renderer) linesToHTML(lines []string, pre bool) safehtml.HTML {
- newline := safehtml.HTMLEscaped("\n")
- htmls := make([]safehtml.HTML, 0, 2*len(lines))
- for _, l := range lines {
- htmls = append(htmls, r.formatLineHTML(l, pre))
- htmls = append(htmls, newline)
- }
- return safehtml.HTMLConcat(htmls...)
-}
-
func (r *Renderer) codeString(ex *doc.Example) (string, error) {
if ex == nil || ex.Code == nil {
return "", errors.New("please include an example with code")
diff --git a/internal/godoc/dochtml/internal/render/linkify_comment.go b/internal/godoc/dochtml/internal/render/linkify_comment.go
new file mode 100644
index 0000000..057ccec
--- /dev/null
+++ b/internal/godoc/dochtml/internal/render/linkify_comment.go
@@ -0,0 +1,306 @@
+// Copyright 2022 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 render
+
+import (
+ "fmt"
+ "go/doc/comment"
+ "regexp"
+ "strings"
+ "unicode"
+
+ safe "github.com/google/safehtml"
+ "github.com/google/safehtml/template"
+)
+
+type link struct {
+ Class string
+ Href string
+ Text any // string or safe.HTML
+}
+
+type heading struct {
+ ID safe.Identifier
+ Title safe.HTML
+}
+
+var (
+ // tocTemplate expects a []heading.
+ tocTemplate = template.Must(template.New("toc").Parse(`<div role="navigation" aria-label="Table of Contents">
+ <ul class="Documentation-toc{{if gt (len .) 5}} Documentation-toc-columns{{end}}">
+ {{range . -}}
+ <li class="Documentation-tocItem">
+ <a href="#{{.ID}}">{{.Title}}</a>
+ </li>
+ {{end -}}
+ </ul>
+</div>
+`))
+
+ italicTemplate = template.Must(template.New("italics").Parse(`<i>{{.}}</i>`))
+
+ codeTemplate = template.Must(template.New("code").Parse(`<pre>{{.}}</pre>`))
+
+ paraTemplate = template.Must(template.New("para").Parse("<p>{{.}}\n</p>"))
+
+ headingTemplate = template.Must(template.New("heading").Parse(
+ `<h4 id="{{.ID}}">{{.Title}} <a class="Documentation-idLink" href="#{{.ID}}">¶</a></h4>`))
+
+ linkTemplate = template.Must(template.New("link").Parse(
+ `<a{{with .Class}}class="{{.}}" {{end}} href="{{.Href}}">{{.Text}}</a>`))
+
+ uListTemplate = template.Must(template.New("ulist").Parse(
+ `<ul>
+{{- range .}}
+ {{.}}
+{{- end}}
+</ul>`))
+
+ oListTemplate = template.Must(template.New("olist").Parse(
+ `<ol>
+ {{range .}}
+ {{.}}
+ {{end}}
+ </ol>`))
+
+ listItemTemplate = template.Must(template.New("li").Parse(
+ `<li{{with .Number}}value="{{.}}" {{end}}>{{.Content}}</li>`))
+)
+
+func (r *Renderer) formatDocHTML(text string, extractLinks bool) safe.HTML {
+ p := comment.Parser{}
+ doc := p.Parse(text)
+ if extractLinks {
+ r.removeLinks(doc)
+ }
+ var headings []heading
+ for _, b := range doc.Content {
+ if h, ok := b.(*comment.Heading); ok {
+ headings = append(headings, r.newHeading(h))
+ }
+ }
+ h := r.blocksToHTML(doc.Content, true, extractLinks)
+ if r.enableCommandTOC && len(headings) > 0 {
+ h = safe.HTMLConcat(ExecuteToHTML(tocTemplate, headings), h)
+ }
+ return h
+}
+
+func (r *Renderer) removeLinks(doc *comment.Doc) {
+ var bs []comment.Block
+ inLinks := false
+ for _, b := range doc.Content {
+ switch b := b.(type) {
+ case *comment.Heading:
+ if textsToString(b.Text) == "Links" {
+ inLinks = true
+ } else {
+ inLinks = false
+ bs = append(bs, b)
+ }
+ case *comment.List:
+ if inLinks {
+ for _, item := range b.Items {
+ fmt.Println(" ", item)
+ if link, ok := itemLink(item); ok {
+ r.links = append(r.links, link)
+ }
+ }
+ } else {
+ bs = append(bs, b)
+ }
+ case *comment.Paragraph:
+ if inLinks {
+ // Links section doesn't require leading whitespace, so
+ // the link may be in a paragraph.
+ s := textsToString(b.Text)
+ r.links = append(r.links, parseLinks(strings.Split(s, "\n"))...)
+ } else {
+ bs = append(bs, b)
+ }
+
+ default:
+ if !inLinks {
+ bs = append(bs, b)
+ }
+ }
+ }
+ doc.Content = bs
+}
+
+func itemLink(item *comment.ListItem) (l Link, ok bool) {
+ // Should be a single Paragraph.
+ if len(item.Content) != 1 {
+ return l, false
+ }
+ p, ok := item.Content[0].(*comment.Paragraph)
+ if !ok {
+ return l, false
+ }
+ // TODO: clean up.
+ if lp := parseLink("- " + textsToString(p.Text)); lp != nil {
+ return *lp, true
+ }
+ return l, false
+}
+
+func (r *Renderer) blocksToHTML(bs []comment.Block, useParagraph, extractLinks bool) safe.HTML {
+ return concatHTML(bs, func(b comment.Block) safe.HTML {
+ return r.blockToHTML(b, useParagraph, extractLinks)
+ })
+}
+
+func (r *Renderer) blockToHTML(b comment.Block, useParagraph, extractLinks bool) safe.HTML {
+ switch b := b.(type) {
+ case *comment.Paragraph:
+ th := r.textsToHTML(b.Text)
+ if useParagraph {
+ return ExecuteToHTML(paraTemplate, th)
+ }
+ return th
+
+ case *comment.Code:
+ return ExecuteToHTML(codeTemplate, b.Text)
+
+ case *comment.Heading:
+ return ExecuteToHTML(headingTemplate, r.newHeading(b))
+
+ case *comment.List:
+ var items []safe.HTML
+ useParagraph = b.BlankBetween()
+ for _, item := range b.Items {
+ items = append(items, ExecuteToHTML(listItemTemplate, struct {
+ Number string
+ Content safe.HTML
+ }{item.Number, r.blocksToHTML(item.Content, useParagraph, false)}))
+ }
+ t := oListTemplate
+ if b.Items[0].Number == "" {
+ t = uListTemplate
+ }
+ return ExecuteToHTML(t, items)
+ default:
+ return badType(b)
+ }
+}
+
+func (r *Renderer) newHeading(h *comment.Heading) heading {
+ return heading{headingID(h), r.textsToHTML(h.Text)}
+}
+
+func (r *Renderer) textsToHTML(ts []comment.Text) safe.HTML {
+ return concatHTML(ts, r.textToHTML)
+}
+
+func (r *Renderer) textToHTML(t comment.Text) safe.HTML {
+ switch t := t.(type) {
+ case comment.Plain:
+ // Don't auto-link URLs. The doc/comment package already does that.
+ return linkRFCs(string(t))
+ case comment.Italic:
+ return ExecuteToHTML(italicTemplate, t)
+ case *comment.Link:
+ return ExecuteToHTML(linkTemplate, link{"", t.URL, r.textsToHTML(t.Text)})
+ case *comment.DocLink:
+ url := r.docLinkURL(t)
+ return ExecuteToHTML(linkTemplate, link{"", url, r.textsToHTML(t.Text)})
+ default:
+ return badType(t)
+ }
+}
+
+func (r *Renderer) docLinkURL(dl *comment.DocLink) string {
+ var url string
+ if dl.ImportPath != "" {
+ url = "/" + dl.ImportPath
+ if r.packageURL != nil {
+ url = r.packageURL(dl.ImportPath)
+ }
+ }
+ id := dl.Name
+ if dl.Recv != "" {
+ id = dl.Recv + "." + id
+ }
+ if id != "" {
+ url += "#" + id
+ }
+ return url
+}
+
+// TODO: any -> *comment.Text | *comment.Block
+func concatHTML[T any](xs []T, toHTML func(T) safe.HTML) safe.HTML {
+ var hs []safe.HTML
+ for _, x := range xs {
+ hs = append(hs, toHTML(x))
+ }
+ return safe.HTMLConcat(hs...)
+}
+
+func badType(x interface{}) safe.HTML {
+ return safe.HTMLEscaped(fmt.Sprintf("bad type %T", x))
+}
+
+func headingID(h *comment.Heading) safe.Identifier {
+ s := textsToString(h.Text)
+ id := badAnchorRx.ReplaceAllString(s, "_")
+ return safe.IdentifierFromConstantPrefix("hdr", id)
+}
+
+func textsToString(ts []comment.Text) string {
+ var b strings.Builder
+ for _, t := range ts {
+ switch t := t.(type) {
+ case comment.Plain:
+ b.WriteString(string(t))
+ case comment.Italic:
+ b.WriteString(string(t))
+ case *comment.Link:
+ b.WriteString(textsToString(t.Text))
+ case *comment.DocLink:
+ b.WriteString(textsToString(t.Text))
+ default:
+ fmt.Fprintf(&b, "bad text type %T", t)
+ }
+ }
+ return b.String()
+}
+
+var rfcRegexp = regexp.MustCompile(rfcRx)
+
+// TODO: merge/replace Renderer.formatLineHTML.
+// TODO: make more efficient.
+func linkRFCs(s string) safe.HTML {
+ var hs []safe.HTML
+ for len(s) > 0 {
+ m0, m1 := len(s), len(s)
+ if m := rfcRegexp.FindStringIndex(s); m != nil {
+ m0, m1 = m[0], m[1]
+ }
+ if m0 > 0 {
+ hs = append(hs, safe.HTMLEscaped(s[:m0]))
+ }
+ if m1 > m0 {
+ word := s[m0:m1]
+ // Strip all characters except for letters, numbers, and '.' to
+ // obtain RFC fields.
+ rfcFields := strings.FieldsFunc(word, func(c rune) bool {
+ return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '.'
+ })
+ var url string
+ if len(rfcFields) >= 4 {
+ // RFC x Section y
+ url = fmt.Sprintf("https://rfc-editor.org/rfc/rfc%s.html#section-%s",
+ rfcFields[1], rfcFields[3])
+ } else if len(rfcFields) >= 2 {
+ url = fmt.Sprintf("https://rfc-editor.org/rfc/rfc%s.html", rfcFields[1])
+ }
+ if url != "" {
+ hs = append(hs, ExecuteToHTML(linkTemplate, link{"", url, word}))
+ }
+ }
+ s = s[m1:]
+ }
+ return safe.HTMLConcat(hs...)
+}
diff --git a/internal/godoc/dochtml/internal/render/linkify_test.go b/internal/godoc/dochtml/internal/render/linkify_test.go
index 87ded26..b7b06b3 100644
--- a/internal/godoc/dochtml/internal/render/linkify_test.go
+++ b/internal/godoc/dochtml/internal/render/linkify_test.go
@@ -19,7 +19,7 @@
"golang.org/x/pkgsite/internal/godoc/internal/doc"
)
-func TestDocHTML(t *testing.T) {
+func TestFormatDocHTML(t *testing.T) {
linksDoc := `Documentation.
The Go Project
@@ -106,8 +106,8 @@
`,
want: `<p>Package tls partially implements TLS 1.2, as specified in <a href="https://rfc-editor.org/rfc/rfc5246.html">RFC 5246</a>, and TLS 1.3, as specified in <a href="https://rfc-editor.org/rfc/rfc8446.html">RFC 8446</a>.
</p><p>In TLS 1.3, this type is called NamedGroup, but at this time this library only supports Elliptic Curve based groups. See <a href="https://rfc-editor.org/rfc/rfc8446.html#section-4.2.7">RFC 8446, Section 4.2.7</a>.
-</p><p>TLSUnique contains the tls-unique channel binding value (see RFC
-5929, section 3). The newline-separated RFC should be linked, but the words RFC and RFCs should not be.
+</p><p>TLSUnique contains the tls-unique channel binding value (see <a href="https://rfc-editor.org/rfc/rfc5929.html#section-3">RFC
+5929, section 3</a>). The newline-separated RFC should be linked, but the words RFC and RFCs should not be.
</p>`,
},
{
@@ -123,14 +123,27 @@
</p>`,
},
{
+ name: "list",
+ doc: `
+ Here is a list:
+ - a
+ - b`,
+ want: `<p>Here is a list:
+</p><ul>
+ <li>a</li>
+ <li>b</li>
+</ul>`,
+ },
+ {
name: "Links section is not extracted",
extractLinks: []bool{false},
doc: linksDoc,
want: `<p>Documentation.
</p><h4 id="hdr-The_Go_Project">The Go Project <a class="Documentation-idLink" href="#hdr-The_Go_Project">¶</a></h4><p>Go is an open source project.
</p><h4 id="hdr-Links">Links <a class="Documentation-idLink" href="#hdr-Links">¶</a></h4><p>- title1, url1
-</p><pre>- title2 , url2
-</pre><h4 id="hdr-Header">Header <a class="Documentation-idLink" href="#hdr-Header">¶</a></h4><p>More doc.
+</p><ul>
+ <li>title2 , url2</li>
+</ul><h4 id="hdr-Header">Header <a class="Documentation-idLink" href="#hdr-Header">¶</a></h4><p>More doc.
</p>`,
},
{
@@ -151,12 +164,6 @@
doc: "For more detail, run ``go help test'' and ``go help testflag''",
want: `<p>For more detail, run “go help test” and “go help testflag”` + "\n" + "</p>",
},
- {
- name: "single quotes in pre block",
- doc: `Join
- [].join() // returns ''`,
- want: `<p>Join` + "\n" + `</p><pre>[].join() // returns ''` + "\n" + `</pre>`,
- },
} {
t.Run(test.name, func(t *testing.T) {
extractLinks := test.extractLinks
@@ -166,10 +173,10 @@
for _, el := range extractLinks {
t.Run(fmt.Sprintf("extractLinks=%t", el), func(t *testing.T) {
r := New(context.Background(), nil, pkgTime, nil)
- got := r.declHTML(test.doc, nil, el).Doc
+ got := r.formatDocHTML(test.doc, el)
want := testconversions.MakeHTMLForTest(test.want)
if diff := cmp.Diff(want, got, cmp.AllowUnexported(safehtml.HTML{})); diff != "" {
- t.Errorf("r.declHTML() mismatch (-want +got)\n%s", diff)
+ t.Errorf("doc mismatch (-want +got)\n%s", diff)
}
if diff := cmp.Diff(test.wantLinks, r.Links()); diff != "" {
t.Errorf("r.Links() mismatch (-want +got)\n%s", diff)
@@ -554,15 +561,15 @@
More text.`
want := testconversions.MakeHTMLForTest(`<div role="navigation" aria-label="Table of Contents">
- <ul class="Documentation-toc">
- <li class="Documentation-tocItem">
- <a href="#hdr-The_Go_Project">The Go Project</a>
- </li>
- <li class="Documentation-tocItem">
- <a href="#hdr-Heading_2">Heading 2</a>
- </li>
- </ul>
- </div>
+ <ul class="Documentation-toc">
+ <li class="Documentation-tocItem">
+ <a href="#hdr-The_Go_Project">The Go Project</a>
+ </li>
+ <li class="Documentation-tocItem">
+ <a href="#hdr-Heading_2">Heading 2</a>
+ </li>
+ </ul>
+</div>
<p>Documentation.
</p><h4 id="hdr-The_Go_Project">The Go Project <a class="Documentation-idLink" href="#hdr-The_Go_Project">¶</a></h4><p>Go is an open source project.
</p><h4 id="hdr-Heading_2">Heading 2 <a class="Documentation-idLink" href="#hdr-Heading_2">¶</a></h4><p>More text.
diff --git a/internal/godoc/dochtml/internal/render/render.go b/internal/godoc/dochtml/internal/render/render.go
index 9ba2f53..1287522 100644
--- a/internal/godoc/dochtml/internal/render/render.go
+++ b/internal/godoc/dochtml/internal/render/render.go
@@ -12,8 +12,6 @@
"go/token"
"regexp"
"strings"
- "unicode"
- "unicode/utf8"
"github.com/google/safehtml"
"github.com/google/safehtml/template"
@@ -202,76 +200,6 @@
return r.codeHTML(ex)
}
-// block is (*heading | *paragraph | *preformat).
-type block interface{}
-
-type (
- lines []string
- heading struct {
- title string
- }
- paragraph struct {
- lines lines
- }
- preformat struct {
- lines lines
- }
-)
-
-// docToBlocks converts doc string into list of blocks.
-//
-// Heading block is a non-blank line, surrounded by blank lines
-// and the next non-blank line is not indented.
-//
-// Preformat block contains single line or consecutive lines which have indent greater than 0.
-//
-// Paragraph block is a default block type if a block does not fall into heading and preformat.
-func docToBlocks(doc string) []block {
- docLines := unindent(strings.Split(strings.Trim(doc, "\n"), "\n"))
-
- // Group the lines based on indentation and blank lines.
- var groups [][]string
- var lastGroup []string
- var wasInCode bool
- for _, line := range docLines {
- hasIndent := indentLength(line) > 0
- nowInCode := hasIndent || (wasInCode && line == "")
- newGroup := wasInCode != nowInCode || (!nowInCode && line == "")
- wasInCode = nowInCode
- if newGroup && len(lastGroup) > 0 {
- groups = append(groups, lastGroup)
- lastGroup = nil
- }
- if line != "" || nowInCode {
- lastGroup = append(lastGroup, line)
- }
- }
- if len(lastGroup) > 0 {
- groups = append(groups, lastGroup)
- }
-
- // Classify groups of lines as individual blocks.
- var blks []block
- var lastBlk block
- for i, group := range groups {
- willParagraph := i+1 < len(groups) && indentLength(groups[i+1][0]) == 0
- for len(group) > 0 && group[len(group)-1] == "" {
- group = group[:len(group)-1] // remove trailing empty lines
- }
- _, wasHeading := lastBlk.(*heading)
- switch {
- case indentLength(group[0]) > 0:
- blks = append(blks, &preformat{unindent(group)})
- case i != 0 && !wasHeading && len(group) == 1 && isHeading(group[0]) && willParagraph:
- blks = append(blks, &heading{group[0]})
- default:
- blks = append(blks, ¶graph{group})
- }
- lastBlk = blks[len(blks)-1]
- }
- return blks
-}
-
func indentLength(s string) int {
return len(s) - len(trimIndent(s))
}
@@ -279,80 +207,3 @@
func trimIndent(s string) string {
return strings.TrimLeft(s, " \t")
}
-
-func commonPrefixLength(a, b string) (n int) {
- for n < len(a) && n < len(b) && a[n] == b[n] {
- n++
- }
- return n
-}
-
-func unindent(lines []string) []string {
- if len(lines) > 0 {
- npre := indentLength(lines[0])
- for _, line := range lines {
- if line != "" {
- npre = commonPrefixLength(lines[0][:npre], line)
- }
- }
- for i, line := range lines {
- if line != "" {
- lines[i] = line[npre:]
- }
- }
- }
- return lines
-}
-
-// isHeading returns bool of if it passes as a section heading or not.
-// This is the copy of the heading function from the standard library.
-// https://go.googlesource.com/go/+/refs/tags/go1.16/src/go/doc/comment.go#212
-func isHeading(line string) bool {
- line = strings.TrimSpace(line)
- if len(line) == 0 {
- return false
- }
-
- // a heading must start with an uppercase letter
- r, _ := utf8.DecodeRuneInString(line)
- if !unicode.IsLetter(r) || !unicode.IsUpper(r) {
- return false
- }
-
- // it must end in a letter or digit:
- r, _ = utf8.DecodeLastRuneInString(line)
- if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
- return false
- }
-
- // exclude lines with illegal characters. we allow "(),"
- if strings.ContainsAny(line, ";:!?+*/=[]{}_^°&§~%#@<\">\\") {
- return false
- }
-
- // allow "'" for possessive "'s" only
- for b := line; ; {
- i := strings.IndexRune(b, '\'')
- if i < 0 {
- break
- }
- if i+1 >= len(b) || b[i+1] != 's' || (i+2 < len(b) && b[i+2] != ' ') {
- return false // not followed by "s "
- }
- b = b[i+2:]
- }
-
- // allow "." when followed by non-space
- for b := line; ; {
- i := strings.IndexRune(b, '.')
- if i < 0 {
- break
- }
- if i+1 >= len(b) || b[i+1] == ' ' {
- return false // not followed by non-space
- }
- b = b[i+1:]
- }
-
- return true
-}
diff --git a/internal/godoc/dochtml/internal/render/render_test.go b/internal/godoc/dochtml/internal/render/render_test.go
index 26fcda9..9b6b2b7 100644
--- a/internal/godoc/dochtml/internal/render/render_test.go
+++ b/internal/godoc/dochtml/internal/render/render_test.go
@@ -10,19 +10,12 @@
"go/token"
"os"
"path/filepath"
- "reflect"
"strings"
- "testing"
"golang.org/x/pkgsite/internal/godoc/internal/doc"
)
-var (
- pkgIO, _ = mustLoadPackage("io")
- pkgOS, _ = mustLoadPackage("os")
- pkgTime, fsetTime = mustLoadPackage("time")
- pkgTar, _ = mustLoadPackage("archive/tar")
-)
+var pkgTime, fsetTime = mustLoadPackage("time")
func mustLoadPackage(path string) (*doc.Package, *token.FileSet) {
// simpleImporter is used by ast.NewPackage.
@@ -50,182 +43,3 @@
astPkg, _ := ast.NewPackage(fset, pkgFiles, simpleImporter, nil)
return doc.New(astPkg, path, 0), fset
}
-
-func TestDocToBlocks(t *testing.T) {
- tests := []struct {
- in string
- want []block
- }{{
- in: `This is a sentence.`,
- want: []block{¶graph{lines{"This is a sentence."}}},
- }, {
- in: ` This is a sentence.`,
- want: []block{¶graph{lines{"This is a sentence."}}},
- }, {
- in: `
- Some code:
- func main() {}
- `,
- want: []block{
- ¶graph{lines{"Some code:"}},
- &preformat{lines{"func main() {}"}},
- },
- }, {
- in: `
- The quick brown fox jumped over the lazy dog.
- This is another sentence. La de dah!
-
- This is a paragraph`,
- want: []block{
- ¶graph{lines{
- "The quick brown fox jumped over the lazy dog.",
- "This is another sentence. La de dah!",
- }},
- ¶graph{lines{"This is a paragraph"}},
- },
- }, {
- in: `
- The quick brown fox jumped over the lazy dog.
- This is another sentence. La de dah!
-
- This is a heading
-
- This is a paragraph.`,
- want: []block{
- ¶graph{lines{
- "The quick brown fox jumped over the lazy dog.",
- "This is another sentence. La de dah!",
- }},
- &heading{"This is a heading"},
- ¶graph{lines{"This is a paragraph."}},
- },
- }, {
- in: `
- This is not a heading
-
- The quick brown fox jumped over the lazy dog.
- This is another sentence. La de dah!
-
- This is not a heading
-
- func main() {}
-
- This is not a heading`,
- want: []block{
- ¶graph{lines{"This is not a heading"}},
- ¶graph{lines{
- "The quick brown fox jumped over the lazy dog.",
- "This is another sentence. La de dah!",
- }},
- ¶graph{lines{"This is not a heading"}},
- &preformat{lines{"func main() {}"}},
- ¶graph{lines{"This is not a heading"}},
- },
- }, {
- in: `
- Xattrs stores extended attributes as PAX records under the
- "SCHILY.xattr." namespace.
-
- The following are semantically equivalent:
- h.Xattrs[key] = value
- h.PAXRecords["SCHILY.xattr."+key] = value
-
- When Writer.WriteHeader is called, the contents of Xattrs will take
- precedence over those in PAXRecords.
-
- Deprecated: Use PAXRecords instead.`,
- want: []block{
- ¶graph{lines{
- "Xattrs stores extended attributes as PAX records under the",
- `"SCHILY.xattr." namespace.`,
- }},
- ¶graph{lines{"The following are semantically equivalent:"}},
- &preformat{lines{
- `h.Xattrs[key] = value`,
- `h.PAXRecords["SCHILY.xattr."+key] = value`,
- }},
- ¶graph{lines{
- "When Writer.WriteHeader is called, the contents of Xattrs will take",
- "precedence over those in PAXRecords.",
- }},
- ¶graph{lines{"Deprecated: Use PAXRecords instead."}},
- },
- }, {
- in: `
- Package testing provides support for automated testing of Go packages.
-
- Benchmarks
-
- Functions of the form
- func BenchmarkXxx(*testing.B)
- are considered benchmarks, and are executed by the "go test" command when
- its -bench flag is provided. Benchmarks are run sequentially.
-
- For a description of the testing flags, see
- https://golang.org/cmd/go/#hdr-Description_of_testing_flags.
-
- A sample benchmark function looks like this:
- func BenchmarkHello(b *testing.B) {
- for i := 0; i < b.N; i++ {
- fmt.Sprintf("hello")
- }
- }
-
- The benchmark function must run the target code b.N times.
- During benchmark execution, b.N is adjusted until the benchmark function lasts
- long enough to be timed reliably. The output
- BenchmarkHello 10000000 282 ns/op
- means that the loop ran 10000000 times at a speed of 282 ns per loop.`,
- want: []block{
- ¶graph{lines{"Package testing provides support for automated testing of Go packages."}},
- &heading{"Benchmarks"},
- ¶graph{lines{"Functions of the form"}},
- &preformat{lines{"func BenchmarkXxx(*testing.B)"}},
- ¶graph{lines{
- `are considered benchmarks, and are executed by the "go test" command when`,
- "its -bench flag is provided. Benchmarks are run sequentially.",
- }},
- ¶graph{lines{
- "For a description of the testing flags, see",
- "https://golang.org/cmd/go/#hdr-Description_of_testing_flags.",
- }},
- ¶graph{lines{"A sample benchmark function looks like this:"}},
- &preformat{lines{
- `func BenchmarkHello(b *testing.B) {`,
- ` for i := 0; i < b.N; i++ {`,
- ` fmt.Sprintf("hello")`,
- ` }`,
- `}`,
- }},
- ¶graph{lines{
- "The benchmark function must run the target code b.N times.",
- "During benchmark execution, b.N is adjusted until the benchmark function lasts",
- "long enough to be timed reliably. The output",
- }},
- &preformat{lines{"BenchmarkHello 10000000 282 ns/op"}},
- ¶graph{lines{"means that the loop ran 10000000 times at a speed of 282 ns per loop."}},
- },
- }, {
- in: `
- See https://golang.org/s/go14customimport for details.
-
- Modules, module versions, and more
-
- Modules are how Go manages dependencies.
-
- A module is a collection of packages that are released, versioned, and distributed together. Modules may be downloaded directly from version control repositories or from module proxy servers.`,
- want: []block{
- ¶graph{lines{"See https://golang.org/s/go14customimport for details."}},
- &heading{"Modules, module versions, and more"},
- ¶graph{lines{"Modules are how Go manages dependencies."}},
- ¶graph{lines{"A module is a collection of packages that are released, versioned, and distributed together. Modules may be downloaded directly from version control repositories or from module proxy servers."}},
- },
- }}
-
- for i, tt := range tests {
- got := docToBlocks(tt.in)
- if !reflect.DeepEqual(got, tt.want) {
- t.Errorf("test %d, docToBlocks:\ngot %v\nwant %v", i, got, tt.want)
- }
- }
-}