summaryrefslogtreecommitdiffstats
path: root/src/go/doc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /src/go/doc
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/go/doc')
-rw-r--r--src/go/doc/Makefile7
-rw-r--r--src/go/doc/comment.go71
-rw-r--r--src/go/doc/comment/doc.go36
-rw-r--r--src/go/doc/comment/html.go169
-rw-r--r--src/go/doc/comment/markdown.go188
-rwxr-xr-xsrc/go/doc/comment/mkstd.sh24
-rw-r--r--src/go/doc/comment/old_test.go80
-rw-r--r--src/go/doc/comment/parse.go1262
-rw-r--r--src/go/doc/comment/parse_test.go12
-rw-r--r--src/go/doc/comment/print.go288
-rw-r--r--src/go/doc/comment/std.go47
-rw-r--r--src/go/doc/comment/std_test.go34
-rw-r--r--src/go/doc/comment/testdata/README.md42
-rw-r--r--src/go/doc/comment/testdata/blank.txt12
-rw-r--r--src/go/doc/comment/testdata/code.txt94
-rw-r--r--src/go/doc/comment/testdata/code2.txt31
-rw-r--r--src/go/doc/comment/testdata/code3.txt33
-rw-r--r--src/go/doc/comment/testdata/code4.txt38
-rw-r--r--src/go/doc/comment/testdata/code5.txt21
-rw-r--r--src/go/doc/comment/testdata/code6.txt24
-rw-r--r--src/go/doc/comment/testdata/crash1.txt16
-rw-r--r--src/go/doc/comment/testdata/doclink.txt21
-rw-r--r--src/go/doc/comment/testdata/doclink2.txt8
-rw-r--r--src/go/doc/comment/testdata/doclink3.txt8
-rw-r--r--src/go/doc/comment/testdata/doclink4.txt7
-rw-r--r--src/go/doc/comment/testdata/doclink5.txt5
-rw-r--r--src/go/doc/comment/testdata/doclink6.txt5
-rw-r--r--src/go/doc/comment/testdata/doclink7.txt4
-rw-r--r--src/go/doc/comment/testdata/escape.txt55
-rw-r--r--src/go/doc/comment/testdata/head.txt92
-rw-r--r--src/go/doc/comment/testdata/head2.txt36
-rw-r--r--src/go/doc/comment/testdata/head3.txt7
-rw-r--r--src/go/doc/comment/testdata/hello.txt35
-rw-r--r--src/go/doc/comment/testdata/link.txt17
-rw-r--r--src/go/doc/comment/testdata/link2.txt31
-rw-r--r--src/go/doc/comment/testdata/link3.txt14
-rw-r--r--src/go/doc/comment/testdata/link4.txt77
-rw-r--r--src/go/doc/comment/testdata/link5.txt36
-rw-r--r--src/go/doc/comment/testdata/link6.txt50
-rw-r--r--src/go/doc/comment/testdata/link7.txt25
-rw-r--r--src/go/doc/comment/testdata/linklist.txt18
-rw-r--r--src/go/doc/comment/testdata/linklist2.txt39
-rw-r--r--src/go/doc/comment/testdata/linklist3.txt31
-rw-r--r--src/go/doc/comment/testdata/linklist4.txt36
-rw-r--r--src/go/doc/comment/testdata/list.txt48
-rw-r--r--src/go/doc/comment/testdata/list10.txt13
-rw-r--r--src/go/doc/comment/testdata/list2.txt57
-rw-r--r--src/go/doc/comment/testdata/list3.txt32
-rw-r--r--src/go/doc/comment/testdata/list4.txt38
-rw-r--r--src/go/doc/comment/testdata/list5.txt40
-rw-r--r--src/go/doc/comment/testdata/list6.txt129
-rw-r--r--src/go/doc/comment/testdata/list7.txt98
-rw-r--r--src/go/doc/comment/testdata/list8.txt56
-rw-r--r--src/go/doc/comment/testdata/list9.txt30
-rw-r--r--src/go/doc/comment/testdata/para.txt17
-rw-r--r--src/go/doc/comment/testdata/quote.txt15
-rw-r--r--src/go/doc/comment/testdata/text.txt62
-rw-r--r--src/go/doc/comment/testdata/text2.txt14
-rw-r--r--src/go/doc/comment/testdata/text3.txt28
-rw-r--r--src/go/doc/comment/testdata/text4.txt29
-rw-r--r--src/go/doc/comment/testdata/text5.txt38
-rw-r--r--src/go/doc/comment/testdata/text6.txt18
-rw-r--r--src/go/doc/comment/testdata/text7.txt21
-rw-r--r--src/go/doc/comment/testdata/text8.txt94
-rw-r--r--src/go/doc/comment/testdata/text9.txt12
-rw-r--r--src/go/doc/comment/testdata/words.txt10
-rw-r--r--src/go/doc/comment/testdata_test.go202
-rw-r--r--src/go/doc/comment/text.go337
-rw-r--r--src/go/doc/comment/wrap_test.go141
-rw-r--r--src/go/doc/comment_test.go67
-rw-r--r--src/go/doc/doc.go354
-rw-r--r--src/go/doc/doc_test.go292
-rw-r--r--src/go/doc/example.go722
-rw-r--r--src/go/doc/example_internal_test.go121
-rw-r--r--src/go/doc/example_test.go336
-rw-r--r--src/go/doc/exports.go324
-rw-r--r--src/go/doc/filter.go106
-rw-r--r--src/go/doc/headscan.go109
-rw-r--r--src/go/doc/reader.go1029
-rw-r--r--src/go/doc/synopsis.go78
-rw-r--r--src/go/doc/synopsis_test.go52
-rw-r--r--src/go/doc/testdata/a.0.golden52
-rw-r--r--src/go/doc/testdata/a.1.golden52
-rw-r--r--src/go/doc/testdata/a.2.golden52
-rw-r--r--src/go/doc/testdata/a0.go40
-rw-r--r--src/go/doc/testdata/a1.go12
-rw-r--r--src/go/doc/testdata/b.0.golden74
-rw-r--r--src/go/doc/testdata/b.1.golden89
-rw-r--r--src/go/doc/testdata/b.2.golden74
-rw-r--r--src/go/doc/testdata/b.go64
-rw-r--r--src/go/doc/testdata/benchmark.go293
-rw-r--r--src/go/doc/testdata/blank.0.golden62
-rw-r--r--src/go/doc/testdata/blank.1.golden83
-rw-r--r--src/go/doc/testdata/blank.2.golden62
-rw-r--r--src/go/doc/testdata/blank.go75
-rw-r--r--src/go/doc/testdata/bugpara.0.golden20
-rw-r--r--src/go/doc/testdata/bugpara.1.golden20
-rw-r--r--src/go/doc/testdata/bugpara.2.golden20
-rw-r--r--src/go/doc/testdata/bugpara.go9
-rw-r--r--src/go/doc/testdata/c.0.golden48
-rw-r--r--src/go/doc/testdata/c.1.golden48
-rw-r--r--src/go/doc/testdata/c.2.golden48
-rw-r--r--src/go/doc/testdata/c.go62
-rw-r--r--src/go/doc/testdata/d.0.golden104
-rw-r--r--src/go/doc/testdata/d.1.golden104
-rw-r--r--src/go/doc/testdata/d.2.golden104
-rw-r--r--src/go/doc/testdata/d1.go57
-rw-r--r--src/go/doc/testdata/d2.go45
-rw-r--r--src/go/doc/testdata/e.0.golden109
-rw-r--r--src/go/doc/testdata/e.1.golden144
-rw-r--r--src/go/doc/testdata/e.2.golden130
-rw-r--r--src/go/doc/testdata/e.go147
-rw-r--r--src/go/doc/testdata/error1.0.golden30
-rw-r--r--src/go/doc/testdata/error1.1.golden32
-rw-r--r--src/go/doc/testdata/error1.2.golden30
-rw-r--r--src/go/doc/testdata/error1.go24
-rw-r--r--src/go/doc/testdata/error2.0.golden27
-rw-r--r--src/go/doc/testdata/error2.1.golden37
-rw-r--r--src/go/doc/testdata/error2.2.golden27
-rw-r--r--src/go/doc/testdata/error2.go29
-rw-r--r--src/go/doc/testdata/example.go81
-rw-r--r--src/go/doc/testdata/examples/README.md12
-rw-r--r--src/go/doc/testdata/examples/empty.go8
-rw-r--r--src/go/doc/testdata/examples/empty.golden6
-rw-r--r--src/go/doc/testdata/examples/generic_constraints.go38
-rw-r--r--src/go/doc/testdata/examples/generic_constraints.golden39
-rw-r--r--src/go/doc/testdata/examples/import_groups.go23
-rw-r--r--src/go/doc/testdata/examples/import_groups.golden27
-rw-r--r--src/go/doc/testdata/examples/import_groups_named.go23
-rw-r--r--src/go/doc/testdata/examples/import_groups_named.golden27
-rw-r--r--src/go/doc/testdata/examples/inspect_signature.go23
-rw-r--r--src/go/doc/testdata/examples/inspect_signature.golden24
-rw-r--r--src/go/doc/testdata/examples/iota.go34
-rw-r--r--src/go/doc/testdata/examples/iota.golden23
-rw-r--r--src/go/doc/testdata/examples/issue43658.go223
-rw-r--r--src/go/doc/testdata/examples/issue43658.golden156
-rw-r--r--src/go/doc/testdata/examples/multiple.go98
-rw-r--r--src/go/doc/testdata/examples/multiple.golden129
-rw-r--r--src/go/doc/testdata/examples/values.go22
-rw-r--r--src/go/doc/testdata/examples/values.golden21
-rw-r--r--src/go/doc/testdata/examples/whole_file.go23
-rw-r--r--src/go/doc/testdata/examples/whole_file.golden21
-rw-r--r--src/go/doc/testdata/examples/whole_function.go13
-rw-r--r--src/go/doc/testdata/examples/whole_function.golden11
-rw-r--r--src/go/doc/testdata/examples/whole_function_external.go12
-rw-r--r--src/go/doc/testdata/examples/whole_function_external.golden8
-rw-r--r--src/go/doc/testdata/f.0.golden13
-rw-r--r--src/go/doc/testdata/f.1.golden16
-rw-r--r--src/go/doc/testdata/f.2.golden13
-rw-r--r--src/go/doc/testdata/f.go14
-rw-r--r--src/go/doc/testdata/g.0.golden32
-rw-r--r--src/go/doc/testdata/g.1.golden34
-rw-r--r--src/go/doc/testdata/g.2.golden32
-rw-r--r--src/go/doc/testdata/g.go25
-rw-r--r--src/go/doc/testdata/generics.0.golden76
-rw-r--r--src/go/doc/testdata/generics.1.golden66
-rw-r--r--src/go/doc/testdata/generics.2.golden76
-rw-r--r--src/go/doc/testdata/generics.go74
-rw-r--r--src/go/doc/testdata/issue12839.0.golden51
-rw-r--r--src/go/doc/testdata/issue12839.1.golden54
-rw-r--r--src/go/doc/testdata/issue12839.2.golden51
-rw-r--r--src/go/doc/testdata/issue12839.go69
-rw-r--r--src/go/doc/testdata/issue13742.0.golden25
-rw-r--r--src/go/doc/testdata/issue13742.1.golden25
-rw-r--r--src/go/doc/testdata/issue13742.2.golden25
-rw-r--r--src/go/doc/testdata/issue13742.go18
-rw-r--r--src/go/doc/testdata/issue16153.0.golden32
-rw-r--r--src/go/doc/testdata/issue16153.1.golden34
-rw-r--r--src/go/doc/testdata/issue16153.2.golden32
-rw-r--r--src/go/doc/testdata/issue16153.go27
-rw-r--r--src/go/doc/testdata/issue17788.0.golden8
-rw-r--r--src/go/doc/testdata/issue17788.1.golden8
-rw-r--r--src/go/doc/testdata/issue17788.2.golden8
-rw-r--r--src/go/doc/testdata/issue17788.go8
-rw-r--r--src/go/doc/testdata/issue22856.0.golden45
-rw-r--r--src/go/doc/testdata/issue22856.1.golden45
-rw-r--r--src/go/doc/testdata/issue22856.2.golden45
-rw-r--r--src/go/doc/testdata/issue22856.go27
-rw-r--r--src/go/doc/testdata/pkgdoc/doc.go24
-rw-r--r--src/go/doc/testdata/predeclared.0.golden8
-rw-r--r--src/go/doc/testdata/predeclared.1.golden22
-rw-r--r--src/go/doc/testdata/predeclared.2.golden8
-rw-r--r--src/go/doc/testdata/predeclared.go22
-rw-r--r--src/go/doc/testdata/template.txt68
-rw-r--r--src/go/doc/testdata/testing.0.golden156
-rw-r--r--src/go/doc/testdata/testing.1.golden298
-rw-r--r--src/go/doc/testdata/testing.2.golden156
-rw-r--r--src/go/doc/testdata/testing.go404
188 files changed, 14293 insertions, 0 deletions
diff --git a/src/go/doc/Makefile b/src/go/doc/Makefile
new file mode 100644
index 0000000..ca4948f
--- /dev/null
+++ b/src/go/doc/Makefile
@@ -0,0 +1,7 @@
+# Copyright 2009 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.
+
+# Script to test heading detection heuristic
+headscan: headscan.go
+ go build headscan.go
diff --git a/src/go/doc/comment.go b/src/go/doc/comment.go
new file mode 100644
index 0000000..4f73664
--- /dev/null
+++ b/src/go/doc/comment.go
@@ -0,0 +1,71 @@
+// Copyright 2009 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 doc
+
+import (
+ "go/doc/comment"
+ "io"
+)
+
+// ToHTML converts comment text to formatted HTML.
+//
+// Deprecated: ToHTML cannot identify documentation links
+// in the doc comment, because they depend on knowing what
+// package the text came from, which is not included in this API.
+//
+// Given the *[doc.Package] p where text was found,
+// ToHTML(w, text, nil) can be replaced by:
+//
+// w.Write(p.HTML(text))
+//
+// which is in turn shorthand for:
+//
+// w.Write(p.Printer().HTML(p.Parser().Parse(text)))
+//
+// If words may be non-nil, the longer replacement is:
+//
+// parser := p.Parser()
+// parser.Words = words
+// w.Write(p.Printer().HTML(parser.Parse(d)))
+func ToHTML(w io.Writer, text string, words map[string]string) {
+ p := new(Package).Parser()
+ p.Words = words
+ d := p.Parse(text)
+ pr := new(comment.Printer)
+ w.Write(pr.HTML(d))
+}
+
+// ToText converts comment text to formatted text.
+//
+// Deprecated: ToText cannot identify documentation links
+// in the doc comment, because they depend on knowing what
+// package the text came from, which is not included in this API.
+//
+// Given the *[doc.Package] p where text was found,
+// ToText(w, text, "", "\t", 80) can be replaced by:
+//
+// w.Write(p.Text(text))
+//
+// In the general case, ToText(w, text, prefix, codePrefix, width)
+// can be replaced by:
+//
+// d := p.Parser().Parse(text)
+// pr := p.Printer()
+// pr.TextPrefix = prefix
+// pr.TextCodePrefix = codePrefix
+// pr.TextWidth = width
+// w.Write(pr.Text(d))
+//
+// See the documentation for [Package.Text] and [comment.Printer.Text]
+// for more details.
+func ToText(w io.Writer, text string, prefix, codePrefix string, width int) {
+ d := new(Package).Parser().Parse(text)
+ pr := &comment.Printer{
+ TextPrefix: prefix,
+ TextCodePrefix: codePrefix,
+ TextWidth: width,
+ }
+ w.Write(pr.Text(d))
+}
diff --git a/src/go/doc/comment/doc.go b/src/go/doc/comment/doc.go
new file mode 100644
index 0000000..45a476a
--- /dev/null
+++ b/src/go/doc/comment/doc.go
@@ -0,0 +1,36 @@
+// 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 comment implements parsing and reformatting of Go doc comments,
+(documentation comments), which are comments that immediately precede
+a top-level declaration of a package, const, func, type, or var.
+
+Go doc comment syntax is a simplified subset of Markdown that supports
+links, headings, paragraphs, lists (without nesting), and preformatted text blocks.
+The details of the syntax are documented at https://go.dev/doc/comment.
+
+To parse the text associated with a doc comment (after removing comment markers),
+use a [Parser]:
+
+ var p comment.Parser
+ doc := p.Parse(text)
+
+The result is a [*Doc].
+To reformat it as a doc comment, HTML, Markdown, or plain text,
+use a [Printer]:
+
+ var pr comment.Printer
+ os.Stdout.Write(pr.Text(doc))
+
+The [Parser] and [Printer] types are structs whose fields can be
+modified to customize the operations.
+For details, see the documentation for those types.
+
+Use cases that need additional control over reformatting can
+implement their own logic by inspecting the parsed syntax itself.
+See the documentation for [Doc], [Block], [Text] for an overview
+and links to additional types.
+*/
+package comment
diff --git a/src/go/doc/comment/html.go b/src/go/doc/comment/html.go
new file mode 100644
index 0000000..bc076f6
--- /dev/null
+++ b/src/go/doc/comment/html.go
@@ -0,0 +1,169 @@
+// 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 comment
+
+import (
+ "bytes"
+ "fmt"
+ "strconv"
+)
+
+// An htmlPrinter holds the state needed for printing a Doc as HTML.
+type htmlPrinter struct {
+ *Printer
+ tight bool
+}
+
+// HTML returns an HTML formatting of the Doc.
+// See the [Printer] documentation for ways to customize the HTML output.
+func (p *Printer) HTML(d *Doc) []byte {
+ hp := &htmlPrinter{Printer: p}
+ var out bytes.Buffer
+ for _, x := range d.Content {
+ hp.block(&out, x)
+ }
+ return out.Bytes()
+}
+
+// block prints the block x to out.
+func (p *htmlPrinter) block(out *bytes.Buffer, x Block) {
+ switch x := x.(type) {
+ default:
+ fmt.Fprintf(out, "?%T", x)
+
+ case *Paragraph:
+ if !p.tight {
+ out.WriteString("<p>")
+ }
+ p.text(out, x.Text)
+ out.WriteString("\n")
+
+ case *Heading:
+ out.WriteString("<h")
+ h := strconv.Itoa(p.headingLevel())
+ out.WriteString(h)
+ if id := p.headingID(x); id != "" {
+ out.WriteString(` id="`)
+ p.escape(out, id)
+ out.WriteString(`"`)
+ }
+ out.WriteString(">")
+ p.text(out, x.Text)
+ out.WriteString("</h")
+ out.WriteString(h)
+ out.WriteString(">\n")
+
+ case *Code:
+ out.WriteString("<pre>")
+ p.escape(out, x.Text)
+ out.WriteString("</pre>\n")
+
+ case *List:
+ kind := "ol>\n"
+ if x.Items[0].Number == "" {
+ kind = "ul>\n"
+ }
+ out.WriteString("<")
+ out.WriteString(kind)
+ next := "1"
+ for _, item := range x.Items {
+ out.WriteString("<li")
+ if n := item.Number; n != "" {
+ if n != next {
+ out.WriteString(` value="`)
+ out.WriteString(n)
+ out.WriteString(`"`)
+ next = n
+ }
+ next = inc(next)
+ }
+ out.WriteString(">")
+ p.tight = !x.BlankBetween()
+ for _, blk := range item.Content {
+ p.block(out, blk)
+ }
+ p.tight = false
+ }
+ out.WriteString("</")
+ out.WriteString(kind)
+ }
+}
+
+// inc increments the decimal string s.
+// For example, inc("1199") == "1200".
+func inc(s string) string {
+ b := []byte(s)
+ for i := len(b) - 1; i >= 0; i-- {
+ if b[i] < '9' {
+ b[i]++
+ return string(b)
+ }
+ b[i] = '0'
+ }
+ return "1" + string(b)
+}
+
+// text prints the text sequence x to out.
+func (p *htmlPrinter) text(out *bytes.Buffer, x []Text) {
+ for _, t := range x {
+ switch t := t.(type) {
+ case Plain:
+ p.escape(out, string(t))
+ case Italic:
+ out.WriteString("<i>")
+ p.escape(out, string(t))
+ out.WriteString("</i>")
+ case *Link:
+ out.WriteString(`<a href="`)
+ p.escape(out, t.URL)
+ out.WriteString(`">`)
+ p.text(out, t.Text)
+ out.WriteString("</a>")
+ case *DocLink:
+ url := p.docLinkURL(t)
+ if url != "" {
+ out.WriteString(`<a href="`)
+ p.escape(out, url)
+ out.WriteString(`">`)
+ }
+ p.text(out, t.Text)
+ if url != "" {
+ out.WriteString("</a>")
+ }
+ }
+ }
+}
+
+// escape prints s to out as plain text,
+// escaping < & " ' and > to avoid being misinterpreted
+// in larger HTML constructs.
+func (p *htmlPrinter) escape(out *bytes.Buffer, s string) {
+ start := 0
+ for i := 0; i < len(s); i++ {
+ switch s[i] {
+ case '<':
+ out.WriteString(s[start:i])
+ out.WriteString("&lt;")
+ start = i + 1
+ case '&':
+ out.WriteString(s[start:i])
+ out.WriteString("&amp;")
+ start = i + 1
+ case '"':
+ out.WriteString(s[start:i])
+ out.WriteString("&quot;")
+ start = i + 1
+ case '\'':
+ out.WriteString(s[start:i])
+ out.WriteString("&apos;")
+ start = i + 1
+ case '>':
+ out.WriteString(s[start:i])
+ out.WriteString("&gt;")
+ start = i + 1
+ }
+ }
+ out.WriteString(s[start:])
+}
diff --git a/src/go/doc/comment/markdown.go b/src/go/doc/comment/markdown.go
new file mode 100644
index 0000000..d8550f2
--- /dev/null
+++ b/src/go/doc/comment/markdown.go
@@ -0,0 +1,188 @@
+// 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 comment
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+)
+
+// An mdPrinter holds the state needed for printing a Doc as Markdown.
+type mdPrinter struct {
+ *Printer
+ headingPrefix string
+ raw bytes.Buffer
+}
+
+// Markdown returns a Markdown formatting of the Doc.
+// See the [Printer] documentation for ways to customize the Markdown output.
+func (p *Printer) Markdown(d *Doc) []byte {
+ mp := &mdPrinter{
+ Printer: p,
+ headingPrefix: strings.Repeat("#", p.headingLevel()) + " ",
+ }
+
+ var out bytes.Buffer
+ for i, x := range d.Content {
+ if i > 0 {
+ out.WriteByte('\n')
+ }
+ mp.block(&out, x)
+ }
+ return out.Bytes()
+}
+
+// block prints the block x to out.
+func (p *mdPrinter) block(out *bytes.Buffer, x Block) {
+ switch x := x.(type) {
+ default:
+ fmt.Fprintf(out, "?%T", x)
+
+ case *Paragraph:
+ p.text(out, x.Text)
+ out.WriteString("\n")
+
+ case *Heading:
+ out.WriteString(p.headingPrefix)
+ p.text(out, x.Text)
+ if id := p.headingID(x); id != "" {
+ out.WriteString(" {#")
+ out.WriteString(id)
+ out.WriteString("}")
+ }
+ out.WriteString("\n")
+
+ case *Code:
+ md := x.Text
+ for md != "" {
+ var line string
+ line, md, _ = strings.Cut(md, "\n")
+ if line != "" {
+ out.WriteString("\t")
+ out.WriteString(line)
+ }
+ out.WriteString("\n")
+ }
+
+ case *List:
+ loose := x.BlankBetween()
+ for i, item := range x.Items {
+ if i > 0 && loose {
+ out.WriteString("\n")
+ }
+ if n := item.Number; n != "" {
+ out.WriteString(" ")
+ out.WriteString(n)
+ out.WriteString(". ")
+ } else {
+ out.WriteString(" - ") // SP SP - SP
+ }
+ for i, blk := range item.Content {
+ const fourSpace = " "
+ if i > 0 {
+ out.WriteString("\n" + fourSpace)
+ }
+ p.text(out, blk.(*Paragraph).Text)
+ out.WriteString("\n")
+ }
+ }
+ }
+}
+
+// text prints the text sequence x to out.
+func (p *mdPrinter) text(out *bytes.Buffer, x []Text) {
+ p.raw.Reset()
+ p.rawText(&p.raw, x)
+ line := bytes.TrimSpace(p.raw.Bytes())
+ if len(line) == 0 {
+ return
+ }
+ switch line[0] {
+ case '+', '-', '*', '#':
+ // Escape what would be the start of an unordered list or heading.
+ out.WriteByte('\\')
+ case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
+ i := 1
+ for i < len(line) && '0' <= line[i] && line[i] <= '9' {
+ i++
+ }
+ if i < len(line) && (line[i] == '.' || line[i] == ')') {
+ // Escape what would be the start of an ordered list.
+ out.Write(line[:i])
+ out.WriteByte('\\')
+ line = line[i:]
+ }
+ }
+ out.Write(line)
+}
+
+// rawText prints the text sequence x to out,
+// without worrying about escaping characters
+// that have special meaning at the start of a Markdown line.
+func (p *mdPrinter) rawText(out *bytes.Buffer, x []Text) {
+ for _, t := range x {
+ switch t := t.(type) {
+ case Plain:
+ p.escape(out, string(t))
+ case Italic:
+ out.WriteString("*")
+ p.escape(out, string(t))
+ out.WriteString("*")
+ case *Link:
+ out.WriteString("[")
+ p.rawText(out, t.Text)
+ out.WriteString("](")
+ out.WriteString(t.URL)
+ out.WriteString(")")
+ case *DocLink:
+ url := p.docLinkURL(t)
+ if url != "" {
+ out.WriteString("[")
+ }
+ p.rawText(out, t.Text)
+ if url != "" {
+ out.WriteString("](")
+ url = strings.ReplaceAll(url, "(", "%28")
+ url = strings.ReplaceAll(url, ")", "%29")
+ out.WriteString(url)
+ out.WriteString(")")
+ }
+ }
+ }
+}
+
+// escape prints s to out as plain text,
+// escaping special characters to avoid being misinterpreted
+// as Markdown markup sequences.
+func (p *mdPrinter) escape(out *bytes.Buffer, s string) {
+ start := 0
+ for i := 0; i < len(s); i++ {
+ switch s[i] {
+ case '\n':
+ // Turn all \n into spaces, for a few reasons:
+ // - Avoid introducing paragraph breaks accidentally.
+ // - Avoid the need to reindent after the newline.
+ // - Avoid problems with Markdown renderers treating
+ // every mid-paragraph newline as a <br>.
+ out.WriteString(s[start:i])
+ out.WriteByte(' ')
+ start = i + 1
+ continue
+ case '`', '_', '*', '[', '<', '\\':
+ // Not all of these need to be escaped all the time,
+ // but is valid and easy to do so.
+ // We assume the Markdown is being passed to a
+ // Markdown renderer, not edited by a person,
+ // so it's fine to have escapes that are not strictly
+ // necessary in some cases.
+ out.WriteString(s[start:i])
+ out.WriteByte('\\')
+ out.WriteByte(s[i])
+ start = i + 1
+ }
+ }
+ out.WriteString(s[start:])
+}
diff --git a/src/go/doc/comment/mkstd.sh b/src/go/doc/comment/mkstd.sh
new file mode 100755
index 0000000..c9dee8c
--- /dev/null
+++ b/src/go/doc/comment/mkstd.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+# 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.
+
+# This could be a good use for embed but go/doc/comment
+# is built into the bootstrap go command, so it can't use embed.
+# Also not using embed lets us emit a string array directly
+# and avoid init-time work.
+
+(
+echo "// 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.
+
+// Code generated by 'go generate' DO NOT EDIT.
+//go:generate ./mkstd.sh
+
+package comment
+
+var stdPkgs = []string{"
+go list std | grep -v / | sort | sed 's/.*/"&",/'
+echo "}"
+) | gofmt >std.go.tmp && mv std.go.tmp std.go
diff --git a/src/go/doc/comment/old_test.go b/src/go/doc/comment/old_test.go
new file mode 100644
index 0000000..944f94d
--- /dev/null
+++ b/src/go/doc/comment/old_test.go
@@ -0,0 +1,80 @@
+// Copyright 2011 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.
+
+// These tests are carried forward from the old go/doc implementation.
+
+package comment
+
+import "testing"
+
+var oldHeadingTests = []struct {
+ line string
+ ok bool
+}{
+ {"Section", true},
+ {"A typical usage", true},
+ {"ΔΛΞ is Greek", true},
+ {"Foo 42", true},
+ {"", false},
+ {"section", false},
+ {"A typical usage:", false},
+ {"This code:", false},
+ {"δ is Greek", false},
+ {"Foo §", false},
+ {"Fermat's Last Sentence", true},
+ {"Fermat's", true},
+ {"'sX", false},
+ {"Ted 'Too' Bar", false},
+ {"Use n+m", false},
+ {"Scanning:", false},
+ {"N:M", false},
+}
+
+func TestIsOldHeading(t *testing.T) {
+ for _, tt := range oldHeadingTests {
+ if isOldHeading(tt.line, []string{"Text.", "", tt.line, "", "Text."}, 2) != tt.ok {
+ t.Errorf("isOldHeading(%q) = %v, want %v", tt.line, !tt.ok, tt.ok)
+ }
+ }
+}
+
+var autoURLTests = []struct {
+ in, out string
+}{
+ {"", ""},
+ {"http://[::1]:8080/foo.txt", "http://[::1]:8080/foo.txt"},
+ {"https://www.google.com) after", "https://www.google.com"},
+ {"https://www.google.com:30/x/y/z:b::c. After", "https://www.google.com:30/x/y/z:b::c"},
+ {"http://www.google.com/path/:;!-/?query=%34b#093124", "http://www.google.com/path/:;!-/?query=%34b#093124"},
+ {"http://www.google.com/path/:;!-/?query=%34bar#093124", "http://www.google.com/path/:;!-/?query=%34bar#093124"},
+ {"http://www.google.com/index.html! After", "http://www.google.com/index.html"},
+ {"http://www.google.com/", "http://www.google.com/"},
+ {"https://www.google.com/", "https://www.google.com/"},
+ {"http://www.google.com/path.", "http://www.google.com/path"},
+ {"http://en.wikipedia.org/wiki/Camellia_(cipher)", "http://en.wikipedia.org/wiki/Camellia_(cipher)"},
+ {"http://www.google.com/)", "http://www.google.com/"},
+ {"http://gmail.com)", "http://gmail.com"},
+ {"http://gmail.com))", "http://gmail.com"},
+ {"http://gmail.com ((http://gmail.com)) ()", "http://gmail.com"},
+ {"http://example.com/ quux!", "http://example.com/"},
+ {"http://example.com/%2f/ /world.", "http://example.com/%2f/"},
+ {"http: ipsum //host/path", ""},
+ {"javascript://is/not/linked", ""},
+ {"http://foo", "http://foo"},
+ {"https://www.example.com/person/][Person Name]]", "https://www.example.com/person/"},
+ {"http://golang.org/)", "http://golang.org/"},
+ {"http://golang.org/hello())", "http://golang.org/hello()"},
+ {"http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD", "http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD"},
+ {"https://foo.bar/bal/x(])", "https://foo.bar/bal/x"}, // inner ] causes (]) to be cut off from URL
+ {"http://bar(])", "http://bar"}, // same
+}
+
+func TestAutoURL(t *testing.T) {
+ for _, tt := range autoURLTests {
+ url, ok := autoURL(tt.in)
+ if url != tt.out || ok != (tt.out != "") {
+ t.Errorf("autoURL(%q) = %q, %v, want %q, %v", tt.in, url, ok, tt.out, tt.out != "")
+ }
+ }
+}
diff --git a/src/go/doc/comment/parse.go b/src/go/doc/comment/parse.go
new file mode 100644
index 0000000..62a0f8f
--- /dev/null
+++ b/src/go/doc/comment/parse.go
@@ -0,0 +1,1262 @@
+// 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 comment
+
+import (
+ "sort"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+// A Doc is a parsed Go doc comment.
+type Doc struct {
+ // Content is the sequence of content blocks in the comment.
+ Content []Block
+
+ // Links is the link definitions in the comment.
+ Links []*LinkDef
+}
+
+// A LinkDef is a single link definition.
+type LinkDef struct {
+ Text string // the link text
+ URL string // the link URL
+ Used bool // whether the comment uses the definition
+}
+
+// A Block is block-level content in a doc comment,
+// one of [*Code], [*Heading], [*List], or [*Paragraph].
+type Block interface {
+ block()
+}
+
+// A Heading is a doc comment heading.
+type Heading struct {
+ Text []Text // the heading text
+}
+
+func (*Heading) block() {}
+
+// A List is a numbered or bullet list.
+// Lists are always non-empty: len(Items) > 0.
+// In a numbered list, every Items[i].Number is a non-empty string.
+// In a bullet list, every Items[i].Number is an empty string.
+type List struct {
+ // Items is the list items.
+ Items []*ListItem
+
+ // ForceBlankBefore indicates that the list must be
+ // preceded by a blank line when reformatting the comment,
+ // overriding the usual conditions. See the BlankBefore method.
+ //
+ // The comment parser sets ForceBlankBefore for any list
+ // that is preceded by a blank line, to make sure
+ // the blank line is preserved when printing.
+ ForceBlankBefore bool
+
+ // ForceBlankBetween indicates that list items must be
+ // separated by blank lines when reformatting the comment,
+ // overriding the usual conditions. See the BlankBetween method.
+ //
+ // The comment parser sets ForceBlankBetween for any list
+ // that has a blank line between any two of its items, to make sure
+ // the blank lines are preserved when printing.
+ ForceBlankBetween bool
+}
+
+func (*List) block() {}
+
+// BlankBefore reports whether a reformatting of the comment
+// should include a blank line before the list.
+// The default rule is the same as for [BlankBetween]:
+// if the list item content contains any blank lines
+// (meaning at least one item has multiple paragraphs)
+// then the list itself must be preceded by a blank line.
+// A preceding blank line can be forced by setting [List].ForceBlankBefore.
+func (l *List) BlankBefore() bool {
+ return l.ForceBlankBefore || l.BlankBetween()
+}
+
+// BlankBetween reports whether a reformatting of the comment
+// should include a blank line between each pair of list items.
+// The default rule is that if the list item content contains any blank lines
+// (meaning at least one item has multiple paragraphs)
+// then list items must themselves be separated by blank lines.
+// Blank line separators can be forced by setting [List].ForceBlankBetween.
+func (l *List) BlankBetween() bool {
+ if l.ForceBlankBetween {
+ return true
+ }
+ for _, item := range l.Items {
+ if len(item.Content) != 1 {
+ // Unreachable for parsed comments today,
+ // since the only way to get multiple item.Content
+ // is multiple paragraphs, which must have been
+ // separated by a blank line.
+ return true
+ }
+ }
+ return false
+}
+
+// A ListItem is a single item in a numbered or bullet list.
+type ListItem struct {
+ // Number is a decimal string in a numbered list
+ // or an empty string in a bullet list.
+ Number string // "1", "2", ...; "" for bullet list
+
+ // Content is the list content.
+ // Currently, restrictions in the parser and printer
+ // require every element of Content to be a *Paragraph.
+ Content []Block // Content of this item.
+}
+
+// A Paragraph is a paragraph of text.
+type Paragraph struct {
+ Text []Text
+}
+
+func (*Paragraph) block() {}
+
+// A Code is a preformatted code block.
+type Code struct {
+ // Text is the preformatted text, ending with a newline character.
+ // It may be multiple lines, each of which ends with a newline character.
+ // It is never empty, nor does it start or end with a blank line.
+ Text string
+}
+
+func (*Code) block() {}
+
+// A Text is text-level content in a doc comment,
+// one of [Plain], [Italic], [*Link], or [*DocLink].
+type Text interface {
+ text()
+}
+
+// A Plain is a string rendered as plain text (not italicized).
+type Plain string
+
+func (Plain) text() {}
+
+// An Italic is a string rendered as italicized text.
+type Italic string
+
+func (Italic) text() {}
+
+// A Link is a link to a specific URL.
+type Link struct {
+ Auto bool // is this an automatic (implicit) link of a literal URL?
+ Text []Text // text of link
+ URL string // target URL of link
+}
+
+func (*Link) text() {}
+
+// A DocLink is a link to documentation for a Go package or symbol.
+type DocLink struct {
+ Text []Text // text of link
+
+ // ImportPath, Recv, and Name identify the Go package or symbol
+ // that is the link target. The potential combinations of
+ // non-empty fields are:
+ // - ImportPath: a link to another package
+ // - ImportPath, Name: a link to a const, func, type, or var in another package
+ // - ImportPath, Recv, Name: a link to a method in another package
+ // - Name: a link to a const, func, type, or var in this package
+ // - Recv, Name: a link to a method in this package
+ ImportPath string // import path
+ Recv string // receiver type, without any pointer star, for methods
+ Name string // const, func, type, var, or method name
+}
+
+func (*DocLink) text() {}
+
+// A Parser is a doc comment parser.
+// The fields in the struct can be filled in before calling Parse
+// in order to customize the details of the parsing process.
+type Parser struct {
+ // Words is a map of Go identifier words that
+ // should be italicized and potentially linked.
+ // If Words[w] is the empty string, then the word w
+ // is only italicized. Otherwise it is linked, using
+ // Words[w] as the link target.
+ // Words corresponds to the [go/doc.ToHTML] words parameter.
+ Words map[string]string
+
+ // LookupPackage resolves a package name to an import path.
+ //
+ // If LookupPackage(name) returns ok == true, then [name]
+ // (or [name.Sym] or [name.Sym.Method])
+ // is considered a documentation link to importPath's package docs.
+ // It is valid to return "", true, in which case name is considered
+ // to refer to the current package.
+ //
+ // If LookupPackage(name) returns ok == false,
+ // then [name] (or [name.Sym] or [name.Sym.Method])
+ // will not be considered a documentation link,
+ // except in the case where name is the full (but single-element) import path
+ // of a package in the standard library, such as in [math] or [io.Reader].
+ // LookupPackage is still called for such names,
+ // in order to permit references to imports of other packages
+ // with the same package names.
+ //
+ // Setting LookupPackage to nil is equivalent to setting it to
+ // a function that always returns "", false.
+ LookupPackage func(name string) (importPath string, ok bool)
+
+ // LookupSym reports whether a symbol name or method name
+ // exists in the current package.
+ //
+ // If LookupSym("", "Name") returns true, then [Name]
+ // is considered a documentation link for a const, func, type, or var.
+ //
+ // Similarly, if LookupSym("Recv", "Name") returns true,
+ // then [Recv.Name] is considered a documentation link for
+ // type Recv's method Name.
+ //
+ // Setting LookupSym to nil is equivalent to setting it to a function
+ // that always returns false.
+ LookupSym func(recv, name string) (ok bool)
+}
+
+// parseDoc is parsing state for a single doc comment.
+type parseDoc struct {
+ *Parser
+ *Doc
+ links map[string]*LinkDef
+ lines []string
+ lookupSym func(recv, name string) bool
+}
+
+// lookupPkg is called to look up the pkg in [pkg], [pkg.Name], and [pkg.Name.Recv].
+// If pkg has a slash, it is assumed to be the full import path and is returned with ok = true.
+//
+// Otherwise, pkg is probably a simple package name like "rand" (not "crypto/rand" or "math/rand").
+// d.LookupPackage provides a way for the caller to allow resolving such names with reference
+// to the imports in the surrounding package.
+//
+// There is one collision between these two cases: single-element standard library names
+// like "math" are full import paths but don't contain slashes. We let d.LookupPackage have
+// the first chance to resolve it, in case there's a different package imported as math,
+// and otherwise we refer to a built-in list of single-element standard library package names.
+func (d *parseDoc) lookupPkg(pkg string) (importPath string, ok bool) {
+ if strings.Contains(pkg, "/") { // assume a full import path
+ if validImportPath(pkg) {
+ return pkg, true
+ }
+ return "", false
+ }
+ if d.LookupPackage != nil {
+ // Give LookupPackage a chance.
+ if path, ok := d.LookupPackage(pkg); ok {
+ return path, true
+ }
+ }
+ return DefaultLookupPackage(pkg)
+}
+
+func isStdPkg(path string) bool {
+ // TODO(rsc): Use sort.Find once we don't have to worry about
+ // copying this code into older Go environments.
+ i := sort.Search(len(stdPkgs), func(i int) bool { return stdPkgs[i] >= path })
+ return i < len(stdPkgs) && stdPkgs[i] == path
+}
+
+// DefaultLookupPackage is the default package lookup
+// function, used when [Parser].LookupPackage is nil.
+// It recognizes names of the packages from the standard
+// library with single-element import paths, such as math,
+// which would otherwise be impossible to name.
+//
+// Note that the go/doc package provides a more sophisticated
+// lookup based on the imports used in the current package.
+func DefaultLookupPackage(name string) (importPath string, ok bool) {
+ if isStdPkg(name) {
+ return name, true
+ }
+ return "", false
+}
+
+// Parse parses the doc comment text and returns the *Doc form.
+// Comment markers (/* // and */) in the text must have already been removed.
+func (p *Parser) Parse(text string) *Doc {
+ lines := unindent(strings.Split(text, "\n"))
+ d := &parseDoc{
+ Parser: p,
+ Doc: new(Doc),
+ links: make(map[string]*LinkDef),
+ lines: lines,
+ lookupSym: func(recv, name string) bool { return false },
+ }
+ if p.LookupSym != nil {
+ d.lookupSym = p.LookupSym
+ }
+
+ // First pass: break into block structure and collect known links.
+ // The text is all recorded as Plain for now.
+ var prev span
+ for _, s := range parseSpans(lines) {
+ var b Block
+ switch s.kind {
+ default:
+ panic("go/doc/comment: internal error: unknown span kind")
+ case spanList:
+ b = d.list(lines[s.start:s.end], prev.end < s.start)
+ case spanCode:
+ b = d.code(lines[s.start:s.end])
+ case spanOldHeading:
+ b = d.oldHeading(lines[s.start])
+ case spanHeading:
+ b = d.heading(lines[s.start])
+ case spanPara:
+ b = d.paragraph(lines[s.start:s.end])
+ }
+ if b != nil {
+ d.Content = append(d.Content, b)
+ }
+ prev = s
+ }
+
+ // Second pass: interpret all the Plain text now that we know the links.
+ for _, b := range d.Content {
+ switch b := b.(type) {
+ case *Paragraph:
+ b.Text = d.parseLinkedText(string(b.Text[0].(Plain)))
+ case *List:
+ for _, i := range b.Items {
+ for _, c := range i.Content {
+ p := c.(*Paragraph)
+ p.Text = d.parseLinkedText(string(p.Text[0].(Plain)))
+ }
+ }
+ }
+ }
+
+ return d.Doc
+}
+
+// A span represents a single span of comment lines (lines[start:end])
+// of an identified kind (code, heading, paragraph, and so on).
+type span struct {
+ start int
+ end int
+ kind spanKind
+}
+
+// A spanKind describes the kind of span.
+type spanKind int
+
+const (
+ _ spanKind = iota
+ spanCode
+ spanHeading
+ spanList
+ spanOldHeading
+ spanPara
+)
+
+func parseSpans(lines []string) []span {
+ var spans []span
+
+ // The loop may process a line twice: once as unindented
+ // and again forced indented. So the maximum expected
+ // number of iterations is 2*len(lines). The repeating logic
+ // can be subtle, though, and to protect against introduction
+ // of infinite loops in future changes, we watch to see that
+ // we are not looping too much. A panic is better than a
+ // quiet infinite loop.
+ watchdog := 2 * len(lines)
+
+ i := 0
+ forceIndent := 0
+Spans:
+ for {
+ // Skip blank lines.
+ for i < len(lines) && lines[i] == "" {
+ i++
+ }
+ if i >= len(lines) {
+ break
+ }
+ if watchdog--; watchdog < 0 {
+ panic("go/doc/comment: internal error: not making progress")
+ }
+
+ var kind spanKind
+ start := i
+ end := i
+ if i < forceIndent || indented(lines[i]) {
+ // Indented (or force indented).
+ // Ends before next unindented. (Blank lines are OK.)
+ // If this is an unindented list that we are heuristically treating as indented,
+ // then accept unindented list item lines up to the first blank lines.
+ // The heuristic is disabled at blank lines to contain its effect
+ // to non-gofmt'ed sections of the comment.
+ unindentedListOK := isList(lines[i]) && i < forceIndent
+ i++
+ for i < len(lines) && (lines[i] == "" || i < forceIndent || indented(lines[i]) || (unindentedListOK && isList(lines[i]))) {
+ if lines[i] == "" {
+ unindentedListOK = false
+ }
+ i++
+ }
+
+ // Drop trailing blank lines.
+ end = i
+ for end > start && lines[end-1] == "" {
+ end--
+ }
+
+ // If indented lines are followed (without a blank line)
+ // by an unindented line ending in a brace,
+ // take that one line too. This fixes the common mistake
+ // of pasting in something like
+ //
+ // func main() {
+ // fmt.Println("hello, world")
+ // }
+ //
+ // and forgetting to indent it.
+ // The heuristic will never trigger on a gofmt'ed comment,
+ // because any gofmt'ed code block or list would be
+ // followed by a blank line or end of comment.
+ if end < len(lines) && strings.HasPrefix(lines[end], "}") {
+ end++
+ }
+
+ if isList(lines[start]) {
+ kind = spanList
+ } else {
+ kind = spanCode
+ }
+ } else {
+ // Unindented. Ends at next blank or indented line.
+ i++
+ for i < len(lines) && lines[i] != "" && !indented(lines[i]) {
+ i++
+ }
+ end = i
+
+ // If unindented lines are followed (without a blank line)
+ // by an indented line that would start a code block,
+ // check whether the final unindented lines
+ // should be left for the indented section.
+ // This can happen for the common mistakes of
+ // unindented code or unindented lists.
+ // The heuristic will never trigger on a gofmt'ed comment,
+ // because any gofmt'ed code block would have a blank line
+ // preceding it after the unindented lines.
+ if i < len(lines) && lines[i] != "" && !isList(lines[i]) {
+ switch {
+ case isList(lines[i-1]):
+ // If the final unindented line looks like a list item,
+ // this may be the first indented line wrap of
+ // a mistakenly unindented list.
+ // Leave all the unindented list items.
+ forceIndent = end
+ end--
+ for end > start && isList(lines[end-1]) {
+ end--
+ }
+
+ case strings.HasSuffix(lines[i-1], "{") || strings.HasSuffix(lines[i-1], `\`):
+ // If the final unindented line ended in { or \
+ // it is probably the start of a misindented code block.
+ // Give the user a single line fix.
+ // Often that's enough; if not, the user can fix the others themselves.
+ forceIndent = end
+ end--
+ }
+
+ if start == end && forceIndent > start {
+ i = start
+ continue Spans
+ }
+ }
+
+ // Span is either paragraph or heading.
+ if end-start == 1 && isHeading(lines[start]) {
+ kind = spanHeading
+ } else if end-start == 1 && isOldHeading(lines[start], lines, start) {
+ kind = spanOldHeading
+ } else {
+ kind = spanPara
+ }
+ }
+
+ spans = append(spans, span{start, end, kind})
+ i = end
+ }
+
+ return spans
+}
+
+// indented reports whether line is indented
+// (starts with a leading space or tab).
+func indented(line string) bool {
+ return line != "" && (line[0] == ' ' || line[0] == '\t')
+}
+
+// unindent removes any common space/tab prefix
+// from each line in lines, returning a copy of lines in which
+// those prefixes have been trimmed from each line.
+// It also replaces any lines containing only spaces with blank lines (empty strings).
+func unindent(lines []string) []string {
+ // Trim leading and trailing blank lines.
+ for len(lines) > 0 && isBlank(lines[0]) {
+ lines = lines[1:]
+ }
+ for len(lines) > 0 && isBlank(lines[len(lines)-1]) {
+ lines = lines[:len(lines)-1]
+ }
+ if len(lines) == 0 {
+ return nil
+ }
+
+ // Compute and remove common indentation.
+ prefix := leadingSpace(lines[0])
+ for _, line := range lines[1:] {
+ if !isBlank(line) {
+ prefix = commonPrefix(prefix, leadingSpace(line))
+ }
+ }
+
+ out := make([]string, len(lines))
+ for i, line := range lines {
+ line = strings.TrimPrefix(line, prefix)
+ if strings.TrimSpace(line) == "" {
+ line = ""
+ }
+ out[i] = line
+ }
+ for len(out) > 0 && out[0] == "" {
+ out = out[1:]
+ }
+ for len(out) > 0 && out[len(out)-1] == "" {
+ out = out[:len(out)-1]
+ }
+ return out
+}
+
+// isBlank reports whether s is a blank line.
+func isBlank(s string) bool {
+ return len(s) == 0 || (len(s) == 1 && s[0] == '\n')
+}
+
+// commonPrefix returns the longest common prefix of a and b.
+func commonPrefix(a, b string) string {
+ i := 0
+ for i < len(a) && i < len(b) && a[i] == b[i] {
+ i++
+ }
+ return a[0:i]
+}
+
+// leadingSpace returns the longest prefix of s consisting of spaces and tabs.
+func leadingSpace(s string) string {
+ i := 0
+ for i < len(s) && (s[i] == ' ' || s[i] == '\t') {
+ i++
+ }
+ return s[:i]
+}
+
+// isOldHeading reports whether line is an old-style section heading.
+// line is all[off].
+func isOldHeading(line string, all []string, off int) bool {
+ if off <= 0 || all[off-1] != "" || off+2 >= len(all) || all[off+1] != "" || leadingSpace(all[off+2]) != "" {
+ return false
+ }
+
+ line = strings.TrimSpace(line)
+
+ // 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; ; {
+ var ok bool
+ if _, b, ok = strings.Cut(b, "'"); !ok {
+ break
+ }
+ if b != "s" && !strings.HasPrefix(b, "s ") {
+ return false // ' not followed by s and then end-of-word
+ }
+ }
+
+ // allow "." when followed by non-space
+ for b := line; ; {
+ var ok bool
+ if _, b, ok = strings.Cut(b, "."); !ok {
+ break
+ }
+ if b == "" || strings.HasPrefix(b, " ") {
+ return false // not followed by non-space
+ }
+ }
+
+ return true
+}
+
+// oldHeading returns the *Heading for the given old-style section heading line.
+func (d *parseDoc) oldHeading(line string) Block {
+ return &Heading{Text: []Text{Plain(strings.TrimSpace(line))}}
+}
+
+// isHeading reports whether line is a new-style section heading.
+func isHeading(line string) bool {
+ return len(line) >= 2 &&
+ line[0] == '#' &&
+ (line[1] == ' ' || line[1] == '\t') &&
+ strings.TrimSpace(line) != "#"
+}
+
+// heading returns the *Heading for the given new-style section heading line.
+func (d *parseDoc) heading(line string) Block {
+ return &Heading{Text: []Text{Plain(strings.TrimSpace(line[1:]))}}
+}
+
+// code returns a code block built from the lines.
+func (d *parseDoc) code(lines []string) *Code {
+ body := unindent(lines)
+ body = append(body, "") // to get final \n from Join
+ return &Code{Text: strings.Join(body, "\n")}
+}
+
+// paragraph returns a paragraph block built from the lines.
+// If the lines are link definitions, paragraph adds them to d and returns nil.
+func (d *parseDoc) paragraph(lines []string) Block {
+ // Is this a block of known links? Handle.
+ var defs []*LinkDef
+ for _, line := range lines {
+ def, ok := parseLink(line)
+ if !ok {
+ goto NoDefs
+ }
+ defs = append(defs, def)
+ }
+ for _, def := range defs {
+ d.Links = append(d.Links, def)
+ if d.links[def.Text] == nil {
+ d.links[def.Text] = def
+ }
+ }
+ return nil
+NoDefs:
+
+ return &Paragraph{Text: []Text{Plain(strings.Join(lines, "\n"))}}
+}
+
+// parseLink parses a single link definition line:
+//
+// [text]: url
+//
+// It returns the link definition and whether the line was well formed.
+func parseLink(line string) (*LinkDef, bool) {
+ if line == "" || line[0] != '[' {
+ return nil, false
+ }
+ i := strings.Index(line, "]:")
+ if i < 0 || i+3 >= len(line) || (line[i+2] != ' ' && line[i+2] != '\t') {
+ return nil, false
+ }
+
+ text := line[1:i]
+ url := strings.TrimSpace(line[i+3:])
+ j := strings.Index(url, "://")
+ if j < 0 || !isScheme(url[:j]) {
+ return nil, false
+ }
+
+ // Line has right form and has valid scheme://.
+ // That's good enough for us - we are not as picky
+ // about the characters beyond the :// as we are
+ // when extracting inline URLs from text.
+ return &LinkDef{Text: text, URL: url}, true
+}
+
+// list returns a list built from the indented lines,
+// using forceBlankBefore as the value of the List's ForceBlankBefore field.
+func (d *parseDoc) list(lines []string, forceBlankBefore bool) *List {
+ num, _, _ := listMarker(lines[0])
+ var (
+ list *List = &List{ForceBlankBefore: forceBlankBefore}
+ item *ListItem
+ text []string
+ )
+ flush := func() {
+ if item != nil {
+ if para := d.paragraph(text); para != nil {
+ item.Content = append(item.Content, para)
+ }
+ }
+ text = nil
+ }
+
+ for _, line := range lines {
+ if n, after, ok := listMarker(line); ok && (n != "") == (num != "") {
+ // start new list item
+ flush()
+
+ item = &ListItem{Number: n}
+ list.Items = append(list.Items, item)
+ line = after
+ }
+ line = strings.TrimSpace(line)
+ if line == "" {
+ list.ForceBlankBetween = true
+ flush()
+ continue
+ }
+ text = append(text, strings.TrimSpace(line))
+ }
+ flush()
+ return list
+}
+
+// listMarker parses the line as beginning with a list marker.
+// If it can do that, it returns the numeric marker ("" for a bullet list),
+// the rest of the line, and ok == true.
+// Otherwise, it returns "", "", false.
+func listMarker(line string) (num, rest string, ok bool) {
+ line = strings.TrimSpace(line)
+ if line == "" {
+ return "", "", false
+ }
+
+ // Can we find a marker?
+ if r, n := utf8.DecodeRuneInString(line); r == '•' || r == '*' || r == '+' || r == '-' {
+ num, rest = "", line[n:]
+ } else if '0' <= line[0] && line[0] <= '9' {
+ n := 1
+ for n < len(line) && '0' <= line[n] && line[n] <= '9' {
+ n++
+ }
+ if n >= len(line) || (line[n] != '.' && line[n] != ')') {
+ return "", "", false
+ }
+ num, rest = line[:n], line[n+1:]
+ } else {
+ return "", "", false
+ }
+
+ if !indented(rest) || strings.TrimSpace(rest) == "" {
+ return "", "", false
+ }
+
+ return num, rest, true
+}
+
+// isList reports whether the line is the first line of a list,
+// meaning starts with a list marker after any indentation.
+// (The caller is responsible for checking the line is indented, as appropriate.)
+func isList(line string) bool {
+ _, _, ok := listMarker(line)
+ return ok
+}
+
+// parseLinkedText parses text that is allowed to contain explicit links,
+// such as [math.Sin] or [Go home page], into a slice of Text items.
+//
+// A “pkg” is only assumed to be a full import path if it starts with
+// a domain name (a path element with a dot) or is one of the packages
+// from the standard library (“[os]”, “[encoding/json]”, and so on).
+// To avoid problems with maps, generics, and array types, doc links
+// must be both preceded and followed by punctuation, spaces, tabs,
+// or the start or end of a line. An example problem would be treating
+// map[ast.Expr]TypeAndValue as containing a link.
+func (d *parseDoc) parseLinkedText(text string) []Text {
+ var out []Text
+ wrote := 0
+ flush := func(i int) {
+ if wrote < i {
+ out = d.parseText(out, text[wrote:i], true)
+ wrote = i
+ }
+ }
+
+ start := -1
+ var buf []byte
+ for i := 0; i < len(text); i++ {
+ c := text[i]
+ if c == '\n' || c == '\t' {
+ c = ' '
+ }
+ switch c {
+ case '[':
+ start = i
+ case ']':
+ if start >= 0 {
+ if def, ok := d.links[string(buf)]; ok {
+ def.Used = true
+ flush(start)
+ out = append(out, &Link{
+ Text: d.parseText(nil, text[start+1:i], false),
+ URL: def.URL,
+ })
+ wrote = i + 1
+ } else if link, ok := d.docLink(text[start+1:i], text[:start], text[i+1:]); ok {
+ flush(start)
+ link.Text = d.parseText(nil, text[start+1:i], false)
+ out = append(out, link)
+ wrote = i + 1
+ }
+ }
+ start = -1
+ buf = buf[:0]
+ }
+ if start >= 0 && i != start {
+ buf = append(buf, c)
+ }
+ }
+
+ flush(len(text))
+ return out
+}
+
+// docLink parses text, which was found inside [ ] brackets,
+// as a doc link if possible, returning the DocLink and ok == true
+// or else nil, false.
+// The before and after strings are the text before the [ and after the ]
+// on the same line. Doc links must be preceded and followed by
+// punctuation, spaces, tabs, or the start or end of a line.
+func (d *parseDoc) docLink(text, before, after string) (link *DocLink, ok bool) {
+ if before != "" {
+ r, _ := utf8.DecodeLastRuneInString(before)
+ if !unicode.IsPunct(r) && r != ' ' && r != '\t' && r != '\n' {
+ return nil, false
+ }
+ }
+ if after != "" {
+ r, _ := utf8.DecodeRuneInString(after)
+ if !unicode.IsPunct(r) && r != ' ' && r != '\t' && r != '\n' {
+ return nil, false
+ }
+ }
+ text = strings.TrimPrefix(text, "*")
+ pkg, name, ok := splitDocName(text)
+ var recv string
+ if ok {
+ pkg, recv, _ = splitDocName(pkg)
+ }
+ if pkg != "" {
+ if pkg, ok = d.lookupPkg(pkg); !ok {
+ return nil, false
+ }
+ } else {
+ if ok = d.lookupSym(recv, name); !ok {
+ return nil, false
+ }
+ }
+ link = &DocLink{
+ ImportPath: pkg,
+ Recv: recv,
+ Name: name,
+ }
+ return link, true
+}
+
+// If text is of the form before.Name, where Name is a capitalized Go identifier,
+// then splitDocName returns before, name, true.
+// Otherwise it returns text, "", false.
+func splitDocName(text string) (before, name string, foundDot bool) {
+ i := strings.LastIndex(text, ".")
+ name = text[i+1:]
+ if !isName(name) {
+ return text, "", false
+ }
+ if i >= 0 {
+ before = text[:i]
+ }
+ return before, name, true
+}
+
+// parseText parses s as text and returns the result of appending
+// those parsed Text elements to out.
+// parseText does not handle explicit links like [math.Sin] or [Go home page]:
+// those are handled by parseLinkedText.
+// If autoLink is true, then parseText recognizes URLs and words from d.Words
+// and converts those to links as appropriate.
+func (d *parseDoc) parseText(out []Text, s string, autoLink bool) []Text {
+ var w strings.Builder
+ wrote := 0
+ writeUntil := func(i int) {
+ w.WriteString(s[wrote:i])
+ wrote = i
+ }
+ flush := func(i int) {
+ writeUntil(i)
+ if w.Len() > 0 {
+ out = append(out, Plain(w.String()))
+ w.Reset()
+ }
+ }
+ for i := 0; i < len(s); {
+ t := s[i:]
+ if autoLink {
+ if url, ok := autoURL(t); ok {
+ flush(i)
+ // Note: The old comment parser would look up the URL in words
+ // and replace the target with words[URL] if it was non-empty.
+ // That would allow creating links that display as one URL but
+ // when clicked go to a different URL. Not sure what the point
+ // of that is, so we're not doing that lookup here.
+ out = append(out, &Link{Auto: true, Text: []Text{Plain(url)}, URL: url})
+ i += len(url)
+ wrote = i
+ continue
+ }
+ if id, ok := ident(t); ok {
+ url, italics := d.Words[id]
+ if !italics {
+ i += len(id)
+ continue
+ }
+ flush(i)
+ if url == "" {
+ out = append(out, Italic(id))
+ } else {
+ out = append(out, &Link{Auto: true, Text: []Text{Italic(id)}, URL: url})
+ }
+ i += len(id)
+ wrote = i
+ continue
+ }
+ }
+ switch {
+ case strings.HasPrefix(t, "``"):
+ if len(t) >= 3 && t[2] == '`' {
+ // Do not convert `` inside ```, in case people are mistakenly writing Markdown.
+ i += 3
+ for i < len(t) && t[i] == '`' {
+ i++
+ }
+ break
+ }
+ writeUntil(i)
+ w.WriteRune('“')
+ i += 2
+ wrote = i
+ case strings.HasPrefix(t, "''"):
+ writeUntil(i)
+ w.WriteRune('”')
+ i += 2
+ wrote = i
+ default:
+ i++
+ }
+ }
+ flush(len(s))
+ return out
+}
+
+// autoURL checks whether s begins with a URL that should be hyperlinked.
+// If so, it returns the URL, which is a prefix of s, and ok == true.
+// Otherwise it returns "", false.
+// The caller should skip over the first len(url) bytes of s
+// before further processing.
+func autoURL(s string) (url string, ok bool) {
+ // Find the ://. Fast path to pick off non-URL,
+ // since we call this at every position in the string.
+ // The shortest possible URL is ftp://x, 7 bytes.
+ var i int
+ switch {
+ case len(s) < 7:
+ return "", false
+ case s[3] == ':':
+ i = 3
+ case s[4] == ':':
+ i = 4
+ case s[5] == ':':
+ i = 5
+ case s[6] == ':':
+ i = 6
+ default:
+ return "", false
+ }
+ if i+3 > len(s) || s[i:i+3] != "://" {
+ return "", false
+ }
+
+ // Check valid scheme.
+ if !isScheme(s[:i]) {
+ return "", false
+ }
+
+ // Scan host part. Must have at least one byte,
+ // and must start and end in non-punctuation.
+ i += 3
+ if i >= len(s) || !isHost(s[i]) || isPunct(s[i]) {
+ return "", false
+ }
+ i++
+ end := i
+ for i < len(s) && isHost(s[i]) {
+ if !isPunct(s[i]) {
+ end = i + 1
+ }
+ i++
+ }
+ i = end
+
+ // At this point we are definitely returning a URL (scheme://host).
+ // We just have to find the longest path we can add to it.
+ // Heuristics abound.
+ // We allow parens, braces, and brackets,
+ // but only if they match (#5043, #22285).
+ // We allow .,:;?! in the path but not at the end,
+ // to avoid end-of-sentence punctuation (#18139, #16565).
+ stk := []byte{}
+ end = i
+Path:
+ for ; i < len(s); i++ {
+ if isPunct(s[i]) {
+ continue
+ }
+ if !isPath(s[i]) {
+ break
+ }
+ switch s[i] {
+ case '(':
+ stk = append(stk, ')')
+ case '{':
+ stk = append(stk, '}')
+ case '[':
+ stk = append(stk, ']')
+ case ')', '}', ']':
+ if len(stk) == 0 || stk[len(stk)-1] != s[i] {
+ break Path
+ }
+ stk = stk[:len(stk)-1]
+ }
+ if len(stk) == 0 {
+ end = i + 1
+ }
+ }
+
+ return s[:end], true
+}
+
+// isScheme reports whether s is a recognized URL scheme.
+// Note that if strings of new length (beyond 3-7)
+// are added here, the fast path at the top of autoURL will need updating.
+func isScheme(s string) bool {
+ switch s {
+ case "file",
+ "ftp",
+ "gopher",
+ "http",
+ "https",
+ "mailto",
+ "nntp":
+ return true
+ }
+ return false
+}
+
+// isHost reports whether c is a byte that can appear in a URL host,
+// like www.example.com or user@[::1]:8080
+func isHost(c byte) bool {
+ // mask is a 128-bit bitmap with 1s for allowed bytes,
+ // so that the byte c can be tested with a shift and an and.
+ // If c > 128, then 1<<c and 1<<(c-64) will both be zero,
+ // and this function will return false.
+ const mask = 0 |
+ (1<<26-1)<<'A' |
+ (1<<26-1)<<'a' |
+ (1<<10-1)<<'0' |
+ 1<<'_' |
+ 1<<'@' |
+ 1<<'-' |
+ 1<<'.' |
+ 1<<'[' |
+ 1<<']' |
+ 1<<':'
+
+ return ((uint64(1)<<c)&(mask&(1<<64-1)) |
+ (uint64(1)<<(c-64))&(mask>>64)) != 0
+}
+
+// isPunct reports whether c is a punctuation byte that can appear
+// inside a path but not at the end.
+func isPunct(c byte) bool {
+ // mask is a 128-bit bitmap with 1s for allowed bytes,
+ // so that the byte c can be tested with a shift and an and.
+ // If c > 128, then 1<<c and 1<<(c-64) will both be zero,
+ // and this function will return false.
+ const mask = 0 |
+ 1<<'.' |
+ 1<<',' |
+ 1<<':' |
+ 1<<';' |
+ 1<<'?' |
+ 1<<'!'
+
+ return ((uint64(1)<<c)&(mask&(1<<64-1)) |
+ (uint64(1)<<(c-64))&(mask>>64)) != 0
+}
+
+// isPath reports whether c is a (non-punctuation) path byte.
+func isPath(c byte) bool {
+ // mask is a 128-bit bitmap with 1s for allowed bytes,
+ // so that the byte c can be tested with a shift and an and.
+ // If c > 128, then 1<<c and 1<<(c-64) will both be zero,
+ // and this function will return false.
+ const mask = 0 |
+ (1<<26-1)<<'A' |
+ (1<<26-1)<<'a' |
+ (1<<10-1)<<'0' |
+ 1<<'$' |
+ 1<<'\'' |
+ 1<<'(' |
+ 1<<')' |
+ 1<<'*' |
+ 1<<'+' |
+ 1<<'&' |
+ 1<<'#' |
+ 1<<'=' |
+ 1<<'@' |
+ 1<<'~' |
+ 1<<'_' |
+ 1<<'/' |
+ 1<<'-' |
+ 1<<'[' |
+ 1<<']' |
+ 1<<'{' |
+ 1<<'}' |
+ 1<<'%'
+
+ return ((uint64(1)<<c)&(mask&(1<<64-1)) |
+ (uint64(1)<<(c-64))&(mask>>64)) != 0
+}
+
+// isName reports whether s is a capitalized Go identifier (like Name).
+func isName(s string) bool {
+ t, ok := ident(s)
+ if !ok || t != s {
+ return false
+ }
+ r, _ := utf8.DecodeRuneInString(s)
+ return unicode.IsUpper(r)
+}
+
+// ident checks whether s begins with a Go identifier.
+// If so, it returns the identifier, which is a prefix of s, and ok == true.
+// Otherwise it returns "", false.
+// The caller should skip over the first len(id) bytes of s
+// before further processing.
+func ident(s string) (id string, ok bool) {
+ // Scan [\pL_][\pL_0-9]*
+ n := 0
+ for n < len(s) {
+ if c := s[n]; c < utf8.RuneSelf {
+ if isIdentASCII(c) && (n > 0 || c < '0' || c > '9') {
+ n++
+ continue
+ }
+ break
+ }
+ r, nr := utf8.DecodeRuneInString(s[n:])
+ if unicode.IsLetter(r) {
+ n += nr
+ continue
+ }
+ break
+ }
+ return s[:n], n > 0
+}
+
+// isIdentASCII reports whether c is an ASCII identifier byte.
+func isIdentASCII(c byte) bool {
+ // mask is a 128-bit bitmap with 1s for allowed bytes,
+ // so that the byte c can be tested with a shift and an and.
+ // If c > 128, then 1<<c and 1<<(c-64) will both be zero,
+ // and this function will return false.
+ const mask = 0 |
+ (1<<26-1)<<'A' |
+ (1<<26-1)<<'a' |
+ (1<<10-1)<<'0' |
+ 1<<'_'
+
+ return ((uint64(1)<<c)&(mask&(1<<64-1)) |
+ (uint64(1)<<(c-64))&(mask>>64)) != 0
+}
+
+// validImportPath reports whether path is a valid import path.
+// It is a lightly edited copy of golang.org/x/mod/module.CheckImportPath.
+func validImportPath(path string) bool {
+ if !utf8.ValidString(path) {
+ return false
+ }
+ if path == "" {
+ return false
+ }
+ if path[0] == '-' {
+ return false
+ }
+ if strings.Contains(path, "//") {
+ return false
+ }
+ if path[len(path)-1] == '/' {
+ return false
+ }
+ elemStart := 0
+ for i, r := range path {
+ if r == '/' {
+ if !validImportPathElem(path[elemStart:i]) {
+ return false
+ }
+ elemStart = i + 1
+ }
+ }
+ return validImportPathElem(path[elemStart:])
+}
+
+func validImportPathElem(elem string) bool {
+ if elem == "" || elem[0] == '.' || elem[len(elem)-1] == '.' {
+ return false
+ }
+ for i := 0; i < len(elem); i++ {
+ if !importPathOK(elem[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+func importPathOK(c byte) bool {
+ // mask is a 128-bit bitmap with 1s for allowed bytes,
+ // so that the byte c can be tested with a shift and an and.
+ // If c > 128, then 1<<c and 1<<(c-64) will both be zero,
+ // and this function will return false.
+ const mask = 0 |
+ (1<<26-1)<<'A' |
+ (1<<26-1)<<'a' |
+ (1<<10-1)<<'0' |
+ 1<<'-' |
+ 1<<'.' |
+ 1<<'~' |
+ 1<<'_' |
+ 1<<'+'
+
+ return ((uint64(1)<<c)&(mask&(1<<64-1)) |
+ (uint64(1)<<(c-64))&(mask>>64)) != 0
+}
diff --git a/src/go/doc/comment/parse_test.go b/src/go/doc/comment/parse_test.go
new file mode 100644
index 0000000..bce733e
--- /dev/null
+++ b/src/go/doc/comment/parse_test.go
@@ -0,0 +1,12 @@
+// 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 comment
+
+import "testing"
+
+// See https://golang.org/issue/52353
+func Test52353(t *testing.T) {
+ ident("𫕐ﯯ")
+}
diff --git a/src/go/doc/comment/print.go b/src/go/doc/comment/print.go
new file mode 100644
index 0000000..e1c070d
--- /dev/null
+++ b/src/go/doc/comment/print.go
@@ -0,0 +1,288 @@
+// 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 comment
+
+import (
+ "bytes"
+ "fmt"
+ "strings"
+)
+
+// A Printer is a doc comment printer.
+// The fields in the struct can be filled in before calling
+// any of the printing methods
+// in order to customize the details of the printing process.
+type Printer struct {
+ // HeadingLevel is the nesting level used for
+ // HTML and Markdown headings.
+ // If HeadingLevel is zero, it defaults to level 3,
+ // meaning to use <h3> and ###.
+ HeadingLevel int
+
+ // HeadingID is a function that computes the heading ID
+ // (anchor tag) to use for the heading h when generating
+ // HTML and Markdown. If HeadingID returns an empty string,
+ // then the heading ID is omitted.
+ // If HeadingID is nil, h.DefaultID is used.
+ HeadingID func(h *Heading) string
+
+ // DocLinkURL is a function that computes the URL for the given DocLink.
+ // If DocLinkURL is nil, then link.DefaultURL(p.DocLinkBaseURL) is used.
+ DocLinkURL func(link *DocLink) string
+
+ // DocLinkBaseURL is used when DocLinkURL is nil,
+ // passed to [DocLink.DefaultURL] to construct a DocLink's URL.
+ // See that method's documentation for details.
+ DocLinkBaseURL string
+
+ // TextPrefix is a prefix to print at the start of every line
+ // when generating text output using the Text method.
+ TextPrefix string
+
+ // TextCodePrefix is the prefix to print at the start of each
+ // preformatted (code block) line when generating text output,
+ // instead of (not in addition to) TextPrefix.
+ // If TextCodePrefix is the empty string, it defaults to TextPrefix+"\t".
+ TextCodePrefix string
+
+ // TextWidth is the maximum width text line to generate,
+ // measured in Unicode code points,
+ // excluding TextPrefix and the newline character.
+ // If TextWidth is zero, it defaults to 80 minus the number of code points in TextPrefix.
+ // If TextWidth is negative, there is no limit.
+ TextWidth int
+}
+
+func (p *Printer) headingLevel() int {
+ if p.HeadingLevel <= 0 {
+ return 3
+ }
+ return p.HeadingLevel
+}
+
+func (p *Printer) headingID(h *Heading) string {
+ if p.HeadingID == nil {
+ return h.DefaultID()
+ }
+ return p.HeadingID(h)
+}
+
+func (p *Printer) docLinkURL(link *DocLink) string {
+ if p.DocLinkURL != nil {
+ return p.DocLinkURL(link)
+ }
+ return link.DefaultURL(p.DocLinkBaseURL)
+}
+
+// DefaultURL constructs and returns the documentation URL for l,
+// using baseURL as a prefix for links to other packages.
+//
+// The possible forms returned by DefaultURL are:
+// - baseURL/ImportPath, for a link to another package
+// - baseURL/ImportPath#Name, for a link to a const, func, type, or var in another package
+// - baseURL/ImportPath#Recv.Name, for a link to a method in another package
+// - #Name, for a link to a const, func, type, or var in this package
+// - #Recv.Name, for a link to a method in this package
+//
+// If baseURL ends in a trailing slash, then DefaultURL inserts
+// a slash between ImportPath and # in the anchored forms.
+// For example, here are some baseURL values and URLs they can generate:
+//
+// "/pkg/" → "/pkg/math/#Sqrt"
+// "/pkg" → "/pkg/math#Sqrt"
+// "/" → "/math/#Sqrt"
+// "" → "/math#Sqrt"
+func (l *DocLink) DefaultURL(baseURL string) string {
+ if l.ImportPath != "" {
+ slash := ""
+ if strings.HasSuffix(baseURL, "/") {
+ slash = "/"
+ } else {
+ baseURL += "/"
+ }
+ switch {
+ case l.Name == "":
+ return baseURL + l.ImportPath + slash
+ case l.Recv != "":
+ return baseURL + l.ImportPath + slash + "#" + l.Recv + "." + l.Name
+ default:
+ return baseURL + l.ImportPath + slash + "#" + l.Name
+ }
+ }
+ if l.Recv != "" {
+ return "#" + l.Recv + "." + l.Name
+ }
+ return "#" + l.Name
+}
+
+// DefaultID returns the default anchor ID for the heading h.
+//
+// The default anchor ID is constructed by converting every
+// rune that is not alphanumeric ASCII to an underscore
+// and then adding the prefix “hdr-”.
+// For example, if the heading text is “Go Doc Comments”,
+// the default ID is “hdr-Go_Doc_Comments”.
+func (h *Heading) DefaultID() string {
+ // Note: The “hdr-” prefix is important to avoid DOM clobbering attacks.
+ // See https://pkg.go.dev/github.com/google/safehtml#Identifier.
+ var out strings.Builder
+ var p textPrinter
+ p.oneLongLine(&out, h.Text)
+ s := strings.TrimSpace(out.String())
+ if s == "" {
+ return ""
+ }
+ out.Reset()
+ out.WriteString("hdr-")
+ for _, r := range s {
+ if r < 0x80 && isIdentASCII(byte(r)) {
+ out.WriteByte(byte(r))
+ } else {
+ out.WriteByte('_')
+ }
+ }
+ return out.String()
+}
+
+type commentPrinter struct {
+ *Printer
+}
+
+// Comment returns the standard Go formatting of the Doc,
+// without any comment markers.
+func (p *Printer) Comment(d *Doc) []byte {
+ cp := &commentPrinter{Printer: p}
+ var out bytes.Buffer
+ for i, x := range d.Content {
+ if i > 0 && blankBefore(x) {
+ out.WriteString("\n")
+ }
+ cp.block(&out, x)
+ }
+
+ // Print one block containing all the link definitions that were used,
+ // and then a second block containing all the unused ones.
+ // This makes it easy to clean up the unused ones: gofmt and
+ // delete the final block. And it's a nice visual signal without
+ // affecting the way the comment formats for users.
+ for i := 0; i < 2; i++ {
+ used := i == 0
+ first := true
+ for _, def := range d.Links {
+ if def.Used == used {
+ if first {
+ out.WriteString("\n")
+ first = false
+ }
+ out.WriteString("[")
+ out.WriteString(def.Text)
+ out.WriteString("]: ")
+ out.WriteString(def.URL)
+ out.WriteString("\n")
+ }
+ }
+ }
+
+ return out.Bytes()
+}
+
+// blankBefore reports whether the block x requires a blank line before it.
+// All blocks do, except for Lists that return false from x.BlankBefore().
+func blankBefore(x Block) bool {
+ if x, ok := x.(*List); ok {
+ return x.BlankBefore()
+ }
+ return true
+}
+
+// block prints the block x to out.
+func (p *commentPrinter) block(out *bytes.Buffer, x Block) {
+ switch x := x.(type) {
+ default:
+ fmt.Fprintf(out, "?%T", x)
+
+ case *Paragraph:
+ p.text(out, "", x.Text)
+ out.WriteString("\n")
+
+ case *Heading:
+ out.WriteString("# ")
+ p.text(out, "", x.Text)
+ out.WriteString("\n")
+
+ case *Code:
+ md := x.Text
+ for md != "" {
+ var line string
+ line, md, _ = strings.Cut(md, "\n")
+ if line != "" {
+ out.WriteString("\t")
+ out.WriteString(line)
+ }
+ out.WriteString("\n")
+ }
+
+ case *List:
+ loose := x.BlankBetween()
+ for i, item := range x.Items {
+ if i > 0 && loose {
+ out.WriteString("\n")
+ }
+ out.WriteString(" ")
+ if item.Number == "" {
+ out.WriteString(" - ")
+ } else {
+ out.WriteString(item.Number)
+ out.WriteString(". ")
+ }
+ for i, blk := range item.Content {
+ const fourSpace = " "
+ if i > 0 {
+ out.WriteString("\n" + fourSpace)
+ }
+ p.text(out, fourSpace, blk.(*Paragraph).Text)
+ out.WriteString("\n")
+ }
+ }
+ }
+}
+
+// text prints the text sequence x to out.
+func (p *commentPrinter) text(out *bytes.Buffer, indent string, x []Text) {
+ for _, t := range x {
+ switch t := t.(type) {
+ case Plain:
+ p.indent(out, indent, string(t))
+ case Italic:
+ p.indent(out, indent, string(t))
+ case *Link:
+ if t.Auto {
+ p.text(out, indent, t.Text)
+ } else {
+ out.WriteString("[")
+ p.text(out, indent, t.Text)
+ out.WriteString("]")
+ }
+ case *DocLink:
+ out.WriteString("[")
+ p.text(out, indent, t.Text)
+ out.WriteString("]")
+ }
+ }
+}
+
+// indent prints s to out, indenting with the indent string
+// after each newline in s.
+func (p *commentPrinter) indent(out *bytes.Buffer, indent, s string) {
+ for s != "" {
+ line, rest, ok := strings.Cut(s, "\n")
+ out.WriteString(line)
+ if ok {
+ out.WriteString("\n")
+ out.WriteString(indent)
+ }
+ s = rest
+ }
+}
diff --git a/src/go/doc/comment/std.go b/src/go/doc/comment/std.go
new file mode 100644
index 0000000..fd8c8ce
--- /dev/null
+++ b/src/go/doc/comment/std.go
@@ -0,0 +1,47 @@
+// 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.
+
+// Code generated by 'go generate' DO NOT EDIT.
+//go:generate ./mkstd.sh
+
+package comment
+
+var stdPkgs = []string{
+ "bufio",
+ "bytes",
+ "cmp",
+ "context",
+ "crypto",
+ "embed",
+ "encoding",
+ "errors",
+ "expvar",
+ "flag",
+ "fmt",
+ "hash",
+ "html",
+ "image",
+ "io",
+ "log",
+ "maps",
+ "math",
+ "mime",
+ "net",
+ "os",
+ "path",
+ "plugin",
+ "reflect",
+ "regexp",
+ "runtime",
+ "slices",
+ "sort",
+ "strconv",
+ "strings",
+ "sync",
+ "syscall",
+ "testing",
+ "time",
+ "unicode",
+ "unsafe",
+}
diff --git a/src/go/doc/comment/std_test.go b/src/go/doc/comment/std_test.go
new file mode 100644
index 0000000..89206e6
--- /dev/null
+++ b/src/go/doc/comment/std_test.go
@@ -0,0 +1,34 @@
+// 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 comment
+
+import (
+ "internal/diff"
+ "internal/testenv"
+ "sort"
+ "strings"
+ "testing"
+)
+
+func TestStd(t *testing.T) {
+ out, err := testenv.Command(t, testenv.GoToolPath(t), "list", "std").CombinedOutput()
+ if err != nil {
+ t.Fatalf("%v\n%s", err, out)
+ }
+
+ var list []string
+ for _, pkg := range strings.Fields(string(out)) {
+ if !strings.Contains(pkg, "/") {
+ list = append(list, pkg)
+ }
+ }
+ sort.Strings(list)
+
+ have := strings.Join(stdPkgs, "\n") + "\n"
+ want := strings.Join(list, "\n") + "\n"
+ if have != want {
+ t.Errorf("stdPkgs is out of date: regenerate with 'go generate'\n%s", diff.Diff("stdPkgs", []byte(have), "want", []byte(want)))
+ }
+}
diff --git a/src/go/doc/comment/testdata/README.md b/src/go/doc/comment/testdata/README.md
new file mode 100644
index 0000000..d6f2c54
--- /dev/null
+++ b/src/go/doc/comment/testdata/README.md
@@ -0,0 +1,42 @@
+This directory contains test files (*.txt) for the comment parser.
+
+The files are in [txtar format](https://pkg.go.dev/golang.org/x/tools/txtar).
+Consider this example:
+
+ -- input --
+ Hello.
+ -- gofmt --
+ Hello.
+ -- html --
+ <p>Hello.
+ -- markdown --
+ Hello.
+ -- text --
+ Hello.
+
+Each `-- name --` line introduces a new file with the given name.
+The file named “input” must be first and contains the input to
+[comment.Parser](https://pkg.go.dev/go/doc/comment/#Parser).
+
+The remaining files contain the expected output for the named format generated by
+[comment.Printer](https://pkg.go.dev/go/doc/comment/#Printer):
+“gofmt” for Printer.Comment (Go comment format, as used by gofmt),
+“html” for Printer.HTML, “markdown” for Printer.Markdown, and “text” for Printer.Text.
+The format can also be “dump” for a textual dump of the raw data structures.
+
+The text before the `-- input --` line, if present, is JSON to be unmarshalled
+to initialize a comment.Printer. For example, this test case sets the Printer's
+TextWidth field to 20:
+
+ {"TextWidth": 20}
+ -- input --
+ Package gob manages streams of gobs - binary values exchanged between an
+ Encoder (transmitter) and a Decoder (receiver).
+ -- text --
+ Package gob
+ manages streams
+ of gobs - binary
+ values exchanged
+ between an Encoder
+ (transmitter) and a
+ Decoder (receiver).
diff --git a/src/go/doc/comment/testdata/blank.txt b/src/go/doc/comment/testdata/blank.txt
new file mode 100644
index 0000000..9049fde
--- /dev/null
+++ b/src/go/doc/comment/testdata/blank.txt
@@ -0,0 +1,12 @@
+-- input --
+ $
+ Blank line at start and end.
+ $
+-- gofmt --
+Blank line at start and end.
+-- text --
+Blank line at start and end.
+-- markdown --
+Blank line at start and end.
+-- html --
+<p>Blank line at start and end.
diff --git a/src/go/doc/comment/testdata/code.txt b/src/go/doc/comment/testdata/code.txt
new file mode 100644
index 0000000..06b1519
--- /dev/null
+++ b/src/go/doc/comment/testdata/code.txt
@@ -0,0 +1,94 @@
+-- input --
+Text.
+ A tab-indented
+ (no, not eight-space indented)
+ code block and haiku.
+More text.
+ One space
+ is
+ enough
+ to
+ start
+ a
+ block.
+More text.
+
+ Blocks
+ can
+
+ have
+ blank
+ lines.
+-- gofmt --
+Text.
+
+ A tab-indented
+ (no, not eight-space indented)
+ code block and haiku.
+
+More text.
+
+ One space
+ is
+ enough
+ to
+ start
+ a
+ block.
+
+More text.
+
+ Blocks
+ can
+
+ have
+ blank
+ lines.
+-- markdown --
+Text.
+
+ A tab-indented
+ (no, not eight-space indented)
+ code block and haiku.
+
+More text.
+
+ One space
+ is
+ enough
+ to
+ start
+ a
+ block.
+
+More text.
+
+ Blocks
+ can
+
+ have
+ blank
+ lines.
+-- html --
+<p>Text.
+<pre>A tab-indented
+(no, not eight-space indented)
+code block and haiku.
+</pre>
+<p>More text.
+<pre>One space
+ is
+ enough
+ to
+ start
+ a
+ block.
+</pre>
+<p>More text.
+<pre> Blocks
+ can
+
+have
+ blank
+ lines.
+</pre>
diff --git a/src/go/doc/comment/testdata/code2.txt b/src/go/doc/comment/testdata/code2.txt
new file mode 100644
index 0000000..0810bed
--- /dev/null
+++ b/src/go/doc/comment/testdata/code2.txt
@@ -0,0 +1,31 @@
+-- input --
+Text.
+
+ A tab-indented
+ (no, not eight-space indented)
+ code block and haiku.
+
+More text.
+-- gofmt --
+Text.
+
+ A tab-indented
+ (no, not eight-space indented)
+ code block and haiku.
+
+More text.
+-- markdown --
+Text.
+
+ A tab-indented
+ (no, not eight-space indented)
+ code block and haiku.
+
+More text.
+-- html --
+<p>Text.
+<pre>A tab-indented
+(no, not eight-space indented)
+code block and haiku.
+</pre>
+<p>More text.
diff --git a/src/go/doc/comment/testdata/code3.txt b/src/go/doc/comment/testdata/code3.txt
new file mode 100644
index 0000000..4a96a0e
--- /dev/null
+++ b/src/go/doc/comment/testdata/code3.txt
@@ -0,0 +1,33 @@
+-- input --
+Text.
+
+ $
+ A tab-indented
+ (surrounded by more blank lines)
+ code block and haiku.
+ $
+
+More text.
+-- gofmt --
+Text.
+
+ A tab-indented
+ (surrounded by more blank lines)
+ code block and haiku.
+
+More text.
+-- markdown --
+Text.
+
+ A tab-indented
+ (surrounded by more blank lines)
+ code block and haiku.
+
+More text.
+-- html --
+<p>Text.
+<pre>A tab-indented
+(surrounded by more blank lines)
+code block and haiku.
+</pre>
+<p>More text.
diff --git a/src/go/doc/comment/testdata/code4.txt b/src/go/doc/comment/testdata/code4.txt
new file mode 100644
index 0000000..f128c9a
--- /dev/null
+++ b/src/go/doc/comment/testdata/code4.txt
@@ -0,0 +1,38 @@
+-- input --
+To test, run this command:
+ go test -more
+
+Or, to test specific things, run this command:
+
+go test -more \
+ -pkg first/package \
+ -pkg second/package \
+ -pkg third/package
+
+Happy testing!
+-- gofmt --
+To test, run this command:
+
+ go test -more
+
+Or, to test specific things, run this command:
+
+ go test -more \
+ -pkg first/package \
+ -pkg second/package \
+ -pkg third/package
+
+Happy testing!
+-- markdown --
+To test, run this command:
+
+ go test -more
+
+Or, to test specific things, run this command:
+
+ go test -more \
+ -pkg first/package \
+ -pkg second/package \
+ -pkg third/package
+
+Happy testing!
diff --git a/src/go/doc/comment/testdata/code5.txt b/src/go/doc/comment/testdata/code5.txt
new file mode 100644
index 0000000..0e340dd
--- /dev/null
+++ b/src/go/doc/comment/testdata/code5.txt
@@ -0,0 +1,21 @@
+-- input --
+L1
+L2
+L3
+L4
+L5
+- L6 {
+ L7
+}
+L8
+-- gofmt --
+L1
+L2
+L3
+L4
+L5
+ - L6 {
+ L7
+ }
+
+L8
diff --git a/src/go/doc/comment/testdata/code6.txt b/src/go/doc/comment/testdata/code6.txt
new file mode 100644
index 0000000..d2915d1
--- /dev/null
+++ b/src/go/doc/comment/testdata/code6.txt
@@ -0,0 +1,24 @@
+-- input --
+Run this program:
+
+func main() {
+ fmt.Println("hello, world")
+}
+
+Or this:
+
+go func() {
+ fmt.Println("hello, world")
+}()
+-- gofmt --
+Run this program:
+
+ func main() {
+ fmt.Println("hello, world")
+ }
+
+Or this:
+
+ go func() {
+ fmt.Println("hello, world")
+ }()
diff --git a/src/go/doc/comment/testdata/crash1.txt b/src/go/doc/comment/testdata/crash1.txt
new file mode 100644
index 0000000..6bb2f6f
--- /dev/null
+++ b/src/go/doc/comment/testdata/crash1.txt
@@ -0,0 +1,16 @@
+-- input --
+[]
+
+[]: http://
+-- gofmt --
+[]
+
+[]: http://
+-- html --
+<p><a href="http://"></a>
+-- markdown --
+[](http://)
+-- text --
+
+
+[]: http://
diff --git a/src/go/doc/comment/testdata/doclink.txt b/src/go/doc/comment/testdata/doclink.txt
new file mode 100644
index 0000000..a932347
--- /dev/null
+++ b/src/go/doc/comment/testdata/doclink.txt
@@ -0,0 +1,21 @@
+-- input --
+In this package, see [Doc] and [Parser.Parse].
+There is no [Undef] or [Undef.Method].
+See also the [comment] package,
+especially [comment.Doc] and [comment.Parser.Parse].
+-- gofmt --
+In this package, see [Doc] and [Parser.Parse].
+There is no [Undef] or [Undef.Method].
+See also the [comment] package,
+especially [comment.Doc] and [comment.Parser.Parse].
+-- text --
+In this package, see Doc and Parser.Parse. There is no [Undef] or
+[Undef.Method]. See also the comment package, especially comment.Doc and
+comment.Parser.Parse.
+-- markdown --
+In this package, see [Doc](#Doc) and [Parser.Parse](#Parser.Parse). There is no \[Undef] or \[Undef.Method]. See also the [comment](/go/doc/comment) package, especially [comment.Doc](/go/doc/comment#Doc) and [comment.Parser.Parse](/go/doc/comment#Parser.Parse).
+-- html --
+<p>In this package, see <a href="#Doc">Doc</a> and <a href="#Parser.Parse">Parser.Parse</a>.
+There is no [Undef] or [Undef.Method].
+See also the <a href="/go/doc/comment">comment</a> package,
+especially <a href="/go/doc/comment#Doc">comment.Doc</a> and <a href="/go/doc/comment#Parser.Parse">comment.Parser.Parse</a>.
diff --git a/src/go/doc/comment/testdata/doclink2.txt b/src/go/doc/comment/testdata/doclink2.txt
new file mode 100644
index 0000000..ecd8e4e
--- /dev/null
+++ b/src/go/doc/comment/testdata/doclink2.txt
@@ -0,0 +1,8 @@
+-- input --
+We use [io.Reader] a lot, and also a few map[io.Reader]string.
+
+Never [io.Reader]int or Slice[io.Reader] though.
+-- markdown --
+We use [io.Reader](/io#Reader) a lot, and also a few map\[io.Reader]string.
+
+Never \[io.Reader]int or Slice\[io.Reader] though.
diff --git a/src/go/doc/comment/testdata/doclink3.txt b/src/go/doc/comment/testdata/doclink3.txt
new file mode 100644
index 0000000..0ccfb3d
--- /dev/null
+++ b/src/go/doc/comment/testdata/doclink3.txt
@@ -0,0 +1,8 @@
+-- input --
+[encoding/json.Marshal] is a doc link.
+
+[rot13.Marshal] is not.
+-- markdown --
+[encoding/json.Marshal](/encoding/json#Marshal) is a doc link.
+
+\[rot13.Marshal] is not.
diff --git a/src/go/doc/comment/testdata/doclink4.txt b/src/go/doc/comment/testdata/doclink4.txt
new file mode 100644
index 0000000..c709527
--- /dev/null
+++ b/src/go/doc/comment/testdata/doclink4.txt
@@ -0,0 +1,7 @@
+-- input --
+[io] at start of comment.
+[io] at start of line.
+At end of line: [io]
+At end of comment: [io]
+-- markdown --
+[io](/io) at start of comment. [io](/io) at start of line. At end of line: [io](/io) At end of comment: [io](/io)
diff --git a/src/go/doc/comment/testdata/doclink5.txt b/src/go/doc/comment/testdata/doclink5.txt
new file mode 100644
index 0000000..ac7b3ae
--- /dev/null
+++ b/src/go/doc/comment/testdata/doclink5.txt
@@ -0,0 +1,5 @@
+{"DocLinkBaseURL": "https://pkg.go.dev"}
+-- input --
+[encoding/json.Marshal] is a doc link.
+-- markdown --
+[encoding/json.Marshal](https://pkg.go.dev/encoding/json#Marshal) is a doc link.
diff --git a/src/go/doc/comment/testdata/doclink6.txt b/src/go/doc/comment/testdata/doclink6.txt
new file mode 100644
index 0000000..1acd03b
--- /dev/null
+++ b/src/go/doc/comment/testdata/doclink6.txt
@@ -0,0 +1,5 @@
+{"DocLinkBaseURL": "https://go.dev/pkg/"}
+-- input --
+[encoding/json.Marshal] is a doc link, and so is [rsc.io/quote.NonExist].
+-- markdown --
+[encoding/json.Marshal](https://go.dev/pkg/encoding/json/#Marshal) is a doc link, and so is [rsc.io/quote.NonExist](https://go.dev/pkg/rsc.io/quote/#NonExist).
diff --git a/src/go/doc/comment/testdata/doclink7.txt b/src/go/doc/comment/testdata/doclink7.txt
new file mode 100644
index 0000000..d34979a
--- /dev/null
+++ b/src/go/doc/comment/testdata/doclink7.txt
@@ -0,0 +1,4 @@
+-- input --
+You see more [*bytes.Buffer] than [bytes.Buffer].
+-- markdown --
+You see more [\*bytes.Buffer](/bytes#Buffer) than [bytes.Buffer](/bytes#Buffer).
diff --git a/src/go/doc/comment/testdata/escape.txt b/src/go/doc/comment/testdata/escape.txt
new file mode 100644
index 0000000..f54663f
--- /dev/null
+++ b/src/go/doc/comment/testdata/escape.txt
@@ -0,0 +1,55 @@
+-- input --
+What the ~!@#$%^&*()_+-=`{}|[]\:";',./<>?
+
++ Line
+
+- Line
+
+* Line
+
+999. Line
+
+## Line
+-- gofmt --
+What the ~!@#$%^&*()_+-=`{}|[]\:";',./<>?
+
++ Line
+
+- Line
+
+* Line
+
+999. Line
+
+## Line
+-- text --
+What the ~!@#$%^&*()_+-=`{}|[]\:";',./<>?
+
++ Line
+
+- Line
+
+* Line
+
+999. Line
+
+## Line
+-- markdown --
+What the ~!@#$%^&\*()\_+-=\`{}|\[]\\:";',./\<>?
+
+\+ Line
+
+\- Line
+
+\* Line
+
+999\. Line
+
+\## Line
+-- html --
+<p>What the ~!@#$%^&amp;*()_+-=`{}|[]\:&quot;;&apos;,./&lt;&gt;?
+<p>+ Line
+<p>- Line
+<p>* Line
+<p>999. Line
+<p>## Line
diff --git a/src/go/doc/comment/testdata/head.txt b/src/go/doc/comment/testdata/head.txt
new file mode 100644
index 0000000..b99a8c5
--- /dev/null
+++ b/src/go/doc/comment/testdata/head.txt
@@ -0,0 +1,92 @@
+-- input --
+Some text.
+
+An Old Heading
+
+Not An Old Heading.
+
+And some text.
+
+# A New Heading.
+
+And some more text.
+
+# Not a heading,
+because text follows it.
+
+Because text precedes it,
+# not a heading.
+
+## Not a heading either.
+
+-- gofmt --
+Some text.
+
+# An Old Heading
+
+Not An Old Heading.
+
+And some text.
+
+# A New Heading.
+
+And some more text.
+
+# Not a heading,
+because text follows it.
+
+Because text precedes it,
+# not a heading.
+
+## Not a heading either.
+
+-- text --
+Some text.
+
+# An Old Heading
+
+Not An Old Heading.
+
+And some text.
+
+# A New Heading.
+
+And some more text.
+
+# Not a heading, because text follows it.
+
+Because text precedes it, # not a heading.
+
+## Not a heading either.
+
+-- markdown --
+Some text.
+
+### An Old Heading {#hdr-An_Old_Heading}
+
+Not An Old Heading.
+
+And some text.
+
+### A New Heading. {#hdr-A_New_Heading_}
+
+And some more text.
+
+\# Not a heading, because text follows it.
+
+Because text precedes it, # not a heading.
+
+\## Not a heading either.
+
+-- html --
+<p>Some text.
+<h3 id="hdr-An_Old_Heading">An Old Heading</h3>
+<p>Not An Old Heading.
+<p>And some text.
+<h3 id="hdr-A_New_Heading_">A New Heading.</h3>
+<p>And some more text.
+<p># Not a heading,
+because text follows it.
+<p>Because text precedes it,
+# not a heading.
+<p>## Not a heading either.
diff --git a/src/go/doc/comment/testdata/head2.txt b/src/go/doc/comment/testdata/head2.txt
new file mode 100644
index 0000000..d357632
--- /dev/null
+++ b/src/go/doc/comment/testdata/head2.txt
@@ -0,0 +1,36 @@
+-- input --
+✦
+
+Almost a+heading
+
+✦
+
+Don't be a heading
+
+✦
+
+A.b is a heading
+
+✦
+
+A. b is not a heading
+
+✦
+-- gofmt --
+✦
+
+Almost a+heading
+
+✦
+
+Don't be a heading
+
+✦
+
+# A.b is a heading
+
+✦
+
+A. b is not a heading
+
+✦
diff --git a/src/go/doc/comment/testdata/head3.txt b/src/go/doc/comment/testdata/head3.txt
new file mode 100644
index 0000000..dbb7cb3
--- /dev/null
+++ b/src/go/doc/comment/testdata/head3.txt
@@ -0,0 +1,7 @@
+{"HeadingLevel": 5}
+-- input --
+# Heading
+-- markdown --
+##### Heading {#hdr-Heading}
+-- html --
+<h5 id="hdr-Heading">Heading</h5>
diff --git a/src/go/doc/comment/testdata/hello.txt b/src/go/doc/comment/testdata/hello.txt
new file mode 100644
index 0000000..fb07f1e
--- /dev/null
+++ b/src/go/doc/comment/testdata/hello.txt
@@ -0,0 +1,35 @@
+-- input --
+ Hello,
+ world
+
+ This is
+ a test.
+-- dump --
+Doc
+ Paragraph
+ Plain
+ "Hello,\n"
+ "world"
+ Paragraph
+ Plain
+ "This is\n"
+ "a test."
+-- gofmt --
+Hello,
+world
+
+This is
+a test.
+-- html --
+<p>Hello,
+world
+<p>This is
+a test.
+-- markdown --
+Hello, world
+
+This is a test.
+-- text --
+Hello, world
+
+This is a test.
diff --git a/src/go/doc/comment/testdata/link.txt b/src/go/doc/comment/testdata/link.txt
new file mode 100644
index 0000000..551e306
--- /dev/null
+++ b/src/go/doc/comment/testdata/link.txt
@@ -0,0 +1,17 @@
+-- input --
+The Go home page is https://go.dev/.
+It used to be https://golang.org.
+
+-- gofmt --
+The Go home page is https://go.dev/.
+It used to be https://golang.org.
+
+-- text --
+The Go home page is https://go.dev/. It used to be https://golang.org.
+
+-- markdown --
+The Go home page is [https://go.dev/](https://go.dev/). It used to be [https://golang.org](https://golang.org).
+
+-- html --
+<p>The Go home page is <a href="https://go.dev/">https://go.dev/</a>.
+It used to be <a href="https://golang.org">https://golang.org</a>.
diff --git a/src/go/doc/comment/testdata/link2.txt b/src/go/doc/comment/testdata/link2.txt
new file mode 100644
index 0000000..8637a32
--- /dev/null
+++ b/src/go/doc/comment/testdata/link2.txt
@@ -0,0 +1,31 @@
+-- input --
+The Go home page is https://go.dev/.
+It used to be https://golang.org.
+https:// is not a link.
+Nor is https://
+https://☺ is not a link.
+https://:80 is not a link.
+
+-- gofmt --
+The Go home page is https://go.dev/.
+It used to be https://golang.org.
+https:// is not a link.
+Nor is https://
+https://☺ is not a link.
+https://:80 is not a link.
+
+-- text --
+The Go home page is https://go.dev/. It used to be https://golang.org. https://
+is not a link. Nor is https:// https://☺ is not a link. https://:80 is not a
+link.
+
+-- markdown --
+The Go home page is [https://go.dev/](https://go.dev/). It used to be [https://golang.org](https://golang.org). https:// is not a link. Nor is https:// https://☺ is not a link. https://:80 is not a link.
+
+-- html --
+<p>The Go home page is <a href="https://go.dev/">https://go.dev/</a>.
+It used to be <a href="https://golang.org">https://golang.org</a>.
+https:// is not a link.
+Nor is https://
+https://☺ is not a link.
+https://:80 is not a link.
diff --git a/src/go/doc/comment/testdata/link3.txt b/src/go/doc/comment/testdata/link3.txt
new file mode 100644
index 0000000..5a115b5
--- /dev/null
+++ b/src/go/doc/comment/testdata/link3.txt
@@ -0,0 +1,14 @@
+-- input --
+Doc text.
+
+[Go home page]: https://go.dev
+-- gofmt --
+Doc text.
+
+[Go home page]: https://go.dev
+-- text --
+Doc text.
+-- markdown --
+Doc text.
+-- html --
+<p>Doc text.
diff --git a/src/go/doc/comment/testdata/link4.txt b/src/go/doc/comment/testdata/link4.txt
new file mode 100644
index 0000000..75f194c
--- /dev/null
+++ b/src/go/doc/comment/testdata/link4.txt
@@ -0,0 +1,77 @@
+-- input --
+These are not links.
+
+[x
+
+[x]:
+
+[x]:https://go.dev
+
+[x]https://go.dev
+
+[x]: surprise://go.dev
+
+[x]: surprise!
+
+But this is, with a tab (although it's unused).
+
+[z]: https://go.dev
+-- gofmt --
+These are not links.
+
+[x
+
+[x]:
+
+[x]:https://go.dev
+
+[x]https://go.dev
+
+[x]: surprise://go.dev
+
+[x]: surprise!
+
+But this is, with a tab (although it's unused).
+
+[z]: https://go.dev
+-- text --
+These are not links.
+
+[x
+
+[x]:
+
+[x]:https://go.dev
+
+[x]https://go.dev
+
+[x]: surprise://go.dev
+
+[x]: surprise!
+
+But this is, with a tab (although it's unused).
+-- markdown --
+These are not links.
+
+\[x
+
+\[x]:
+
+\[x]:[https://go.dev](https://go.dev)
+
+\[x][https://go.dev](https://go.dev)
+
+\[x]: surprise://go.dev
+
+\[x]: surprise!
+
+But this is, with a tab (although it's unused).
+-- html --
+<p>These are not links.
+<p>[x
+<p>[x]:
+<p>[x]:<a href="https://go.dev">https://go.dev</a>
+<p>[x]<a href="https://go.dev">https://go.dev</a>
+<p>[x]: surprise://go.dev
+<p>[x]: surprise!
+<p>But this is, with a tab (although it&apos;s unused).
diff --git a/src/go/doc/comment/testdata/link5.txt b/src/go/doc/comment/testdata/link5.txt
new file mode 100644
index 0000000..b4fb588
--- /dev/null
+++ b/src/go/doc/comment/testdata/link5.txt
@@ -0,0 +1,36 @@
+-- input --
+See the [Go home page] and the [pkg
+site].
+
+[Go home page]: https://go.dev/
+[pkg site]: https://pkg.go.dev
+[Go home page]: https://duplicate.ignored
+
+They're really great!
+
+-- gofmt --
+See the [Go home page] and the [pkg
+site].
+
+They're really great!
+
+[Go home page]: https://go.dev/
+[pkg site]: https://pkg.go.dev
+
+[Go home page]: https://duplicate.ignored
+
+-- text --
+See the Go home page and the pkg site.
+
+They're really great!
+
+[Go home page]: https://go.dev/
+[pkg site]: https://pkg.go.dev
+-- markdown --
+See the [Go home page](https://go.dev/) and the [pkg site](https://pkg.go.dev).
+
+They're really great!
+-- html --
+<p>See the <a href="https://go.dev/">Go home page</a> and the <a href="https://pkg.go.dev">pkg
+site</a>.
+<p>They&apos;re really great!
diff --git a/src/go/doc/comment/testdata/link6.txt b/src/go/doc/comment/testdata/link6.txt
new file mode 100644
index 0000000..ff629b4
--- /dev/null
+++ b/src/go/doc/comment/testdata/link6.txt
@@ -0,0 +1,50 @@
+-- input --
+URLs with punctuation are hard.
+We don't want to consume the end-of-sentence punctuation.
+
+For example, https://en.wikipedia.org/wiki/John_Adams_(miniseries).
+And https://example.com/[foo]/bar{.
+And https://example.com/(foo)/bar!
+And https://example.com/{foo}/bar{.
+And https://example.com/)baz{foo}.
+
+[And https://example.com/].
+
+-- gofmt --
+URLs with punctuation are hard.
+We don't want to consume the end-of-sentence punctuation.
+
+For example, https://en.wikipedia.org/wiki/John_Adams_(miniseries).
+And https://example.com/[foo]/bar{.
+And https://example.com/(foo)/bar!
+And https://example.com/{foo}/bar{.
+And https://example.com/)baz{foo}.
+
+[And https://example.com/].
+
+-- text --
+URLs with punctuation are hard. We don't want to consume the end-of-sentence
+punctuation.
+
+For example, https://en.wikipedia.org/wiki/John_Adams_(miniseries).
+And https://example.com/[foo]/bar{. And https://example.com/(foo)/bar! And
+https://example.com/{foo}/bar{. And https://example.com/)baz{foo}.
+
+[And https://example.com/].
+
+-- markdown --
+URLs with punctuation are hard. We don't want to consume the end-of-sentence punctuation.
+
+For example, [https://en.wikipedia.org/wiki/John\_Adams\_(miniseries)](https://en.wikipedia.org/wiki/John_Adams_(miniseries)). And [https://example.com/\[foo]/bar](https://example.com/[foo]/bar){. And [https://example.com/(foo)/bar](https://example.com/(foo)/bar)! And [https://example.com/{foo}/bar](https://example.com/{foo}/bar){. And [https://example.com/](https://example.com/))baz{foo}.
+
+\[And [https://example.com/](https://example.com/)].
+
+-- html --
+<p>URLs with punctuation are hard.
+We don&apos;t want to consume the end-of-sentence punctuation.
+<p>For example, <a href="https://en.wikipedia.org/wiki/John_Adams_(miniseries)">https://en.wikipedia.org/wiki/John_Adams_(miniseries)</a>.
+And <a href="https://example.com/[foo]/bar">https://example.com/[foo]/bar</a>{.
+And <a href="https://example.com/(foo)/bar">https://example.com/(foo)/bar</a>!
+And <a href="https://example.com/{foo}/bar">https://example.com/{foo}/bar</a>{.
+And <a href="https://example.com/">https://example.com/</a>)baz{foo}.
+<p>[And <a href="https://example.com/">https://example.com/</a>].
diff --git a/src/go/doc/comment/testdata/link7.txt b/src/go/doc/comment/testdata/link7.txt
new file mode 100644
index 0000000..89a8b31
--- /dev/null
+++ b/src/go/doc/comment/testdata/link7.txt
@@ -0,0 +1,25 @@
+-- input --
+[math] is a package but this is not a doc link.
+
+[io] is a doc link.
+
+[math]: https://example.com
+-- gofmt --
+[math] is a package but this is not a doc link.
+
+[io] is a doc link.
+
+[math]: https://example.com
+-- text --
+math is a package but this is not a doc link.
+
+io is a doc link.
+
+[math]: https://example.com
+-- markdown --
+[math](https://example.com) is a package but this is not a doc link.
+
+[io](/io) is a doc link.
+-- html --
+<p><a href="https://example.com">math</a> is a package but this is not a doc link.
+<p><a href="/io">io</a> is a doc link.
diff --git a/src/go/doc/comment/testdata/linklist.txt b/src/go/doc/comment/testdata/linklist.txt
new file mode 100644
index 0000000..baf4062
--- /dev/null
+++ b/src/go/doc/comment/testdata/linklist.txt
@@ -0,0 +1,18 @@
+{"DocLinkBaseURL": "https://pkg.go.dev"}
+-- input --
+Did you know?
+
+ - [encoding/json.Marshal] is a doc link. So is [encoding/json.Unmarshal].
+-- text --
+Did you know?
+
+ - encoding/json.Marshal is a doc link. So is encoding/json.Unmarshal.
+-- markdown --
+Did you know?
+
+ - [encoding/json.Marshal](https://pkg.go.dev/encoding/json#Marshal) is a doc link. So is [encoding/json.Unmarshal](https://pkg.go.dev/encoding/json#Unmarshal).
+-- html --
+<p>Did you know?
+<ul>
+<li><a href="https://pkg.go.dev/encoding/json#Marshal">encoding/json.Marshal</a> is a doc link. So is <a href="https://pkg.go.dev/encoding/json#Unmarshal">encoding/json.Unmarshal</a>.
+</ul>
diff --git a/src/go/doc/comment/testdata/linklist2.txt b/src/go/doc/comment/testdata/linklist2.txt
new file mode 100644
index 0000000..81b3061
--- /dev/null
+++ b/src/go/doc/comment/testdata/linklist2.txt
@@ -0,0 +1,39 @@
+{"DocLinkBaseURL": "https://pkg.go.dev"}
+-- input --
+Did you know?
+
+ - [testing.T] is one doc link.
+ - So is [testing.M].
+ - So is [testing.B].
+ This is the same list paragraph.
+
+ There is [testing.PB] in this list item, too!
+-- text --
+Did you know?
+
+ - testing.T is one doc link.
+
+ - So is testing.M.
+
+ - So is testing.B. This is the same list paragraph.
+
+ There is testing.PB in this list item, too!
+-- markdown --
+Did you know?
+
+ - [testing.T](https://pkg.go.dev/testing#T) is one doc link.
+
+ - So is [testing.M](https://pkg.go.dev/testing#M).
+
+ - So is [testing.B](https://pkg.go.dev/testing#B). This is the same list paragraph.
+
+ There is [testing.PB](https://pkg.go.dev/testing#PB) in this list item, too!
+-- html --
+<p>Did you know?
+<ul>
+<li><p><a href="https://pkg.go.dev/testing#T">testing.T</a> is one doc link.
+<li><p>So is <a href="https://pkg.go.dev/testing#M">testing.M</a>.
+<li><p>So is <a href="https://pkg.go.dev/testing#B">testing.B</a>.
+This is the same list paragraph.
+<p>There is <a href="https://pkg.go.dev/testing#PB">testing.PB</a> in this list item, too!
+</ul>
diff --git a/src/go/doc/comment/testdata/linklist3.txt b/src/go/doc/comment/testdata/linklist3.txt
new file mode 100644
index 0000000..701a54e
--- /dev/null
+++ b/src/go/doc/comment/testdata/linklist3.txt
@@ -0,0 +1,31 @@
+{"DocLinkBaseURL": "https://pkg.go.dev"}
+-- input --
+Cool things:
+
+ - Foo
+ - [Go]
+ - Bar
+
+[Go]: https://go.dev/
+-- text --
+Cool things:
+
+ - Foo
+ - Go
+ - Bar
+
+[Go]: https://go.dev/
+-- markdown --
+Cool things:
+
+ - Foo
+ - [Go](https://go.dev/)
+ - Bar
+
+-- html --
+<p>Cool things:
+<ul>
+<li>Foo
+<li><a href="https://go.dev/">Go</a>
+<li>Bar
+</ul>
diff --git a/src/go/doc/comment/testdata/linklist4.txt b/src/go/doc/comment/testdata/linklist4.txt
new file mode 100644
index 0000000..db39ec4
--- /dev/null
+++ b/src/go/doc/comment/testdata/linklist4.txt
@@ -0,0 +1,36 @@
+{"DocLinkBaseURL": "https://pkg.go.dev"}
+-- input --
+Cool things:
+
+ - Foo
+ - [Go] is great
+
+ [Go]: https://go.dev/
+ - Bar
+
+-- text --
+Cool things:
+
+ - Foo
+
+ - Go is great
+
+ - Bar
+
+[Go]: https://go.dev/
+-- markdown --
+Cool things:
+
+ - Foo
+
+ - [Go](https://go.dev/) is great
+
+ - Bar
+
+-- html --
+<p>Cool things:
+<ul>
+<li><p>Foo
+<li><p><a href="https://go.dev/">Go</a> is great
+<li><p>Bar
+</ul>
diff --git a/src/go/doc/comment/testdata/list.txt b/src/go/doc/comment/testdata/list.txt
new file mode 100644
index 0000000..455782f
--- /dev/null
+++ b/src/go/doc/comment/testdata/list.txt
@@ -0,0 +1,48 @@
+-- input --
+Text.
+- Not a list.
+ - Here is the list.
+ • Using multiple bullets.
+ * Indentation does not matter.
+ + Lots of bullets.
+More text.
+
+-- gofmt --
+Text.
+- Not a list.
+ - Here is the list.
+ - Using multiple bullets.
+ - Indentation does not matter.
+ - Lots of bullets.
+
+More text.
+
+-- text --
+Text. - Not a list.
+ - Here is the list.
+ - Using multiple bullets.
+ - Indentation does not matter.
+ - Lots of bullets.
+
+More text.
+
+-- markdown --
+Text. - Not a list.
+
+ - Here is the list.
+ - Using multiple bullets.
+ - Indentation does not matter.
+ - Lots of bullets.
+
+More text.
+
+-- html --
+<p>Text.
+- Not a list.
+<ul>
+<li>Here is the list.
+<li>Using multiple bullets.
+<li>Indentation does not matter.
+<li>Lots of bullets.
+</ul>
+<p>More text.
diff --git a/src/go/doc/comment/testdata/list10.txt b/src/go/doc/comment/testdata/list10.txt
new file mode 100644
index 0000000..9c49083
--- /dev/null
+++ b/src/go/doc/comment/testdata/list10.txt
@@ -0,0 +1,13 @@
+-- input --
+
+ 1. This list
+ 2. Starts the comment
+ 3. And also has a blank line before it.
+
+All of which is a little weird.
+-- gofmt --
+ 1. This list
+ 2. Starts the comment
+ 3. And also has a blank line before it.
+
+All of which is a little weird.
diff --git a/src/go/doc/comment/testdata/list2.txt b/src/go/doc/comment/testdata/list2.txt
new file mode 100644
index 0000000..c390b3d
--- /dev/null
+++ b/src/go/doc/comment/testdata/list2.txt
@@ -0,0 +1,57 @@
+-- input --
+Text.
+ 1. Uno
+ 2) Dos
+ 3. Tres
+ 5. Cinco
+ 7. Siete
+ 11. Once
+ 12. Doce
+ 13. Trece.
+
+-- gofmt --
+Text.
+ 1. Uno
+ 2. Dos
+ 3. Tres
+ 5. Cinco
+ 7. Siete
+ 11. Once
+ 12. Doce
+ 13. Trece.
+
+-- text --
+Text.
+ 1. Uno
+ 2. Dos
+ 3. Tres
+ 5. Cinco
+ 7. Siete
+ 11. Once
+ 12. Doce
+ 13. Trece.
+
+-- markdown --
+Text.
+
+ 1. Uno
+ 2. Dos
+ 3. Tres
+ 5. Cinco
+ 7. Siete
+ 11. Once
+ 12. Doce
+ 13. Trece.
+
+-- html --
+<p>Text.
+<ol>
+<li>Uno
+<li>Dos
+<li>Tres
+<li value="5">Cinco
+<li value="7">Siete
+<li value="11">Once
+<li>Doce
+<li>Trece.
+</ol>
diff --git a/src/go/doc/comment/testdata/list3.txt b/src/go/doc/comment/testdata/list3.txt
new file mode 100644
index 0000000..d7d345d
--- /dev/null
+++ b/src/go/doc/comment/testdata/list3.txt
@@ -0,0 +1,32 @@
+-- input --
+Text.
+
+ 1. Uno
+ 1. Dos
+ 1. Tres
+ 1. Quatro
+
+-- gofmt --
+Text.
+
+ 1. Uno
+ 1. Dos
+ 1. Tres
+ 1. Quatro
+
+-- markdown --
+Text.
+
+ 1. Uno
+ 1. Dos
+ 1. Tres
+ 1. Quatro
+
+-- html --
+<p>Text.
+<ol>
+<li>Uno
+<li value="1">Dos
+<li value="1">Tres
+<li value="1">Quatro
+</ol>
diff --git a/src/go/doc/comment/testdata/list4.txt b/src/go/doc/comment/testdata/list4.txt
new file mode 100644
index 0000000..9c28d65
--- /dev/null
+++ b/src/go/doc/comment/testdata/list4.txt
@@ -0,0 +1,38 @@
+-- input --
+Text.
+ 1. List
+2. Not indented, not a list.
+ 3. Another list.
+
+-- gofmt --
+Text.
+ 1. List
+
+2. Not indented, not a list.
+ 3. Another list.
+
+-- text --
+Text.
+ 1. List
+
+2. Not indented, not a list.
+ 3. Another list.
+
+-- markdown --
+Text.
+
+ 1. List
+
+2\. Not indented, not a list.
+
+ 3. Another list.
+
+-- html --
+<p>Text.
+<ol>
+<li>List
+</ol>
+<p>2. Not indented, not a list.
+<ol>
+<li value="3">Another list.
+</ol>
diff --git a/src/go/doc/comment/testdata/list5.txt b/src/go/doc/comment/testdata/list5.txt
new file mode 100644
index 0000000..a5128e5
--- /dev/null
+++ b/src/go/doc/comment/testdata/list5.txt
@@ -0,0 +1,40 @@
+-- input --
+Text.
+
+ 1. One
+ 999999999999999999999. Big
+ 1000000000000000000000. Bigger
+ 1000000000000000000001. Biggest
+
+-- gofmt --
+Text.
+
+ 1. One
+ 999999999999999999999. Big
+ 1000000000000000000000. Bigger
+ 1000000000000000000001. Biggest
+
+-- text --
+Text.
+
+ 1. One
+ 999999999999999999999. Big
+ 1000000000000000000000. Bigger
+ 1000000000000000000001. Biggest
+
+-- markdown --
+Text.
+
+ 1. One
+ 999999999999999999999. Big
+ 1000000000000000000000. Bigger
+ 1000000000000000000001. Biggest
+
+-- html --
+<p>Text.
+<ol>
+<li>One
+<li value="999999999999999999999">Big
+<li>Bigger
+<li>Biggest
+</ol>
diff --git a/src/go/doc/comment/testdata/list6.txt b/src/go/doc/comment/testdata/list6.txt
new file mode 100644
index 0000000..ffc0122
--- /dev/null
+++ b/src/go/doc/comment/testdata/list6.txt
@@ -0,0 +1,129 @@
+-- input --
+Text.
+ - List immediately after.
+ - Another.
+
+More text.
+
+ - List after blank line.
+ - Another.
+
+Even more text.
+ - List immediately after.
+
+ - Blank line between items.
+
+Yet more text.
+
+ - Another list after blank line.
+
+ - Blank line between items.
+
+Still more text.
+ - One list item.
+
+ Multiple paragraphs.
+-- dump --
+Doc
+ Paragraph
+ Plain "Text."
+ List ForceBlankBefore=false ForceBlankBetween=false
+ Item Number=""
+ Paragraph
+ Plain "List immediately after."
+ Item Number=""
+ Paragraph
+ Plain "Another."
+ Paragraph
+ Plain "More text."
+ List ForceBlankBefore=true ForceBlankBetween=false
+ Item Number=""
+ Paragraph
+ Plain "List after blank line."
+ Item Number=""
+ Paragraph
+ Plain "Another."
+ Paragraph
+ Plain "Even more text."
+ List ForceBlankBefore=false ForceBlankBetween=true
+ Item Number=""
+ Paragraph
+ Plain "List immediately after."
+ Item Number=""
+ Paragraph
+ Plain "Blank line between items."
+ Paragraph
+ Plain "Yet more text."
+ List ForceBlankBefore=true ForceBlankBetween=true
+ Item Number=""
+ Paragraph
+ Plain "Another list after blank line."
+ Item Number=""
+ Paragraph
+ Plain "Blank line between items."
+ Paragraph
+ Plain "Still more text."
+ List ForceBlankBefore=false ForceBlankBetween=true
+ Item Number=""
+ Paragraph
+ Plain "One list item."
+ Paragraph
+ Plain "Multiple paragraphs."
+
+-- gofmt --
+Text.
+ - List immediately after.
+ - Another.
+
+More text.
+
+ - List after blank line.
+ - Another.
+
+Even more text.
+
+ - List immediately after.
+
+ - Blank line between items.
+
+Yet more text.
+
+ - Another list after blank line.
+
+ - Blank line between items.
+
+Still more text.
+
+ - One list item.
+
+ Multiple paragraphs.
+
+-- markdown --
+Text.
+
+ - List immediately after.
+ - Another.
+
+More text.
+
+ - List after blank line.
+ - Another.
+
+Even more text.
+
+ - List immediately after.
+
+ - Blank line between items.
+
+Yet more text.
+
+ - Another list after blank line.
+
+ - Blank line between items.
+
+Still more text.
+
+ - One list item.
+
+ Multiple paragraphs.
+
diff --git a/src/go/doc/comment/testdata/list7.txt b/src/go/doc/comment/testdata/list7.txt
new file mode 100644
index 0000000..4466050
--- /dev/null
+++ b/src/go/doc/comment/testdata/list7.txt
@@ -0,0 +1,98 @@
+-- input --
+Almost list markers (but not quite):
+
+ -
+
+❦
+
+ - $
+
+❦
+
+ - $
+
+❦
+
+ $
+ $
+
+❦
+
+ 1! List.
+
+❦
+-- gofmt --
+Almost list markers (but not quite):
+
+ -
+
+❦
+
+ - $
+
+❦
+
+ - $
+
+❦
+
+❦
+
+ 1! List.
+
+❦
+-- text --
+Almost list markers (but not quite):
+
+ -
+
+❦
+
+ -
+
+❦
+
+ -
+
+❦
+
+❦
+
+ 1! List.
+
+❦
+-- markdown --
+Almost list markers (but not quite):
+
+ -
+
+❦
+
+ - $
+
+❦
+
+ - $
+
+❦
+
+❦
+
+ 1! List.
+
+❦
+-- html --
+<p>Almost list markers (but not quite):
+<pre>-
+</pre>
+<p>❦
+<pre>- $
+</pre>
+<p>❦
+<pre>- $
+</pre>
+<p>❦
+<p>❦
+<pre>1! List.
+</pre>
+<p>❦
diff --git a/src/go/doc/comment/testdata/list8.txt b/src/go/doc/comment/testdata/list8.txt
new file mode 100644
index 0000000..fc46b0d
--- /dev/null
+++ b/src/go/doc/comment/testdata/list8.txt
@@ -0,0 +1,56 @@
+-- input --
+Loose lists.
+ - A
+
+ B
+ - C
+ D
+ - E
+ - F
+-- gofmt --
+Loose lists.
+
+ - A
+
+ B
+
+ - C
+ D
+
+ - E
+
+ - F
+-- text --
+Loose lists.
+
+ - A
+
+ B
+
+ - C D
+
+ - E
+
+ - F
+-- markdown --
+Loose lists.
+
+ - A
+
+ B
+
+ - C D
+
+ - E
+
+ - F
+-- html --
+<p>Loose lists.
+<ul>
+<li><p>A
+<p>B
+<li><p>C
+D
+<li><p>E
+<li><p>F
+</ul>
diff --git a/src/go/doc/comment/testdata/list9.txt b/src/go/doc/comment/testdata/list9.txt
new file mode 100644
index 0000000..48e4673
--- /dev/null
+++ b/src/go/doc/comment/testdata/list9.txt
@@ -0,0 +1,30 @@
+-- input --
+Text.
+
+1. Not a list
+2. because it is
+3. unindented.
+
+4. This one
+ is a list
+ because of the indented text.
+5. More wrapped
+ items.
+6. And unwrapped.
+
+7. The blank line stops the heuristic.
+-- gofmt --
+Text.
+
+1. Not a list
+2. because it is
+3. unindented.
+
+ 4. This one
+ is a list
+ because of the indented text.
+ 5. More wrapped
+ items.
+ 6. And unwrapped.
+
+7. The blank line stops the heuristic.
diff --git a/src/go/doc/comment/testdata/para.txt b/src/go/doc/comment/testdata/para.txt
new file mode 100644
index 0000000..2355fa8
--- /dev/null
+++ b/src/go/doc/comment/testdata/para.txt
@@ -0,0 +1,17 @@
+-- input --
+Hello, world.
+This is a paragraph.
+
+-- gofmt --
+Hello, world.
+This is a paragraph.
+
+-- text --
+Hello, world. This is a paragraph.
+
+-- markdown --
+Hello, world. This is a paragraph.
+
+-- html --
+<p>Hello, world.
+This is a paragraph.
diff --git a/src/go/doc/comment/testdata/quote.txt b/src/go/doc/comment/testdata/quote.txt
new file mode 100644
index 0000000..b64adae
--- /dev/null
+++ b/src/go/doc/comment/testdata/quote.txt
@@ -0,0 +1,15 @@
+-- input --
+Doubled single quotes like `` and '' turn into Unicode double quotes,
+but single quotes ` and ' do not.
+Misplaced markdown fences ``` do not either.
+-- gofmt --
+Doubled single quotes like “ and ” turn into Unicode double quotes,
+but single quotes ` and ' do not.
+Misplaced markdown fences ``` do not either.
+-- text --
+Doubled single quotes like “ and ” turn into Unicode double quotes, but single
+quotes ` and ' do not. Misplaced markdown fences ``` do not either.
+-- html --
+<p>Doubled single quotes like “ and ” turn into Unicode double quotes,
+but single quotes ` and &apos; do not.
+Misplaced markdown fences ``` do not either.
diff --git a/src/go/doc/comment/testdata/text.txt b/src/go/doc/comment/testdata/text.txt
new file mode 100644
index 0000000..c4de6e2
--- /dev/null
+++ b/src/go/doc/comment/testdata/text.txt
@@ -0,0 +1,62 @@
+{"TextPrefix":"|", "TextCodePrefix": "@"}
+-- input --
+Hello, world
+ Code block here.
+More text.
+Tight list
+ - one
+ - two
+ - three
+Loose list
+ - one
+
+ - two
+
+ - three
+
+# Heading
+
+More text.
+-- gofmt --
+Hello, world
+
+ Code block here.
+
+More text.
+Tight list
+ - one
+ - two
+ - three
+
+Loose list
+
+ - one
+
+ - two
+
+ - three
+
+# Heading
+
+More text.
+-- text --
+|Hello, world
+|
+@Code block here.
+|
+|More text. Tight list
+| - one
+| - two
+| - three
+|
+|Loose list
+|
+| - one
+|
+| - two
+|
+| - three
+|
+|# Heading
+|
+|More text.
diff --git a/src/go/doc/comment/testdata/text2.txt b/src/go/doc/comment/testdata/text2.txt
new file mode 100644
index 0000000..a099d0b
--- /dev/null
+++ b/src/go/doc/comment/testdata/text2.txt
@@ -0,0 +1,14 @@
+{"TextWidth": -1}
+-- input --
+Package gob manages streams of gobs - binary values exchanged between an
+Encoder (transmitter) and a Decoder (receiver). A typical use is
+transporting arguments and results of remote procedure calls (RPCs) such as
+those provided by package "net/rpc".
+
+The implementation compiles a custom codec for each data type in the stream
+and is most efficient when a single Encoder is used to transmit a stream of
+values, amortizing the cost of compilation.
+-- text --
+Package gob manages streams of gobs - binary values exchanged between an Encoder (transmitter) and a Decoder (receiver). A typical use is transporting arguments and results of remote procedure calls (RPCs) such as those provided by package "net/rpc".
+
+The implementation compiles a custom codec for each data type in the stream and is most efficient when a single Encoder is used to transmit a stream of values, amortizing the cost of compilation.
diff --git a/src/go/doc/comment/testdata/text3.txt b/src/go/doc/comment/testdata/text3.txt
new file mode 100644
index 0000000..75d2c37
--- /dev/null
+++ b/src/go/doc/comment/testdata/text3.txt
@@ -0,0 +1,28 @@
+{"TextWidth": 30}
+-- input --
+Package gob manages streams of gobs - binary values exchanged between an
+Encoder (transmitter) and a Decoder (receiver). A typical use is
+transporting arguments and results of remote procedure calls (RPCs) such as
+those provided by package "net/rpc".
+
+The implementation compiles a custom codec for each data type in the stream
+and is most efficient when a single Encoder is used to transmit a stream of
+values, amortizing the cost of compilation.
+-- text --
+Package gob manages streams
+of gobs - binary values
+exchanged between an Encoder
+(transmitter) and a Decoder
+(receiver). A typical use is
+transporting arguments and
+results of remote procedure
+calls (RPCs) such as those
+provided by package "net/rpc".
+
+The implementation compiles
+a custom codec for each data
+type in the stream and is
+most efficient when a single
+Encoder is used to transmit a
+stream of values, amortizing
+the cost of compilation.
diff --git a/src/go/doc/comment/testdata/text4.txt b/src/go/doc/comment/testdata/text4.txt
new file mode 100644
index 0000000..e429985
--- /dev/null
+++ b/src/go/doc/comment/testdata/text4.txt
@@ -0,0 +1,29 @@
+{"TextWidth": 29}
+-- input --
+Package gob manages streams of gobs - binary values exchanged between an
+Encoder (transmitter) and a Decoder (receiver). A typical use is
+transporting arguments and results of remote procedure calls (RPCs) such as
+those provided by package "net/rpc".
+
+The implementation compiles a custom codec for each data type in the stream
+and is most efficient when a single Encoder is used to transmit a stream of
+values, amortizing the cost of compilation.
+-- text --
+Package gob manages streams
+of gobs - binary values
+exchanged between an Encoder
+(transmitter) and a Decoder
+(receiver). A typical use
+is transporting arguments
+and results of remote
+procedure calls (RPCs) such
+as those provided by package
+"net/rpc".
+
+The implementation compiles
+a custom codec for each data
+type in the stream and is
+most efficient when a single
+Encoder is used to transmit a
+stream of values, amortizing
+the cost of compilation.
diff --git a/src/go/doc/comment/testdata/text5.txt b/src/go/doc/comment/testdata/text5.txt
new file mode 100644
index 0000000..2408fc5
--- /dev/null
+++ b/src/go/doc/comment/testdata/text5.txt
@@ -0,0 +1,38 @@
+{"TextWidth": 20}
+-- input --
+Package gob manages streams of gobs - binary values exchanged between an
+Encoder (transmitter) and a Decoder (receiver). A typical use is
+transporting arguments and results of remote procedure calls (RPCs) such as
+those provided by package "net/rpc".
+
+The implementation compiles a custom codec for each data type in the stream
+and is most efficient when a single Encoder is used to transmit a stream of
+values, amortizing the cost of compilation.
+-- text --
+Package gob
+manages streams
+of gobs - binary
+values exchanged
+between an Encoder
+(transmitter) and a
+Decoder (receiver).
+A typical use
+is transporting
+arguments and
+results of remote
+procedure calls
+(RPCs) such as those
+provided by package
+"net/rpc".
+
+The implementation
+compiles a custom
+codec for each
+data type in the
+stream and is most
+efficient when a
+single Encoder is
+used to transmit a
+stream of values,
+amortizing the cost
+of compilation.
diff --git a/src/go/doc/comment/testdata/text6.txt b/src/go/doc/comment/testdata/text6.txt
new file mode 100644
index 0000000..d6deff5
--- /dev/null
+++ b/src/go/doc/comment/testdata/text6.txt
@@ -0,0 +1,18 @@
+-- input --
+Package gob manages streams of gobs - binary values exchanged between an
+Encoder (transmitter) and a Decoder (receiver). A typical use is
+transporting arguments and results of remote procedure calls (RPCs) such as
+those provided by package "net/rpc".
+
+The implementation compiles a custom codec for each data type in the stream
+and is most efficient when a single Encoder is used to transmit a stream of
+values, amortizing the cost of compilation.
+-- text --
+Package gob manages streams of gobs - binary values exchanged between an Encoder
+(transmitter) and a Decoder (receiver). A typical use is transporting arguments
+and results of remote procedure calls (RPCs) such as those provided by package
+"net/rpc".
+
+The implementation compiles a custom codec for each data type in the stream and
+is most efficient when a single Encoder is used to transmit a stream of values,
+amortizing the cost of compilation.
diff --git a/src/go/doc/comment/testdata/text7.txt b/src/go/doc/comment/testdata/text7.txt
new file mode 100644
index 0000000..c9fb6d3
--- /dev/null
+++ b/src/go/doc/comment/testdata/text7.txt
@@ -0,0 +1,21 @@
+{"TextPrefix": " "}
+-- input --
+Package gob manages streams of gobs - binary values exchanged between an
+Encoder (transmitter) and a Decoder (receiver). A typical use is
+transporting arguments and results of remote procedure calls (RPCs) such as
+those provided by package "net/rpc".
+
+The implementation compiles a custom codec for each data type in the stream
+and is most efficient when a single Encoder is used to transmit a stream of
+values, amortizing the cost of compilation.
+-- text --
+ Package gob manages streams of gobs - binary values
+ exchanged between an Encoder (transmitter) and a Decoder
+ (receiver). A typical use is transporting arguments and
+ results of remote procedure calls (RPCs) such as those
+ provided by package "net/rpc".
+
+ The implementation compiles a custom codec for each data
+ type in the stream and is most efficient when a single
+ Encoder is used to transmit a stream of values, amortizing
+ the cost of compilation.
diff --git a/src/go/doc/comment/testdata/text8.txt b/src/go/doc/comment/testdata/text8.txt
new file mode 100644
index 0000000..560ac95
--- /dev/null
+++ b/src/go/doc/comment/testdata/text8.txt
@@ -0,0 +1,94 @@
+{"TextWidth": 40}
+-- input --
+If the arguments have version suffixes (like @latest or @v1.0.0), "go install"
+builds packages in module-aware mode, ignoring the go.mod file in the current
+directory or any parent directory, if there is one. This is useful for
+installing executables without affecting the dependencies of the main module.
+To eliminate ambiguity about which module versions are used in the build, the
+arguments must satisfy the following constraints:
+
+ - Arguments must be package paths or package patterns (with "..." wildcards).
+ They must not be standard packages (like fmt), meta-patterns (std, cmd,
+ all), or relative or absolute file paths.
+
+ - All arguments must have the same version suffix. Different queries are not
+ allowed, even if they refer to the same version.
+
+ - All arguments must refer to packages in the same module at the same version.
+
+ - Package path arguments must refer to main packages. Pattern arguments
+ will only match main packages.
+
+ - No module is considered the "main" module. If the module containing
+ packages named on the command line has a go.mod file, it must not contain
+ directives (replace and exclude) that would cause it to be interpreted
+ differently than if it were the main module. The module must not require
+ a higher version of itself.
+
+ - Vendor directories are not used in any module. (Vendor directories are not
+ included in the module zip files downloaded by 'go install'.)
+
+If the arguments don't have version suffixes, "go install" may run in
+module-aware mode or GOPATH mode, depending on the GO111MODULE environment
+variable and the presence of a go.mod file. See 'go help modules' for details.
+If module-aware mode is enabled, "go install" runs in the context of the main
+module.
+-- text --
+If the arguments have version suffixes
+(like @latest or @v1.0.0), "go install"
+builds packages in module-aware mode,
+ignoring the go.mod file in the current
+directory or any parent directory,
+if there is one. This is useful for
+installing executables without affecting
+the dependencies of the main module.
+To eliminate ambiguity about which
+module versions are used in the build,
+the arguments must satisfy the following
+constraints:
+
+ - Arguments must be package paths
+ or package patterns (with "..."
+ wildcards). They must not be
+ standard packages (like fmt),
+ meta-patterns (std, cmd, all),
+ or relative or absolute file paths.
+
+ - All arguments must have the same
+ version suffix. Different queries
+ are not allowed, even if they refer
+ to the same version.
+
+ - All arguments must refer to packages
+ in the same module at the same
+ version.
+
+ - Package path arguments must refer
+ to main packages. Pattern arguments
+ will only match main packages.
+
+ - No module is considered the "main"
+ module. If the module containing
+ packages named on the command line
+ has a go.mod file, it must not
+ contain directives (replace and
+ exclude) that would cause it to be
+ interpreted differently than if it
+ were the main module. The module
+ must not require a higher version of
+ itself.
+
+ - Vendor directories are not used in
+ any module. (Vendor directories are
+ not included in the module zip files
+ downloaded by 'go install'.)
+
+If the arguments don't have version
+suffixes, "go install" may run in
+module-aware mode or GOPATH mode,
+depending on the GO111MODULE environment
+variable and the presence of a go.mod
+file. See 'go help modules' for details.
+If module-aware mode is enabled,
+"go install" runs in the context of the
+main module.
diff --git a/src/go/doc/comment/testdata/text9.txt b/src/go/doc/comment/testdata/text9.txt
new file mode 100644
index 0000000..07a64aa
--- /dev/null
+++ b/src/go/doc/comment/testdata/text9.txt
@@ -0,0 +1,12 @@
+{"TextPrefix":"|", "TextCodePrefix": "@"}
+-- input --
+Hello, world
+ Code block here.
+-- gofmt --
+Hello, world
+
+ Code block here.
+-- text --
+|Hello, world
+|
+@Code block here.
diff --git a/src/go/doc/comment/testdata/words.txt b/src/go/doc/comment/testdata/words.txt
new file mode 100644
index 0000000..63c7e1a
--- /dev/null
+++ b/src/go/doc/comment/testdata/words.txt
@@ -0,0 +1,10 @@
+-- input --
+This is an italicword and a linkedword and Unicöde.
+-- gofmt --
+This is an italicword and a linkedword and Unicöde.
+-- text --
+This is an italicword and a linkedword and Unicöde.
+-- markdown --
+This is an *italicword* and a [*linkedword*](https://example.com/linkedword) and Unicöde.
+-- html --
+<p>This is an <i>italicword</i> and a <a href="https://example.com/linkedword"><i>linkedword</i></a> and Unicöde.
diff --git a/src/go/doc/comment/testdata_test.go b/src/go/doc/comment/testdata_test.go
new file mode 100644
index 0000000..0676d86
--- /dev/null
+++ b/src/go/doc/comment/testdata_test.go
@@ -0,0 +1,202 @@
+// 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 comment
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "internal/diff"
+ "internal/txtar"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func TestTestdata(t *testing.T) {
+ files, _ := filepath.Glob("testdata/*.txt")
+ if len(files) == 0 {
+ t.Fatalf("no testdata")
+ }
+ var p Parser
+ p.Words = map[string]string{
+ "italicword": "",
+ "linkedword": "https://example.com/linkedword",
+ }
+ p.LookupPackage = func(name string) (importPath string, ok bool) {
+ if name == "comment" {
+ return "go/doc/comment", true
+ }
+ return DefaultLookupPackage(name)
+ }
+ p.LookupSym = func(recv, name string) (ok bool) {
+ if recv == "Parser" && name == "Parse" ||
+ recv == "" && name == "Doc" ||
+ recv == "" && name == "NoURL" {
+ return true
+ }
+ return false
+ }
+
+ stripDollars := func(b []byte) []byte {
+ // Remove trailing $ on lines.
+ // They make it easier to see lines with trailing spaces,
+ // as well as turning them into lines without trailing spaces,
+ // in case editors remove trailing spaces.
+ return bytes.ReplaceAll(b, []byte("$\n"), []byte("\n"))
+ }
+ for _, file := range files {
+ t.Run(filepath.Base(file), func(t *testing.T) {
+ var pr Printer
+ a, err := txtar.ParseFile(file)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(a.Comment) > 0 {
+ err := json.Unmarshal(a.Comment, &pr)
+ if err != nil {
+ t.Fatalf("unmarshalling top json: %v", err)
+ }
+ }
+ if len(a.Files) < 1 || a.Files[0].Name != "input" {
+ t.Fatalf("first file is not %q", "input")
+ }
+ d := p.Parse(string(stripDollars(a.Files[0].Data)))
+ for _, f := range a.Files[1:] {
+ want := stripDollars(f.Data)
+ for len(want) >= 2 && want[len(want)-1] == '\n' && want[len(want)-2] == '\n' {
+ want = want[:len(want)-1]
+ }
+ var out []byte
+ switch f.Name {
+ default:
+ t.Fatalf("unknown output file %q", f.Name)
+ case "dump":
+ out = dump(d)
+ case "gofmt":
+ out = pr.Comment(d)
+ case "html":
+ out = pr.HTML(d)
+ case "markdown":
+ out = pr.Markdown(d)
+ case "text":
+ out = pr.Text(d)
+ }
+ if string(out) != string(want) {
+ t.Errorf("%s: %s", file, diff.Diff(f.Name, want, "have", out))
+ }
+ }
+ })
+ }
+}
+
+func dump(d *Doc) []byte {
+ var out bytes.Buffer
+ dumpTo(&out, 0, d)
+ return out.Bytes()
+}
+
+func dumpTo(out *bytes.Buffer, indent int, x any) {
+ switch x := x.(type) {
+ default:
+ fmt.Fprintf(out, "?%T", x)
+
+ case *Doc:
+ fmt.Fprintf(out, "Doc")
+ dumpTo(out, indent+1, x.Content)
+ if len(x.Links) > 0 {
+ dumpNL(out, indent+1)
+ fmt.Fprintf(out, "Links")
+ dumpTo(out, indent+2, x.Links)
+ }
+ fmt.Fprintf(out, "\n")
+
+ case []*LinkDef:
+ for _, def := range x {
+ dumpNL(out, indent)
+ dumpTo(out, indent, def)
+ }
+
+ case *LinkDef:
+ fmt.Fprintf(out, "LinkDef Used:%v Text:%q URL:%s", x.Used, x.Text, x.URL)
+
+ case []Block:
+ for _, blk := range x {
+ dumpNL(out, indent)
+ dumpTo(out, indent, blk)
+ }
+
+ case *Heading:
+ fmt.Fprintf(out, "Heading")
+ dumpTo(out, indent+1, x.Text)
+
+ case *List:
+ fmt.Fprintf(out, "List ForceBlankBefore=%v ForceBlankBetween=%v", x.ForceBlankBefore, x.ForceBlankBetween)
+ dumpTo(out, indent+1, x.Items)
+
+ case []*ListItem:
+ for _, item := range x {
+ dumpNL(out, indent)
+ dumpTo(out, indent, item)
+ }
+
+ case *ListItem:
+ fmt.Fprintf(out, "Item Number=%q", x.Number)
+ dumpTo(out, indent+1, x.Content)
+
+ case *Paragraph:
+ fmt.Fprintf(out, "Paragraph")
+ dumpTo(out, indent+1, x.Text)
+
+ case *Code:
+ fmt.Fprintf(out, "Code")
+ dumpTo(out, indent+1, x.Text)
+
+ case []Text:
+ for _, t := range x {
+ dumpNL(out, indent)
+ dumpTo(out, indent, t)
+ }
+
+ case Plain:
+ if !strings.Contains(string(x), "\n") {
+ fmt.Fprintf(out, "Plain %q", string(x))
+ } else {
+ fmt.Fprintf(out, "Plain")
+ dumpTo(out, indent+1, string(x))
+ }
+
+ case Italic:
+ if !strings.Contains(string(x), "\n") {
+ fmt.Fprintf(out, "Italic %q", string(x))
+ } else {
+ fmt.Fprintf(out, "Italic")
+ dumpTo(out, indent+1, string(x))
+ }
+
+ case string:
+ for _, line := range strings.SplitAfter(x, "\n") {
+ if line != "" {
+ dumpNL(out, indent)
+ fmt.Fprintf(out, "%q", line)
+ }
+ }
+
+ case *Link:
+ fmt.Fprintf(out, "Link %q", x.URL)
+ dumpTo(out, indent+1, x.Text)
+
+ case *DocLink:
+ fmt.Fprintf(out, "DocLink pkg:%q, recv:%q, name:%q", x.ImportPath, x.Recv, x.Name)
+ dumpTo(out, indent+1, x.Text)
+ }
+}
+
+func dumpNL(out *bytes.Buffer, n int) {
+ out.WriteByte('\n')
+ for i := 0; i < n; i++ {
+ out.WriteByte('\t')
+ }
+}
diff --git a/src/go/doc/comment/text.go b/src/go/doc/comment/text.go
new file mode 100644
index 0000000..6f9c2e2
--- /dev/null
+++ b/src/go/doc/comment/text.go
@@ -0,0 +1,337 @@
+// 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 comment
+
+import (
+ "bytes"
+ "fmt"
+ "sort"
+ "strings"
+ "unicode/utf8"
+)
+
+// A textPrinter holds the state needed for printing a Doc as plain text.
+type textPrinter struct {
+ *Printer
+ long strings.Builder
+ prefix string
+ codePrefix string
+ width int
+}
+
+// Text returns a textual formatting of the Doc.
+// See the [Printer] documentation for ways to customize the text output.
+func (p *Printer) Text(d *Doc) []byte {
+ tp := &textPrinter{
+ Printer: p,
+ prefix: p.TextPrefix,
+ codePrefix: p.TextCodePrefix,
+ width: p.TextWidth,
+ }
+ if tp.codePrefix == "" {
+ tp.codePrefix = p.TextPrefix + "\t"
+ }
+ if tp.width == 0 {
+ tp.width = 80 - utf8.RuneCountInString(tp.prefix)
+ }
+
+ var out bytes.Buffer
+ for i, x := range d.Content {
+ if i > 0 && blankBefore(x) {
+ out.WriteString(tp.prefix)
+ writeNL(&out)
+ }
+ tp.block(&out, x)
+ }
+ anyUsed := false
+ for _, def := range d.Links {
+ if def.Used {
+ anyUsed = true
+ break
+ }
+ }
+ if anyUsed {
+ writeNL(&out)
+ for _, def := range d.Links {
+ if def.Used {
+ fmt.Fprintf(&out, "[%s]: %s\n", def.Text, def.URL)
+ }
+ }
+ }
+ return out.Bytes()
+}
+
+// writeNL calls out.WriteByte('\n')
+// but first trims trailing spaces on the previous line.
+func writeNL(out *bytes.Buffer) {
+ // Trim trailing spaces.
+ data := out.Bytes()
+ n := 0
+ for n < len(data) && (data[len(data)-n-1] == ' ' || data[len(data)-n-1] == '\t') {
+ n++
+ }
+ if n > 0 {
+ out.Truncate(len(data) - n)
+ }
+ out.WriteByte('\n')
+}
+
+// block prints the block x to out.
+func (p *textPrinter) block(out *bytes.Buffer, x Block) {
+ switch x := x.(type) {
+ default:
+ fmt.Fprintf(out, "?%T\n", x)
+
+ case *Paragraph:
+ out.WriteString(p.prefix)
+ p.text(out, "", x.Text)
+
+ case *Heading:
+ out.WriteString(p.prefix)
+ out.WriteString("# ")
+ p.text(out, "", x.Text)
+
+ case *Code:
+ text := x.Text
+ for text != "" {
+ var line string
+ line, text, _ = strings.Cut(text, "\n")
+ if line != "" {
+ out.WriteString(p.codePrefix)
+ out.WriteString(line)
+ }
+ writeNL(out)
+ }
+
+ case *List:
+ loose := x.BlankBetween()
+ for i, item := range x.Items {
+ if i > 0 && loose {
+ out.WriteString(p.prefix)
+ writeNL(out)
+ }
+ out.WriteString(p.prefix)
+ out.WriteString(" ")
+ if item.Number == "" {
+ out.WriteString(" - ")
+ } else {
+ out.WriteString(item.Number)
+ out.WriteString(". ")
+ }
+ for i, blk := range item.Content {
+ const fourSpace = " "
+ if i > 0 {
+ writeNL(out)
+ out.WriteString(p.prefix)
+ out.WriteString(fourSpace)
+ }
+ p.text(out, fourSpace, blk.(*Paragraph).Text)
+ }
+ }
+ }
+}
+
+// text prints the text sequence x to out.
+func (p *textPrinter) text(out *bytes.Buffer, indent string, x []Text) {
+ p.oneLongLine(&p.long, x)
+ words := strings.Fields(p.long.String())
+ p.long.Reset()
+
+ var seq []int
+ if p.width < 0 || len(words) == 0 {
+ seq = []int{0, len(words)} // one long line
+ } else {
+ seq = wrap(words, p.width-utf8.RuneCountInString(indent))
+ }
+ for i := 0; i+1 < len(seq); i++ {
+ if i > 0 {
+ out.WriteString(p.prefix)
+ out.WriteString(indent)
+ }
+ for j, w := range words[seq[i]:seq[i+1]] {
+ if j > 0 {
+ out.WriteString(" ")
+ }
+ out.WriteString(w)
+ }
+ writeNL(out)
+ }
+}
+
+// oneLongLine prints the text sequence x to out as one long line,
+// without worrying about line wrapping.
+// Explicit links have the [ ] dropped to improve readability.
+func (p *textPrinter) oneLongLine(out *strings.Builder, x []Text) {
+ for _, t := range x {
+ switch t := t.(type) {
+ case Plain:
+ out.WriteString(string(t))
+ case Italic:
+ out.WriteString(string(t))
+ case *Link:
+ p.oneLongLine(out, t.Text)
+ case *DocLink:
+ p.oneLongLine(out, t.Text)
+ }
+ }
+}
+
+// wrap wraps words into lines of at most max runes,
+// minimizing the sum of the squares of the leftover lengths
+// at the end of each line (except the last, of course),
+// with a preference for ending lines at punctuation (.,:;).
+//
+// The returned slice gives the indexes of the first words
+// on each line in the wrapped text with a final entry of len(words).
+// Thus the lines are words[seq[0]:seq[1]], words[seq[1]:seq[2]],
+// ..., words[seq[len(seq)-2]:seq[len(seq)-1]].
+//
+// The implementation runs in O(n log n) time, where n = len(words),
+// using the algorithm described in D. S. Hirschberg and L. L. Larmore,
+// “[The least weight subsequence problem],” FOCS 1985, pp. 137-143.
+//
+// [The least weight subsequence problem]: https://doi.org/10.1109/SFCS.1985.60
+func wrap(words []string, max int) (seq []int) {
+ // The algorithm requires that our scoring function be concave,
+ // meaning that for all i₀ ≤ i₁ < j₀ ≤ j₁,
+ // weight(i₀, j₀) + weight(i₁, j₁) ≤ weight(i₀, j₁) + weight(i₁, j₀).
+ //
+ // Our weights are two-element pairs [hi, lo]
+ // ordered by elementwise comparison.
+ // The hi entry counts the weight for lines that are longer than max,
+ // and the lo entry counts the weight for lines that are not.
+ // This forces the algorithm to first minimize the number of lines
+ // that are longer than max, which correspond to lines with
+ // single very long words. Having done that, it can move on to
+ // minimizing the lo score, which is more interesting.
+ //
+ // The lo score is the sum for each line of the square of the
+ // number of spaces remaining at the end of the line and a
+ // penalty of 64 given out for not ending the line in a
+ // punctuation character (.,:;).
+ // The penalty is somewhat arbitrarily chosen by trying
+ // different amounts and judging how nice the wrapped text looks.
+ // Roughly speaking, using 64 means that we are willing to
+ // end a line with eight blank spaces in order to end at a
+ // punctuation character, even if the next word would fit in
+ // those spaces.
+ //
+ // We care about ending in punctuation characters because
+ // it makes the text easier to skim if not too many sentences
+ // or phrases begin with a single word on the previous line.
+
+ // A score is the score (also called weight) for a given line.
+ // add and cmp add and compare scores.
+ type score struct {
+ hi int64
+ lo int64
+ }
+ add := func(s, t score) score { return score{s.hi + t.hi, s.lo + t.lo} }
+ cmp := func(s, t score) int {
+ switch {
+ case s.hi < t.hi:
+ return -1
+ case s.hi > t.hi:
+ return +1
+ case s.lo < t.lo:
+ return -1
+ case s.lo > t.lo:
+ return +1
+ }
+ return 0
+ }
+
+ // total[j] is the total number of runes
+ // (including separating spaces) in words[:j].
+ total := make([]int, len(words)+1)
+ total[0] = 0
+ for i, s := range words {
+ total[1+i] = total[i] + utf8.RuneCountInString(s) + 1
+ }
+
+ // weight returns weight(i, j).
+ weight := func(i, j int) score {
+ // On the last line, there is zero weight for being too short.
+ n := total[j] - 1 - total[i]
+ if j == len(words) && n <= max {
+ return score{0, 0}
+ }
+
+ // Otherwise the weight is the penalty plus the square of the number of
+ // characters remaining on the line or by which the line goes over.
+ // In the latter case, that value goes in the hi part of the score.
+ // (See note above.)
+ p := wrapPenalty(words[j-1])
+ v := int64(max-n) * int64(max-n)
+ if n > max {
+ return score{v, p}
+ }
+ return score{0, v + p}
+ }
+
+ // The rest of this function is “The Basic Algorithm” from
+ // Hirschberg and Larmore's conference paper,
+ // using the same names as in the paper.
+ f := []score{{0, 0}}
+ g := func(i, j int) score { return add(f[i], weight(i, j)) }
+
+ bridge := func(a, b, c int) bool {
+ k := c + sort.Search(len(words)+1-c, func(k int) bool {
+ k += c
+ return cmp(g(a, k), g(b, k)) > 0
+ })
+ if k > len(words) {
+ return true
+ }
+ return cmp(g(c, k), g(b, k)) <= 0
+ }
+
+ // d is a one-ended deque implemented as a slice.
+ d := make([]int, 1, len(words))
+ d[0] = 0
+ bestleft := make([]int, 1, len(words))
+ bestleft[0] = -1
+ for m := 1; m < len(words); m++ {
+ f = append(f, g(d[0], m))
+ bestleft = append(bestleft, d[0])
+ for len(d) > 1 && cmp(g(d[1], m+1), g(d[0], m+1)) <= 0 {
+ d = d[1:] // “Retire”
+ }
+ for len(d) > 1 && bridge(d[len(d)-2], d[len(d)-1], m) {
+ d = d[:len(d)-1] // “Fire”
+ }
+ if cmp(g(m, len(words)), g(d[len(d)-1], len(words))) < 0 {
+ d = append(d, m) // “Hire”
+ // The next few lines are not in the paper but are necessary
+ // to handle two-word inputs correctly. It appears to be
+ // just a bug in the paper's pseudocode.
+ if len(d) == 2 && cmp(g(d[1], m+1), g(d[0], m+1)) <= 0 {
+ d = d[1:]
+ }
+ }
+ }
+ bestleft = append(bestleft, d[0])
+
+ // Recover least weight sequence from bestleft.
+ n := 1
+ for m := len(words); m > 0; m = bestleft[m] {
+ n++
+ }
+ seq = make([]int, n)
+ for m := len(words); m > 0; m = bestleft[m] {
+ n--
+ seq[n] = m
+ }
+ return seq
+}
+
+// wrapPenalty is the penalty for inserting a line break after word s.
+func wrapPenalty(s string) int64 {
+ switch s[len(s)-1] {
+ case '.', ',', ':', ';':
+ return 0
+ }
+ return 64
+}
diff --git a/src/go/doc/comment/wrap_test.go b/src/go/doc/comment/wrap_test.go
new file mode 100644
index 0000000..f9802c9
--- /dev/null
+++ b/src/go/doc/comment/wrap_test.go
@@ -0,0 +1,141 @@
+// 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 comment
+
+import (
+ "flag"
+ "fmt"
+ "math/rand"
+ "testing"
+ "time"
+ "unicode/utf8"
+)
+
+var wrapSeed = flag.Int64("wrapseed", 0, "use `seed` for wrap test (default auto-seeds)")
+
+func TestWrap(t *testing.T) {
+ if *wrapSeed == 0 {
+ *wrapSeed = time.Now().UnixNano()
+ }
+ t.Logf("-wrapseed=%#x\n", *wrapSeed)
+ r := rand.New(rand.NewSource(*wrapSeed))
+
+ // Generate words of random length.
+ s := "1234567890αβcdefghijklmnopqrstuvwxyz"
+ sN := utf8.RuneCountInString(s)
+ var words []string
+ for i := 0; i < 100; i++ {
+ n := 1 + r.Intn(sN-1)
+ if n >= 12 {
+ n++ // extra byte for β
+ }
+ if n >= 11 {
+ n++ // extra byte for α
+ }
+ words = append(words, s[:n])
+ }
+
+ for n := 1; n <= len(words) && !t.Failed(); n++ {
+ t.Run(fmt.Sprint("n=", n), func(t *testing.T) {
+ words := words[:n]
+ t.Logf("words: %v", words)
+ for max := 1; max < 100 && !t.Failed(); max++ {
+ t.Run(fmt.Sprint("max=", max), func(t *testing.T) {
+ seq := wrap(words, max)
+
+ // Compute score for seq.
+ start := 0
+ score := int64(0)
+ if len(seq) == 0 {
+ t.Fatalf("wrap seq is empty")
+ }
+ if seq[0] != 0 {
+ t.Fatalf("wrap seq does not start with 0")
+ }
+ for _, n := range seq[1:] {
+ if n <= start {
+ t.Fatalf("wrap seq is non-increasing: %v", seq)
+ }
+ if n > len(words) {
+ t.Fatalf("wrap seq contains %d > %d: %v", n, len(words), seq)
+ }
+ size := -1
+ for _, s := range words[start:n] {
+ size += 1 + utf8.RuneCountInString(s)
+ }
+ if n-start == 1 && size >= max {
+ // no score
+ } else if size > max {
+ t.Fatalf("wrap used overlong line %d:%d: %v", start, n, words[start:n])
+ } else if n != len(words) {
+ score += int64(max-size)*int64(max-size) + wrapPenalty(words[n-1])
+ }
+ start = n
+ }
+ if start != len(words) {
+ t.Fatalf("wrap seq does not use all words (%d < %d): %v", start, len(words), seq)
+ }
+
+ // Check that score matches slow reference implementation.
+ slowSeq, slowScore := wrapSlow(words, max)
+ if score != slowScore {
+ t.Fatalf("wrap score = %d != wrapSlow score %d\nwrap: %v\nslow: %v", score, slowScore, seq, slowSeq)
+ }
+ })
+ }
+ })
+ }
+}
+
+// wrapSlow is an O(n²) reference implementation for wrap.
+// It returns a minimal-score sequence along with the score.
+// It is OK if wrap returns a different sequence as long as that
+// sequence has the same score.
+func wrapSlow(words []string, max int) (seq []int, score int64) {
+ // Quadratic dynamic programming algorithm for line wrapping problem.
+ // best[i] tracks the best score possible for words[:i],
+ // assuming that for i < len(words) the line breaks after those words.
+ // bestleft[i] tracks the previous line break for best[i].
+ best := make([]int64, len(words)+1)
+ bestleft := make([]int, len(words)+1)
+ best[0] = 0
+ for i, w := range words {
+ if utf8.RuneCountInString(w) >= max {
+ // Overlong word must appear on line by itself. No effect on score.
+ best[i+1] = best[i]
+ continue
+ }
+ best[i+1] = 1e18
+ p := wrapPenalty(w)
+ n := -1
+ for j := i; j >= 0; j-- {
+ n += 1 + utf8.RuneCountInString(words[j])
+ if n > max {
+ break
+ }
+ line := int64(n-max)*int64(n-max) + p
+ if i == len(words)-1 {
+ line = 0 // no score for final line being too short
+ }
+ s := best[j] + line
+ if best[i+1] > s {
+ best[i+1] = s
+ bestleft[i+1] = j
+ }
+ }
+ }
+
+ // Recover least weight sequence from bestleft.
+ n := 1
+ for m := len(words); m > 0; m = bestleft[m] {
+ n++
+ }
+ seq = make([]int, n)
+ for m := len(words); m > 0; m = bestleft[m] {
+ n--
+ seq[n] = m
+ }
+ return seq, best[len(words)]
+}
diff --git a/src/go/doc/comment_test.go b/src/go/doc/comment_test.go
new file mode 100644
index 0000000..004ae9d
--- /dev/null
+++ b/src/go/doc/comment_test.go
@@ -0,0 +1,67 @@
+// 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 doc
+
+import (
+ "bytes"
+ "go/parser"
+ "go/token"
+ "internal/diff"
+ "testing"
+)
+
+func TestComment(t *testing.T) {
+ fset := token.NewFileSet()
+ pkgs, err := parser.ParseDir(fset, "testdata/pkgdoc", nil, parser.ParseComments)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if pkgs["pkgdoc"] == nil {
+ t.Fatal("missing package pkgdoc")
+ }
+ pkg := New(pkgs["pkgdoc"], "testdata/pkgdoc", 0)
+
+ var (
+ input = "[T] and [U] are types, and [T.M] is a method, but [V] is a broken link. [rand.Int] and [crand.Reader] are things. [G.M1] and [G.M2] are generic methods.\n"
+ wantHTML = `<p><a href="#T">T</a> and <a href="#U">U</a> are types, and <a href="#T.M">T.M</a> is a method, but [V] is a broken link. <a href="/math/rand#Int">rand.Int</a> and <a href="/crypto/rand#Reader">crand.Reader</a> are things. <a href="#G.M1">G.M1</a> and <a href="#G.M2">G.M2</a> are generic methods.` + "\n"
+ wantOldHTML = "<p>[T] and [U] are <i>types</i>, and [T.M] is a method, but [V] is a broken link. [rand.Int] and [crand.Reader] are things. [G.M1] and [G.M2] are generic methods.\n"
+ wantMarkdown = "[T](#T) and [U](#U) are types, and [T.M](#T.M) is a method, but \\[V] is a broken link. [rand.Int](/math/rand#Int) and [crand.Reader](/crypto/rand#Reader) are things. [G.M1](#G.M1) and [G.M2](#G.M2) are generic methods.\n"
+ wantText = "T and U are types, and T.M is a method, but [V] is a broken link. rand.Int and\ncrand.Reader are things. G.M1 and G.M2 are generic methods.\n"
+ wantOldText = "[T] and [U] are types, and [T.M] is a method, but [V] is a broken link.\n[rand.Int] and [crand.Reader] are things. [G.M1] and [G.M2] are generic methods.\n"
+ wantSynopsis = "T and U are types, and T.M is a method, but [V] is a broken link."
+ wantOldSynopsis = "[T] and [U] are types, and [T.M] is a method, but [V] is a broken link."
+ )
+
+ if b := pkg.HTML(input); string(b) != wantHTML {
+ t.Errorf("%s", diff.Diff("pkg.HTML", b, "want", []byte(wantHTML)))
+ }
+ if b := pkg.Markdown(input); string(b) != wantMarkdown {
+ t.Errorf("%s", diff.Diff("pkg.Markdown", b, "want", []byte(wantMarkdown)))
+ }
+ if b := pkg.Text(input); string(b) != wantText {
+ t.Errorf("%s", diff.Diff("pkg.Text", b, "want", []byte(wantText)))
+ }
+ if b := pkg.Synopsis(input); b != wantSynopsis {
+ t.Errorf("%s", diff.Diff("pkg.Synopsis", []byte(b), "want", []byte(wantText)))
+ }
+
+ var buf bytes.Buffer
+
+ buf.Reset()
+ ToHTML(&buf, input, map[string]string{"types": ""})
+ if b := buf.Bytes(); string(b) != wantOldHTML {
+ t.Errorf("%s", diff.Diff("ToHTML", b, "want", []byte(wantOldHTML)))
+ }
+
+ buf.Reset()
+ ToText(&buf, input, "", "\t", 80)
+ if b := buf.Bytes(); string(b) != wantOldText {
+ t.Errorf("%s", diff.Diff("ToText", b, "want", []byte(wantOldText)))
+ }
+
+ if b := Synopsis(input); b != wantOldSynopsis {
+ t.Errorf("%s", diff.Diff("Synopsis", []byte(b), "want", []byte(wantOldText)))
+ }
+}
diff --git a/src/go/doc/doc.go b/src/go/doc/doc.go
new file mode 100644
index 0000000..eefadfa
--- /dev/null
+++ b/src/go/doc/doc.go
@@ -0,0 +1,354 @@
+// Copyright 2009 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 doc extracts source code documentation from a Go AST.
+package doc
+
+import (
+ "fmt"
+ "go/ast"
+ "go/doc/comment"
+ "go/token"
+ "strings"
+)
+
+// Package is the documentation for an entire package.
+type Package struct {
+ Doc string
+ Name string
+ ImportPath string
+ Imports []string
+ Filenames []string
+ Notes map[string][]*Note
+
+ // Deprecated: For backward compatibility Bugs is still populated,
+ // but all new code should use Notes instead.
+ Bugs []string
+
+ // declarations
+ Consts []*Value
+ Types []*Type
+ Vars []*Value
+ Funcs []*Func
+
+ // Examples is a sorted list of examples associated with
+ // the package. Examples are extracted from _test.go files
+ // provided to NewFromFiles.
+ Examples []*Example
+
+ importByName map[string]string
+ syms map[string]bool
+}
+
+// Value is the documentation for a (possibly grouped) var or const declaration.
+type Value struct {
+ Doc string
+ Names []string // var or const names in declaration order
+ Decl *ast.GenDecl
+
+ order int
+}
+
+// Type is the documentation for a type declaration.
+type Type struct {
+ Doc string
+ Name string
+ Decl *ast.GenDecl
+
+ // associated declarations
+ Consts []*Value // sorted list of constants of (mostly) this type
+ Vars []*Value // sorted list of variables of (mostly) this type
+ Funcs []*Func // sorted list of functions returning this type
+ Methods []*Func // sorted list of methods (including embedded ones) of this type
+
+ // Examples is a sorted list of examples associated with
+ // this type. Examples are extracted from _test.go files
+ // provided to NewFromFiles.
+ Examples []*Example
+}
+
+// Func is the documentation for a func declaration.
+type Func struct {
+ Doc string
+ Name string
+ Decl *ast.FuncDecl
+
+ // methods
+ // (for functions, these fields have the respective zero value)
+ Recv string // actual receiver "T" or "*T" possibly followed by type parameters [P1, ..., Pn]
+ Orig string // original receiver "T" or "*T"
+ Level int // embedding level; 0 means not embedded
+
+ // Examples is a sorted list of examples associated with this
+ // function or method. Examples are extracted from _test.go files
+ // provided to NewFromFiles.
+ Examples []*Example
+}
+
+// A Note represents a marked comment starting with "MARKER(uid): note body".
+// Any note with a marker of 2 or more upper case [A-Z] letters and a uid of
+// at least one character is recognized. The ":" following the uid is optional.
+// Notes are collected in the Package.Notes map indexed by the notes marker.
+type Note struct {
+ Pos, End token.Pos // position range of the comment containing the marker
+ UID string // uid found with the marker
+ Body string // note body text
+}
+
+// Mode values control the operation of New and NewFromFiles.
+type Mode int
+
+const (
+ // AllDecls says to extract documentation for all package-level
+ // declarations, not just exported ones.
+ AllDecls Mode = 1 << iota
+
+ // AllMethods says to show all embedded methods, not just the ones of
+ // invisible (unexported) anonymous fields.
+ AllMethods
+
+ // PreserveAST says to leave the AST unmodified. Originally, pieces of
+ // the AST such as function bodies were nil-ed out to save memory in
+ // godoc, but not all programs want that behavior.
+ PreserveAST
+)
+
+// New computes the package documentation for the given package AST.
+// New takes ownership of the AST pkg and may edit or overwrite it.
+// To have the Examples fields populated, use NewFromFiles and include
+// the package's _test.go files.
+func New(pkg *ast.Package, importPath string, mode Mode) *Package {
+ var r reader
+ r.readPackage(pkg, mode)
+ r.computeMethodSets()
+ r.cleanupTypes()
+ p := &Package{
+ Doc: r.doc,
+ Name: pkg.Name,
+ ImportPath: importPath,
+ Imports: sortedKeys(r.imports),
+ Filenames: r.filenames,
+ Notes: r.notes,
+ Bugs: noteBodies(r.notes["BUG"]),
+ Consts: sortedValues(r.values, token.CONST),
+ Types: sortedTypes(r.types, mode&AllMethods != 0),
+ Vars: sortedValues(r.values, token.VAR),
+ Funcs: sortedFuncs(r.funcs, true),
+
+ importByName: r.importByName,
+ syms: make(map[string]bool),
+ }
+
+ p.collectValues(p.Consts)
+ p.collectValues(p.Vars)
+ p.collectTypes(p.Types)
+ p.collectFuncs(p.Funcs)
+
+ return p
+}
+
+func (p *Package) collectValues(values []*Value) {
+ for _, v := range values {
+ for _, name := range v.Names {
+ p.syms[name] = true
+ }
+ }
+}
+
+func (p *Package) collectTypes(types []*Type) {
+ for _, t := range types {
+ if p.syms[t.Name] {
+ // Shouldn't be any cycles but stop just in case.
+ continue
+ }
+ p.syms[t.Name] = true
+ p.collectValues(t.Consts)
+ p.collectValues(t.Vars)
+ p.collectFuncs(t.Funcs)
+ p.collectFuncs(t.Methods)
+ }
+}
+
+func (p *Package) collectFuncs(funcs []*Func) {
+ for _, f := range funcs {
+ if f.Recv != "" {
+ r := strings.TrimPrefix(f.Recv, "*")
+ if i := strings.IndexByte(r, '['); i >= 0 {
+ r = r[:i] // remove type parameters
+ }
+ p.syms[r+"."+f.Name] = true
+ } else {
+ p.syms[f.Name] = true
+ }
+ }
+}
+
+// NewFromFiles computes documentation for a package.
+//
+// The package is specified by a list of *ast.Files and corresponding
+// file set, which must not be nil.
+// NewFromFiles uses all provided files when computing documentation,
+// so it is the caller's responsibility to provide only the files that
+// match the desired build context. "go/build".Context.MatchFile can
+// be used for determining whether a file matches a build context with
+// the desired GOOS and GOARCH values, and other build constraints.
+// The import path of the package is specified by importPath.
+//
+// Examples found in _test.go files are associated with the corresponding
+// type, function, method, or the package, based on their name.
+// If the example has a suffix in its name, it is set in the
+// Example.Suffix field. Examples with malformed names are skipped.
+//
+// Optionally, a single extra argument of type Mode can be provided to
+// control low-level aspects of the documentation extraction behavior.
+//
+// NewFromFiles takes ownership of the AST files and may edit them,
+// unless the PreserveAST Mode bit is on.
+func NewFromFiles(fset *token.FileSet, files []*ast.File, importPath string, opts ...any) (*Package, error) {
+ // Check for invalid API usage.
+ if fset == nil {
+ panic(fmt.Errorf("doc.NewFromFiles: no token.FileSet provided (fset == nil)"))
+ }
+ var mode Mode
+ switch len(opts) { // There can only be 0 or 1 options, so a simple switch works for now.
+ case 0:
+ // Nothing to do.
+ case 1:
+ m, ok := opts[0].(Mode)
+ if !ok {
+ panic(fmt.Errorf("doc.NewFromFiles: option argument type must be doc.Mode"))
+ }
+ mode = m
+ default:
+ panic(fmt.Errorf("doc.NewFromFiles: there must not be more than 1 option argument"))
+ }
+
+ // Collect .go and _test.go files.
+ var (
+ goFiles = make(map[string]*ast.File)
+ testGoFiles []*ast.File
+ )
+ for i := range files {
+ f := fset.File(files[i].Pos())
+ if f == nil {
+ return nil, fmt.Errorf("file files[%d] is not found in the provided file set", i)
+ }
+ switch name := f.Name(); {
+ case strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go"):
+ goFiles[name] = files[i]
+ case strings.HasSuffix(name, "_test.go"):
+ testGoFiles = append(testGoFiles, files[i])
+ default:
+ return nil, fmt.Errorf("file files[%d] filename %q does not have a .go extension", i, name)
+ }
+ }
+
+ // TODO(dmitshur,gri): A relatively high level call to ast.NewPackage with a simpleImporter
+ // ast.Importer implementation is made below. It might be possible to short-circuit and simplify.
+
+ // Compute package documentation.
+ pkg, _ := ast.NewPackage(fset, goFiles, simpleImporter, nil) // Ignore errors that can happen due to unresolved identifiers.
+ p := New(pkg, importPath, mode)
+ classifyExamples(p, Examples(testGoFiles...))
+ return p, nil
+}
+
+// simpleImporter returns a (dummy) package object named by the last path
+// component of the provided package path (as is the convention for packages).
+// This is sufficient to resolve package identifiers without doing an actual
+// import. It never returns an error.
+func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) {
+ pkg := imports[path]
+ if pkg == nil {
+ // note that strings.LastIndex returns -1 if there is no "/"
+ pkg = ast.NewObj(ast.Pkg, path[strings.LastIndex(path, "/")+1:])
+ pkg.Data = ast.NewScope(nil) // required by ast.NewPackage for dot-import
+ imports[path] = pkg
+ }
+ return pkg, nil
+}
+
+// lookupSym reports whether the package has a given symbol or method.
+//
+// If recv == "", HasSym reports whether the package has a top-level
+// const, func, type, or var named name.
+//
+// If recv != "", HasSym reports whether the package has a type
+// named recv with a method named name.
+func (p *Package) lookupSym(recv, name string) bool {
+ if recv != "" {
+ return p.syms[recv+"."+name]
+ }
+ return p.syms[name]
+}
+
+// lookupPackage returns the import path identified by name
+// in the given package. If name uniquely identifies a single import,
+// then lookupPackage returns that import.
+// If multiple packages are imported as name, importPath returns "", false.
+// Otherwise, if name is the name of p itself, importPath returns "", true,
+// to signal a reference to p.
+// Otherwise, importPath returns "", false.
+func (p *Package) lookupPackage(name string) (importPath string, ok bool) {
+ if path, ok := p.importByName[name]; ok {
+ if path == "" {
+ return "", false // multiple imports used the name
+ }
+ return path, true // found import
+ }
+ if p.Name == name {
+ return "", true // allow reference to this package
+ }
+ return "", false // unknown name
+}
+
+// Parser returns a doc comment parser configured
+// for parsing doc comments from package p.
+// Each call returns a new parser, so that the caller may
+// customize it before use.
+func (p *Package) Parser() *comment.Parser {
+ return &comment.Parser{
+ LookupPackage: p.lookupPackage,
+ LookupSym: p.lookupSym,
+ }
+}
+
+// Printer returns a doc comment printer configured
+// for printing doc comments from package p.
+// Each call returns a new printer, so that the caller may
+// customize it before use.
+func (p *Package) Printer() *comment.Printer {
+ // No customization today, but having p.Printer()
+ // gives us flexibility in the future, and it is convenient for callers.
+ return &comment.Printer{}
+}
+
+// HTML returns formatted HTML for the doc comment text.
+//
+// To customize details of the HTML, use [Package.Printer]
+// to obtain a [comment.Printer], and configure it
+// before calling its HTML method.
+func (p *Package) HTML(text string) []byte {
+ return p.Printer().HTML(p.Parser().Parse(text))
+}
+
+// Markdown returns formatted Markdown for the doc comment text.
+//
+// To customize details of the Markdown, use [Package.Printer]
+// to obtain a [comment.Printer], and configure it
+// before calling its Markdown method.
+func (p *Package) Markdown(text string) []byte {
+ return p.Printer().Markdown(p.Parser().Parse(text))
+}
+
+// Text returns formatted text for the doc comment text,
+// wrapped to 80 Unicode code points and using tabs for
+// code block indentation.
+//
+// To customize details of the formatting, use [Package.Printer]
+// to obtain a [comment.Printer], and configure it
+// before calling its Text method.
+func (p *Package) Text(text string) []byte {
+ return p.Printer().Text(p.Parser().Parse(text))
+}
diff --git a/src/go/doc/doc_test.go b/src/go/doc/doc_test.go
new file mode 100644
index 0000000..b79087e
--- /dev/null
+++ b/src/go/doc/doc_test.go
@@ -0,0 +1,292 @@
+// Copyright 2012 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 doc
+
+import (
+ "bytes"
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/parser"
+ "go/printer"
+ "go/token"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "testing"
+ "text/template"
+)
+
+var update = flag.Bool("update", false, "update golden (.out) files")
+var files = flag.String("files", "", "consider only Go test files matching this regular expression")
+
+const dataDir = "testdata"
+
+var templateTxt = readTemplate("template.txt")
+
+func readTemplate(filename string) *template.Template {
+ t := template.New(filename)
+ t.Funcs(template.FuncMap{
+ "node": nodeFmt,
+ "synopsis": synopsisFmt,
+ "indent": indentFmt,
+ })
+ return template.Must(t.ParseFiles(filepath.Join(dataDir, filename)))
+}
+
+func nodeFmt(node any, fset *token.FileSet) string {
+ var buf bytes.Buffer
+ printer.Fprint(&buf, fset, node)
+ return strings.ReplaceAll(strings.TrimSpace(buf.String()), "\n", "\n\t")
+}
+
+func synopsisFmt(s string) string {
+ const n = 64
+ if len(s) > n {
+ // cut off excess text and go back to a word boundary
+ s = s[0:n]
+ if i := strings.LastIndexAny(s, "\t\n "); i >= 0 {
+ s = s[0:i]
+ }
+ s = strings.TrimSpace(s) + " ..."
+ }
+ return "// " + strings.ReplaceAll(s, "\n", " ")
+}
+
+func indentFmt(indent, s string) string {
+ end := ""
+ if strings.HasSuffix(s, "\n") {
+ end = "\n"
+ s = s[:len(s)-1]
+ }
+ return indent + strings.ReplaceAll(s, "\n", "\n"+indent) + end
+}
+
+func isGoFile(fi fs.FileInfo) bool {
+ name := fi.Name()
+ return !fi.IsDir() &&
+ len(name) > 0 && name[0] != '.' && // ignore .files
+ filepath.Ext(name) == ".go"
+}
+
+type bundle struct {
+ *Package
+ FSet *token.FileSet
+}
+
+func test(t *testing.T, mode Mode) {
+ // determine file filter
+ filter := isGoFile
+ if *files != "" {
+ rx, err := regexp.Compile(*files)
+ if err != nil {
+ t.Fatal(err)
+ }
+ filter = func(fi fs.FileInfo) bool {
+ return isGoFile(fi) && rx.MatchString(fi.Name())
+ }
+ }
+
+ // get packages
+ fset := token.NewFileSet()
+ pkgs, err := parser.ParseDir(fset, dataDir, filter, parser.ParseComments)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // test packages
+ for _, pkg := range pkgs {
+ t.Run(pkg.Name, func(t *testing.T) {
+ importPath := dataDir + "/" + pkg.Name
+ var files []*ast.File
+ for _, f := range pkg.Files {
+ files = append(files, f)
+ }
+ doc, err := NewFromFiles(fset, files, importPath, mode)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // golden files always use / in filenames - canonicalize them
+ for i, filename := range doc.Filenames {
+ doc.Filenames[i] = filepath.ToSlash(filename)
+ }
+
+ // print documentation
+ var buf bytes.Buffer
+ if err := templateTxt.Execute(&buf, bundle{doc, fset}); err != nil {
+ t.Fatal(err)
+ }
+ got := buf.Bytes()
+
+ // update golden file if necessary
+ golden := filepath.Join(dataDir, fmt.Sprintf("%s.%d.golden", pkg.Name, mode))
+ if *update {
+ err := os.WriteFile(golden, got, 0644)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // get golden file
+ want, err := os.ReadFile(golden)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // compare
+ if !bytes.Equal(got, want) {
+ t.Errorf("package %s\n\tgot:\n%s\n\twant:\n%s", pkg.Name, got, want)
+ }
+ })
+ }
+}
+
+func Test(t *testing.T) {
+ t.Run("default", func(t *testing.T) { test(t, 0) })
+ t.Run("AllDecls", func(t *testing.T) { test(t, AllDecls) })
+ t.Run("AllMethods", func(t *testing.T) { test(t, AllMethods) })
+}
+
+func TestFuncs(t *testing.T) {
+ fset := token.NewFileSet()
+ file, err := parser.ParseFile(fset, "funcs.go", strings.NewReader(funcsTestFile), parser.ParseComments)
+ if err != nil {
+ t.Fatal(err)
+ }
+ doc, err := NewFromFiles(fset, []*ast.File{file}, "importPath", Mode(0))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, f := range doc.Funcs {
+ f.Decl = nil
+ }
+ for _, ty := range doc.Types {
+ for _, f := range ty.Funcs {
+ f.Decl = nil
+ }
+ for _, m := range ty.Methods {
+ m.Decl = nil
+ }
+ }
+
+ compareFuncs := func(t *testing.T, msg string, got, want *Func) {
+ // ignore Decl and Examples
+ got.Decl = nil
+ got.Examples = nil
+ if !(got.Doc == want.Doc &&
+ got.Name == want.Name &&
+ got.Recv == want.Recv &&
+ got.Orig == want.Orig &&
+ got.Level == want.Level) {
+ t.Errorf("%s:\ngot %+v\nwant %+v", msg, got, want)
+ }
+ }
+
+ compareSlices(t, "Funcs", doc.Funcs, funcsPackage.Funcs, compareFuncs)
+ compareSlices(t, "Types", doc.Types, funcsPackage.Types, func(t *testing.T, msg string, got, want *Type) {
+ if got.Name != want.Name {
+ t.Errorf("%s.Name: got %q, want %q", msg, got.Name, want.Name)
+ } else {
+ compareSlices(t, got.Name+".Funcs", got.Funcs, want.Funcs, compareFuncs)
+ compareSlices(t, got.Name+".Methods", got.Methods, want.Methods, compareFuncs)
+ }
+ })
+}
+
+func compareSlices[E any](t *testing.T, name string, got, want []E, compareElem func(*testing.T, string, E, E)) {
+ if len(got) != len(want) {
+ t.Errorf("%s: got %d, want %d", name, len(got), len(want))
+ }
+ for i := 0; i < len(got) && i < len(want); i++ {
+ compareElem(t, fmt.Sprintf("%s[%d]", name, i), got[i], want[i])
+ }
+}
+
+const funcsTestFile = `
+package funcs
+
+func F() {}
+
+type S1 struct {
+ S2 // embedded, exported
+ s3 // embedded, unexported
+}
+
+func NewS1() S1 {return S1{} }
+func NewS1p() *S1 { return &S1{} }
+
+func (S1) M1() {}
+func (r S1) M2() {}
+func(S1) m3() {} // unexported not shown
+func (*S1) P1() {} // pointer receiver
+
+type S2 int
+func (S2) M3() {} // shown on S2
+
+type s3 int
+func (s3) M4() {} // shown on S1
+
+type G1[T any] struct {
+ *s3
+}
+
+func NewG1[T any]() G1[T] { return G1[T]{} }
+
+func (G1[T]) MG1() {}
+func (*G1[U]) MG2() {}
+
+type G2[T, U any] struct {}
+
+func NewG2[T, U any]() G2[T, U] { return G2[T, U]{} }
+
+func (G2[T, U]) MG3() {}
+func (*G2[A, B]) MG4() {}
+
+
+`
+
+var funcsPackage = &Package{
+ Funcs: []*Func{{Name: "F"}},
+ Types: []*Type{
+ {
+ Name: "G1",
+ Funcs: []*Func{{Name: "NewG1"}},
+ Methods: []*Func{
+ {Name: "M4", Recv: "G1", // TODO: synthesize a param for G1?
+ Orig: "s3", Level: 1},
+ {Name: "MG1", Recv: "G1[T]", Orig: "G1[T]", Level: 0},
+ {Name: "MG2", Recv: "*G1[U]", Orig: "*G1[U]", Level: 0},
+ },
+ },
+ {
+ Name: "G2",
+ Funcs: []*Func{{Name: "NewG2"}},
+ Methods: []*Func{
+ {Name: "MG3", Recv: "G2[T, U]", Orig: "G2[T, U]", Level: 0},
+ {Name: "MG4", Recv: "*G2[A, B]", Orig: "*G2[A, B]", Level: 0},
+ },
+ },
+ {
+ Name: "S1",
+ Funcs: []*Func{{Name: "NewS1"}, {Name: "NewS1p"}},
+ Methods: []*Func{
+ {Name: "M1", Recv: "S1", Orig: "S1", Level: 0},
+ {Name: "M2", Recv: "S1", Orig: "S1", Level: 0},
+ {Name: "M4", Recv: "S1", Orig: "s3", Level: 1},
+ {Name: "P1", Recv: "*S1", Orig: "*S1", Level: 0},
+ },
+ },
+ {
+ Name: "S2",
+ Methods: []*Func{
+ {Name: "M3", Recv: "S2", Orig: "S2", Level: 0},
+ },
+ },
+ },
+}
diff --git a/src/go/doc/example.go b/src/go/doc/example.go
new file mode 100644
index 0000000..65ca885
--- /dev/null
+++ b/src/go/doc/example.go
@@ -0,0 +1,722 @@
+// Copyright 2011 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.
+
+// Extract example functions from file ASTs.
+
+package doc
+
+import (
+ "go/ast"
+ "go/token"
+ "internal/lazyregexp"
+ "path"
+ "sort"
+ "strconv"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+// An Example represents an example function found in a test source file.
+type Example struct {
+ Name string // name of the item being exemplified (including optional suffix)
+ Suffix string // example suffix, without leading '_' (only populated by NewFromFiles)
+ Doc string // example function doc string
+ Code ast.Node
+ Play *ast.File // a whole program version of the example
+ Comments []*ast.CommentGroup
+ Output string // expected output
+ Unordered bool
+ EmptyOutput bool // expect empty output
+ Order int // original source code order
+}
+
+// Examples returns the examples found in testFiles, sorted by Name field.
+// The Order fields record the order in which the examples were encountered.
+// The Suffix field is not populated when Examples is called directly, it is
+// only populated by NewFromFiles for examples it finds in _test.go files.
+//
+// Playable Examples must be in a package whose name ends in "_test".
+// An Example is "playable" (the Play field is non-nil) in either of these
+// circumstances:
+// - The example function is self-contained: the function references only
+// identifiers from other packages (or predeclared identifiers, such as
+// "int") and the test file does not include a dot import.
+// - The entire test file is the example: the file contains exactly one
+// example function, zero test, fuzz test, or benchmark function, and at
+// least one top-level function, type, variable, or constant declaration
+// other than the example function.
+func Examples(testFiles ...*ast.File) []*Example {
+ var list []*Example
+ for _, file := range testFiles {
+ hasTests := false // file contains tests, fuzz test, or benchmarks
+ numDecl := 0 // number of non-import declarations in the file
+ var flist []*Example
+ for _, decl := range file.Decls {
+ if g, ok := decl.(*ast.GenDecl); ok && g.Tok != token.IMPORT {
+ numDecl++
+ continue
+ }
+ f, ok := decl.(*ast.FuncDecl)
+ if !ok || f.Recv != nil {
+ continue
+ }
+ numDecl++
+ name := f.Name.Name
+ if isTest(name, "Test") || isTest(name, "Benchmark") || isTest(name, "Fuzz") {
+ hasTests = true
+ continue
+ }
+ if !isTest(name, "Example") {
+ continue
+ }
+ if params := f.Type.Params; len(params.List) != 0 {
+ continue // function has params; not a valid example
+ }
+ if f.Body == nil { // ast.File.Body nil dereference (see issue 28044)
+ continue
+ }
+ var doc string
+ if f.Doc != nil {
+ doc = f.Doc.Text()
+ }
+ output, unordered, hasOutput := exampleOutput(f.Body, file.Comments)
+ flist = append(flist, &Example{
+ Name: name[len("Example"):],
+ Doc: doc,
+ Code: f.Body,
+ Play: playExample(file, f),
+ Comments: file.Comments,
+ Output: output,
+ Unordered: unordered,
+ EmptyOutput: output == "" && hasOutput,
+ Order: len(flist),
+ })
+ }
+ if !hasTests && numDecl > 1 && len(flist) == 1 {
+ // If this file only has one example function, some
+ // other top-level declarations, and no tests or
+ // benchmarks, use the whole file as the example.
+ flist[0].Code = file
+ flist[0].Play = playExampleFile(file)
+ }
+ list = append(list, flist...)
+ }
+ // sort by name
+ sort.Slice(list, func(i, j int) bool {
+ return list[i].Name < list[j].Name
+ })
+ return list
+}
+
+var outputPrefix = lazyregexp.New(`(?i)^[[:space:]]*(unordered )?output:`)
+
+// Extracts the expected output and whether there was a valid output comment.
+func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) (output string, unordered, ok bool) {
+ if _, last := lastComment(b, comments); last != nil {
+ // test that it begins with the correct prefix
+ text := last.Text()
+ if loc := outputPrefix.FindStringSubmatchIndex(text); loc != nil {
+ if loc[2] != -1 {
+ unordered = true
+ }
+ text = text[loc[1]:]
+ // Strip zero or more spaces followed by \n or a single space.
+ text = strings.TrimLeft(text, " ")
+ if len(text) > 0 && text[0] == '\n' {
+ text = text[1:]
+ }
+ return text, unordered, true
+ }
+ }
+ return "", false, false // no suitable comment found
+}
+
+// isTest tells whether name looks like a test, example, fuzz test, or
+// benchmark. It is a Test (say) if there is a character after Test that is not
+// a lower-case letter. (We don't want Testiness.)
+func isTest(name, prefix string) bool {
+ if !strings.HasPrefix(name, prefix) {
+ return false
+ }
+ if len(name) == len(prefix) { // "Test" is ok
+ return true
+ }
+ rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
+ return !unicode.IsLower(rune)
+}
+
+// playExample synthesizes a new *ast.File based on the provided
+// file with the provided function body as the body of main.
+func playExample(file *ast.File, f *ast.FuncDecl) *ast.File {
+ body := f.Body
+
+ if !strings.HasSuffix(file.Name.Name, "_test") {
+ // We don't support examples that are part of the
+ // greater package (yet).
+ return nil
+ }
+
+ // Collect top-level declarations in the file.
+ topDecls := make(map[*ast.Object]ast.Decl)
+ typMethods := make(map[string][]ast.Decl)
+
+ for _, decl := range file.Decls {
+ switch d := decl.(type) {
+ case *ast.FuncDecl:
+ if d.Recv == nil {
+ topDecls[d.Name.Obj] = d
+ } else {
+ if len(d.Recv.List) == 1 {
+ t := d.Recv.List[0].Type
+ tname, _ := baseTypeName(t)
+ typMethods[tname] = append(typMethods[tname], d)
+ }
+ }
+ case *ast.GenDecl:
+ for _, spec := range d.Specs {
+ switch s := spec.(type) {
+ case *ast.TypeSpec:
+ topDecls[s.Name.Obj] = d
+ case *ast.ValueSpec:
+ for _, name := range s.Names {
+ topDecls[name.Obj] = d
+ }
+ }
+ }
+ }
+ }
+
+ // Find unresolved identifiers and uses of top-level declarations.
+ depDecls, unresolved := findDeclsAndUnresolved(body, topDecls, typMethods)
+
+ // Remove predeclared identifiers from unresolved list.
+ for n := range unresolved {
+ if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] {
+ delete(unresolved, n)
+ }
+ }
+
+ // Use unresolved identifiers to determine the imports used by this
+ // example. The heuristic assumes package names match base import
+ // paths for imports w/o renames (should be good enough most of the time).
+ var namedImports []ast.Spec
+ var blankImports []ast.Spec // _ imports
+
+ // To preserve the blank lines between groups of imports, find the
+ // start position of each group, and assign that position to all
+ // imports from that group.
+ groupStarts := findImportGroupStarts(file.Imports)
+ groupStart := func(s *ast.ImportSpec) token.Pos {
+ for i, start := range groupStarts {
+ if s.Path.ValuePos < start {
+ return groupStarts[i-1]
+ }
+ }
+ return groupStarts[len(groupStarts)-1]
+ }
+
+ for _, s := range file.Imports {
+ p, err := strconv.Unquote(s.Path.Value)
+ if err != nil {
+ continue
+ }
+ if p == "syscall/js" {
+ // We don't support examples that import syscall/js,
+ // because the package syscall/js is not available in the playground.
+ return nil
+ }
+ n := path.Base(p)
+ if s.Name != nil {
+ n = s.Name.Name
+ switch n {
+ case "_":
+ blankImports = append(blankImports, s)
+ continue
+ case ".":
+ // We can't resolve dot imports (yet).
+ return nil
+ }
+ }
+ if unresolved[n] {
+ // Copy the spec and its path to avoid modifying the original.
+ spec := *s
+ path := *s.Path
+ spec.Path = &path
+ spec.Path.ValuePos = groupStart(&spec)
+ namedImports = append(namedImports, &spec)
+ delete(unresolved, n)
+ }
+ }
+
+ // If there are other unresolved identifiers, give up because this
+ // synthesized file is not going to build.
+ if len(unresolved) > 0 {
+ return nil
+ }
+
+ // Include documentation belonging to blank imports.
+ var comments []*ast.CommentGroup
+ for _, s := range blankImports {
+ if c := s.(*ast.ImportSpec).Doc; c != nil {
+ comments = append(comments, c)
+ }
+ }
+
+ // Include comments that are inside the function body.
+ for _, c := range file.Comments {
+ if body.Pos() <= c.Pos() && c.End() <= body.End() {
+ comments = append(comments, c)
+ }
+ }
+
+ // Strip the "Output:" or "Unordered output:" comment and adjust body
+ // end position.
+ body, comments = stripOutputComment(body, comments)
+
+ // Include documentation belonging to dependent declarations.
+ for _, d := range depDecls {
+ switch d := d.(type) {
+ case *ast.GenDecl:
+ if d.Doc != nil {
+ comments = append(comments, d.Doc)
+ }
+ case *ast.FuncDecl:
+ if d.Doc != nil {
+ comments = append(comments, d.Doc)
+ }
+ }
+ }
+
+ // Synthesize import declaration.
+ importDecl := &ast.GenDecl{
+ Tok: token.IMPORT,
+ Lparen: 1, // Need non-zero Lparen and Rparen so that printer
+ Rparen: 1, // treats this as a factored import.
+ }
+ importDecl.Specs = append(namedImports, blankImports...)
+
+ // Synthesize main function.
+ funcDecl := &ast.FuncDecl{
+ Name: ast.NewIdent("main"),
+ Type: f.Type,
+ Body: body,
+ }
+
+ decls := make([]ast.Decl, 0, 2+len(depDecls))
+ decls = append(decls, importDecl)
+ decls = append(decls, depDecls...)
+ decls = append(decls, funcDecl)
+
+ sort.Slice(decls, func(i, j int) bool {
+ return decls[i].Pos() < decls[j].Pos()
+ })
+ sort.Slice(comments, func(i, j int) bool {
+ return comments[i].Pos() < comments[j].Pos()
+ })
+
+ // Synthesize file.
+ return &ast.File{
+ Name: ast.NewIdent("main"),
+ Decls: decls,
+ Comments: comments,
+ }
+}
+
+// findDeclsAndUnresolved returns all the top-level declarations mentioned in
+// the body, and a set of unresolved symbols (those that appear in the body but
+// have no declaration in the program).
+//
+// topDecls maps objects to the top-level declaration declaring them (not
+// necessarily obj.Decl, as obj.Decl will be a Spec for GenDecls, but
+// topDecls[obj] will be the GenDecl itself).
+func findDeclsAndUnresolved(body ast.Node, topDecls map[*ast.Object]ast.Decl, typMethods map[string][]ast.Decl) ([]ast.Decl, map[string]bool) {
+ // This function recursively finds every top-level declaration used
+ // transitively by the body, populating usedDecls and usedObjs. Then it
+ // trims down the declarations to include only the symbols actually
+ // referenced by the body.
+
+ unresolved := make(map[string]bool)
+ var depDecls []ast.Decl
+ usedDecls := make(map[ast.Decl]bool) // set of top-level decls reachable from the body
+ usedObjs := make(map[*ast.Object]bool) // set of objects reachable from the body (each declared by a usedDecl)
+
+ var inspectFunc func(ast.Node) bool
+ inspectFunc = func(n ast.Node) bool {
+ switch e := n.(type) {
+ case *ast.Ident:
+ if e.Obj == nil && e.Name != "_" {
+ unresolved[e.Name] = true
+ } else if d := topDecls[e.Obj]; d != nil {
+
+ usedObjs[e.Obj] = true
+ if !usedDecls[d] {
+ usedDecls[d] = true
+ depDecls = append(depDecls, d)
+ }
+ }
+ return true
+ case *ast.SelectorExpr:
+ // For selector expressions, only inspect the left hand side.
+ // (For an expression like fmt.Println, only add "fmt" to the
+ // set of unresolved names, not "Println".)
+ ast.Inspect(e.X, inspectFunc)
+ return false
+ case *ast.KeyValueExpr:
+ // For key value expressions, only inspect the value
+ // as the key should be resolved by the type of the
+ // composite literal.
+ ast.Inspect(e.Value, inspectFunc)
+ return false
+ }
+ return true
+ }
+
+ inspectFieldList := func(fl *ast.FieldList) {
+ if fl != nil {
+ for _, f := range fl.List {
+ ast.Inspect(f.Type, inspectFunc)
+ }
+ }
+ }
+
+ // Find the decls immediately referenced by body.
+ ast.Inspect(body, inspectFunc)
+ // Now loop over them, adding to the list when we find a new decl that the
+ // body depends on. Keep going until we don't find anything new.
+ for i := 0; i < len(depDecls); i++ {
+ switch d := depDecls[i].(type) {
+ case *ast.FuncDecl:
+ // Inpect type parameters.
+ inspectFieldList(d.Type.TypeParams)
+ // Inspect types of parameters and results. See #28492.
+ inspectFieldList(d.Type.Params)
+ inspectFieldList(d.Type.Results)
+
+ // Functions might not have a body. See #42706.
+ if d.Body != nil {
+ ast.Inspect(d.Body, inspectFunc)
+ }
+ case *ast.GenDecl:
+ for _, spec := range d.Specs {
+ switch s := spec.(type) {
+ case *ast.TypeSpec:
+ inspectFieldList(s.TypeParams)
+ ast.Inspect(s.Type, inspectFunc)
+ depDecls = append(depDecls, typMethods[s.Name.Name]...)
+ case *ast.ValueSpec:
+ if s.Type != nil {
+ ast.Inspect(s.Type, inspectFunc)
+ }
+ for _, val := range s.Values {
+ ast.Inspect(val, inspectFunc)
+ }
+ }
+ }
+ }
+ }
+
+ // Some decls include multiple specs, such as a variable declaration with
+ // multiple variables on the same line, or a parenthesized declaration. Trim
+ // the declarations to include only the specs that are actually mentioned.
+ // However, if there is a constant group with iota, leave it all: later
+ // constant declarations in the group may have no value and so cannot stand
+ // on their own, and removing any constant from the group could change the
+ // values of subsequent ones.
+ // See testdata/examples/iota.go for a minimal example.
+ var ds []ast.Decl
+ for _, d := range depDecls {
+ switch d := d.(type) {
+ case *ast.FuncDecl:
+ ds = append(ds, d)
+ case *ast.GenDecl:
+ containsIota := false // does any spec have iota?
+ // Collect all Specs that were mentioned in the example.
+ var specs []ast.Spec
+ for _, s := range d.Specs {
+ switch s := s.(type) {
+ case *ast.TypeSpec:
+ if usedObjs[s.Name.Obj] {
+ specs = append(specs, s)
+ }
+ case *ast.ValueSpec:
+ if !containsIota {
+ containsIota = hasIota(s)
+ }
+ // A ValueSpec may have multiple names (e.g. "var a, b int").
+ // Keep only the names that were mentioned in the example.
+ // Exception: the multiple names have a single initializer (which
+ // would be a function call with multiple return values). In that
+ // case, keep everything.
+ if len(s.Names) > 1 && len(s.Values) == 1 {
+ specs = append(specs, s)
+ continue
+ }
+ ns := *s
+ ns.Names = nil
+ ns.Values = nil
+ for i, n := range s.Names {
+ if usedObjs[n.Obj] {
+ ns.Names = append(ns.Names, n)
+ if s.Values != nil {
+ ns.Values = append(ns.Values, s.Values[i])
+ }
+ }
+ }
+ if len(ns.Names) > 0 {
+ specs = append(specs, &ns)
+ }
+ }
+ }
+ if len(specs) > 0 {
+ // Constant with iota? Keep it all.
+ if d.Tok == token.CONST && containsIota {
+ ds = append(ds, d)
+ } else {
+ // Synthesize a GenDecl with just the Specs we need.
+ nd := *d // copy the GenDecl
+ nd.Specs = specs
+ if len(specs) == 1 {
+ // Remove grouping parens if there is only one spec.
+ nd.Lparen = 0
+ }
+ ds = append(ds, &nd)
+ }
+ }
+ }
+ }
+ return ds, unresolved
+}
+
+func hasIota(s ast.Spec) bool {
+ has := false
+ ast.Inspect(s, func(n ast.Node) bool {
+ // Check that this is the special built-in "iota" identifier, not
+ // a user-defined shadow.
+ if id, ok := n.(*ast.Ident); ok && id.Name == "iota" && id.Obj == nil {
+ has = true
+ return false
+ }
+ return true
+ })
+ return has
+}
+
+// findImportGroupStarts finds the start positions of each sequence of import
+// specs that are not separated by a blank line.
+func findImportGroupStarts(imps []*ast.ImportSpec) []token.Pos {
+ startImps := findImportGroupStarts1(imps)
+ groupStarts := make([]token.Pos, len(startImps))
+ for i, imp := range startImps {
+ groupStarts[i] = imp.Pos()
+ }
+ return groupStarts
+}
+
+// Helper for findImportGroupStarts to ease testing.
+func findImportGroupStarts1(origImps []*ast.ImportSpec) []*ast.ImportSpec {
+ // Copy to avoid mutation.
+ imps := make([]*ast.ImportSpec, len(origImps))
+ copy(imps, origImps)
+ // Assume the imports are sorted by position.
+ sort.Slice(imps, func(i, j int) bool { return imps[i].Pos() < imps[j].Pos() })
+ // Assume gofmt has been applied, so there is a blank line between adjacent imps
+ // if and only if they are more than 2 positions apart (newline, tab).
+ var groupStarts []*ast.ImportSpec
+ prevEnd := token.Pos(-2)
+ for _, imp := range imps {
+ if imp.Pos()-prevEnd > 2 {
+ groupStarts = append(groupStarts, imp)
+ }
+ prevEnd = imp.End()
+ // Account for end-of-line comments.
+ if imp.Comment != nil {
+ prevEnd = imp.Comment.End()
+ }
+ }
+ return groupStarts
+}
+
+// playExampleFile takes a whole file example and synthesizes a new *ast.File
+// such that the example is function main in package main.
+func playExampleFile(file *ast.File) *ast.File {
+ // Strip copyright comment if present.
+ comments := file.Comments
+ if len(comments) > 0 && strings.HasPrefix(comments[0].Text(), "Copyright") {
+ comments = comments[1:]
+ }
+
+ // Copy declaration slice, rewriting the ExampleX function to main.
+ var decls []ast.Decl
+ for _, d := range file.Decls {
+ if f, ok := d.(*ast.FuncDecl); ok && isTest(f.Name.Name, "Example") {
+ // Copy the FuncDecl, as it may be used elsewhere.
+ newF := *f
+ newF.Name = ast.NewIdent("main")
+ newF.Body, comments = stripOutputComment(f.Body, comments)
+ d = &newF
+ }
+ decls = append(decls, d)
+ }
+
+ // Copy the File, as it may be used elsewhere.
+ f := *file
+ f.Name = ast.NewIdent("main")
+ f.Decls = decls
+ f.Comments = comments
+ return &f
+}
+
+// stripOutputComment finds and removes the "Output:" or "Unordered output:"
+// comment from body and comments, and adjusts the body block's end position.
+func stripOutputComment(body *ast.BlockStmt, comments []*ast.CommentGroup) (*ast.BlockStmt, []*ast.CommentGroup) {
+ // Do nothing if there is no "Output:" or "Unordered output:" comment.
+ i, last := lastComment(body, comments)
+ if last == nil || !outputPrefix.MatchString(last.Text()) {
+ return body, comments
+ }
+
+ // Copy body and comments, as the originals may be used elsewhere.
+ newBody := &ast.BlockStmt{
+ Lbrace: body.Lbrace,
+ List: body.List,
+ Rbrace: last.Pos(),
+ }
+ newComments := make([]*ast.CommentGroup, len(comments)-1)
+ copy(newComments, comments[:i])
+ copy(newComments[i:], comments[i+1:])
+ return newBody, newComments
+}
+
+// lastComment returns the last comment inside the provided block.
+func lastComment(b *ast.BlockStmt, c []*ast.CommentGroup) (i int, last *ast.CommentGroup) {
+ if b == nil {
+ return
+ }
+ pos, end := b.Pos(), b.End()
+ for j, cg := range c {
+ if cg.Pos() < pos {
+ continue
+ }
+ if cg.End() > end {
+ break
+ }
+ i, last = j, cg
+ }
+ return
+}
+
+// classifyExamples classifies examples and assigns them to the Examples field
+// of the relevant Func, Type, or Package that the example is associated with.
+//
+// The classification process is ambiguous in some cases:
+//
+// - ExampleFoo_Bar matches a type named Foo_Bar
+// or a method named Foo.Bar.
+// - ExampleFoo_bar matches a type named Foo_bar
+// or Foo (with a "bar" suffix).
+//
+// Examples with malformed names are not associated with anything.
+func classifyExamples(p *Package, examples []*Example) {
+ if len(examples) == 0 {
+ return
+ }
+ // Mapping of names for funcs, types, and methods to the example listing.
+ ids := make(map[string]*[]*Example)
+ ids[""] = &p.Examples // package-level examples have an empty name
+ for _, f := range p.Funcs {
+ if !token.IsExported(f.Name) {
+ continue
+ }
+ ids[f.Name] = &f.Examples
+ }
+ for _, t := range p.Types {
+ if !token.IsExported(t.Name) {
+ continue
+ }
+ ids[t.Name] = &t.Examples
+ for _, f := range t.Funcs {
+ if !token.IsExported(f.Name) {
+ continue
+ }
+ ids[f.Name] = &f.Examples
+ }
+ for _, m := range t.Methods {
+ if !token.IsExported(m.Name) {
+ continue
+ }
+ ids[strings.TrimPrefix(nameWithoutInst(m.Recv), "*")+"_"+m.Name] = &m.Examples
+ }
+ }
+
+ // Group each example with the associated func, type, or method.
+ for _, ex := range examples {
+ // Consider all possible split points for the suffix
+ // by starting at the end of string (no suffix case),
+ // then trying all positions that contain a '_' character.
+ //
+ // An association is made on the first successful match.
+ // Examples with malformed names that match nothing are skipped.
+ for i := len(ex.Name); i >= 0; i = strings.LastIndexByte(ex.Name[:i], '_') {
+ prefix, suffix, ok := splitExampleName(ex.Name, i)
+ if !ok {
+ continue
+ }
+ exs, ok := ids[prefix]
+ if !ok {
+ continue
+ }
+ ex.Suffix = suffix
+ *exs = append(*exs, ex)
+ break
+ }
+ }
+
+ // Sort list of example according to the user-specified suffix name.
+ for _, exs := range ids {
+ sort.Slice((*exs), func(i, j int) bool {
+ return (*exs)[i].Suffix < (*exs)[j].Suffix
+ })
+ }
+}
+
+// nameWithoutInst returns name if name has no brackets. If name contains
+// brackets, then it returns name with all the contents between (and including)
+// the outermost left and right bracket removed.
+//
+// Adapted from debug/gosym/symtab.go:Sym.nameWithoutInst.
+func nameWithoutInst(name string) string {
+ start := strings.Index(name, "[")
+ if start < 0 {
+ return name
+ }
+ end := strings.LastIndex(name, "]")
+ if end < 0 {
+ // Malformed name, should contain closing bracket too.
+ return name
+ }
+ return name[0:start] + name[end+1:]
+}
+
+// splitExampleName attempts to split example name s at index i,
+// and reports if that produces a valid split. The suffix may be
+// absent. Otherwise, it must start with a lower-case letter and
+// be preceded by '_'.
+//
+// One of i == len(s) or s[i] == '_' must be true.
+func splitExampleName(s string, i int) (prefix, suffix string, ok bool) {
+ if i == len(s) {
+ return s, "", true
+ }
+ if i == len(s)-1 {
+ return "", "", false
+ }
+ prefix, suffix = s[:i], s[i+1:]
+ return prefix, suffix, isExampleSuffix(suffix)
+}
+
+func isExampleSuffix(s string) bool {
+ r, size := utf8.DecodeRuneInString(s)
+ return size > 0 && unicode.IsLower(r)
+}
diff --git a/src/go/doc/example_internal_test.go b/src/go/doc/example_internal_test.go
new file mode 100644
index 0000000..08ddfaf
--- /dev/null
+++ b/src/go/doc/example_internal_test.go
@@ -0,0 +1,121 @@
+// 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 doc
+
+import (
+ "go/parser"
+ "go/token"
+ "reflect"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+func TestImportGroupStarts(t *testing.T) {
+ for _, test := range []struct {
+ name string
+ in string
+ want []string // paths of group-starting imports
+ }{
+ {
+ name: "one group",
+ in: `package p
+import (
+ "a"
+ "b"
+ "c"
+ "d"
+)
+`,
+ want: []string{"a"},
+ },
+ {
+ name: "several groups",
+ in: `package p
+import (
+ "a"
+
+ "b"
+ "c"
+
+ "d"
+)
+`,
+ want: []string{"a", "b", "d"},
+ },
+ {
+ name: "extra space",
+ in: `package p
+import (
+ "a"
+
+
+ "b"
+ "c"
+
+
+ "d"
+)
+`,
+ want: []string{"a", "b", "d"},
+ },
+ {
+ name: "line comment",
+ in: `package p
+import (
+ "a" // comment
+ "b" // comment
+
+ "c"
+)`,
+ want: []string{"a", "c"},
+ },
+ {
+ name: "named import",
+ in: `package p
+import (
+ "a"
+ n "b"
+
+ m "c"
+ "d"
+)`,
+ want: []string{"a", "c"},
+ },
+ {
+ name: "blank import",
+ in: `package p
+import (
+ "a"
+
+ _ "b"
+
+ _ "c"
+ "d"
+)`,
+ want: []string{"a", "b", "c"},
+ },
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ fset := token.NewFileSet()
+ file, err := parser.ParseFile(fset, "test.go", strings.NewReader(test.in), parser.ParseComments)
+ if err != nil {
+ t.Fatal(err)
+ }
+ imps := findImportGroupStarts1(file.Imports)
+ got := make([]string, len(imps))
+ for i, imp := range imps {
+ got[i], err = strconv.Unquote(imp.Path.Value)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ if !reflect.DeepEqual(got, test.want) {
+ t.Errorf("got %v, want %v", got, test.want)
+ }
+ })
+ }
+
+}
diff --git a/src/go/doc/example_test.go b/src/go/doc/example_test.go
new file mode 100644
index 0000000..7919c3a
--- /dev/null
+++ b/src/go/doc/example_test.go
@@ -0,0 +1,336 @@
+// Copyright 2013 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 doc_test
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/doc"
+ "go/format"
+ "go/parser"
+ "go/token"
+ "internal/diff"
+ "internal/txtar"
+ "path/filepath"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func TestExamples(t *testing.T) {
+ dir := filepath.Join("testdata", "examples")
+ filenames, err := filepath.Glob(filepath.Join(dir, "*.go"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, filename := range filenames {
+ t.Run(strings.TrimSuffix(filepath.Base(filename), ".go"), func(t *testing.T) {
+ fset := token.NewFileSet()
+ astFile, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
+ if err != nil {
+ t.Fatal(err)
+ }
+ goldenFilename := strings.TrimSuffix(filename, ".go") + ".golden"
+ archive, err := txtar.ParseFile(goldenFilename)
+ if err != nil {
+ t.Fatal(err)
+ }
+ golden := map[string]string{}
+ for _, f := range archive.Files {
+ golden[f.Name] = strings.TrimSpace(string(f.Data))
+ }
+
+ // Collect the results of doc.Examples in a map keyed by example name.
+ examples := map[string]*doc.Example{}
+ for _, e := range doc.Examples(astFile) {
+ examples[e.Name] = e
+ // Treat missing sections in the golden as empty.
+ for _, kind := range []string{"Play", "Output"} {
+ key := e.Name + "." + kind
+ if _, ok := golden[key]; !ok {
+ golden[key] = ""
+ }
+ }
+ }
+
+ // Each section in the golden file corresponds to an example we expect
+ // to see.
+ for sectionName, want := range golden {
+ name, kind, found := strings.Cut(sectionName, ".")
+ if !found {
+ t.Fatalf("bad section name %q, want EXAMPLE_NAME.KIND", sectionName)
+ }
+ ex := examples[name]
+ if ex == nil {
+ t.Fatalf("no example named %q", name)
+ }
+
+ var got string
+ switch kind {
+ case "Play":
+ got = strings.TrimSpace(formatFile(t, fset, ex.Play))
+
+ case "Output":
+ got = strings.TrimSpace(ex.Output)
+ default:
+ t.Fatalf("bad section kind %q", kind)
+ }
+
+ if got != want {
+ t.Errorf("%s mismatch:\n%s", sectionName,
+ diff.Diff("want", []byte(want), "got", []byte(got)))
+ }
+ }
+ })
+ }
+}
+
+func formatFile(t *testing.T, fset *token.FileSet, n *ast.File) string {
+ t.Helper()
+ if n == nil {
+ return "<nil>"
+ }
+ var buf bytes.Buffer
+ if err := format.Node(&buf, fset, n); err != nil {
+ t.Fatal(err)
+ }
+ return buf.String()
+}
+
+// This example illustrates how to use NewFromFiles
+// to compute package documentation with examples.
+func ExampleNewFromFiles() {
+ // src and test are two source files that make up
+ // a package whose documentation will be computed.
+ const src = `
+// This is the package comment.
+package p
+
+import "fmt"
+
+// This comment is associated with the Greet function.
+func Greet(who string) {
+ fmt.Printf("Hello, %s!\n", who)
+}
+`
+ const test = `
+package p_test
+
+// This comment is associated with the ExampleGreet_world example.
+func ExampleGreet_world() {
+ Greet("world")
+}
+`
+
+ // Create the AST by parsing src and test.
+ fset := token.NewFileSet()
+ files := []*ast.File{
+ mustParse(fset, "src.go", src),
+ mustParse(fset, "src_test.go", test),
+ }
+
+ // Compute package documentation with examples.
+ p, err := doc.NewFromFiles(fset, files, "example.com/p")
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Printf("package %s - %s", p.Name, p.Doc)
+ fmt.Printf("func %s - %s", p.Funcs[0].Name, p.Funcs[0].Doc)
+ fmt.Printf(" ⤷ example with suffix %q - %s", p.Funcs[0].Examples[0].Suffix, p.Funcs[0].Examples[0].Doc)
+
+ // Output:
+ // package p - This is the package comment.
+ // func Greet - This comment is associated with the Greet function.
+ // ⤷ example with suffix "world" - This comment is associated with the ExampleGreet_world example.
+}
+
+func TestClassifyExamples(t *testing.T) {
+ const src = `
+package p
+
+const Const1 = 0
+var Var1 = 0
+
+type (
+ Type1 int
+ Type1_Foo int
+ Type1_foo int
+ type2 int
+
+ Embed struct { Type1 }
+ Uembed struct { type2 }
+)
+
+func Func1() {}
+func Func1_Foo() {}
+func Func1_foo() {}
+func func2() {}
+
+func (Type1) Func1() {}
+func (Type1) Func1_Foo() {}
+func (Type1) Func1_foo() {}
+func (Type1) func2() {}
+
+func (type2) Func1() {}
+
+type (
+ Conflict int
+ Conflict_Conflict int
+ Conflict_conflict int
+)
+
+func (Conflict) Conflict() {}
+
+func GFunc[T any]() {}
+
+type GType[T any] int
+
+func (GType[T]) M() {}
+`
+ const test = `
+package p_test
+
+func ExampleConst1() {} // invalid - no support for consts and vars
+func ExampleVar1() {} // invalid - no support for consts and vars
+
+func Example() {}
+func Example_() {} // invalid - suffix must start with a lower-case letter
+func Example_suffix() {}
+func Example_suffix_xX_X_x() {}
+func Example_世界() {} // invalid - suffix must start with a lower-case letter
+func Example_123() {} // invalid - suffix must start with a lower-case letter
+func Example_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+
+func ExampleType1() {}
+func ExampleType1_() {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_suffix() {}
+func ExampleType1_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_Foo() {}
+func ExampleType1_Foo_suffix() {}
+func ExampleType1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_foo() {}
+func ExampleType1_foo_suffix() {}
+func ExampleType1_foo_Suffix() {} // matches Type1, instead of Type1_foo
+func Exampletype2() {} // invalid - cannot match unexported
+
+func ExampleFunc1() {}
+func ExampleFunc1_() {} // invalid - suffix must start with a lower-case letter
+func ExampleFunc1_suffix() {}
+func ExampleFunc1_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+func ExampleFunc1_Foo() {}
+func ExampleFunc1_Foo_suffix() {}
+func ExampleFunc1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+func ExampleFunc1_foo() {}
+func ExampleFunc1_foo_suffix() {}
+func ExampleFunc1_foo_Suffix() {} // matches Func1, instead of Func1_foo
+func Examplefunc1() {} // invalid - cannot match unexported
+
+func ExampleType1_Func1() {}
+func ExampleType1_Func1_() {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_Func1_suffix() {}
+func ExampleType1_Func1_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_Func1_Foo() {}
+func ExampleType1_Func1_Foo_suffix() {}
+func ExampleType1_Func1_Foo_BadSuffix() {} // invalid - suffix must start with a lower-case letter
+func ExampleType1_Func1_foo() {}
+func ExampleType1_Func1_foo_suffix() {}
+func ExampleType1_Func1_foo_Suffix() {} // matches Type1.Func1, instead of Type1.Func1_foo
+func ExampleType1_func2() {} // matches Type1, instead of Type1.func2
+
+func ExampleEmbed_Func1() {} // invalid - no support for forwarded methods from embedding exported type
+func ExampleUembed_Func1() {} // methods from embedding unexported types are OK
+func ExampleUembed_Func1_suffix() {}
+
+func ExampleConflict_Conflict() {} // ambiguous with either Conflict or Conflict_Conflict type
+func ExampleConflict_conflict() {} // ambiguous with either Conflict or Conflict_conflict type
+func ExampleConflict_Conflict_suffix() {} // ambiguous with either Conflict or Conflict_Conflict type
+func ExampleConflict_conflict_suffix() {} // ambiguous with either Conflict or Conflict_conflict type
+
+func ExampleGFunc() {}
+func ExampleGFunc_suffix() {}
+
+func ExampleGType_M() {}
+func ExampleGType_M_suffix() {}
+`
+
+ // Parse literal source code as a *doc.Package.
+ fset := token.NewFileSet()
+ files := []*ast.File{
+ mustParse(fset, "src.go", src),
+ mustParse(fset, "src_test.go", test),
+ }
+ p, err := doc.NewFromFiles(fset, files, "example.com/p")
+ if err != nil {
+ t.Fatalf("doc.NewFromFiles: %v", err)
+ }
+
+ // Collect the association of examples to top-level identifiers.
+ got := map[string][]string{}
+ got[""] = exampleNames(p.Examples)
+ for _, f := range p.Funcs {
+ got[f.Name] = exampleNames(f.Examples)
+ }
+ for _, t := range p.Types {
+ got[t.Name] = exampleNames(t.Examples)
+ for _, f := range t.Funcs {
+ got[f.Name] = exampleNames(f.Examples)
+ }
+ for _, m := range t.Methods {
+ got[t.Name+"."+m.Name] = exampleNames(m.Examples)
+ }
+ }
+
+ want := map[string][]string{
+ "": {"", "suffix", "suffix_xX_X_x"}, // Package-level examples.
+
+ "Type1": {"", "foo_Suffix", "func2", "suffix"},
+ "Type1_Foo": {"", "suffix"},
+ "Type1_foo": {"", "suffix"},
+
+ "Func1": {"", "foo_Suffix", "suffix"},
+ "Func1_Foo": {"", "suffix"},
+ "Func1_foo": {"", "suffix"},
+
+ "Type1.Func1": {"", "foo_Suffix", "suffix"},
+ "Type1.Func1_Foo": {"", "suffix"},
+ "Type1.Func1_foo": {"", "suffix"},
+
+ "Uembed.Func1": {"", "suffix"},
+
+ // These are implementation dependent due to the ambiguous parsing.
+ "Conflict_Conflict": {"", "suffix"},
+ "Conflict_conflict": {"", "suffix"},
+
+ "GFunc": {"", "suffix"},
+ "GType.M": {"", "suffix"},
+ }
+
+ for id := range got {
+ if !reflect.DeepEqual(got[id], want[id]) {
+ t.Errorf("classification mismatch for %q:\ngot %q\nwant %q", id, got[id], want[id])
+ }
+ delete(want, id)
+ }
+ if len(want) > 0 {
+ t.Errorf("did not find:\n%q", want)
+ }
+}
+
+func exampleNames(exs []*doc.Example) (out []string) {
+ for _, ex := range exs {
+ out = append(out, ex.Suffix)
+ }
+ return out
+}
+
+func mustParse(fset *token.FileSet, filename, src string) *ast.File {
+ f, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
+ if err != nil {
+ panic(err)
+ }
+ return f
+}
diff --git a/src/go/doc/exports.go b/src/go/doc/exports.go
new file mode 100644
index 0000000..655e889
--- /dev/null
+++ b/src/go/doc/exports.go
@@ -0,0 +1,324 @@
+// Copyright 2011 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.
+
+// This file implements export filtering of an AST.
+
+package doc
+
+import (
+ "go/ast"
+ "go/token"
+)
+
+// filterIdentList removes unexported names from list in place
+// and returns the resulting list.
+func filterIdentList(list []*ast.Ident) []*ast.Ident {
+ j := 0
+ for _, x := range list {
+ if token.IsExported(x.Name) {
+ list[j] = x
+ j++
+ }
+ }
+ return list[0:j]
+}
+
+var underscore = ast.NewIdent("_")
+
+func filterCompositeLit(lit *ast.CompositeLit, filter Filter, export bool) {
+ n := len(lit.Elts)
+ lit.Elts = filterExprList(lit.Elts, filter, export)
+ if len(lit.Elts) < n {
+ lit.Incomplete = true
+ }
+}
+
+func filterExprList(list []ast.Expr, filter Filter, export bool) []ast.Expr {
+ j := 0
+ for _, exp := range list {
+ switch x := exp.(type) {
+ case *ast.CompositeLit:
+ filterCompositeLit(x, filter, export)
+ case *ast.KeyValueExpr:
+ if x, ok := x.Key.(*ast.Ident); ok && !filter(x.Name) {
+ continue
+ }
+ if x, ok := x.Value.(*ast.CompositeLit); ok {
+ filterCompositeLit(x, filter, export)
+ }
+ }
+ list[j] = exp
+ j++
+ }
+ return list[0:j]
+}
+
+// updateIdentList replaces all unexported identifiers with underscore
+// and reports whether at least one exported name exists.
+func updateIdentList(list []*ast.Ident) (hasExported bool) {
+ for i, x := range list {
+ if token.IsExported(x.Name) {
+ hasExported = true
+ } else {
+ list[i] = underscore
+ }
+ }
+ return hasExported
+}
+
+// hasExportedName reports whether list contains any exported names.
+func hasExportedName(list []*ast.Ident) bool {
+ for _, x := range list {
+ if x.IsExported() {
+ return true
+ }
+ }
+ return false
+}
+
+// removeAnonymousField removes anonymous fields named name from an interface.
+func removeAnonymousField(name string, ityp *ast.InterfaceType) {
+ list := ityp.Methods.List // we know that ityp.Methods != nil
+ j := 0
+ for _, field := range list {
+ keepField := true
+ if n := len(field.Names); n == 0 {
+ // anonymous field
+ if fname, _ := baseTypeName(field.Type); fname == name {
+ keepField = false
+ }
+ }
+ if keepField {
+ list[j] = field
+ j++
+ }
+ }
+ if j < len(list) {
+ ityp.Incomplete = true
+ }
+ ityp.Methods.List = list[0:j]
+}
+
+// filterFieldList removes unexported fields (field names) from the field list
+// in place and reports whether fields were removed. Anonymous fields are
+// recorded with the parent type. filterType is called with the types of
+// all remaining fields.
+func (r *reader) filterFieldList(parent *namedType, fields *ast.FieldList, ityp *ast.InterfaceType) (removedFields bool) {
+ if fields == nil {
+ return
+ }
+ list := fields.List
+ j := 0
+ for _, field := range list {
+ keepField := false
+ if n := len(field.Names); n == 0 {
+ // anonymous field or embedded type or union element
+ fname := r.recordAnonymousField(parent, field.Type)
+ if fname != "" {
+ if token.IsExported(fname) {
+ keepField = true
+ } else if ityp != nil && predeclaredTypes[fname] {
+ // possibly an embedded predeclared type; keep it for now but
+ // remember this interface so that it can be fixed if name is also
+ // defined locally
+ keepField = true
+ r.remember(fname, ityp)
+ }
+ } else {
+ // If we're operating on an interface, assume that this is an embedded
+ // type or union element.
+ //
+ // TODO(rfindley): consider traversing into approximation/unions
+ // elements to see if they are entirely unexported.
+ keepField = ityp != nil
+ }
+ } else {
+ field.Names = filterIdentList(field.Names)
+ if len(field.Names) < n {
+ removedFields = true
+ }
+ if len(field.Names) > 0 {
+ keepField = true
+ }
+ }
+ if keepField {
+ r.filterType(nil, field.Type)
+ list[j] = field
+ j++
+ }
+ }
+ if j < len(list) {
+ removedFields = true
+ }
+ fields.List = list[0:j]
+ return
+}
+
+// filterParamList applies filterType to each parameter type in fields.
+func (r *reader) filterParamList(fields *ast.FieldList) {
+ if fields != nil {
+ for _, f := range fields.List {
+ r.filterType(nil, f.Type)
+ }
+ }
+}
+
+// filterType strips any unexported struct fields or method types from typ
+// in place. If fields (or methods) have been removed, the corresponding
+// struct or interface type has the Incomplete field set to true.
+func (r *reader) filterType(parent *namedType, typ ast.Expr) {
+ switch t := typ.(type) {
+ case *ast.Ident:
+ // nothing to do
+ case *ast.ParenExpr:
+ r.filterType(nil, t.X)
+ case *ast.StarExpr: // possibly an embedded type literal
+ r.filterType(nil, t.X)
+ case *ast.UnaryExpr:
+ if t.Op == token.TILDE { // approximation element
+ r.filterType(nil, t.X)
+ }
+ case *ast.BinaryExpr:
+ if t.Op == token.OR { // union
+ r.filterType(nil, t.X)
+ r.filterType(nil, t.Y)
+ }
+ case *ast.ArrayType:
+ r.filterType(nil, t.Elt)
+ case *ast.StructType:
+ if r.filterFieldList(parent, t.Fields, nil) {
+ t.Incomplete = true
+ }
+ case *ast.FuncType:
+ r.filterParamList(t.TypeParams)
+ r.filterParamList(t.Params)
+ r.filterParamList(t.Results)
+ case *ast.InterfaceType:
+ if r.filterFieldList(parent, t.Methods, t) {
+ t.Incomplete = true
+ }
+ case *ast.MapType:
+ r.filterType(nil, t.Key)
+ r.filterType(nil, t.Value)
+ case *ast.ChanType:
+ r.filterType(nil, t.Value)
+ }
+}
+
+func (r *reader) filterSpec(spec ast.Spec) bool {
+ switch s := spec.(type) {
+ case *ast.ImportSpec:
+ // always keep imports so we can collect them
+ return true
+ case *ast.ValueSpec:
+ s.Values = filterExprList(s.Values, token.IsExported, true)
+ if len(s.Values) > 0 || s.Type == nil && len(s.Values) == 0 {
+ // If there are values declared on RHS, just replace the unexported
+ // identifiers on the LHS with underscore, so that it matches
+ // the sequence of expression on the RHS.
+ //
+ // Similarly, if there are no type and values, then this expression
+ // must be following an iota expression, where order matters.
+ if updateIdentList(s.Names) {
+ r.filterType(nil, s.Type)
+ return true
+ }
+ } else {
+ s.Names = filterIdentList(s.Names)
+ if len(s.Names) > 0 {
+ r.filterType(nil, s.Type)
+ return true
+ }
+ }
+ case *ast.TypeSpec:
+ // Don't filter type parameters here, by analogy with function parameters
+ // which are not filtered for top-level function declarations.
+ if name := s.Name.Name; token.IsExported(name) {
+ r.filterType(r.lookupType(s.Name.Name), s.Type)
+ return true
+ } else if IsPredeclared(name) {
+ if r.shadowedPredecl == nil {
+ r.shadowedPredecl = make(map[string]bool)
+ }
+ r.shadowedPredecl[name] = true
+ }
+ }
+ return false
+}
+
+// copyConstType returns a copy of typ with position pos.
+// typ must be a valid constant type.
+// In practice, only (possibly qualified) identifiers are possible.
+func copyConstType(typ ast.Expr, pos token.Pos) ast.Expr {
+ switch typ := typ.(type) {
+ case *ast.Ident:
+ return &ast.Ident{Name: typ.Name, NamePos: pos}
+ case *ast.SelectorExpr:
+ if id, ok := typ.X.(*ast.Ident); ok {
+ // presumably a qualified identifier
+ return &ast.SelectorExpr{
+ Sel: ast.NewIdent(typ.Sel.Name),
+ X: &ast.Ident{Name: id.Name, NamePos: pos},
+ }
+ }
+ }
+ return nil // shouldn't happen, but be conservative and don't panic
+}
+
+func (r *reader) filterSpecList(list []ast.Spec, tok token.Token) []ast.Spec {
+ if tok == token.CONST {
+ // Propagate any type information that would get lost otherwise
+ // when unexported constants are filtered.
+ var prevType ast.Expr
+ for _, spec := range list {
+ spec := spec.(*ast.ValueSpec)
+ if spec.Type == nil && len(spec.Values) == 0 && prevType != nil {
+ // provide current spec with an explicit type
+ spec.Type = copyConstType(prevType, spec.Pos())
+ }
+ if hasExportedName(spec.Names) {
+ // exported names are preserved so there's no need to propagate the type
+ prevType = nil
+ } else {
+ prevType = spec.Type
+ }
+ }
+ }
+
+ j := 0
+ for _, s := range list {
+ if r.filterSpec(s) {
+ list[j] = s
+ j++
+ }
+ }
+ return list[0:j]
+}
+
+func (r *reader) filterDecl(decl ast.Decl) bool {
+ switch d := decl.(type) {
+ case *ast.GenDecl:
+ d.Specs = r.filterSpecList(d.Specs, d.Tok)
+ return len(d.Specs) > 0
+ case *ast.FuncDecl:
+ // ok to filter these methods early because any
+ // conflicting method will be filtered here, too -
+ // thus, removing these methods early will not lead
+ // to the false removal of possible conflicts
+ return token.IsExported(d.Name.Name)
+ }
+ return false
+}
+
+// fileExports removes unexported declarations from src in place.
+func (r *reader) fileExports(src *ast.File) {
+ j := 0
+ for _, d := range src.Decls {
+ if r.filterDecl(d) {
+ src.Decls[j] = d
+ j++
+ }
+ }
+ src.Decls = src.Decls[0:j]
+}
diff --git a/src/go/doc/filter.go b/src/go/doc/filter.go
new file mode 100644
index 0000000..f8d3e1f
--- /dev/null
+++ b/src/go/doc/filter.go
@@ -0,0 +1,106 @@
+// Copyright 2009 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 doc
+
+import "go/ast"
+
+type Filter func(string) bool
+
+func matchFields(fields *ast.FieldList, f Filter) bool {
+ if fields != nil {
+ for _, field := range fields.List {
+ for _, name := range field.Names {
+ if f(name.Name) {
+ return true
+ }
+ }
+ }
+ }
+ return false
+}
+
+func matchDecl(d *ast.GenDecl, f Filter) bool {
+ for _, d := range d.Specs {
+ switch v := d.(type) {
+ case *ast.ValueSpec:
+ for _, name := range v.Names {
+ if f(name.Name) {
+ return true
+ }
+ }
+ case *ast.TypeSpec:
+ if f(v.Name.Name) {
+ return true
+ }
+ // We don't match ordinary parameters in filterFuncs, so by analogy don't
+ // match type parameters here.
+ switch t := v.Type.(type) {
+ case *ast.StructType:
+ if matchFields(t.Fields, f) {
+ return true
+ }
+ case *ast.InterfaceType:
+ if matchFields(t.Methods, f) {
+ return true
+ }
+ }
+ }
+ }
+ return false
+}
+
+func filterValues(a []*Value, f Filter) []*Value {
+ w := 0
+ for _, vd := range a {
+ if matchDecl(vd.Decl, f) {
+ a[w] = vd
+ w++
+ }
+ }
+ return a[0:w]
+}
+
+func filterFuncs(a []*Func, f Filter) []*Func {
+ w := 0
+ for _, fd := range a {
+ if f(fd.Name) {
+ a[w] = fd
+ w++
+ }
+ }
+ return a[0:w]
+}
+
+func filterTypes(a []*Type, f Filter) []*Type {
+ w := 0
+ for _, td := range a {
+ n := 0 // number of matches
+ if matchDecl(td.Decl, f) {
+ n = 1
+ } else {
+ // type name doesn't match, but we may have matching consts, vars, factories or methods
+ td.Consts = filterValues(td.Consts, f)
+ td.Vars = filterValues(td.Vars, f)
+ td.Funcs = filterFuncs(td.Funcs, f)
+ td.Methods = filterFuncs(td.Methods, f)
+ n += len(td.Consts) + len(td.Vars) + len(td.Funcs) + len(td.Methods)
+ }
+ if n > 0 {
+ a[w] = td
+ w++
+ }
+ }
+ return a[0:w]
+}
+
+// Filter eliminates documentation for names that don't pass through the filter f.
+// TODO(gri): Recognize "Type.Method" as a name.
+func (p *Package) Filter(f Filter) {
+ p.Consts = filterValues(p.Consts, f)
+ p.Vars = filterValues(p.Vars, f)
+ p.Types = filterTypes(p.Types, f)
+ p.Funcs = filterFuncs(p.Funcs, f)
+ p.Doc = "" // don't show top-level package doc
+}
diff --git a/src/go/doc/headscan.go b/src/go/doc/headscan.go
new file mode 100644
index 0000000..82f5fed
--- /dev/null
+++ b/src/go/doc/headscan.go
@@ -0,0 +1,109 @@
+// Copyright 2011 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.
+
+//go:build ignore
+
+/*
+The headscan command extracts comment headings from package files;
+it is used to detect false positives which may require an adjustment
+to the comment formatting heuristics in comment.go.
+
+Usage: headscan [-root root_directory]
+
+By default, the $GOROOT/src directory is scanned.
+*/
+package main
+
+import (
+ "flag"
+ "fmt"
+ "go/doc"
+ "go/parser"
+ "go/token"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strings"
+)
+
+var (
+ root = flag.String("root", filepath.Join(runtime.GOROOT(), "src"), "root of filesystem tree to scan")
+ verbose = flag.Bool("v", false, "verbose mode")
+)
+
+// ToHTML in comment.go assigns a (possibly blank) ID to each heading
+var html_h = regexp.MustCompile(`<h3 id="[^"]*">`)
+
+const html_endh = "</h3>\n"
+
+func isGoFile(fi fs.FileInfo) bool {
+ return strings.HasSuffix(fi.Name(), ".go") &&
+ !strings.HasSuffix(fi.Name(), "_test.go")
+}
+
+func appendHeadings(list []string, comment string) []string {
+ var buf strings.Builder
+ doc.ToHTML(&buf, comment, nil)
+ for s := buf.String(); s != ""; {
+ loc := html_h.FindStringIndex(s)
+ if len(loc) == 0 {
+ break
+ }
+ var inner string
+ inner, s, _ = strings.Cut(s[loc[1]:], html_endh)
+ list = append(list, inner)
+ }
+ return list
+}
+
+func main() {
+ flag.Parse()
+ fset := token.NewFileSet()
+ nheadings := 0
+ err := filepath.WalkDir(*root, func(path string, info fs.DirEntry, err error) error {
+ if !info.IsDir() {
+ return nil
+ }
+ pkgs, err := parser.ParseDir(fset, path, isGoFile, parser.ParseComments)
+ if err != nil {
+ if *verbose {
+ fmt.Fprintln(os.Stderr, err)
+ }
+ return nil
+ }
+ for _, pkg := range pkgs {
+ d := doc.New(pkg, path, doc.Mode(0))
+ list := appendHeadings(nil, d.Doc)
+ for _, d := range d.Consts {
+ list = appendHeadings(list, d.Doc)
+ }
+ for _, d := range d.Types {
+ list = appendHeadings(list, d.Doc)
+ }
+ for _, d := range d.Vars {
+ list = appendHeadings(list, d.Doc)
+ }
+ for _, d := range d.Funcs {
+ list = appendHeadings(list, d.Doc)
+ }
+ if len(list) > 0 {
+ // directories may contain multiple packages;
+ // print path and package name
+ fmt.Printf("%s (package %s)\n", path, pkg.Name)
+ for _, h := range list {
+ fmt.Printf("\t%s\n", h)
+ }
+ nheadings += len(list)
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ fmt.Println(nheadings, "headings found")
+}
diff --git a/src/go/doc/reader.go b/src/go/doc/reader.go
new file mode 100644
index 0000000..8f9fda4
--- /dev/null
+++ b/src/go/doc/reader.go
@@ -0,0 +1,1029 @@
+// Copyright 2009 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 doc
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "internal/lazyregexp"
+ "path"
+ "sort"
+ "strconv"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+// ----------------------------------------------------------------------------
+// function/method sets
+//
+// Internally, we treat functions like methods and collect them in method sets.
+
+// A methodSet describes a set of methods. Entries where Decl == nil are conflict
+// entries (more than one method with the same name at the same embedding level).
+type methodSet map[string]*Func
+
+// recvString returns a string representation of recv of the form "T", "*T",
+// "T[A, ...]", "*T[A, ...]" or "BADRECV" (if not a proper receiver type).
+func recvString(recv ast.Expr) string {
+ switch t := recv.(type) {
+ case *ast.Ident:
+ return t.Name
+ case *ast.StarExpr:
+ return "*" + recvString(t.X)
+ case *ast.IndexExpr:
+ // Generic type with one parameter.
+ return fmt.Sprintf("%s[%s]", recvString(t.X), recvParam(t.Index))
+ case *ast.IndexListExpr:
+ // Generic type with multiple parameters.
+ if len(t.Indices) > 0 {
+ var b strings.Builder
+ b.WriteString(recvString(t.X))
+ b.WriteByte('[')
+ b.WriteString(recvParam(t.Indices[0]))
+ for _, e := range t.Indices[1:] {
+ b.WriteString(", ")
+ b.WriteString(recvParam(e))
+ }
+ b.WriteByte(']')
+ return b.String()
+ }
+ }
+ return "BADRECV"
+}
+
+func recvParam(p ast.Expr) string {
+ if id, ok := p.(*ast.Ident); ok {
+ return id.Name
+ }
+ return "BADPARAM"
+}
+
+// set creates the corresponding Func for f and adds it to mset.
+// If there are multiple f's with the same name, set keeps the first
+// one with documentation; conflicts are ignored. The boolean
+// specifies whether to leave the AST untouched.
+func (mset methodSet) set(f *ast.FuncDecl, preserveAST bool) {
+ name := f.Name.Name
+ if g := mset[name]; g != nil && g.Doc != "" {
+ // A function with the same name has already been registered;
+ // since it has documentation, assume f is simply another
+ // implementation and ignore it. This does not happen if the
+ // caller is using go/build.ScanDir to determine the list of
+ // files implementing a package.
+ return
+ }
+ // function doesn't exist or has no documentation; use f
+ recv := ""
+ if f.Recv != nil {
+ var typ ast.Expr
+ // be careful in case of incorrect ASTs
+ if list := f.Recv.List; len(list) == 1 {
+ typ = list[0].Type
+ }
+ recv = recvString(typ)
+ }
+ mset[name] = &Func{
+ Doc: f.Doc.Text(),
+ Name: name,
+ Decl: f,
+ Recv: recv,
+ Orig: recv,
+ }
+ if !preserveAST {
+ f.Doc = nil // doc consumed - remove from AST
+ }
+}
+
+// add adds method m to the method set; m is ignored if the method set
+// already contains a method with the same name at the same or a higher
+// level than m.
+func (mset methodSet) add(m *Func) {
+ old := mset[m.Name]
+ if old == nil || m.Level < old.Level {
+ mset[m.Name] = m
+ return
+ }
+ if m.Level == old.Level {
+ // conflict - mark it using a method with nil Decl
+ mset[m.Name] = &Func{
+ Name: m.Name,
+ Level: m.Level,
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Named types
+
+// baseTypeName returns the name of the base type of x (or "")
+// and whether the type is imported or not.
+func baseTypeName(x ast.Expr) (name string, imported bool) {
+ switch t := x.(type) {
+ case *ast.Ident:
+ return t.Name, false
+ case *ast.IndexExpr:
+ return baseTypeName(t.X)
+ case *ast.IndexListExpr:
+ return baseTypeName(t.X)
+ case *ast.SelectorExpr:
+ if _, ok := t.X.(*ast.Ident); ok {
+ // only possible for qualified type names;
+ // assume type is imported
+ return t.Sel.Name, true
+ }
+ case *ast.ParenExpr:
+ return baseTypeName(t.X)
+ case *ast.StarExpr:
+ return baseTypeName(t.X)
+ }
+ return "", false
+}
+
+// An embeddedSet describes a set of embedded types.
+type embeddedSet map[*namedType]bool
+
+// A namedType represents a named unqualified (package local, or possibly
+// predeclared) type. The namedType for a type name is always found via
+// reader.lookupType.
+type namedType struct {
+ doc string // doc comment for type
+ name string // type name
+ decl *ast.GenDecl // nil if declaration hasn't been seen yet
+
+ isEmbedded bool // true if this type is embedded
+ isStruct bool // true if this type is a struct
+ embedded embeddedSet // true if the embedded type is a pointer
+
+ // associated declarations
+ values []*Value // consts and vars
+ funcs methodSet
+ methods methodSet
+}
+
+// ----------------------------------------------------------------------------
+// AST reader
+
+// reader accumulates documentation for a single package.
+// It modifies the AST: Comments (declaration documentation)
+// that have been collected by the reader are set to nil
+// in the respective AST nodes so that they are not printed
+// twice (once when printing the documentation and once when
+// printing the corresponding AST node).
+type reader struct {
+ mode Mode
+
+ // package properties
+ doc string // package documentation, if any
+ filenames []string
+ notes map[string][]*Note
+
+ // imports
+ imports map[string]int
+ hasDotImp bool // if set, package contains a dot import
+ importByName map[string]string
+
+ // declarations
+ values []*Value // consts and vars
+ order int // sort order of const and var declarations (when we can't use a name)
+ types map[string]*namedType
+ funcs methodSet
+
+ // support for package-local shadowing of predeclared types
+ shadowedPredecl map[string]bool
+ fixmap map[string][]*ast.InterfaceType
+}
+
+func (r *reader) isVisible(name string) bool {
+ return r.mode&AllDecls != 0 || token.IsExported(name)
+}
+
+// lookupType returns the base type with the given name.
+// If the base type has not been encountered yet, a new
+// type with the given name but no associated declaration
+// is added to the type map.
+func (r *reader) lookupType(name string) *namedType {
+ if name == "" || name == "_" {
+ return nil // no type docs for anonymous types
+ }
+ if typ, found := r.types[name]; found {
+ return typ
+ }
+ // type not found - add one without declaration
+ typ := &namedType{
+ name: name,
+ embedded: make(embeddedSet),
+ funcs: make(methodSet),
+ methods: make(methodSet),
+ }
+ r.types[name] = typ
+ return typ
+}
+
+// recordAnonymousField registers fieldType as the type of an
+// anonymous field in the parent type. If the field is imported
+// (qualified name) or the parent is nil, the field is ignored.
+// The function returns the field name.
+func (r *reader) recordAnonymousField(parent *namedType, fieldType ast.Expr) (fname string) {
+ fname, imp := baseTypeName(fieldType)
+ if parent == nil || imp {
+ return
+ }
+ if ftype := r.lookupType(fname); ftype != nil {
+ ftype.isEmbedded = true
+ _, ptr := fieldType.(*ast.StarExpr)
+ parent.embedded[ftype] = ptr
+ }
+ return
+}
+
+func (r *reader) readDoc(comment *ast.CommentGroup) {
+ // By convention there should be only one package comment
+ // but collect all of them if there are more than one.
+ text := comment.Text()
+ if r.doc == "" {
+ r.doc = text
+ return
+ }
+ r.doc += "\n" + text
+}
+
+func (r *reader) remember(predecl string, typ *ast.InterfaceType) {
+ if r.fixmap == nil {
+ r.fixmap = make(map[string][]*ast.InterfaceType)
+ }
+ r.fixmap[predecl] = append(r.fixmap[predecl], typ)
+}
+
+func specNames(specs []ast.Spec) []string {
+ names := make([]string, 0, len(specs)) // reasonable estimate
+ for _, s := range specs {
+ // s guaranteed to be an *ast.ValueSpec by readValue
+ for _, ident := range s.(*ast.ValueSpec).Names {
+ names = append(names, ident.Name)
+ }
+ }
+ return names
+}
+
+// readValue processes a const or var declaration.
+func (r *reader) readValue(decl *ast.GenDecl) {
+ // determine if decl should be associated with a type
+ // Heuristic: For each typed entry, determine the type name, if any.
+ // If there is exactly one type name that is sufficiently
+ // frequent, associate the decl with the respective type.
+ domName := ""
+ domFreq := 0
+ prev := ""
+ n := 0
+ for _, spec := range decl.Specs {
+ s, ok := spec.(*ast.ValueSpec)
+ if !ok {
+ continue // should not happen, but be conservative
+ }
+ name := ""
+ switch {
+ case s.Type != nil:
+ // a type is present; determine its name
+ if n, imp := baseTypeName(s.Type); !imp {
+ name = n
+ }
+ case decl.Tok == token.CONST && len(s.Values) == 0:
+ // no type or value is present but we have a constant declaration;
+ // use the previous type name (possibly the empty string)
+ name = prev
+ }
+ if name != "" {
+ // entry has a named type
+ if domName != "" && domName != name {
+ // more than one type name - do not associate
+ // with any type
+ domName = ""
+ break
+ }
+ domName = name
+ domFreq++
+ }
+ prev = name
+ n++
+ }
+
+ // nothing to do w/o a legal declaration
+ if n == 0 {
+ return
+ }
+
+ // determine values list with which to associate the Value for this decl
+ values := &r.values
+ const threshold = 0.75
+ if domName != "" && r.isVisible(domName) && domFreq >= int(float64(len(decl.Specs))*threshold) {
+ // typed entries are sufficiently frequent
+ if typ := r.lookupType(domName); typ != nil {
+ values = &typ.values // associate with that type
+ }
+ }
+
+ *values = append(*values, &Value{
+ Doc: decl.Doc.Text(),
+ Names: specNames(decl.Specs),
+ Decl: decl,
+ order: r.order,
+ })
+ if r.mode&PreserveAST == 0 {
+ decl.Doc = nil // doc consumed - remove from AST
+ }
+ // Note: It's important that the order used here is global because the cleanupTypes
+ // methods may move values associated with types back into the global list. If the
+ // order is list-specific, sorting is not deterministic because the same order value
+ // may appear multiple times (was bug, found when fixing #16153).
+ r.order++
+}
+
+// fields returns a struct's fields or an interface's methods.
+func fields(typ ast.Expr) (list []*ast.Field, isStruct bool) {
+ var fields *ast.FieldList
+ switch t := typ.(type) {
+ case *ast.StructType:
+ fields = t.Fields
+ isStruct = true
+ case *ast.InterfaceType:
+ fields = t.Methods
+ }
+ if fields != nil {
+ list = fields.List
+ }
+ return
+}
+
+// readType processes a type declaration.
+func (r *reader) readType(decl *ast.GenDecl, spec *ast.TypeSpec) {
+ typ := r.lookupType(spec.Name.Name)
+ if typ == nil {
+ return // no name or blank name - ignore the type
+ }
+
+ // A type should be added at most once, so typ.decl
+ // should be nil - if it is not, simply overwrite it.
+ typ.decl = decl
+
+ // compute documentation
+ doc := spec.Doc
+ if doc == nil {
+ // no doc associated with the spec, use the declaration doc, if any
+ doc = decl.Doc
+ }
+ if r.mode&PreserveAST == 0 {
+ spec.Doc = nil // doc consumed - remove from AST
+ decl.Doc = nil // doc consumed - remove from AST
+ }
+ typ.doc = doc.Text()
+
+ // record anonymous fields (they may contribute methods)
+ // (some fields may have been recorded already when filtering
+ // exports, but that's ok)
+ var list []*ast.Field
+ list, typ.isStruct = fields(spec.Type)
+ for _, field := range list {
+ if len(field.Names) == 0 {
+ r.recordAnonymousField(typ, field.Type)
+ }
+ }
+}
+
+// isPredeclared reports whether n denotes a predeclared type.
+func (r *reader) isPredeclared(n string) bool {
+ return predeclaredTypes[n] && r.types[n] == nil
+}
+
+// readFunc processes a func or method declaration.
+func (r *reader) readFunc(fun *ast.FuncDecl) {
+ // strip function body if requested.
+ if r.mode&PreserveAST == 0 {
+ fun.Body = nil
+ }
+
+ // associate methods with the receiver type, if any
+ if fun.Recv != nil {
+ // method
+ if len(fun.Recv.List) == 0 {
+ // should not happen (incorrect AST); (See issue 17788)
+ // don't show this method
+ return
+ }
+ recvTypeName, imp := baseTypeName(fun.Recv.List[0].Type)
+ if imp {
+ // should not happen (incorrect AST);
+ // don't show this method
+ return
+ }
+ if typ := r.lookupType(recvTypeName); typ != nil {
+ typ.methods.set(fun, r.mode&PreserveAST != 0)
+ }
+ // otherwise ignore the method
+ // TODO(gri): There may be exported methods of non-exported types
+ // that can be called because of exported values (consts, vars, or
+ // function results) of that type. Could determine if that is the
+ // case and then show those methods in an appropriate section.
+ return
+ }
+
+ // Associate factory functions with the first visible result type, as long as
+ // others are predeclared types.
+ if fun.Type.Results.NumFields() >= 1 {
+ var typ *namedType // type to associate the function with
+ numResultTypes := 0
+ for _, res := range fun.Type.Results.List {
+ factoryType := res.Type
+ if t, ok := factoryType.(*ast.ArrayType); ok {
+ // We consider functions that return slices or arrays of type
+ // T (or pointers to T) as factory functions of T.
+ factoryType = t.Elt
+ }
+ if n, imp := baseTypeName(factoryType); !imp && r.isVisible(n) && !r.isPredeclared(n) {
+ if lookupTypeParam(n, fun.Type.TypeParams) != nil {
+ // Issue #49477: don't associate fun with its type parameter result.
+ // A type parameter is not a defined type.
+ continue
+ }
+ if t := r.lookupType(n); t != nil {
+ typ = t
+ numResultTypes++
+ if numResultTypes > 1 {
+ break
+ }
+ }
+ }
+ }
+ // If there is exactly one result type,
+ // associate the function with that type.
+ if numResultTypes == 1 {
+ typ.funcs.set(fun, r.mode&PreserveAST != 0)
+ return
+ }
+ }
+
+ // just an ordinary function
+ r.funcs.set(fun, r.mode&PreserveAST != 0)
+}
+
+// lookupTypeParam searches for type parameters named name within the tparams
+// field list, returning the relevant identifier if found, or nil if not.
+func lookupTypeParam(name string, tparams *ast.FieldList) *ast.Ident {
+ if tparams == nil {
+ return nil
+ }
+ for _, field := range tparams.List {
+ for _, id := range field.Names {
+ if id.Name == name {
+ return id
+ }
+ }
+ }
+ return nil
+}
+
+var (
+ noteMarker = `([A-Z][A-Z]+)\(([^)]+)\):?` // MARKER(uid), MARKER at least 2 chars, uid at least 1 char
+ noteMarkerRx = lazyregexp.New(`^[ \t]*` + noteMarker) // MARKER(uid) at text start
+ noteCommentRx = lazyregexp.New(`^/[/*][ \t]*` + noteMarker) // MARKER(uid) at comment start
+)
+
+// clean replaces each sequence of space, \r, or \t characters
+// with a single space and removes any trailing and leading spaces.
+func clean(s string) string {
+ var b []byte
+ p := byte(' ')
+ for i := 0; i < len(s); i++ {
+ q := s[i]
+ if q == '\r' || q == '\t' {
+ q = ' '
+ }
+ if q != ' ' || p != ' ' {
+ b = append(b, q)
+ p = q
+ }
+ }
+ // remove trailing blank, if any
+ if n := len(b); n > 0 && p == ' ' {
+ b = b[0 : n-1]
+ }
+ return string(b)
+}
+
+// readNote collects a single note from a sequence of comments.
+func (r *reader) readNote(list []*ast.Comment) {
+ text := (&ast.CommentGroup{List: list}).Text()
+ if m := noteMarkerRx.FindStringSubmatchIndex(text); m != nil {
+ // The note body starts after the marker.
+ // We remove any formatting so that we don't
+ // get spurious line breaks/indentation when
+ // showing the TODO body.
+ body := clean(text[m[1]:])
+ if body != "" {
+ marker := text[m[2]:m[3]]
+ r.notes[marker] = append(r.notes[marker], &Note{
+ Pos: list[0].Pos(),
+ End: list[len(list)-1].End(),
+ UID: text[m[4]:m[5]],
+ Body: body,
+ })
+ }
+ }
+}
+
+// readNotes extracts notes from comments.
+// A note must start at the beginning of a comment with "MARKER(uid):"
+// and is followed by the note body (e.g., "// BUG(gri): fix this").
+// The note ends at the end of the comment group or at the start of
+// another note in the same comment group, whichever comes first.
+func (r *reader) readNotes(comments []*ast.CommentGroup) {
+ for _, group := range comments {
+ i := -1 // comment index of most recent note start, valid if >= 0
+ list := group.List
+ for j, c := range list {
+ if noteCommentRx.MatchString(c.Text) {
+ if i >= 0 {
+ r.readNote(list[i:j])
+ }
+ i = j
+ }
+ }
+ if i >= 0 {
+ r.readNote(list[i:])
+ }
+ }
+}
+
+// readFile adds the AST for a source file to the reader.
+func (r *reader) readFile(src *ast.File) {
+ // add package documentation
+ if src.Doc != nil {
+ r.readDoc(src.Doc)
+ if r.mode&PreserveAST == 0 {
+ src.Doc = nil // doc consumed - remove from AST
+ }
+ }
+
+ // add all declarations but for functions which are processed in a separate pass
+ for _, decl := range src.Decls {
+ switch d := decl.(type) {
+ case *ast.GenDecl:
+ switch d.Tok {
+ case token.IMPORT:
+ // imports are handled individually
+ for _, spec := range d.Specs {
+ if s, ok := spec.(*ast.ImportSpec); ok {
+ if import_, err := strconv.Unquote(s.Path.Value); err == nil {
+ r.imports[import_] = 1
+ var name string
+ if s.Name != nil {
+ name = s.Name.Name
+ if name == "." {
+ r.hasDotImp = true
+ }
+ }
+ if name != "." {
+ if name == "" {
+ name = assumedPackageName(import_)
+ }
+ old, ok := r.importByName[name]
+ if !ok {
+ r.importByName[name] = import_
+ } else if old != import_ && old != "" {
+ r.importByName[name] = "" // ambiguous
+ }
+ }
+ }
+ }
+ }
+ case token.CONST, token.VAR:
+ // constants and variables are always handled as a group
+ r.readValue(d)
+ case token.TYPE:
+ // types are handled individually
+ if len(d.Specs) == 1 && !d.Lparen.IsValid() {
+ // common case: single declaration w/o parentheses
+ // (if a single declaration is parenthesized,
+ // create a new fake declaration below, so that
+ // go/doc type declarations always appear w/o
+ // parentheses)
+ if s, ok := d.Specs[0].(*ast.TypeSpec); ok {
+ r.readType(d, s)
+ }
+ break
+ }
+ for _, spec := range d.Specs {
+ if s, ok := spec.(*ast.TypeSpec); ok {
+ // use an individual (possibly fake) declaration
+ // for each type; this also ensures that each type
+ // gets to (re-)use the declaration documentation
+ // if there's none associated with the spec itself
+ fake := &ast.GenDecl{
+ Doc: d.Doc,
+ // don't use the existing TokPos because it
+ // will lead to the wrong selection range for
+ // the fake declaration if there are more
+ // than one type in the group (this affects
+ // src/cmd/godoc/godoc.go's posLink_urlFunc)
+ TokPos: s.Pos(),
+ Tok: token.TYPE,
+ Specs: []ast.Spec{s},
+ }
+ r.readType(fake, s)
+ }
+ }
+ }
+ }
+ }
+
+ // collect MARKER(...): annotations
+ r.readNotes(src.Comments)
+ if r.mode&PreserveAST == 0 {
+ src.Comments = nil // consumed unassociated comments - remove from AST
+ }
+}
+
+func (r *reader) readPackage(pkg *ast.Package, mode Mode) {
+ // initialize reader
+ r.filenames = make([]string, len(pkg.Files))
+ r.imports = make(map[string]int)
+ r.mode = mode
+ r.types = make(map[string]*namedType)
+ r.funcs = make(methodSet)
+ r.notes = make(map[string][]*Note)
+ r.importByName = make(map[string]string)
+
+ // sort package files before reading them so that the
+ // result does not depend on map iteration order
+ i := 0
+ for filename := range pkg.Files {
+ r.filenames[i] = filename
+ i++
+ }
+ sort.Strings(r.filenames)
+
+ // process files in sorted order
+ for _, filename := range r.filenames {
+ f := pkg.Files[filename]
+ if mode&AllDecls == 0 {
+ r.fileExports(f)
+ }
+ r.readFile(f)
+ }
+
+ for name, path := range r.importByName {
+ if path == "" {
+ delete(r.importByName, name)
+ }
+ }
+
+ // process functions now that we have better type information
+ for _, f := range pkg.Files {
+ for _, decl := range f.Decls {
+ if d, ok := decl.(*ast.FuncDecl); ok {
+ r.readFunc(d)
+ }
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Types
+
+func customizeRecv(f *Func, recvTypeName string, embeddedIsPtr bool, level int) *Func {
+ if f == nil || f.Decl == nil || f.Decl.Recv == nil || len(f.Decl.Recv.List) != 1 {
+ return f // shouldn't happen, but be safe
+ }
+
+ // copy existing receiver field and set new type
+ newField := *f.Decl.Recv.List[0]
+ origPos := newField.Type.Pos()
+ _, origRecvIsPtr := newField.Type.(*ast.StarExpr)
+ newIdent := &ast.Ident{NamePos: origPos, Name: recvTypeName}
+ var typ ast.Expr = newIdent
+ if !embeddedIsPtr && origRecvIsPtr {
+ newIdent.NamePos++ // '*' is one character
+ typ = &ast.StarExpr{Star: origPos, X: newIdent}
+ }
+ newField.Type = typ
+
+ // copy existing receiver field list and set new receiver field
+ newFieldList := *f.Decl.Recv
+ newFieldList.List = []*ast.Field{&newField}
+
+ // copy existing function declaration and set new receiver field list
+ newFuncDecl := *f.Decl
+ newFuncDecl.Recv = &newFieldList
+
+ // copy existing function documentation and set new declaration
+ newF := *f
+ newF.Decl = &newFuncDecl
+ newF.Recv = recvString(typ)
+ // the Orig field never changes
+ newF.Level = level
+
+ return &newF
+}
+
+// collectEmbeddedMethods collects the embedded methods of typ in mset.
+func (r *reader) collectEmbeddedMethods(mset methodSet, typ *namedType, recvTypeName string, embeddedIsPtr bool, level int, visited embeddedSet) {
+ visited[typ] = true
+ for embedded, isPtr := range typ.embedded {
+ // Once an embedded type is embedded as a pointer type
+ // all embedded types in those types are treated like
+ // pointer types for the purpose of the receiver type
+ // computation; i.e., embeddedIsPtr is sticky for this
+ // embedding hierarchy.
+ thisEmbeddedIsPtr := embeddedIsPtr || isPtr
+ for _, m := range embedded.methods {
+ // only top-level methods are embedded
+ if m.Level == 0 {
+ mset.add(customizeRecv(m, recvTypeName, thisEmbeddedIsPtr, level))
+ }
+ }
+ if !visited[embedded] {
+ r.collectEmbeddedMethods(mset, embedded, recvTypeName, thisEmbeddedIsPtr, level+1, visited)
+ }
+ }
+ delete(visited, typ)
+}
+
+// computeMethodSets determines the actual method sets for each type encountered.
+func (r *reader) computeMethodSets() {
+ for _, t := range r.types {
+ // collect embedded methods for t
+ if t.isStruct {
+ // struct
+ r.collectEmbeddedMethods(t.methods, t, t.name, false, 1, make(embeddedSet))
+ } else {
+ // interface
+ // TODO(gri) fix this
+ }
+ }
+
+ // For any predeclared names that are declared locally, don't treat them as
+ // exported fields anymore.
+ for predecl := range r.shadowedPredecl {
+ for _, ityp := range r.fixmap[predecl] {
+ removeAnonymousField(predecl, ityp)
+ }
+ }
+}
+
+// cleanupTypes removes the association of functions and methods with
+// types that have no declaration. Instead, these functions and methods
+// are shown at the package level. It also removes types with missing
+// declarations or which are not visible.
+func (r *reader) cleanupTypes() {
+ for _, t := range r.types {
+ visible := r.isVisible(t.name)
+ predeclared := predeclaredTypes[t.name]
+
+ if t.decl == nil && (predeclared || visible && (t.isEmbedded || r.hasDotImp)) {
+ // t.name is a predeclared type (and was not redeclared in this package),
+ // or it was embedded somewhere but its declaration is missing (because
+ // the AST is incomplete), or we have a dot-import (and all bets are off):
+ // move any associated values, funcs, and methods back to the top-level so
+ // that they are not lost.
+ // 1) move values
+ r.values = append(r.values, t.values...)
+ // 2) move factory functions
+ for name, f := range t.funcs {
+ // in a correct AST, package-level function names
+ // are all different - no need to check for conflicts
+ r.funcs[name] = f
+ }
+ // 3) move methods
+ if !predeclared {
+ for name, m := range t.methods {
+ // don't overwrite functions with the same name - drop them
+ if _, found := r.funcs[name]; !found {
+ r.funcs[name] = m
+ }
+ }
+ }
+ }
+ // remove types w/o declaration or which are not visible
+ if t.decl == nil || !visible {
+ delete(r.types, t.name)
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Sorting
+
+type data struct {
+ n int
+ swap func(i, j int)
+ less func(i, j int) bool
+}
+
+func (d *data) Len() int { return d.n }
+func (d *data) Swap(i, j int) { d.swap(i, j) }
+func (d *data) Less(i, j int) bool { return d.less(i, j) }
+
+// sortBy is a helper function for sorting.
+func sortBy(less func(i, j int) bool, swap func(i, j int), n int) {
+ sort.Sort(&data{n, swap, less})
+}
+
+func sortedKeys(m map[string]int) []string {
+ list := make([]string, len(m))
+ i := 0
+ for key := range m {
+ list[i] = key
+ i++
+ }
+ sort.Strings(list)
+ return list
+}
+
+// sortingName returns the name to use when sorting d into place.
+func sortingName(d *ast.GenDecl) string {
+ if len(d.Specs) == 1 {
+ if s, ok := d.Specs[0].(*ast.ValueSpec); ok {
+ return s.Names[0].Name
+ }
+ }
+ return ""
+}
+
+func sortedValues(m []*Value, tok token.Token) []*Value {
+ list := make([]*Value, len(m)) // big enough in any case
+ i := 0
+ for _, val := range m {
+ if val.Decl.Tok == tok {
+ list[i] = val
+ i++
+ }
+ }
+ list = list[0:i]
+
+ sortBy(
+ func(i, j int) bool {
+ if ni, nj := sortingName(list[i].Decl), sortingName(list[j].Decl); ni != nj {
+ return ni < nj
+ }
+ return list[i].order < list[j].order
+ },
+ func(i, j int) { list[i], list[j] = list[j], list[i] },
+ len(list),
+ )
+
+ return list
+}
+
+func sortedTypes(m map[string]*namedType, allMethods bool) []*Type {
+ list := make([]*Type, len(m))
+ i := 0
+ for _, t := range m {
+ list[i] = &Type{
+ Doc: t.doc,
+ Name: t.name,
+ Decl: t.decl,
+ Consts: sortedValues(t.values, token.CONST),
+ Vars: sortedValues(t.values, token.VAR),
+ Funcs: sortedFuncs(t.funcs, true),
+ Methods: sortedFuncs(t.methods, allMethods),
+ }
+ i++
+ }
+
+ sortBy(
+ func(i, j int) bool { return list[i].Name < list[j].Name },
+ func(i, j int) { list[i], list[j] = list[j], list[i] },
+ len(list),
+ )
+
+ return list
+}
+
+func removeStar(s string) string {
+ if len(s) > 0 && s[0] == '*' {
+ return s[1:]
+ }
+ return s
+}
+
+func sortedFuncs(m methodSet, allMethods bool) []*Func {
+ list := make([]*Func, len(m))
+ i := 0
+ for _, m := range m {
+ // determine which methods to include
+ switch {
+ case m.Decl == nil:
+ // exclude conflict entry
+ case allMethods, m.Level == 0, !token.IsExported(removeStar(m.Orig)):
+ // forced inclusion, method not embedded, or method
+ // embedded but original receiver type not exported
+ list[i] = m
+ i++
+ }
+ }
+ list = list[0:i]
+ sortBy(
+ func(i, j int) bool { return list[i].Name < list[j].Name },
+ func(i, j int) { list[i], list[j] = list[j], list[i] },
+ len(list),
+ )
+ return list
+}
+
+// noteBodies returns a list of note body strings given a list of notes.
+// This is only used to populate the deprecated Package.Bugs field.
+func noteBodies(notes []*Note) []string {
+ var list []string
+ for _, n := range notes {
+ list = append(list, n.Body)
+ }
+ return list
+}
+
+// ----------------------------------------------------------------------------
+// Predeclared identifiers
+
+// IsPredeclared reports whether s is a predeclared identifier.
+func IsPredeclared(s string) bool {
+ return predeclaredTypes[s] || predeclaredFuncs[s] || predeclaredConstants[s]
+}
+
+var predeclaredTypes = map[string]bool{
+ "any": true,
+ "bool": true,
+ "byte": true,
+ "comparable": true,
+ "complex64": true,
+ "complex128": true,
+ "error": true,
+ "float32": true,
+ "float64": true,
+ "int": true,
+ "int8": true,
+ "int16": true,
+ "int32": true,
+ "int64": true,
+ "rune": true,
+ "string": true,
+ "uint": true,
+ "uint8": true,
+ "uint16": true,
+ "uint32": true,
+ "uint64": true,
+ "uintptr": true,
+}
+
+var predeclaredFuncs = map[string]bool{
+ "append": true,
+ "cap": true,
+ "close": true,
+ "complex": true,
+ "copy": true,
+ "delete": true,
+ "imag": true,
+ "len": true,
+ "make": true,
+ "new": true,
+ "panic": true,
+ "print": true,
+ "println": true,
+ "real": true,
+ "recover": true,
+}
+
+var predeclaredConstants = map[string]bool{
+ "false": true,
+ "iota": true,
+ "nil": true,
+ "true": true,
+}
+
+// assumedPackageName returns the assumed package name
+// for a given import path. This is a copy of
+// golang.org/x/tools/internal/imports.ImportPathToAssumedName.
+func assumedPackageName(importPath string) string {
+ notIdentifier := func(ch rune) bool {
+ return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' ||
+ '0' <= ch && ch <= '9' ||
+ ch == '_' ||
+ ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch)))
+ }
+
+ base := path.Base(importPath)
+ if strings.HasPrefix(base, "v") {
+ if _, err := strconv.Atoi(base[1:]); err == nil {
+ dir := path.Dir(importPath)
+ if dir != "." {
+ base = path.Base(dir)
+ }
+ }
+ }
+ base = strings.TrimPrefix(base, "go-")
+ if i := strings.IndexFunc(base, notIdentifier); i >= 0 {
+ base = base[:i]
+ }
+ return base
+}
diff --git a/src/go/doc/synopsis.go b/src/go/doc/synopsis.go
new file mode 100644
index 0000000..3c9e7e9
--- /dev/null
+++ b/src/go/doc/synopsis.go
@@ -0,0 +1,78 @@
+// Copyright 2012 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 doc
+
+import (
+ "go/doc/comment"
+ "strings"
+ "unicode"
+)
+
+// firstSentence returns the first sentence in s.
+// The sentence ends after the first period followed by space and
+// not preceded by exactly one uppercase letter.
+func firstSentence(s string) string {
+ var ppp, pp, p rune
+ for i, q := range s {
+ if q == '\n' || q == '\r' || q == '\t' {
+ q = ' '
+ }
+ if q == ' ' && p == '.' && (!unicode.IsUpper(pp) || unicode.IsUpper(ppp)) {
+ return s[:i]
+ }
+ if p == '。' || p == '.' {
+ return s[:i]
+ }
+ ppp, pp, p = pp, p, q
+ }
+ return s
+}
+
+// Synopsis returns a cleaned version of the first sentence in text.
+//
+// Deprecated: New programs should use [Package.Synopsis] instead,
+// which handles links in text properly.
+func Synopsis(text string) string {
+ var p Package
+ return p.Synopsis(text)
+}
+
+// IllegalPrefixes is a list of lower-case prefixes that identify
+// a comment as not being a doc comment.
+// This helps to avoid misinterpreting the common mistake
+// of a copyright notice immediately before a package statement
+// as being a doc comment.
+var IllegalPrefixes = []string{
+ "copyright",
+ "all rights",
+ "author",
+}
+
+// Synopsis returns a cleaned version of the first sentence in text.
+// That sentence ends after the first period followed by space and not
+// preceded by exactly one uppercase letter, or at the first paragraph break.
+// The result string has no \n, \r, or \t characters and uses only single
+// spaces between words. If text starts with any of the IllegalPrefixes,
+// the result is the empty string.
+func (p *Package) Synopsis(text string) string {
+ text = firstSentence(text)
+ lower := strings.ToLower(text)
+ for _, prefix := range IllegalPrefixes {
+ if strings.HasPrefix(lower, prefix) {
+ return ""
+ }
+ }
+ pr := p.Printer()
+ pr.TextWidth = -1
+ d := p.Parser().Parse(text)
+ if len(d.Content) == 0 {
+ return ""
+ }
+ if _, ok := d.Content[0].(*comment.Paragraph); !ok {
+ return ""
+ }
+ d.Content = d.Content[:1] // might be blank lines, code blocks, etc in “first sentence”
+ return strings.TrimSpace(string(pr.Text(d)))
+}
diff --git a/src/go/doc/synopsis_test.go b/src/go/doc/synopsis_test.go
new file mode 100644
index 0000000..158c734
--- /dev/null
+++ b/src/go/doc/synopsis_test.go
@@ -0,0 +1,52 @@
+// Copyright 2012 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 doc
+
+import "testing"
+
+var tests = []struct {
+ txt string
+ fsl int
+ syn string
+}{
+ {"", 0, ""},
+ {"foo", 3, "foo"},
+ {"foo.", 4, "foo."},
+ {"foo.bar", 7, "foo.bar"},
+ {" foo. ", 6, "foo."},
+ {" foo\t bar.\n", 12, "foo bar."},
+ {" foo\t bar.\n", 12, "foo bar."},
+ {"a b\n\nc\r\rd\t\t", 12, "a b"},
+ {"a b\n\nc\r\rd\t\t . BLA", 15, "a b"},
+ {"Package poems by T.S.Eliot. To rhyme...", 27, "Package poems by T.S.Eliot."},
+ {"Package poems by T. S. Eliot. To rhyme...", 29, "Package poems by T. S. Eliot."},
+ {"foo implements the foo ABI. The foo ABI is...", 27, "foo implements the foo ABI."},
+ {"Package\nfoo. ..", 12, "Package foo."},
+ {"P . Q.", 3, "P ."},
+ {"P. Q. ", 8, "P. Q."},
+ {"Package Καλημέρα κόσμε.", 36, "Package Καλημέρα κόσμε."},
+ {"Package こんにちは 世界\n", 31, "Package こんにちは 世界"},
+ {"Package こんにちは。世界", 26, "Package こんにちは。"},
+ {"Package 안녕.世界", 17, "Package 안녕."},
+ {"Package foo does bar.", 21, "Package foo does bar."},
+ {"Copyright 2012 Google, Inc. Package foo does bar.", 27, ""},
+ {"All Rights reserved. Package foo does bar.", 20, ""},
+ {"All rights reserved. Package foo does bar.", 20, ""},
+ {"Authors: foo@bar.com. Package foo does bar.", 21, ""},
+ {"typically invoked as ``go tool asm'',", 37, "typically invoked as “go tool asm”,"},
+}
+
+func TestSynopsis(t *testing.T) {
+ for _, e := range tests {
+ fs := firstSentence(e.txt)
+ if fs != e.txt[:e.fsl] {
+ t.Errorf("firstSentence(%q) = %q, want %q", e.txt, fs, e.txt[:e.fsl])
+ }
+ syn := Synopsis(e.txt)
+ if syn != e.syn {
+ t.Errorf("Synopsis(%q) = %q, want %q", e.txt, syn, e.syn)
+ }
+ }
+}
diff --git a/src/go/doc/testdata/a.0.golden b/src/go/doc/testdata/a.0.golden
new file mode 100644
index 0000000..7e680b8
--- /dev/null
+++ b/src/go/doc/testdata/a.0.golden
@@ -0,0 +1,52 @@
+// comment 0 comment 1
+PACKAGE a
+
+IMPORTPATH
+ testdata/a
+
+FILENAMES
+ testdata/a0.go
+ testdata/a1.go
+
+BUGS .Bugs is now deprecated, please use .Notes instead
+ bug0
+
+ bug1
+
+
+BUGS
+BUG(uid) bug0
+
+BUG(uid) bug1
+
+
+NOTES
+NOTE(uid)
+
+NOTE(foo) 1 of 4 - this is the first line of note 1
+ - note 1 continues on this 2nd line
+ - note 1 continues on this 3rd line
+
+NOTE(foo) 2 of 4
+
+NOTE(bar) 3 of 4
+
+NOTE(bar) 4 of 4
+ - this is the last line of note 4
+
+NOTE(bam) This note which contains a (parenthesized) subphrase
+ must appear in its entirety.
+
+NOTE(xxx) The ':' after the marker and uid is optional.
+
+
+SECBUGS
+SECBUG(uid) sec hole 0
+ need to fix asap
+
+
+TODOS
+TODO(uid) todo0
+
+TODO(uid) todo1
+
diff --git a/src/go/doc/testdata/a.1.golden b/src/go/doc/testdata/a.1.golden
new file mode 100644
index 0000000..7e680b8
--- /dev/null
+++ b/src/go/doc/testdata/a.1.golden
@@ -0,0 +1,52 @@
+// comment 0 comment 1
+PACKAGE a
+
+IMPORTPATH
+ testdata/a
+
+FILENAMES
+ testdata/a0.go
+ testdata/a1.go
+
+BUGS .Bugs is now deprecated, please use .Notes instead
+ bug0
+
+ bug1
+
+
+BUGS
+BUG(uid) bug0
+
+BUG(uid) bug1
+
+
+NOTES
+NOTE(uid)
+
+NOTE(foo) 1 of 4 - this is the first line of note 1
+ - note 1 continues on this 2nd line
+ - note 1 continues on this 3rd line
+
+NOTE(foo) 2 of 4
+
+NOTE(bar) 3 of 4
+
+NOTE(bar) 4 of 4
+ - this is the last line of note 4
+
+NOTE(bam) This note which contains a (parenthesized) subphrase
+ must appear in its entirety.
+
+NOTE(xxx) The ':' after the marker and uid is optional.
+
+
+SECBUGS
+SECBUG(uid) sec hole 0
+ need to fix asap
+
+
+TODOS
+TODO(uid) todo0
+
+TODO(uid) todo1
+
diff --git a/src/go/doc/testdata/a.2.golden b/src/go/doc/testdata/a.2.golden
new file mode 100644
index 0000000..7e680b8
--- /dev/null
+++ b/src/go/doc/testdata/a.2.golden
@@ -0,0 +1,52 @@
+// comment 0 comment 1
+PACKAGE a
+
+IMPORTPATH
+ testdata/a
+
+FILENAMES
+ testdata/a0.go
+ testdata/a1.go
+
+BUGS .Bugs is now deprecated, please use .Notes instead
+ bug0
+
+ bug1
+
+
+BUGS
+BUG(uid) bug0
+
+BUG(uid) bug1
+
+
+NOTES
+NOTE(uid)
+
+NOTE(foo) 1 of 4 - this is the first line of note 1
+ - note 1 continues on this 2nd line
+ - note 1 continues on this 3rd line
+
+NOTE(foo) 2 of 4
+
+NOTE(bar) 3 of 4
+
+NOTE(bar) 4 of 4
+ - this is the last line of note 4
+
+NOTE(bam) This note which contains a (parenthesized) subphrase
+ must appear in its entirety.
+
+NOTE(xxx) The ':' after the marker and uid is optional.
+
+
+SECBUGS
+SECBUG(uid) sec hole 0
+ need to fix asap
+
+
+TODOS
+TODO(uid) todo0
+
+TODO(uid) todo1
+
diff --git a/src/go/doc/testdata/a0.go b/src/go/doc/testdata/a0.go
new file mode 100644
index 0000000..2420c8a
--- /dev/null
+++ b/src/go/doc/testdata/a0.go
@@ -0,0 +1,40 @@
+// Copyright 2012 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.
+
+// comment 0
+package a
+
+//BUG(uid): bug0
+
+//TODO(uid): todo0
+
+// A note with some spaces after it, should be ignored (watch out for
+// emacs modes that remove trailing whitespace).
+//NOTE(uid):
+
+// SECBUG(uid): sec hole 0
+// need to fix asap
+
+// Multiple notes may be in the same comment group and should be
+// recognized individually. Notes may start in the middle of a
+// comment group as long as they start at the beginning of an
+// individual comment.
+//
+// NOTE(foo): 1 of 4 - this is the first line of note 1
+// - note 1 continues on this 2nd line
+// - note 1 continues on this 3rd line
+// NOTE(foo): 2 of 4
+// NOTE(bar): 3 of 4
+/* NOTE(bar): 4 of 4 */
+// - this is the last line of note 4
+//
+//
+
+// NOTE(bam): This note which contains a (parenthesized) subphrase
+// must appear in its entirety.
+
+// NOTE(xxx) The ':' after the marker and uid is optional.
+
+// NOTE(): NO uid - should not show up.
+// NOTE() NO uid - should not show up.
diff --git a/src/go/doc/testdata/a1.go b/src/go/doc/testdata/a1.go
new file mode 100644
index 0000000..9fad1e0
--- /dev/null
+++ b/src/go/doc/testdata/a1.go
@@ -0,0 +1,12 @@
+// Copyright 2012 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.
+
+// comment 1
+package a
+
+//BUG(uid): bug1
+
+//TODO(uid): todo1
+
+//TODO(): ignored
diff --git a/src/go/doc/testdata/b.0.golden b/src/go/doc/testdata/b.0.golden
new file mode 100644
index 0000000..c06246a
--- /dev/null
+++ b/src/go/doc/testdata/b.0.golden
@@ -0,0 +1,74 @@
+//
+PACKAGE b
+
+IMPORTPATH
+ testdata/b
+
+IMPORTS
+ a
+
+FILENAMES
+ testdata/b.go
+
+CONSTANTS
+ //
+ const (
+ C1 notExported = iota
+ C2
+
+ C4
+ C5
+ )
+
+ //
+ const C notExported = 0
+
+ //
+ const Pi = 3.14 // Pi
+
+
+VARIABLES
+ //
+ var (
+ U1, U2, U4, U5 notExported
+
+ U7 notExported = 7
+ )
+
+ //
+ var MaxInt int // MaxInt
+
+ //
+ var V notExported
+
+ //
+ var V1, V2, V4, V5 notExported
+
+
+FUNCTIONS
+ // Associated with comparable type if AllDecls is set.
+ func ComparableFactory() comparable
+
+ //
+ func F(x int) int
+
+ //
+ func F1() notExported
+
+ // Always under the package functions list.
+ func NotAFactory() int
+
+ // Associated with uint type if AllDecls is set.
+ func UintFactory() uint
+
+
+TYPES
+ //
+ type T struct{} // T
+
+ //
+ var V T // v
+
+ //
+ func (x *T) M()
+
diff --git a/src/go/doc/testdata/b.1.golden b/src/go/doc/testdata/b.1.golden
new file mode 100644
index 0000000..2b62c34
--- /dev/null
+++ b/src/go/doc/testdata/b.1.golden
@@ -0,0 +1,89 @@
+//
+PACKAGE b
+
+IMPORTPATH
+ testdata/b
+
+IMPORTS
+ a
+
+FILENAMES
+ testdata/b.go
+
+CONSTANTS
+ //
+ const Pi = 3.14 // Pi
+
+
+VARIABLES
+ //
+ var MaxInt int // MaxInt
+
+
+FUNCTIONS
+ //
+ func F(x int) int
+
+ // Always under the package functions list.
+ func NotAFactory() int
+
+
+TYPES
+ //
+ type T struct{} // T
+
+ //
+ var V T // v
+
+ //
+ func (x *T) M()
+
+ // Should only appear if AllDecls is set.
+ type comparable struct{} // overrides a predeclared type comparable
+
+ // Associated with comparable type if AllDecls is set.
+ func ComparableFactory() comparable
+
+ //
+ type notExported int
+
+ //
+ const (
+ C1 notExported = iota
+ C2
+ c3
+ C4
+ C5
+ )
+
+ //
+ const C notExported = 0
+
+ //
+ var (
+ U1, U2, u3, U4, U5 notExported
+ u6 notExported
+ U7 notExported = 7
+ )
+
+ //
+ var V notExported
+
+ //
+ var V1, V2, v3, V4, V5 notExported
+
+ //
+ func F1() notExported
+
+ //
+ func f2() notExported
+
+ // Should only appear if AllDecls is set.
+ type uint struct{} // overrides a predeclared type uint
+
+ // Associated with uint type if AllDecls is set.
+ func UintFactory() uint
+
+ // Associated with uint type if AllDecls is set.
+ func uintFactory() uint
+
diff --git a/src/go/doc/testdata/b.2.golden b/src/go/doc/testdata/b.2.golden
new file mode 100644
index 0000000..c06246a
--- /dev/null
+++ b/src/go/doc/testdata/b.2.golden
@@ -0,0 +1,74 @@
+//
+PACKAGE b
+
+IMPORTPATH
+ testdata/b
+
+IMPORTS
+ a
+
+FILENAMES
+ testdata/b.go
+
+CONSTANTS
+ //
+ const (
+ C1 notExported = iota
+ C2
+
+ C4
+ C5
+ )
+
+ //
+ const C notExported = 0
+
+ //
+ const Pi = 3.14 // Pi
+
+
+VARIABLES
+ //
+ var (
+ U1, U2, U4, U5 notExported
+
+ U7 notExported = 7
+ )
+
+ //
+ var MaxInt int // MaxInt
+
+ //
+ var V notExported
+
+ //
+ var V1, V2, V4, V5 notExported
+
+
+FUNCTIONS
+ // Associated with comparable type if AllDecls is set.
+ func ComparableFactory() comparable
+
+ //
+ func F(x int) int
+
+ //
+ func F1() notExported
+
+ // Always under the package functions list.
+ func NotAFactory() int
+
+ // Associated with uint type if AllDecls is set.
+ func UintFactory() uint
+
+
+TYPES
+ //
+ type T struct{} // T
+
+ //
+ var V T // v
+
+ //
+ func (x *T) M()
+
diff --git a/src/go/doc/testdata/b.go b/src/go/doc/testdata/b.go
new file mode 100644
index 0000000..61b512b
--- /dev/null
+++ b/src/go/doc/testdata/b.go
@@ -0,0 +1,64 @@
+// Copyright 2012 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 b
+
+import "a"
+
+// ----------------------------------------------------------------------------
+// Basic declarations
+
+const Pi = 3.14 // Pi
+var MaxInt int // MaxInt
+type T struct{} // T
+var V T // v
+func F(x int) int {} // F
+func (x *T) M() {} // M
+
+// Corner cases: association with (presumed) predeclared types
+
+// Always under the package functions list.
+func NotAFactory() int {}
+
+// Associated with uint type if AllDecls is set.
+func UintFactory() uint {}
+
+// Associated with uint type if AllDecls is set.
+func uintFactory() uint {}
+
+// Associated with comparable type if AllDecls is set.
+func ComparableFactory() comparable {}
+
+// Should only appear if AllDecls is set.
+type uint struct{} // overrides a predeclared type uint
+
+// Should only appear if AllDecls is set.
+type comparable struct{} // overrides a predeclared type comparable
+
+// ----------------------------------------------------------------------------
+// Exported declarations associated with non-exported types must always be shown.
+
+type notExported int
+
+const C notExported = 0
+
+const (
+ C1 notExported = iota
+ C2
+ c3
+ C4
+ C5
+)
+
+var V notExported
+var V1, V2, v3, V4, V5 notExported
+
+var (
+ U1, U2, u3, U4, U5 notExported
+ u6 notExported
+ U7 notExported = 7
+)
+
+func F1() notExported {}
+func f2() notExported {}
diff --git a/src/go/doc/testdata/benchmark.go b/src/go/doc/testdata/benchmark.go
new file mode 100644
index 0000000..dbf6b4f
--- /dev/null
+++ b/src/go/doc/testdata/benchmark.go
@@ -0,0 +1,293 @@
+// Copyright 2009 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 testing
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "runtime"
+ "time"
+)
+
+var matchBenchmarks = flag.String("test.bench", "", "regular expression to select benchmarks to run")
+var benchTime = flag.Duration("test.benchtime", 1*time.Second, "approximate run time for each benchmark")
+
+// An internal type but exported because it is cross-package; part of the implementation
+// of go test.
+type InternalBenchmark struct {
+ Name string
+ F func(b *B)
+}
+
+// B is a type passed to Benchmark functions to manage benchmark
+// timing and to specify the number of iterations to run.
+type B struct {
+ common
+ N int
+ benchmark InternalBenchmark
+ bytes int64
+ timerOn bool
+ result BenchmarkResult
+}
+
+// StartTimer starts timing a test. This function is called automatically
+// before a benchmark starts, but it can also used to resume timing after
+// a call to StopTimer.
+func (b *B) StartTimer() {
+ if !b.timerOn {
+ b.start = time.Now()
+ b.timerOn = true
+ }
+}
+
+// StopTimer stops timing a test. This can be used to pause the timer
+// while performing complex initialization that you don't
+// want to measure.
+func (b *B) StopTimer() {
+ if b.timerOn {
+ b.duration += time.Since(b.start)
+ b.timerOn = false
+ }
+}
+
+// ResetTimer sets the elapsed benchmark time to zero.
+// It does not affect whether the timer is running.
+func (b *B) ResetTimer() {
+ if b.timerOn {
+ b.start = time.Now()
+ }
+ b.duration = 0
+}
+
+// SetBytes records the number of bytes processed in a single operation.
+// If this is called, the benchmark will report ns/op and MB/s.
+func (b *B) SetBytes(n int64) { b.bytes = n }
+
+func (b *B) nsPerOp() int64 {
+ if b.N <= 0 {
+ return 0
+ }
+ return b.duration.Nanoseconds() / int64(b.N)
+}
+
+// runN runs a single benchmark for the specified number of iterations.
+func (b *B) runN(n int) {
+ // Try to get a comparable environment for each run
+ // by clearing garbage from previous runs.
+ runtime.GC()
+ b.N = n
+ b.ResetTimer()
+ b.StartTimer()
+ b.benchmark.F(b)
+ b.StopTimer()
+}
+
+func min(x, y int) int {
+ if x > y {
+ return y
+ }
+ return x
+}
+
+func max(x, y int) int {
+ if x < y {
+ return y
+ }
+ return x
+}
+
+// roundDown10 rounds a number down to the nearest power of 10.
+func roundDown10(n int) int {
+ var tens = 0
+ // tens = floor(log_10(n))
+ for n > 10 {
+ n = n / 10
+ tens++
+ }
+ // result = 10^tens
+ result := 1
+ for i := 0; i < tens; i++ {
+ result *= 10
+ }
+ return result
+}
+
+// roundUp rounds x up to a number of the form [1eX, 2eX, 5eX].
+func roundUp(n int) int {
+ base := roundDown10(n)
+ if n < (2 * base) {
+ return 2 * base
+ }
+ if n < (5 * base) {
+ return 5 * base
+ }
+ return 10 * base
+}
+
+// run times the benchmark function in a separate goroutine.
+func (b *B) run() BenchmarkResult {
+ go b.launch()
+ <-b.signal
+ return b.result
+}
+
+// launch launches the benchmark function. It gradually increases the number
+// of benchmark iterations until the benchmark runs for a second in order
+// to get a reasonable measurement. It prints timing information in this form
+// testing.BenchmarkHello 100000 19 ns/op
+// launch is run by the fun function as a separate goroutine.
+func (b *B) launch() {
+ // Run the benchmark for a single iteration in case it's expensive.
+ n := 1
+
+ // Signal that we're done whether we return normally
+ // or by FailNow's runtime.Goexit.
+ defer func() {
+ b.signal <- b
+ }()
+
+ b.runN(n)
+ // Run the benchmark for at least the specified amount of time.
+ d := *benchTime
+ for !b.failed && b.duration < d && n < 1e9 {
+ last := n
+ // Predict iterations/sec.
+ if b.nsPerOp() == 0 {
+ n = 1e9
+ } else {
+ n = int(d.Nanoseconds() / b.nsPerOp())
+ }
+ // Run more iterations than we think we'll need for a second (1.5x).
+ // Don't grow too fast in case we had timing errors previously.
+ // Be sure to run at least one more than last time.
+ n = max(min(n+n/2, 100*last), last+1)
+ // Round up to something easy to read.
+ n = roundUp(n)
+ b.runN(n)
+ }
+ b.result = BenchmarkResult{b.N, b.duration, b.bytes}
+}
+
+// The results of a benchmark run.
+type BenchmarkResult struct {
+ N int // The number of iterations.
+ T time.Duration // The total time taken.
+ Bytes int64 // Bytes processed in one iteration.
+}
+
+func (r BenchmarkResult) NsPerOp() int64 {
+ if r.N <= 0 {
+ return 0
+ }
+ return r.T.Nanoseconds() / int64(r.N)
+}
+
+func (r BenchmarkResult) mbPerSec() float64 {
+ if r.Bytes <= 0 || r.T <= 0 || r.N <= 0 {
+ return 0
+ }
+ return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds()
+}
+
+func (r BenchmarkResult) String() string {
+ mbs := r.mbPerSec()
+ mb := ""
+ if mbs != 0 {
+ mb = fmt.Sprintf("\t%7.2f MB/s", mbs)
+ }
+ nsop := r.NsPerOp()
+ ns := fmt.Sprintf("%10d ns/op", nsop)
+ if r.N > 0 && nsop < 100 {
+ // The format specifiers here make sure that
+ // the ones digits line up for all three possible formats.
+ if nsop < 10 {
+ ns = fmt.Sprintf("%13.2f ns/op", float64(r.T.Nanoseconds())/float64(r.N))
+ } else {
+ ns = fmt.Sprintf("%12.1f ns/op", float64(r.T.Nanoseconds())/float64(r.N))
+ }
+ }
+ return fmt.Sprintf("%8d\t%s%s", r.N, ns, mb)
+}
+
+// An internal function but exported because it is cross-package; part of the implementation
+// of go test.
+func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark) {
+ // If no flag was specified, don't run benchmarks.
+ if len(*matchBenchmarks) == 0 {
+ return
+ }
+ for _, Benchmark := range benchmarks {
+ matched, err := matchString(*matchBenchmarks, Benchmark.Name)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.bench: %s\n", err)
+ os.Exit(1)
+ }
+ if !matched {
+ continue
+ }
+ for _, procs := range cpuList {
+ runtime.GOMAXPROCS(procs)
+ b := &B{
+ common: common{
+ signal: make(chan any),
+ },
+ benchmark: Benchmark,
+ }
+ benchName := Benchmark.Name
+ if procs != 1 {
+ benchName = fmt.Sprintf("%s-%d", Benchmark.Name, procs)
+ }
+ fmt.Printf("%s\t", benchName)
+ r := b.run()
+ if b.failed {
+ // The output could be very long here, but probably isn't.
+ // We print it all, regardless, because we don't want to trim the reason
+ // the benchmark failed.
+ fmt.Printf("--- FAIL: %s\n%s", benchName, b.output)
+ continue
+ }
+ fmt.Printf("%v\n", r)
+ // Unlike with tests, we ignore the -chatty flag and always print output for
+ // benchmarks since the output generation time will skew the results.
+ if len(b.output) > 0 {
+ b.trimOutput()
+ fmt.Printf("--- BENCH: %s\n%s", benchName, b.output)
+ }
+ if p := runtime.GOMAXPROCS(-1); p != procs {
+ fmt.Fprintf(os.Stderr, "testing: %s left GOMAXPROCS set to %d\n", benchName, p)
+ }
+ }
+ }
+}
+
+// trimOutput shortens the output from a benchmark, which can be very long.
+func (b *B) trimOutput() {
+ // The output is likely to appear multiple times because the benchmark
+ // is run multiple times, but at least it will be seen. This is not a big deal
+ // because benchmarks rarely print, but just in case, we trim it if it's too long.
+ const maxNewlines = 10
+ for nlCount, j := 0, 0; j < len(b.output); j++ {
+ if b.output[j] == '\n' {
+ nlCount++
+ if nlCount >= maxNewlines {
+ b.output = append(b.output[:j], "\n\t... [output truncated]\n"...)
+ break
+ }
+ }
+ }
+}
+
+// Benchmark benchmarks a single function. Useful for creating
+// custom benchmarks that do not use go test.
+func Benchmark(f func(b *B)) BenchmarkResult {
+ b := &B{
+ common: common{
+ signal: make(chan any),
+ },
+ benchmark: InternalBenchmark{"", f},
+ }
+ return b.run()
+}
diff --git a/src/go/doc/testdata/blank.0.golden b/src/go/doc/testdata/blank.0.golden
new file mode 100644
index 0000000..70f2929
--- /dev/null
+++ b/src/go/doc/testdata/blank.0.golden
@@ -0,0 +1,62 @@
+// Package blank is a go/doc test for the handling of _. See issue ...
+PACKAGE blank
+
+IMPORTPATH
+ testdata/blank
+
+IMPORTS
+ os
+
+FILENAMES
+ testdata/blank.go
+
+CONSTANTS
+ // T constants counting from unexported constants.
+ const (
+ C1 T
+ C2
+
+ C3
+
+ C4 int
+ )
+
+ // Constants with a single type that is not propagated.
+ const (
+ Default = 0644
+ Useless = 0312
+ WideOpen = 0777
+ )
+
+ // Constants with an imported type that is propagated.
+ const (
+ M1 os.FileMode
+ M2
+ M3
+ )
+
+ // Package constants.
+ const (
+ I1 int
+ I2
+ )
+
+
+TYPES
+ // S has a padding field.
+ type S struct {
+ H uint32
+
+ A uint8
+ // contains filtered or unexported fields
+ }
+
+ //
+ type T int
+
+ // T constants counting from a blank constant.
+ const (
+ T1 T
+ T2
+ )
+
diff --git a/src/go/doc/testdata/blank.1.golden b/src/go/doc/testdata/blank.1.golden
new file mode 100644
index 0000000..8098cb6
--- /dev/null
+++ b/src/go/doc/testdata/blank.1.golden
@@ -0,0 +1,83 @@
+// Package blank is a go/doc test for the handling of _. See issue ...
+PACKAGE blank
+
+IMPORTPATH
+ testdata/blank
+
+IMPORTS
+ os
+
+FILENAMES
+ testdata/blank.go
+
+CONSTANTS
+ // T constants counting from unexported constants.
+ const (
+ tweedledee T = iota
+ tweedledum
+ C1
+ C2
+ alice
+ C3
+ redQueen int = iota
+ C4
+ )
+
+ // Constants with a single type that is not propagated.
+ const (
+ zero os.FileMode = 0
+ Default = 0644
+ Useless = 0312
+ WideOpen = 0777
+ )
+
+ // Constants with an imported type that is propagated.
+ const (
+ zero os.FileMode = 0
+ M1
+ M2
+ M3
+ )
+
+ // Package constants.
+ const (
+ _ int = iota
+ I1
+ I2
+ )
+
+ // Unexported constants counting from blank iota. See issue 9615.
+ const (
+ _ = iota
+ one = iota + 1
+ )
+
+
+VARIABLES
+ //
+ var _ = T(55)
+
+
+FUNCTIONS
+ //
+ func _()
+
+
+TYPES
+ // S has a padding field.
+ type S struct {
+ H uint32
+ _ uint8
+ A uint8
+ }
+
+ //
+ type T int
+
+ // T constants counting from a blank constant.
+ const (
+ _ T = iota
+ T1
+ T2
+ )
+
diff --git a/src/go/doc/testdata/blank.2.golden b/src/go/doc/testdata/blank.2.golden
new file mode 100644
index 0000000..70f2929
--- /dev/null
+++ b/src/go/doc/testdata/blank.2.golden
@@ -0,0 +1,62 @@
+// Package blank is a go/doc test for the handling of _. See issue ...
+PACKAGE blank
+
+IMPORTPATH
+ testdata/blank
+
+IMPORTS
+ os
+
+FILENAMES
+ testdata/blank.go
+
+CONSTANTS
+ // T constants counting from unexported constants.
+ const (
+ C1 T
+ C2
+
+ C3
+
+ C4 int
+ )
+
+ // Constants with a single type that is not propagated.
+ const (
+ Default = 0644
+ Useless = 0312
+ WideOpen = 0777
+ )
+
+ // Constants with an imported type that is propagated.
+ const (
+ M1 os.FileMode
+ M2
+ M3
+ )
+
+ // Package constants.
+ const (
+ I1 int
+ I2
+ )
+
+
+TYPES
+ // S has a padding field.
+ type S struct {
+ H uint32
+
+ A uint8
+ // contains filtered or unexported fields
+ }
+
+ //
+ type T int
+
+ // T constants counting from a blank constant.
+ const (
+ T1 T
+ T2
+ )
+
diff --git a/src/go/doc/testdata/blank.go b/src/go/doc/testdata/blank.go
new file mode 100644
index 0000000..5ea6186
--- /dev/null
+++ b/src/go/doc/testdata/blank.go
@@ -0,0 +1,75 @@
+// Copyright 2014 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 blank is a go/doc test for the handling of _.
+// See issue 5397.
+package blank
+
+import "os"
+
+type T int
+
+// T constants counting from a blank constant.
+const (
+ _ T = iota
+ T1
+ T2
+)
+
+// T constants counting from unexported constants.
+const (
+ tweedledee T = iota
+ tweedledum
+ C1
+ C2
+ alice
+ C3
+ redQueen int = iota
+ C4
+)
+
+// Constants with a single type that is not propagated.
+const (
+ zero os.FileMode = 0
+ Default = 0644
+ Useless = 0312
+ WideOpen = 0777
+)
+
+// Constants with an imported type that is propagated.
+const (
+ zero os.FileMode = 0
+ M1
+ M2
+ M3
+)
+
+// Package constants.
+const (
+ _ int = iota
+ I1
+ I2
+)
+
+// Unexported constants counting from blank iota.
+// See issue 9615.
+const (
+ _ = iota
+ one = iota + 1
+)
+
+// Blanks not in doc output:
+
+// S has a padding field.
+type S struct {
+ H uint32
+ _ uint8
+ A uint8
+}
+
+func _() {}
+
+type _ T
+
+var _ = T(55)
diff --git a/src/go/doc/testdata/bugpara.0.golden b/src/go/doc/testdata/bugpara.0.golden
new file mode 100644
index 0000000..5804859
--- /dev/null
+++ b/src/go/doc/testdata/bugpara.0.golden
@@ -0,0 +1,20 @@
+//
+PACKAGE bugpara
+
+IMPORTPATH
+ testdata/bugpara
+
+FILENAMES
+ testdata/bugpara.go
+
+BUGS .Bugs is now deprecated, please use .Notes instead
+ Sometimes bugs have multiple paragraphs.
+
+ Like this one.
+
+
+BUGS
+BUG(rsc) Sometimes bugs have multiple paragraphs.
+
+ Like this one.
+
diff --git a/src/go/doc/testdata/bugpara.1.golden b/src/go/doc/testdata/bugpara.1.golden
new file mode 100644
index 0000000..5804859
--- /dev/null
+++ b/src/go/doc/testdata/bugpara.1.golden
@@ -0,0 +1,20 @@
+//
+PACKAGE bugpara
+
+IMPORTPATH
+ testdata/bugpara
+
+FILENAMES
+ testdata/bugpara.go
+
+BUGS .Bugs is now deprecated, please use .Notes instead
+ Sometimes bugs have multiple paragraphs.
+
+ Like this one.
+
+
+BUGS
+BUG(rsc) Sometimes bugs have multiple paragraphs.
+
+ Like this one.
+
diff --git a/src/go/doc/testdata/bugpara.2.golden b/src/go/doc/testdata/bugpara.2.golden
new file mode 100644
index 0000000..5804859
--- /dev/null
+++ b/src/go/doc/testdata/bugpara.2.golden
@@ -0,0 +1,20 @@
+//
+PACKAGE bugpara
+
+IMPORTPATH
+ testdata/bugpara
+
+FILENAMES
+ testdata/bugpara.go
+
+BUGS .Bugs is now deprecated, please use .Notes instead
+ Sometimes bugs have multiple paragraphs.
+
+ Like this one.
+
+
+BUGS
+BUG(rsc) Sometimes bugs have multiple paragraphs.
+
+ Like this one.
+
diff --git a/src/go/doc/testdata/bugpara.go b/src/go/doc/testdata/bugpara.go
new file mode 100644
index 0000000..0360a6f
--- /dev/null
+++ b/src/go/doc/testdata/bugpara.go
@@ -0,0 +1,9 @@
+// Copyright 2013 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 bugpara
+
+// BUG(rsc): Sometimes bugs have multiple paragraphs.
+//
+// Like this one.
diff --git a/src/go/doc/testdata/c.0.golden b/src/go/doc/testdata/c.0.golden
new file mode 100644
index 0000000..e21959b
--- /dev/null
+++ b/src/go/doc/testdata/c.0.golden
@@ -0,0 +1,48 @@
+//
+PACKAGE c
+
+IMPORTPATH
+ testdata/c
+
+IMPORTS
+ a
+
+FILENAMES
+ testdata/c.go
+
+TYPES
+ // A (should see this)
+ type A struct{}
+
+ // B (should see this)
+ type B struct{}
+
+ // C (should see this)
+ type C struct{}
+
+ // D (should see this)
+ type D struct{}
+
+ // E1 (should see this)
+ type E1 struct{}
+
+ // E (should see this for E2 and E3)
+ type E2 struct{}
+
+ // E (should see this for E2 and E3)
+ type E3 struct{}
+
+ // E4 (should see this)
+ type E4 struct{}
+
+ //
+ type T1 struct{}
+
+ //
+ func (t1 *T1) M()
+
+ // T2 must not show methods of local T1
+ type T2 struct {
+ a.T1 // not the same as locally declared T1
+ }
+
diff --git a/src/go/doc/testdata/c.1.golden b/src/go/doc/testdata/c.1.golden
new file mode 100644
index 0000000..e21959b
--- /dev/null
+++ b/src/go/doc/testdata/c.1.golden
@@ -0,0 +1,48 @@
+//
+PACKAGE c
+
+IMPORTPATH
+ testdata/c
+
+IMPORTS
+ a
+
+FILENAMES
+ testdata/c.go
+
+TYPES
+ // A (should see this)
+ type A struct{}
+
+ // B (should see this)
+ type B struct{}
+
+ // C (should see this)
+ type C struct{}
+
+ // D (should see this)
+ type D struct{}
+
+ // E1 (should see this)
+ type E1 struct{}
+
+ // E (should see this for E2 and E3)
+ type E2 struct{}
+
+ // E (should see this for E2 and E3)
+ type E3 struct{}
+
+ // E4 (should see this)
+ type E4 struct{}
+
+ //
+ type T1 struct{}
+
+ //
+ func (t1 *T1) M()
+
+ // T2 must not show methods of local T1
+ type T2 struct {
+ a.T1 // not the same as locally declared T1
+ }
+
diff --git a/src/go/doc/testdata/c.2.golden b/src/go/doc/testdata/c.2.golden
new file mode 100644
index 0000000..e21959b
--- /dev/null
+++ b/src/go/doc/testdata/c.2.golden
@@ -0,0 +1,48 @@
+//
+PACKAGE c
+
+IMPORTPATH
+ testdata/c
+
+IMPORTS
+ a
+
+FILENAMES
+ testdata/c.go
+
+TYPES
+ // A (should see this)
+ type A struct{}
+
+ // B (should see this)
+ type B struct{}
+
+ // C (should see this)
+ type C struct{}
+
+ // D (should see this)
+ type D struct{}
+
+ // E1 (should see this)
+ type E1 struct{}
+
+ // E (should see this for E2 and E3)
+ type E2 struct{}
+
+ // E (should see this for E2 and E3)
+ type E3 struct{}
+
+ // E4 (should see this)
+ type E4 struct{}
+
+ //
+ type T1 struct{}
+
+ //
+ func (t1 *T1) M()
+
+ // T2 must not show methods of local T1
+ type T2 struct {
+ a.T1 // not the same as locally declared T1
+ }
+
diff --git a/src/go/doc/testdata/c.go b/src/go/doc/testdata/c.go
new file mode 100644
index 0000000..e0f3919
--- /dev/null
+++ b/src/go/doc/testdata/c.go
@@ -0,0 +1,62 @@
+// Copyright 2012 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 c
+
+import "a"
+
+// ----------------------------------------------------------------------------
+// Test that empty declarations don't cause problems
+
+const ()
+
+type ()
+
+var ()
+
+// ----------------------------------------------------------------------------
+// Test that types with documentation on both, the Decl and the Spec node
+// are handled correctly.
+
+// A (should see this)
+type A struct{}
+
+// B (should see this)
+type (
+ B struct{}
+)
+
+type (
+ // C (should see this)
+ C struct{}
+)
+
+// D (should not see this)
+type (
+ // D (should see this)
+ D struct{}
+)
+
+// E (should see this for E2 and E3)
+type (
+ // E1 (should see this)
+ E1 struct{}
+ E2 struct{}
+ E3 struct{}
+ // E4 (should see this)
+ E4 struct{}
+)
+
+// ----------------------------------------------------------------------------
+// Test that local and imported types are different when
+// handling anonymous fields.
+
+type T1 struct{}
+
+func (t1 *T1) M() {}
+
+// T2 must not show methods of local T1
+type T2 struct {
+ a.T1 // not the same as locally declared T1
+}
diff --git a/src/go/doc/testdata/d.0.golden b/src/go/doc/testdata/d.0.golden
new file mode 100644
index 0000000..c005199
--- /dev/null
+++ b/src/go/doc/testdata/d.0.golden
@@ -0,0 +1,104 @@
+//
+PACKAGE d
+
+IMPORTPATH
+ testdata/d
+
+FILENAMES
+ testdata/d1.go
+ testdata/d2.go
+
+CONSTANTS
+ // CBx constants should appear before CAx constants.
+ const (
+ CB2 = iota // before CB1
+ CB1 // before CB0
+ CB0 // at end
+ )
+
+ // CAx constants should appear after CBx constants.
+ const (
+ CA2 = iota // before CA1
+ CA1 // before CA0
+ CA0 // at end
+ )
+
+ // C0 should be first.
+ const C0 = 0
+
+ // C1 should be second.
+ const C1 = 1
+
+ // C2 should be third.
+ const C2 = 2
+
+ //
+ const (
+ // Single const declarations inside ()'s are considered ungrouped
+ // and show up in sorted order.
+ Cungrouped = 0
+ )
+
+
+VARIABLES
+ // VBx variables should appear before VAx variables.
+ var (
+ VB2 int // before VB1
+ VB1 int // before VB0
+ VB0 int // at end
+ )
+
+ // VAx variables should appear after VBx variables.
+ var (
+ VA2 int // before VA1
+ VA1 int // before VA0
+ VA0 int // at end
+ )
+
+ // V0 should be first.
+ var V0 uintptr
+
+ // V1 should be second.
+ var V1 uint
+
+ // V2 should be third.
+ var V2 int
+
+ //
+ var (
+ // Single var declarations inside ()'s are considered ungrouped
+ // and show up in sorted order.
+ Vungrouped = 0
+ )
+
+
+FUNCTIONS
+ // F0 should be first.
+ func F0()
+
+ // F1 should be second.
+ func F1()
+
+ // F2 should be third.
+ func F2()
+
+
+TYPES
+ // T0 should be first.
+ type T0 struct{}
+
+ // T1 should be second.
+ type T1 struct{}
+
+ // T2 should be third.
+ type T2 struct{}
+
+ // TG0 should be first.
+ type TG0 struct{}
+
+ // TG1 should be second.
+ type TG1 struct{}
+
+ // TG2 should be third.
+ type TG2 struct{}
+
diff --git a/src/go/doc/testdata/d.1.golden b/src/go/doc/testdata/d.1.golden
new file mode 100644
index 0000000..c005199
--- /dev/null
+++ b/src/go/doc/testdata/d.1.golden
@@ -0,0 +1,104 @@
+//
+PACKAGE d
+
+IMPORTPATH
+ testdata/d
+
+FILENAMES
+ testdata/d1.go
+ testdata/d2.go
+
+CONSTANTS
+ // CBx constants should appear before CAx constants.
+ const (
+ CB2 = iota // before CB1
+ CB1 // before CB0
+ CB0 // at end
+ )
+
+ // CAx constants should appear after CBx constants.
+ const (
+ CA2 = iota // before CA1
+ CA1 // before CA0
+ CA0 // at end
+ )
+
+ // C0 should be first.
+ const C0 = 0
+
+ // C1 should be second.
+ const C1 = 1
+
+ // C2 should be third.
+ const C2 = 2
+
+ //
+ const (
+ // Single const declarations inside ()'s are considered ungrouped
+ // and show up in sorted order.
+ Cungrouped = 0
+ )
+
+
+VARIABLES
+ // VBx variables should appear before VAx variables.
+ var (
+ VB2 int // before VB1
+ VB1 int // before VB0
+ VB0 int // at end
+ )
+
+ // VAx variables should appear after VBx variables.
+ var (
+ VA2 int // before VA1
+ VA1 int // before VA0
+ VA0 int // at end
+ )
+
+ // V0 should be first.
+ var V0 uintptr
+
+ // V1 should be second.
+ var V1 uint
+
+ // V2 should be third.
+ var V2 int
+
+ //
+ var (
+ // Single var declarations inside ()'s are considered ungrouped
+ // and show up in sorted order.
+ Vungrouped = 0
+ )
+
+
+FUNCTIONS
+ // F0 should be first.
+ func F0()
+
+ // F1 should be second.
+ func F1()
+
+ // F2 should be third.
+ func F2()
+
+
+TYPES
+ // T0 should be first.
+ type T0 struct{}
+
+ // T1 should be second.
+ type T1 struct{}
+
+ // T2 should be third.
+ type T2 struct{}
+
+ // TG0 should be first.
+ type TG0 struct{}
+
+ // TG1 should be second.
+ type TG1 struct{}
+
+ // TG2 should be third.
+ type TG2 struct{}
+
diff --git a/src/go/doc/testdata/d.2.golden b/src/go/doc/testdata/d.2.golden
new file mode 100644
index 0000000..c005199
--- /dev/null
+++ b/src/go/doc/testdata/d.2.golden
@@ -0,0 +1,104 @@
+//
+PACKAGE d
+
+IMPORTPATH
+ testdata/d
+
+FILENAMES
+ testdata/d1.go
+ testdata/d2.go
+
+CONSTANTS
+ // CBx constants should appear before CAx constants.
+ const (
+ CB2 = iota // before CB1
+ CB1 // before CB0
+ CB0 // at end
+ )
+
+ // CAx constants should appear after CBx constants.
+ const (
+ CA2 = iota // before CA1
+ CA1 // before CA0
+ CA0 // at end
+ )
+
+ // C0 should be first.
+ const C0 = 0
+
+ // C1 should be second.
+ const C1 = 1
+
+ // C2 should be third.
+ const C2 = 2
+
+ //
+ const (
+ // Single const declarations inside ()'s are considered ungrouped
+ // and show up in sorted order.
+ Cungrouped = 0
+ )
+
+
+VARIABLES
+ // VBx variables should appear before VAx variables.
+ var (
+ VB2 int // before VB1
+ VB1 int // before VB0
+ VB0 int // at end
+ )
+
+ // VAx variables should appear after VBx variables.
+ var (
+ VA2 int // before VA1
+ VA1 int // before VA0
+ VA0 int // at end
+ )
+
+ // V0 should be first.
+ var V0 uintptr
+
+ // V1 should be second.
+ var V1 uint
+
+ // V2 should be third.
+ var V2 int
+
+ //
+ var (
+ // Single var declarations inside ()'s are considered ungrouped
+ // and show up in sorted order.
+ Vungrouped = 0
+ )
+
+
+FUNCTIONS
+ // F0 should be first.
+ func F0()
+
+ // F1 should be second.
+ func F1()
+
+ // F2 should be third.
+ func F2()
+
+
+TYPES
+ // T0 should be first.
+ type T0 struct{}
+
+ // T1 should be second.
+ type T1 struct{}
+
+ // T2 should be third.
+ type T2 struct{}
+
+ // TG0 should be first.
+ type TG0 struct{}
+
+ // TG1 should be second.
+ type TG1 struct{}
+
+ // TG2 should be third.
+ type TG2 struct{}
+
diff --git a/src/go/doc/testdata/d1.go b/src/go/doc/testdata/d1.go
new file mode 100644
index 0000000..ebd6941
--- /dev/null
+++ b/src/go/doc/testdata/d1.go
@@ -0,0 +1,57 @@
+// Copyright 2012 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.
+
+// Test cases for sort order of declarations.
+
+package d
+
+// C2 should be third.
+const C2 = 2
+
+// V2 should be third.
+var V2 int
+
+// CBx constants should appear before CAx constants.
+const (
+ CB2 = iota // before CB1
+ CB1 // before CB0
+ CB0 // at end
+)
+
+// VBx variables should appear before VAx variables.
+var (
+ VB2 int // before VB1
+ VB1 int // before VB0
+ VB0 int // at end
+)
+
+const (
+ // Single const declarations inside ()'s are considered ungrouped
+ // and show up in sorted order.
+ Cungrouped = 0
+)
+
+var (
+ // Single var declarations inside ()'s are considered ungrouped
+ // and show up in sorted order.
+ Vungrouped = 0
+)
+
+// T2 should be third.
+type T2 struct{}
+
+// Grouped types are sorted nevertheless.
+type (
+ // TG2 should be third.
+ TG2 struct{}
+
+ // TG1 should be second.
+ TG1 struct{}
+
+ // TG0 should be first.
+ TG0 struct{}
+)
+
+// F2 should be third.
+func F2() {}
diff --git a/src/go/doc/testdata/d2.go b/src/go/doc/testdata/d2.go
new file mode 100644
index 0000000..2f56f4f
--- /dev/null
+++ b/src/go/doc/testdata/d2.go
@@ -0,0 +1,45 @@
+// Copyright 2012 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.
+
+// Test cases for sort order of declarations.
+
+package d
+
+// C1 should be second.
+const C1 = 1
+
+// C0 should be first.
+const C0 = 0
+
+// V1 should be second.
+var V1 uint
+
+// V0 should be first.
+var V0 uintptr
+
+// CAx constants should appear after CBx constants.
+const (
+ CA2 = iota // before CA1
+ CA1 // before CA0
+ CA0 // at end
+)
+
+// VAx variables should appear after VBx variables.
+var (
+ VA2 int // before VA1
+ VA1 int // before VA0
+ VA0 int // at end
+)
+
+// T1 should be second.
+type T1 struct{}
+
+// T0 should be first.
+type T0 struct{}
+
+// F1 should be second.
+func F1() {}
+
+// F0 should be first.
+func F0() {}
diff --git a/src/go/doc/testdata/e.0.golden b/src/go/doc/testdata/e.0.golden
new file mode 100644
index 0000000..6987e58
--- /dev/null
+++ b/src/go/doc/testdata/e.0.golden
@@ -0,0 +1,109 @@
+// The package e is a go/doc test for embedded methods.
+PACKAGE e
+
+IMPORTPATH
+ testdata/e
+
+FILENAMES
+ testdata/e.go
+
+TYPES
+ // T1 has no embedded (level 1) M method due to conflict.
+ type T1 struct {
+ // contains filtered or unexported fields
+ }
+
+ // T2 has only M as top-level method.
+ type T2 struct {
+ // contains filtered or unexported fields
+ }
+
+ // T2.M should appear as method of T2.
+ func (T2) M()
+
+ // T3 has only M as top-level method.
+ type T3 struct {
+ // contains filtered or unexported fields
+ }
+
+ // T3.M should appear as method of T3.
+ func (T3) M()
+
+ //
+ type T4 struct{}
+
+ // T4.M should appear as method of T5 only if AllMethods is set.
+ func (*T4) M()
+
+ //
+ type T5 struct {
+ T4
+ }
+
+ //
+ type U1 struct {
+ *U1
+ }
+
+ // U1.M should appear as method of U1.
+ func (*U1) M()
+
+ //
+ type U2 struct {
+ *U3
+ }
+
+ // U2.M should appear as method of U2 and as method of U3 only if ...
+ func (*U2) M()
+
+ //
+ type U3 struct {
+ *U2
+ }
+
+ // U3.N should appear as method of U3 and as method of U2 only if ...
+ func (*U3) N()
+
+ //
+ type U4 struct {
+ // contains filtered or unexported fields
+ }
+
+ // U4.M should appear as method of U4.
+ func (*U4) M()
+
+ //
+ type V1 struct {
+ *V2
+ *V5
+ }
+
+ //
+ type V2 struct {
+ *V3
+ }
+
+ //
+ type V3 struct {
+ *V4
+ }
+
+ //
+ type V4 struct {
+ *V5
+ }
+
+ // V4.M should appear as method of V2 and V3 if AllMethods is set.
+ func (*V4) M()
+
+ //
+ type V5 struct {
+ *V6
+ }
+
+ //
+ type V6 struct{}
+
+ // V6.M should appear as method of V1 and V5 if AllMethods is set.
+ func (*V6) M()
+
diff --git a/src/go/doc/testdata/e.1.golden b/src/go/doc/testdata/e.1.golden
new file mode 100644
index 0000000..cbe22e0
--- /dev/null
+++ b/src/go/doc/testdata/e.1.golden
@@ -0,0 +1,144 @@
+// The package e is a go/doc test for embedded methods.
+PACKAGE e
+
+IMPORTPATH
+ testdata/e
+
+FILENAMES
+ testdata/e.go
+
+TYPES
+ // T1 has no embedded (level 1) M method due to conflict.
+ type T1 struct {
+ t1
+ t2
+ }
+
+ // T2 has only M as top-level method.
+ type T2 struct {
+ t1
+ }
+
+ // T2.M should appear as method of T2.
+ func (T2) M()
+
+ // T3 has only M as top-level method.
+ type T3 struct {
+ t1e
+ t2e
+ }
+
+ // T3.M should appear as method of T3.
+ func (T3) M()
+
+ //
+ type T4 struct{}
+
+ // T4.M should appear as method of T5 only if AllMethods is set.
+ func (*T4) M()
+
+ //
+ type T5 struct {
+ T4
+ }
+
+ //
+ type U1 struct {
+ *U1
+ }
+
+ // U1.M should appear as method of U1.
+ func (*U1) M()
+
+ //
+ type U2 struct {
+ *U3
+ }
+
+ // U2.M should appear as method of U2 and as method of U3 only if ...
+ func (*U2) M()
+
+ //
+ type U3 struct {
+ *U2
+ }
+
+ // U3.N should appear as method of U3 and as method of U2 only if ...
+ func (*U3) N()
+
+ //
+ type U4 struct {
+ *u5
+ }
+
+ // U4.M should appear as method of U4.
+ func (*U4) M()
+
+ //
+ type V1 struct {
+ *V2
+ *V5
+ }
+
+ //
+ type V2 struct {
+ *V3
+ }
+
+ //
+ type V3 struct {
+ *V4
+ }
+
+ //
+ type V4 struct {
+ *V5
+ }
+
+ // V4.M should appear as method of V2 and V3 if AllMethods is set.
+ func (*V4) M()
+
+ //
+ type V5 struct {
+ *V6
+ }
+
+ //
+ type V6 struct{}
+
+ // V6.M should appear as method of V1 and V5 if AllMethods is set.
+ func (*V6) M()
+
+ //
+ type t1 struct{}
+
+ // t1.M should not appear as method in a Tx type.
+ func (t1) M()
+
+ //
+ type t1e struct {
+ t1
+ }
+
+ // t1.M should not appear as method in a Tx type.
+ func (t1e) M()
+
+ //
+ type t2 struct{}
+
+ // t2.M should not appear as method in a Tx type.
+ func (t2) M()
+
+ //
+ type t2e struct {
+ t2
+ }
+
+ // t2.M should not appear as method in a Tx type.
+ func (t2e) M()
+
+ //
+ type u5 struct {
+ *U4
+ }
+
diff --git a/src/go/doc/testdata/e.2.golden b/src/go/doc/testdata/e.2.golden
new file mode 100644
index 0000000..e7b05e8
--- /dev/null
+++ b/src/go/doc/testdata/e.2.golden
@@ -0,0 +1,130 @@
+// The package e is a go/doc test for embedded methods.
+PACKAGE e
+
+IMPORTPATH
+ testdata/e
+
+FILENAMES
+ testdata/e.go
+
+TYPES
+ // T1 has no embedded (level 1) M method due to conflict.
+ type T1 struct {
+ // contains filtered or unexported fields
+ }
+
+ // T2 has only M as top-level method.
+ type T2 struct {
+ // contains filtered or unexported fields
+ }
+
+ // T2.M should appear as method of T2.
+ func (T2) M()
+
+ // T3 has only M as top-level method.
+ type T3 struct {
+ // contains filtered or unexported fields
+ }
+
+ // T3.M should appear as method of T3.
+ func (T3) M()
+
+ //
+ type T4 struct{}
+
+ // T4.M should appear as method of T5 only if AllMethods is set.
+ func (*T4) M()
+
+ //
+ type T5 struct {
+ T4
+ }
+
+ // T4.M should appear as method of T5 only if AllMethods is set.
+ func (*T5) M()
+
+ //
+ type U1 struct {
+ *U1
+ }
+
+ // U1.M should appear as method of U1.
+ func (*U1) M()
+
+ //
+ type U2 struct {
+ *U3
+ }
+
+ // U2.M should appear as method of U2 and as method of U3 only if ...
+ func (*U2) M()
+
+ // U3.N should appear as method of U3 and as method of U2 only if ...
+ func (U2) N()
+
+ //
+ type U3 struct {
+ *U2
+ }
+
+ // U2.M should appear as method of U2 and as method of U3 only if ...
+ func (U3) M()
+
+ // U3.N should appear as method of U3 and as method of U2 only if ...
+ func (*U3) N()
+
+ //
+ type U4 struct {
+ // contains filtered or unexported fields
+ }
+
+ // U4.M should appear as method of U4.
+ func (*U4) M()
+
+ //
+ type V1 struct {
+ *V2
+ *V5
+ }
+
+ // V6.M should appear as method of V1 and V5 if AllMethods is set.
+ func (V1) M()
+
+ //
+ type V2 struct {
+ *V3
+ }
+
+ // V4.M should appear as method of V2 and V3 if AllMethods is set.
+ func (V2) M()
+
+ //
+ type V3 struct {
+ *V4
+ }
+
+ // V4.M should appear as method of V2 and V3 if AllMethods is set.
+ func (V3) M()
+
+ //
+ type V4 struct {
+ *V5
+ }
+
+ // V4.M should appear as method of V2 and V3 if AllMethods is set.
+ func (*V4) M()
+
+ //
+ type V5 struct {
+ *V6
+ }
+
+ // V6.M should appear as method of V1 and V5 if AllMethods is set.
+ func (V5) M()
+
+ //
+ type V6 struct{}
+
+ // V6.M should appear as method of V1 and V5 if AllMethods is set.
+ func (*V6) M()
+
diff --git a/src/go/doc/testdata/e.go b/src/go/doc/testdata/e.go
new file mode 100644
index 0000000..ec432e3
--- /dev/null
+++ b/src/go/doc/testdata/e.go
@@ -0,0 +1,147 @@
+// Copyright 2012 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.
+
+// The package e is a go/doc test for embedded methods.
+package e
+
+// ----------------------------------------------------------------------------
+// Conflicting methods M must not show up.
+
+type t1 struct{}
+
+// t1.M should not appear as method in a Tx type.
+func (t1) M() {}
+
+type t2 struct{}
+
+// t2.M should not appear as method in a Tx type.
+func (t2) M() {}
+
+// T1 has no embedded (level 1) M method due to conflict.
+type T1 struct {
+ t1
+ t2
+}
+
+// ----------------------------------------------------------------------------
+// Higher-level method M wins over lower-level method M.
+
+// T2 has only M as top-level method.
+type T2 struct {
+ t1
+}
+
+// T2.M should appear as method of T2.
+func (T2) M() {}
+
+// ----------------------------------------------------------------------------
+// Higher-level method M wins over lower-level conflicting methods M.
+
+type t1e struct {
+ t1
+}
+
+type t2e struct {
+ t2
+}
+
+// T3 has only M as top-level method.
+type T3 struct {
+ t1e
+ t2e
+}
+
+// T3.M should appear as method of T3.
+func (T3) M() {}
+
+// ----------------------------------------------------------------------------
+// Don't show conflicting methods M embedded via an exported and non-exported
+// type.
+
+// T1 has no embedded (level 1) M method due to conflict.
+type T4 struct {
+ t2
+ T2
+}
+
+// ----------------------------------------------------------------------------
+// Don't show embedded methods of exported anonymous fields unless AllMethods
+// is set.
+
+type T4 struct{}
+
+// T4.M should appear as method of T5 only if AllMethods is set.
+func (*T4) M() {}
+
+type T5 struct {
+ T4
+}
+
+// ----------------------------------------------------------------------------
+// Recursive type declarations must not lead to endless recursion.
+
+type U1 struct {
+ *U1
+}
+
+// U1.M should appear as method of U1.
+func (*U1) M() {}
+
+type U2 struct {
+ *U3
+}
+
+// U2.M should appear as method of U2 and as method of U3 only if AllMethods is set.
+func (*U2) M() {}
+
+type U3 struct {
+ *U2
+}
+
+// U3.N should appear as method of U3 and as method of U2 only if AllMethods is set.
+func (*U3) N() {}
+
+type U4 struct {
+ *u5
+}
+
+// U4.M should appear as method of U4.
+func (*U4) M() {}
+
+type u5 struct {
+ *U4
+}
+
+// ----------------------------------------------------------------------------
+// A higher-level embedded type (and its methods) wins over the same type (and
+// its methods) embedded at a lower level.
+
+type V1 struct {
+ *V2
+ *V5
+}
+
+type V2 struct {
+ *V3
+}
+
+type V3 struct {
+ *V4
+}
+
+type V4 struct {
+ *V5
+}
+
+type V5 struct {
+ *V6
+}
+
+type V6 struct{}
+
+// V4.M should appear as method of V2 and V3 if AllMethods is set.
+func (*V4) M() {}
+
+// V6.M should appear as method of V1 and V5 if AllMethods is set.
+func (*V6) M() {}
diff --git a/src/go/doc/testdata/error1.0.golden b/src/go/doc/testdata/error1.0.golden
new file mode 100644
index 0000000..6c6fe5d
--- /dev/null
+++ b/src/go/doc/testdata/error1.0.golden
@@ -0,0 +1,30 @@
+//
+PACKAGE error1
+
+IMPORTPATH
+ testdata/error1
+
+FILENAMES
+ testdata/error1.go
+
+TYPES
+ //
+ type I0 interface {
+ // When embedded, the predeclared error interface
+ // must remain visible in interface types.
+ error
+ }
+
+ //
+ type S0 struct {
+ // contains filtered or unexported fields
+ }
+
+ //
+ type T0 struct {
+ ExportedField interface {
+ // error should be visible
+ error
+ }
+ }
+
diff --git a/src/go/doc/testdata/error1.1.golden b/src/go/doc/testdata/error1.1.golden
new file mode 100644
index 0000000..a8dc2e7
--- /dev/null
+++ b/src/go/doc/testdata/error1.1.golden
@@ -0,0 +1,32 @@
+//
+PACKAGE error1
+
+IMPORTPATH
+ testdata/error1
+
+FILENAMES
+ testdata/error1.go
+
+TYPES
+ //
+ type I0 interface {
+ // When embedded, the predeclared error interface
+ // must remain visible in interface types.
+ error
+ }
+
+ //
+ type S0 struct {
+ // In struct types, an embedded error must only be visible
+ // if AllDecls is set.
+ error
+ }
+
+ //
+ type T0 struct {
+ ExportedField interface {
+ // error should be visible
+ error
+ }
+ }
+
diff --git a/src/go/doc/testdata/error1.2.golden b/src/go/doc/testdata/error1.2.golden
new file mode 100644
index 0000000..6c6fe5d
--- /dev/null
+++ b/src/go/doc/testdata/error1.2.golden
@@ -0,0 +1,30 @@
+//
+PACKAGE error1
+
+IMPORTPATH
+ testdata/error1
+
+FILENAMES
+ testdata/error1.go
+
+TYPES
+ //
+ type I0 interface {
+ // When embedded, the predeclared error interface
+ // must remain visible in interface types.
+ error
+ }
+
+ //
+ type S0 struct {
+ // contains filtered or unexported fields
+ }
+
+ //
+ type T0 struct {
+ ExportedField interface {
+ // error should be visible
+ error
+ }
+ }
+
diff --git a/src/go/doc/testdata/error1.go b/src/go/doc/testdata/error1.go
new file mode 100644
index 0000000..3c777a7
--- /dev/null
+++ b/src/go/doc/testdata/error1.go
@@ -0,0 +1,24 @@
+// Copyright 2012 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 error1
+
+type I0 interface {
+ // When embedded, the predeclared error interface
+ // must remain visible in interface types.
+ error
+}
+
+type T0 struct {
+ ExportedField interface {
+ // error should be visible
+ error
+ }
+}
+
+type S0 struct {
+ // In struct types, an embedded error must only be visible
+ // if AllDecls is set.
+ error
+}
diff --git a/src/go/doc/testdata/error2.0.golden b/src/go/doc/testdata/error2.0.golden
new file mode 100644
index 0000000..dedfe41
--- /dev/null
+++ b/src/go/doc/testdata/error2.0.golden
@@ -0,0 +1,27 @@
+//
+PACKAGE error2
+
+IMPORTPATH
+ testdata/error2
+
+FILENAMES
+ testdata/error2.go
+
+TYPES
+ //
+ type I0 interface {
+ // contains filtered or unexported methods
+ }
+
+ //
+ type S0 struct {
+ // contains filtered or unexported fields
+ }
+
+ //
+ type T0 struct {
+ ExportedField interface {
+ // contains filtered or unexported methods
+ }
+ }
+
diff --git a/src/go/doc/testdata/error2.1.golden b/src/go/doc/testdata/error2.1.golden
new file mode 100644
index 0000000..dbcc1b0
--- /dev/null
+++ b/src/go/doc/testdata/error2.1.golden
@@ -0,0 +1,37 @@
+//
+PACKAGE error2
+
+IMPORTPATH
+ testdata/error2
+
+FILENAMES
+ testdata/error2.go
+
+TYPES
+ //
+ type I0 interface {
+ // When embedded, the locally-declared error interface
+ // is only visible if all declarations are shown.
+ error
+ }
+
+ //
+ type S0 struct {
+ // In struct types, an embedded error must only be visible
+ // if AllDecls is set.
+ error
+ }
+
+ //
+ type T0 struct {
+ ExportedField interface {
+ // error should not be visible
+ error
+ }
+ }
+
+ // This error declaration shadows the predeclared error type.
+ type error interface {
+ Error() string
+ }
+
diff --git a/src/go/doc/testdata/error2.2.golden b/src/go/doc/testdata/error2.2.golden
new file mode 100644
index 0000000..dedfe41
--- /dev/null
+++ b/src/go/doc/testdata/error2.2.golden
@@ -0,0 +1,27 @@
+//
+PACKAGE error2
+
+IMPORTPATH
+ testdata/error2
+
+FILENAMES
+ testdata/error2.go
+
+TYPES
+ //
+ type I0 interface {
+ // contains filtered or unexported methods
+ }
+
+ //
+ type S0 struct {
+ // contains filtered or unexported fields
+ }
+
+ //
+ type T0 struct {
+ ExportedField interface {
+ // contains filtered or unexported methods
+ }
+ }
+
diff --git a/src/go/doc/testdata/error2.go b/src/go/doc/testdata/error2.go
new file mode 100644
index 0000000..6ee96c2
--- /dev/null
+++ b/src/go/doc/testdata/error2.go
@@ -0,0 +1,29 @@
+// Copyright 2012 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 error2
+
+type I0 interface {
+ // When embedded, the locally-declared error interface
+ // is only visible if all declarations are shown.
+ error
+}
+
+type T0 struct {
+ ExportedField interface {
+ // error should not be visible
+ error
+ }
+}
+
+type S0 struct {
+ // In struct types, an embedded error must only be visible
+ // if AllDecls is set.
+ error
+}
+
+// This error declaration shadows the predeclared error type.
+type error interface {
+ Error() string
+}
diff --git a/src/go/doc/testdata/example.go b/src/go/doc/testdata/example.go
new file mode 100644
index 0000000..0b70339
--- /dev/null
+++ b/src/go/doc/testdata/example.go
@@ -0,0 +1,81 @@
+// Copyright 2009 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 testing
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+ "time"
+)
+
+type InternalExample struct {
+ Name string
+ F func()
+ Output string
+}
+
+func RunExamples(examples []InternalExample) (ok bool) {
+ ok = true
+
+ var eg InternalExample
+
+ stdout, stderr := os.Stdout, os.Stderr
+ defer func() {
+ os.Stdout, os.Stderr = stdout, stderr
+ if e := recover(); e != nil {
+ fmt.Printf("--- FAIL: %s\npanic: %v\n", eg.Name, e)
+ os.Exit(1)
+ }
+ }()
+
+ for _, eg = range examples {
+ if *chatty {
+ fmt.Printf("=== RUN: %s\n", eg.Name)
+ }
+
+ // capture stdout and stderr
+ r, w, err := os.Pipe()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ os.Stdout, os.Stderr = w, w
+ outC := make(chan string)
+ go func() {
+ buf := new(bytes.Buffer)
+ _, err := io.Copy(buf, r)
+ if err != nil {
+ fmt.Fprintf(stderr, "testing: copying pipe: %v\n", err)
+ os.Exit(1)
+ }
+ outC <- buf.String()
+ }()
+
+ // run example
+ t0 := time.Now()
+ eg.F()
+ dt := time.Since(t0)
+
+ // close pipe, restore stdout/stderr, get output
+ w.Close()
+ os.Stdout, os.Stderr = stdout, stderr
+ out := <-outC
+
+ // report any errors
+ tstr := fmt.Sprintf("(%.2f seconds)", dt.Seconds())
+ if g, e := strings.TrimSpace(out), strings.TrimSpace(eg.Output); g != e {
+ fmt.Printf("--- FAIL: %s %s\ngot:\n%s\nwant:\n%s\n",
+ eg.Name, tstr, g, e)
+ ok = false
+ } else if *chatty {
+ fmt.Printf("--- PASS: %s %s\n", eg.Name, tstr)
+ }
+ }
+
+ return
+}
diff --git a/src/go/doc/testdata/examples/README.md b/src/go/doc/testdata/examples/README.md
new file mode 100644
index 0000000..a1c18e8
--- /dev/null
+++ b/src/go/doc/testdata/examples/README.md
@@ -0,0 +1,12 @@
+These files are processed by example_test.go:TestExamples.
+
+A .golden file is a txtar file with two sections for each example that should be
+created by doc.Examples from the corresponding .go file.
+
+One section, named EXAMPLE_NAME.Output, contains the example's output,
+the value of the field Example.Output.
+
+The other, named EXAMPLE_NAME.Play, contains the formatted code for a playable
+version of the example, the value of the field Example.Play.
+
+If a section is missing, it is treated as being empty.
diff --git a/src/go/doc/testdata/examples/empty.go b/src/go/doc/testdata/examples/empty.go
new file mode 100644
index 0000000..0b10420
--- /dev/null
+++ b/src/go/doc/testdata/examples/empty.go
@@ -0,0 +1,8 @@
+// Copyright 2021 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 p
+
+func Example() {}
+func Example_a()
diff --git a/src/go/doc/testdata/examples/empty.golden b/src/go/doc/testdata/examples/empty.golden
new file mode 100644
index 0000000..2aafd20
--- /dev/null
+++ b/src/go/doc/testdata/examples/empty.golden
@@ -0,0 +1,6 @@
+-- .Play --
+package main
+
+func main() {}
+func main()
+
diff --git a/src/go/doc/testdata/examples/generic_constraints.go b/src/go/doc/testdata/examples/generic_constraints.go
new file mode 100644
index 0000000..ea5d2b3
--- /dev/null
+++ b/src/go/doc/testdata/examples/generic_constraints.go
@@ -0,0 +1,38 @@
+// Copyright 2021 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 p_test
+
+import (
+ "fmt"
+ "time"
+)
+
+type C1 interface {
+ string | int
+}
+
+type C2 interface {
+ M(time.Time)
+}
+
+type G[T C1] int
+
+func g[T C2](x T) {}
+
+type Tm int
+
+func (Tm) M(time.Time) {}
+
+type Foo int
+
+func Example() {
+ fmt.Println("hello")
+}
+
+func ExampleGeneric() {
+ var x G[string]
+ g(Tm(3))
+ fmt.Println(x)
+}
diff --git a/src/go/doc/testdata/examples/generic_constraints.golden b/src/go/doc/testdata/examples/generic_constraints.golden
new file mode 100644
index 0000000..6c7b0ed
--- /dev/null
+++ b/src/go/doc/testdata/examples/generic_constraints.golden
@@ -0,0 +1,39 @@
+-- .Play --
+package main
+
+import (
+ "fmt"
+)
+
+func main() {
+ fmt.Println("hello")
+}
+-- Generic.Play --
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+type C1 interface {
+ string | int
+}
+
+type C2 interface {
+ M(time.Time)
+}
+
+type G[T C1] int
+
+func g[T C2](x T) {}
+
+type Tm int
+
+func (Tm) M(time.Time) {}
+
+func main() {
+ var x G[string]
+ g(Tm(3))
+ fmt.Println(x)
+}
diff --git a/src/go/doc/testdata/examples/import_groups.go b/src/go/doc/testdata/examples/import_groups.go
new file mode 100644
index 0000000..05f21ca
--- /dev/null
+++ b/src/go/doc/testdata/examples/import_groups.go
@@ -0,0 +1,23 @@
+// 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 foo_test
+
+import (
+ "fmt"
+ "time"
+
+ "golang.org/x/time/rate"
+)
+
+func Example() {
+ fmt.Println("Hello, world!")
+ // Output: Hello, world!
+}
+
+func ExampleLimiter() {
+ // Uses fmt, time and rate.
+ l := rate.NewLimiter(rate.Every(time.Second), 1)
+ fmt.Println(l)
+}
diff --git a/src/go/doc/testdata/examples/import_groups.golden b/src/go/doc/testdata/examples/import_groups.golden
new file mode 100644
index 0000000..efe2cc1
--- /dev/null
+++ b/src/go/doc/testdata/examples/import_groups.golden
@@ -0,0 +1,27 @@
+-- .Play --
+package main
+
+import (
+ "fmt"
+)
+
+func main() {
+ fmt.Println("Hello, world!")
+}
+-- .Output --
+Hello, world!
+-- Limiter.Play --
+package main
+
+import (
+ "fmt"
+ "time"
+
+ "golang.org/x/time/rate"
+)
+
+func main() {
+ // Uses fmt, time and rate.
+ l := rate.NewLimiter(rate.Every(time.Second), 1)
+ fmt.Println(l)
+}
diff --git a/src/go/doc/testdata/examples/import_groups_named.go b/src/go/doc/testdata/examples/import_groups_named.go
new file mode 100644
index 0000000..377022b
--- /dev/null
+++ b/src/go/doc/testdata/examples/import_groups_named.go
@@ -0,0 +1,23 @@
+// 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 foo_test
+
+import (
+ "fmt"
+ tm "time"
+
+ r "golang.org/x/time/rate"
+)
+
+func Example() {
+ fmt.Println("Hello, world!")
+ // Output: Hello, world!
+}
+
+func ExampleLimiter() {
+ // Uses fmt, time and rate.
+ l := r.NewLimiter(r.Every(tm.Second), 1)
+ fmt.Println(l)
+}
diff --git a/src/go/doc/testdata/examples/import_groups_named.golden b/src/go/doc/testdata/examples/import_groups_named.golden
new file mode 100644
index 0000000..9baf373
--- /dev/null
+++ b/src/go/doc/testdata/examples/import_groups_named.golden
@@ -0,0 +1,27 @@
+-- .Play --
+package main
+
+import (
+ "fmt"
+)
+
+func main() {
+ fmt.Println("Hello, world!")
+}
+-- .Output --
+Hello, world!
+-- Limiter.Play --
+package main
+
+import (
+ "fmt"
+ tm "time"
+
+ r "golang.org/x/time/rate"
+)
+
+func main() {
+ // Uses fmt, time and rate.
+ l := r.NewLimiter(r.Every(tm.Second), 1)
+ fmt.Println(l)
+}
diff --git a/src/go/doc/testdata/examples/inspect_signature.go b/src/go/doc/testdata/examples/inspect_signature.go
new file mode 100644
index 0000000..c4a36e7
--- /dev/null
+++ b/src/go/doc/testdata/examples/inspect_signature.go
@@ -0,0 +1,23 @@
+// Copyright 2021 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 foo_test
+
+import (
+ "bytes"
+ "io"
+)
+
+func getReader() io.Reader { return nil }
+
+func do(b bytes.Reader) {}
+
+func Example() {
+ getReader()
+ do()
+ // Output:
+}
+
+func ExampleIgnored() {
+}
diff --git a/src/go/doc/testdata/examples/inspect_signature.golden b/src/go/doc/testdata/examples/inspect_signature.golden
new file mode 100644
index 0000000..c0d9b2e
--- /dev/null
+++ b/src/go/doc/testdata/examples/inspect_signature.golden
@@ -0,0 +1,24 @@
+-- .Play --
+package main
+
+import (
+ "bytes"
+ "io"
+)
+
+func getReader() io.Reader { return nil }
+
+func do(b bytes.Reader) {}
+
+func main() {
+ getReader()
+ do()
+}
+-- Ignored.Play --
+package main
+
+import ()
+
+func main() {
+}
+
diff --git a/src/go/doc/testdata/examples/iota.go b/src/go/doc/testdata/examples/iota.go
new file mode 100644
index 0000000..c878b77
--- /dev/null
+++ b/src/go/doc/testdata/examples/iota.go
@@ -0,0 +1,34 @@
+// Copyright 2021 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 foo_test
+
+const (
+ a = iota
+ b
+)
+
+const (
+ c = 3
+ d = 4
+)
+
+const (
+ e = iota
+ f
+)
+
+// The example refers to only one of the constants in the iota group, but we
+// must keep all of them because of the iota. The second group of constants can
+// be trimmed. The third has an iota, but is unused, so it can be eliminated.
+
+func Example() {
+ _ = b
+ _ = d
+}
+
+// Need two examples to hit the playExample function.
+
+func Example2() {
+}
diff --git a/src/go/doc/testdata/examples/iota.golden b/src/go/doc/testdata/examples/iota.golden
new file mode 100644
index 0000000..7487702
--- /dev/null
+++ b/src/go/doc/testdata/examples/iota.golden
@@ -0,0 +1,23 @@
+-- .Play --
+package main
+
+import ()
+
+const (
+ a = iota
+ b
+)
+
+const d = 4
+
+func main() {
+ _ = b
+ _ = d
+}
+-- 2.Play --
+package main
+
+import ()
+
+func main() {
+}
diff --git a/src/go/doc/testdata/examples/issue43658.go b/src/go/doc/testdata/examples/issue43658.go
new file mode 100644
index 0000000..385223a
--- /dev/null
+++ b/src/go/doc/testdata/examples/issue43658.go
@@ -0,0 +1,223 @@
+// Copyright ©2016 The Gonum Authors. All rights reserved.
+// Copyright 2021 The Go Authors. All rights reserved.
+// (above line required for our license-header checker)
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package community_test
+
+import (
+ "fmt"
+ "log"
+ "sort"
+
+ "golang.org/x/exp/rand"
+
+ "gonum.org/v1/gonum/graph/community"
+ "gonum.org/v1/gonum/graph/internal/ordered"
+ "gonum.org/v1/gonum/graph/simple"
+)
+
+func ExampleProfile_simple() {
+ // Profile calls Modularize which implements the Louvain modularization algorithm.
+ // Since this is a randomized algorithm we use a defined random source to ensure
+ // consistency between test runs. In practice, results will not differ greatly
+ // between runs with different PRNG seeds.
+ src := rand.NewSource(1)
+
+ // Create dumbell graph:
+ //
+ // 0 4
+ // |\ /|
+ // | 2 - 3 |
+ // |/ \|
+ // 1 5
+ //
+ g := simple.NewUndirectedGraph()
+ for u, e := range smallDumbell {
+ for v := range e {
+ g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
+ }
+ }
+
+ // Get the profile of internal node weight for resolutions
+ // between 0.1 and 10 using logarithmic bisection.
+ p, err := community.Profile(
+ community.ModularScore(g, community.Weight, 10, src),
+ true, 1e-3, 0.1, 10,
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Print out each step with communities ordered.
+ for _, d := range p {
+ comm := d.Communities()
+ for _, c := range comm {
+ sort.Sort(ordered.ByID(c))
+ }
+ sort.Sort(ordered.BySliceIDs(comm))
+ fmt.Printf("Low:%.2v High:%.2v Score:%v Communities:%v Q=%.3v\n",
+ d.Low, d.High, d.Score, comm, community.Q(g, comm, d.Low))
+ }
+
+ // Output:
+ // Low:0.1 High:0.29 Score:14 Communities:[[0 1 2 3 4 5]] Q=0.9
+ // Low:0.29 High:2.3 Score:12 Communities:[[0 1 2] [3 4 5]] Q=0.714
+ // Low:2.3 High:3.5 Score:4 Communities:[[0 1] [2] [3] [4 5]] Q=-0.31
+ // Low:3.5 High:10 Score:0 Communities:[[0] [1] [2] [3] [4] [5]] Q=-0.607
+}
+
+// intset is an integer set.
+type intset map[int]struct{}
+
+func linksTo(i ...int) intset {
+ if len(i) == 0 {
+ return nil
+ }
+ s := make(intset)
+ for _, v := range i {
+ s[v] = struct{}{}
+ }
+ return s
+}
+
+var (
+ smallDumbell = []intset{
+ 0: linksTo(1, 2),
+ 1: linksTo(2),
+ 2: linksTo(3),
+ 3: linksTo(4, 5),
+ 4: linksTo(5),
+ 5: nil,
+ }
+
+ // http://www.slate.com/blogs/the_world_/2014/07/17/the_middle_east_friendship_chart.html
+ middleEast = struct{ friends, complicated, enemies []intset }{
+ // green cells
+ friends: []intset{
+ 0: nil,
+ 1: linksTo(5, 7, 9, 12),
+ 2: linksTo(11),
+ 3: linksTo(4, 5, 10),
+ 4: linksTo(3, 5, 10),
+ 5: linksTo(1, 3, 4, 8, 10, 12),
+ 6: nil,
+ 7: linksTo(1, 12),
+ 8: linksTo(5, 9, 11),
+ 9: linksTo(1, 8, 12),
+ 10: linksTo(3, 4, 5),
+ 11: linksTo(2, 8),
+ 12: linksTo(1, 5, 7, 9),
+ },
+
+ // yellow cells
+ complicated: []intset{
+ 0: linksTo(2, 4),
+ 1: linksTo(4, 8),
+ 2: linksTo(0, 3, 4, 5, 8, 9),
+ 3: linksTo(2, 8, 11),
+ 4: linksTo(0, 1, 2, 8),
+ 5: linksTo(2),
+ 6: nil,
+ 7: linksTo(9, 11),
+ 8: linksTo(1, 2, 3, 4, 10, 12),
+ 9: linksTo(2, 7, 11),
+ 10: linksTo(8),
+ 11: linksTo(3, 7, 9, 12),
+ 12: linksTo(8, 11),
+ },
+
+ // red cells
+ enemies: []intset{
+ 0: linksTo(1, 3, 5, 6, 7, 8, 9, 10, 11, 12),
+ 1: linksTo(0, 2, 3, 6, 10, 11),
+ 2: linksTo(1, 6, 7, 10, 12),
+ 3: linksTo(0, 1, 6, 7, 9, 12),
+ 4: linksTo(6, 7, 9, 11, 12),
+ 5: linksTo(0, 6, 7, 9, 11),
+ 6: linksTo(0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12),
+ 7: linksTo(0, 2, 3, 4, 5, 6, 8, 10),
+ 8: linksTo(0, 6, 7),
+ 9: linksTo(0, 3, 4, 5, 6, 10),
+ 10: linksTo(0, 1, 2, 6, 7, 9, 11, 12),
+ 11: linksTo(0, 1, 4, 5, 6, 10),
+ 12: linksTo(0, 2, 3, 4, 6, 10),
+ },
+ }
+)
+
+var friends, enemies *simple.WeightedUndirectedGraph
+
+func init() {
+ friends = simple.NewWeightedUndirectedGraph(0, 0)
+ for u, e := range middleEast.friends {
+ // Ensure unconnected nodes are included.
+ if friends.Node(int64(u)) == nil {
+ friends.AddNode(simple.Node(u))
+ }
+ for v := range e {
+ friends.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: 1})
+ }
+ }
+ enemies = simple.NewWeightedUndirectedGraph(0, 0)
+ for u, e := range middleEast.enemies {
+ // Ensure unconnected nodes are included.
+ if enemies.Node(int64(u)) == nil {
+ enemies.AddNode(simple.Node(u))
+ }
+ for v := range e {
+ enemies.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: -1})
+ }
+ }
+}
+
+func ExampleProfile_multiplex() {
+ // Profile calls ModularizeMultiplex which implements the Louvain modularization
+ // algorithm. Since this is a randomized algorithm we use a defined random source
+ // to ensure consistency between test runs. In practice, results will not differ
+ // greatly between runs with different PRNG seeds.
+ src := rand.NewSource(1)
+
+ // The undirected graphs, friends and enemies, are the political relationships
+ // in the Middle East as described in the Slate article:
+ // http://www.slate.com/blogs/the_world_/2014/07/17/the_middle_east_friendship_chart.html
+ g, err := community.NewUndirectedLayers(friends, enemies)
+ if err != nil {
+ log.Fatal(err)
+ }
+ weights := []float64{1, -1}
+
+ // Get the profile of internal node weight for resolutions
+ // between 0.1 and 10 using logarithmic bisection.
+ p, err := community.Profile(
+ community.ModularMultiplexScore(g, weights, true, community.WeightMultiplex, 10, src),
+ true, 1e-3, 0.1, 10,
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Print out each step with communities ordered.
+ for _, d := range p {
+ comm := d.Communities()
+ for _, c := range comm {
+ sort.Sort(ordered.ByID(c))
+ }
+ sort.Sort(ordered.BySliceIDs(comm))
+ fmt.Printf("Low:%.2v High:%.2v Score:%v Communities:%v Q=%.3v\n",
+ d.Low, d.High, d.Score, comm, community.QMultiplex(g, comm, weights, []float64{d.Low}))
+ }
+
+ // Output:
+ // Low:0.1 High:0.72 Score:26 Communities:[[0] [1 7 9 12] [2 8 11] [3 4 5 10] [6]] Q=[24.7 1.97]
+ // Low:0.72 High:1.1 Score:24 Communities:[[0 6] [1 7 9 12] [2 8 11] [3 4 5 10]] Q=[16.9 14.1]
+ // Low:1.1 High:1.2 Score:18 Communities:[[0 2 6 11] [1 7 9 12] [3 4 5 8 10]] Q=[9.16 25.1]
+ // Low:1.2 High:1.6 Score:10 Communities:[[0 3 4 5 6 10] [1 7 9 12] [2 8 11]] Q=[10.5 26.7]
+ // Low:1.6 High:1.6 Score:8 Communities:[[0 1 6 7 9 12] [2 8 11] [3 4 5 10]] Q=[5.56 39.8]
+ // Low:1.6 High:1.8 Score:2 Communities:[[0 2 3 4 5 6 10] [1 7 8 9 11 12]] Q=[-1.82 48.6]
+ // Low:1.8 High:2.3 Score:-6 Communities:[[0 2 3 4 5 6 8 10 11] [1 7 9 12]] Q=[-5 57.5]
+ // Low:2.3 High:2.4 Score:-10 Communities:[[0 1 2 6 7 8 9 11 12] [3 4 5 10]] Q=[-11.2 79]
+ // Low:2.4 High:4.3 Score:-52 Communities:[[0 1 2 3 4 5 6 7 8 9 10 11 12]] Q=[-46.1 117]
+ // Low:4.3 High:10 Score:-54 Communities:[[0 1 2 3 4 6 7 8 9 10 11 12] [5]] Q=[-82 254]
+}
diff --git a/src/go/doc/testdata/examples/issue43658.golden b/src/go/doc/testdata/examples/issue43658.golden
new file mode 100644
index 0000000..5200d14
--- /dev/null
+++ b/src/go/doc/testdata/examples/issue43658.golden
@@ -0,0 +1,156 @@
+-- Profile_simple.Play --
+package main
+
+import (
+ "fmt"
+ "log"
+ "sort"
+
+ "golang.org/x/exp/rand"
+
+ "gonum.org/v1/gonum/graph/community"
+ "gonum.org/v1/gonum/graph/internal/ordered"
+ "gonum.org/v1/gonum/graph/simple"
+)
+
+func main() {
+ // Profile calls Modularize which implements the Louvain modularization algorithm.
+ // Since this is a randomized algorithm we use a defined random source to ensure
+ // consistency between test runs. In practice, results will not differ greatly
+ // between runs with different PRNG seeds.
+ src := rand.NewSource(1)
+
+ // Create dumbell graph:
+ //
+ // 0 4
+ // |\ /|
+ // | 2 - 3 |
+ // |/ \|
+ // 1 5
+ //
+ g := simple.NewUndirectedGraph()
+ for u, e := range smallDumbell {
+ for v := range e {
+ g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
+ }
+ }
+
+ // Get the profile of internal node weight for resolutions
+ // between 0.1 and 10 using logarithmic bisection.
+ p, err := community.Profile(
+ community.ModularScore(g, community.Weight, 10, src),
+ true, 1e-3, 0.1, 10,
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Print out each step with communities ordered.
+ for _, d := range p {
+ comm := d.Communities()
+ for _, c := range comm {
+ sort.Sort(ordered.ByID(c))
+ }
+ sort.Sort(ordered.BySliceIDs(comm))
+ fmt.Printf("Low:%.2v High:%.2v Score:%v Communities:%v Q=%.3v\n",
+ d.Low, d.High, d.Score, comm, community.Q(g, comm, d.Low))
+ }
+
+}
+
+// intset is an integer set.
+type intset map[int]struct{}
+
+func linksTo(i ...int) intset {
+ if len(i) == 0 {
+ return nil
+ }
+ s := make(intset)
+ for _, v := range i {
+ s[v] = struct{}{}
+ }
+ return s
+}
+
+var smallDumbell = []intset{
+ 0: linksTo(1, 2),
+ 1: linksTo(2),
+ 2: linksTo(3),
+ 3: linksTo(4, 5),
+ 4: linksTo(5),
+ 5: nil,
+}
+
+-- Profile_simple.Output --
+Low:0.1 High:0.29 Score:14 Communities:[[0 1 2 3 4 5]] Q=0.9
+Low:0.29 High:2.3 Score:12 Communities:[[0 1 2] [3 4 5]] Q=0.714
+Low:2.3 High:3.5 Score:4 Communities:[[0 1] [2] [3] [4 5]] Q=-0.31
+Low:3.5 High:10 Score:0 Communities:[[0] [1] [2] [3] [4] [5]] Q=-0.607
+
+-- Profile_multiplex.Play --
+
+package main
+
+import (
+ "fmt"
+ "log"
+ "sort"
+
+ "golang.org/x/exp/rand"
+
+ "gonum.org/v1/gonum/graph/community"
+ "gonum.org/v1/gonum/graph/internal/ordered"
+ "gonum.org/v1/gonum/graph/simple"
+)
+
+var friends, enemies *simple.WeightedUndirectedGraph
+
+func main() {
+ // Profile calls ModularizeMultiplex which implements the Louvain modularization
+ // algorithm. Since this is a randomized algorithm we use a defined random source
+ // to ensure consistency between test runs. In practice, results will not differ
+ // greatly between runs with different PRNG seeds.
+ src := rand.NewSource(1)
+
+ // The undirected graphs, friends and enemies, are the political relationships
+ // in the Middle East as described in the Slate article:
+ // http://www.slate.com/blogs/the_world_/2014/07/17/the_middle_east_friendship_chart.html
+ g, err := community.NewUndirectedLayers(friends, enemies)
+ if err != nil {
+ log.Fatal(err)
+ }
+ weights := []float64{1, -1}
+
+ // Get the profile of internal node weight for resolutions
+ // between 0.1 and 10 using logarithmic bisection.
+ p, err := community.Profile(
+ community.ModularMultiplexScore(g, weights, true, community.WeightMultiplex, 10, src),
+ true, 1e-3, 0.1, 10,
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Print out each step with communities ordered.
+ for _, d := range p {
+ comm := d.Communities()
+ for _, c := range comm {
+ sort.Sort(ordered.ByID(c))
+ }
+ sort.Sort(ordered.BySliceIDs(comm))
+ fmt.Printf("Low:%.2v High:%.2v Score:%v Communities:%v Q=%.3v\n",
+ d.Low, d.High, d.Score, comm, community.QMultiplex(g, comm, weights, []float64{d.Low}))
+ }
+
+}
+-- Profile_multiplex.Output --
+Low:0.1 High:0.72 Score:26 Communities:[[0] [1 7 9 12] [2 8 11] [3 4 5 10] [6]] Q=[24.7 1.97]
+Low:0.72 High:1.1 Score:24 Communities:[[0 6] [1 7 9 12] [2 8 11] [3 4 5 10]] Q=[16.9 14.1]
+Low:1.1 High:1.2 Score:18 Communities:[[0 2 6 11] [1 7 9 12] [3 4 5 8 10]] Q=[9.16 25.1]
+Low:1.2 High:1.6 Score:10 Communities:[[0 3 4 5 6 10] [1 7 9 12] [2 8 11]] Q=[10.5 26.7]
+Low:1.6 High:1.6 Score:8 Communities:[[0 1 6 7 9 12] [2 8 11] [3 4 5 10]] Q=[5.56 39.8]
+Low:1.6 High:1.8 Score:2 Communities:[[0 2 3 4 5 6 10] [1 7 8 9 11 12]] Q=[-1.82 48.6]
+Low:1.8 High:2.3 Score:-6 Communities:[[0 2 3 4 5 6 8 10 11] [1 7 9 12]] Q=[-5 57.5]
+Low:2.3 High:2.4 Score:-10 Communities:[[0 1 2 6 7 8 9 11 12] [3 4 5 10]] Q=[-11.2 79]
+Low:2.4 High:4.3 Score:-52 Communities:[[0 1 2 3 4 5 6 7 8 9 10 11 12]] Q=[-46.1 117]
+Low:4.3 High:10 Score:-54 Communities:[[0 1 2 3 4 6 7 8 9 10 11 12] [5]] Q=[-82 254]
diff --git a/src/go/doc/testdata/examples/multiple.go b/src/go/doc/testdata/examples/multiple.go
new file mode 100644
index 0000000..2728264
--- /dev/null
+++ b/src/go/doc/testdata/examples/multiple.go
@@ -0,0 +1,98 @@
+// Copyright 2021 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 foo_test
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os/exec"
+ "sort"
+)
+
+func ExampleHello() {
+ fmt.Println("Hello, world!")
+ // Output: Hello, world!
+}
+
+func ExampleImport() {
+ out, err := exec.Command("date").Output()
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("The date is %s\n", out)
+}
+
+func ExampleKeyValue() {
+ v := struct {
+ a string
+ b int
+ }{
+ a: "A",
+ b: 1,
+ }
+ fmt.Print(v)
+ // Output: a: "A", b: 1
+}
+
+func ExampleKeyValueImport() {
+ f := flag.Flag{
+ Name: "play",
+ }
+ fmt.Print(f)
+ // Output: Name: "play"
+}
+
+var keyValueTopDecl = struct {
+ a string
+ b int
+}{
+ a: "B",
+ b: 2,
+}
+
+func ExampleKeyValueTopDecl() {
+ fmt.Print(keyValueTopDecl)
+ // Output: a: "B", b: 2
+}
+
+// Person represents a person by name and age.
+type Person struct {
+ Name string
+ Age int
+}
+
+// String returns a string representation of the Person.
+func (p Person) String() string {
+ return fmt.Sprintf("%s: %d", p.Name, p.Age)
+}
+
+// ByAge implements sort.Interface for []Person based on
+// the Age field.
+type ByAge []Person
+
+// Len returns the number of elements in ByAge.
+func (a ByAge) Len() int { return len(a) }
+
+// Swap swaps the elements in ByAge.
+func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
+
+// people is the array of Person
+var people = []Person{
+ {"Bob", 31},
+ {"John", 42},
+ {"Michael", 17},
+ {"Jenny", 26},
+}
+
+func ExampleSort() {
+ fmt.Println(people)
+ sort.Sort(ByAge(people))
+ fmt.Println(people)
+ // Output:
+ // [Bob: 31 John: 42 Michael: 17 Jenny: 26]
+ // [Michael: 17 Jenny: 26 Bob: 31 John: 42]
+}
diff --git a/src/go/doc/testdata/examples/multiple.golden b/src/go/doc/testdata/examples/multiple.golden
new file mode 100644
index 0000000..d2d791e
--- /dev/null
+++ b/src/go/doc/testdata/examples/multiple.golden
@@ -0,0 +1,129 @@
+-- Hello.Play --
+package main
+
+import (
+ "fmt"
+)
+
+func main() {
+ fmt.Println("Hello, world!")
+}
+-- Hello.Output --
+Hello, world!
+-- Import.Play --
+package main
+
+import (
+ "fmt"
+ "log"
+ "os/exec"
+)
+
+func main() {
+ out, err := exec.Command("date").Output()
+ if err != nil {
+ log.Fatal(err)
+ }
+ fmt.Printf("The date is %s\n", out)
+}
+-- KeyValue.Play --
+package main
+
+import (
+ "fmt"
+)
+
+func main() {
+ v := struct {
+ a string
+ b int
+ }{
+ a: "A",
+ b: 1,
+ }
+ fmt.Print(v)
+}
+-- KeyValue.Output --
+a: "A", b: 1
+-- KeyValueImport.Play --
+package main
+
+import (
+ "flag"
+ "fmt"
+)
+
+func main() {
+ f := flag.Flag{
+ Name: "play",
+ }
+ fmt.Print(f)
+}
+-- KeyValueImport.Output --
+Name: "play"
+-- KeyValueTopDecl.Play --
+package main
+
+import (
+ "fmt"
+)
+
+var keyValueTopDecl = struct {
+ a string
+ b int
+}{
+ a: "B",
+ b: 2,
+}
+
+func main() {
+ fmt.Print(keyValueTopDecl)
+}
+-- KeyValueTopDecl.Output --
+a: "B", b: 2
+-- Sort.Play --
+package main
+
+import (
+ "fmt"
+ "sort"
+)
+
+// Person represents a person by name and age.
+type Person struct {
+ Name string
+ Age int
+}
+
+// String returns a string representation of the Person.
+func (p Person) String() string {
+ return fmt.Sprintf("%s: %d", p.Name, p.Age)
+}
+
+// ByAge implements sort.Interface for []Person based on
+// the Age field.
+type ByAge []Person
+
+// Len returns the number of elements in ByAge.
+func (a ByAge) Len() int { return len(a) }
+
+// Swap swaps the elements in ByAge.
+func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
+
+// people is the array of Person
+var people = []Person{
+ {"Bob", 31},
+ {"John", 42},
+ {"Michael", 17},
+ {"Jenny", 26},
+}
+
+func main() {
+ fmt.Println(people)
+ sort.Sort(ByAge(people))
+ fmt.Println(people)
+}
+-- Sort.Output --
+[Bob: 31 John: 42 Michael: 17 Jenny: 26]
+[Michael: 17 Jenny: 26 Bob: 31 John: 42]
diff --git a/src/go/doc/testdata/examples/values.go b/src/go/doc/testdata/examples/values.go
new file mode 100644
index 0000000..64b0de4
--- /dev/null
+++ b/src/go/doc/testdata/examples/values.go
@@ -0,0 +1,22 @@
+// Copyright 2021 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 foo_test
+
+// Variable declaration with fewer values than names.
+
+func f() (int, int) {
+ return 1, 2
+}
+
+var a, b = f()
+
+// Need two examples to hit playExample.
+
+func ExampleA() {
+ _ = a
+}
+
+func ExampleB() {
+}
diff --git a/src/go/doc/testdata/examples/values.golden b/src/go/doc/testdata/examples/values.golden
new file mode 100644
index 0000000..00c1991
--- /dev/null
+++ b/src/go/doc/testdata/examples/values.golden
@@ -0,0 +1,21 @@
+-- A.Play --
+package main
+
+import ()
+
+func f() (int, int) {
+ return 1, 2
+}
+
+var a, b = f()
+
+func main() {
+ _ = a
+}
+-- B.Play --
+package main
+
+import ()
+
+func main() {
+}
diff --git a/src/go/doc/testdata/examples/whole_file.go b/src/go/doc/testdata/examples/whole_file.go
new file mode 100644
index 0000000..61954ce
--- /dev/null
+++ b/src/go/doc/testdata/examples/whole_file.go
@@ -0,0 +1,23 @@
+// Copyright 2021 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 foo_test
+
+import "fmt"
+
+type X int
+
+func (X) Foo() {
+}
+
+func (X) TestBlah() {
+}
+
+func (X) BenchmarkFoo() {
+}
+
+func Example() {
+ fmt.Println("Hello, world!")
+ // Output: Hello, world!
+}
diff --git a/src/go/doc/testdata/examples/whole_file.golden b/src/go/doc/testdata/examples/whole_file.golden
new file mode 100644
index 0000000..74a2291
--- /dev/null
+++ b/src/go/doc/testdata/examples/whole_file.golden
@@ -0,0 +1,21 @@
+-- .Play --
+package main
+
+import "fmt"
+
+type X int
+
+func (X) Foo() {
+}
+
+func (X) TestBlah() {
+}
+
+func (X) BenchmarkFoo() {
+}
+
+func main() {
+ fmt.Println("Hello, world!")
+}
+-- .Output --
+Hello, world!
diff --git a/src/go/doc/testdata/examples/whole_function.go b/src/go/doc/testdata/examples/whole_function.go
new file mode 100644
index 0000000..1754ee3
--- /dev/null
+++ b/src/go/doc/testdata/examples/whole_function.go
@@ -0,0 +1,13 @@
+// Copyright 2021 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 foo_test
+
+func Foo(x int) {
+}
+
+func Example() {
+ fmt.Println("Hello, world!")
+ // Output: Hello, world!
+}
diff --git a/src/go/doc/testdata/examples/whole_function.golden b/src/go/doc/testdata/examples/whole_function.golden
new file mode 100644
index 0000000..7d5b5cb
--- /dev/null
+++ b/src/go/doc/testdata/examples/whole_function.golden
@@ -0,0 +1,11 @@
+-- .Play --
+package main
+
+func Foo(x int) {
+}
+
+func main() {
+ fmt.Println("Hello, world!")
+}
+-- .Output --
+Hello, world!
diff --git a/src/go/doc/testdata/examples/whole_function_external.go b/src/go/doc/testdata/examples/whole_function_external.go
new file mode 100644
index 0000000..0e0e2f5
--- /dev/null
+++ b/src/go/doc/testdata/examples/whole_function_external.go
@@ -0,0 +1,12 @@
+// Copyright 2021 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 foo_test
+
+func foo(int)
+
+func Example() {
+ foo(42)
+ // Output:
+}
diff --git a/src/go/doc/testdata/examples/whole_function_external.golden b/src/go/doc/testdata/examples/whole_function_external.golden
new file mode 100644
index 0000000..ec8f114
--- /dev/null
+++ b/src/go/doc/testdata/examples/whole_function_external.golden
@@ -0,0 +1,8 @@
+-- .Play --
+package main
+
+func foo(int)
+
+func main() {
+ foo(42)
+}
diff --git a/src/go/doc/testdata/f.0.golden b/src/go/doc/testdata/f.0.golden
new file mode 100644
index 0000000..8175901
--- /dev/null
+++ b/src/go/doc/testdata/f.0.golden
@@ -0,0 +1,13 @@
+// The package f is a go/doc test for functions and factory ...
+PACKAGE f
+
+IMPORTPATH
+ testdata/f
+
+FILENAMES
+ testdata/f.go
+
+FUNCTIONS
+ // Exported must always be visible. Was issue 2824.
+ func Exported() private
+
diff --git a/src/go/doc/testdata/f.1.golden b/src/go/doc/testdata/f.1.golden
new file mode 100644
index 0000000..ba68e88
--- /dev/null
+++ b/src/go/doc/testdata/f.1.golden
@@ -0,0 +1,16 @@
+// The package f is a go/doc test for functions and factory ...
+PACKAGE f
+
+IMPORTPATH
+ testdata/f
+
+FILENAMES
+ testdata/f.go
+
+TYPES
+ //
+ type private struct{}
+
+ // Exported must always be visible. Was issue 2824.
+ func Exported() private
+
diff --git a/src/go/doc/testdata/f.2.golden b/src/go/doc/testdata/f.2.golden
new file mode 100644
index 0000000..8175901
--- /dev/null
+++ b/src/go/doc/testdata/f.2.golden
@@ -0,0 +1,13 @@
+// The package f is a go/doc test for functions and factory ...
+PACKAGE f
+
+IMPORTPATH
+ testdata/f
+
+FILENAMES
+ testdata/f.go
+
+FUNCTIONS
+ // Exported must always be visible. Was issue 2824.
+ func Exported() private
+
diff --git a/src/go/doc/testdata/f.go b/src/go/doc/testdata/f.go
new file mode 100644
index 0000000..7e9add9
--- /dev/null
+++ b/src/go/doc/testdata/f.go
@@ -0,0 +1,14 @@
+// Copyright 2012 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.
+
+// The package f is a go/doc test for functions and factory methods.
+package f
+
+// ----------------------------------------------------------------------------
+// Factory functions for non-exported types must not get lost.
+
+type private struct{}
+
+// Exported must always be visible. Was issue 2824.
+func Exported() private {}
diff --git a/src/go/doc/testdata/g.0.golden b/src/go/doc/testdata/g.0.golden
new file mode 100644
index 0000000..487cf06
--- /dev/null
+++ b/src/go/doc/testdata/g.0.golden
@@ -0,0 +1,32 @@
+// The package g is a go/doc test for mixed exported/unexported ...
+PACKAGE g
+
+IMPORTPATH
+ testdata/g
+
+FILENAMES
+ testdata/g.go
+
+CONSTANTS
+ //
+ const (
+ A, _ = iota, iota
+ _, D
+ E, _
+ G, H
+ )
+
+
+VARIABLES
+ //
+ var (
+ _, C2, _ = 1, 2, 3
+ C4, _, C6 = 4, 5, 6
+ _, C8, _ = 7, 8, 9
+ )
+
+ //
+ var (
+ _, X = f()
+ )
+
diff --git a/src/go/doc/testdata/g.1.golden b/src/go/doc/testdata/g.1.golden
new file mode 100644
index 0000000..438441a
--- /dev/null
+++ b/src/go/doc/testdata/g.1.golden
@@ -0,0 +1,34 @@
+// The package g is a go/doc test for mixed exported/unexported ...
+PACKAGE g
+
+IMPORTPATH
+ testdata/g
+
+FILENAMES
+ testdata/g.go
+
+CONSTANTS
+ //
+ const (
+ A, b = iota, iota
+ c, D
+ E, f
+ G, H
+ )
+
+
+VARIABLES
+ //
+ var (
+ c1, C2, c3 = 1, 2, 3
+ C4, c5, C6 = 4, 5, 6
+ c7, C8, c9 = 7, 8, 9
+ xx, yy, zz = 0, 0, 0 // all unexported and hidden
+ )
+
+ //
+ var (
+ x, X = f()
+ y, z = f()
+ )
+
diff --git a/src/go/doc/testdata/g.2.golden b/src/go/doc/testdata/g.2.golden
new file mode 100644
index 0000000..487cf06
--- /dev/null
+++ b/src/go/doc/testdata/g.2.golden
@@ -0,0 +1,32 @@
+// The package g is a go/doc test for mixed exported/unexported ...
+PACKAGE g
+
+IMPORTPATH
+ testdata/g
+
+FILENAMES
+ testdata/g.go
+
+CONSTANTS
+ //
+ const (
+ A, _ = iota, iota
+ _, D
+ E, _
+ G, H
+ )
+
+
+VARIABLES
+ //
+ var (
+ _, C2, _ = 1, 2, 3
+ C4, _, C6 = 4, 5, 6
+ _, C8, _ = 7, 8, 9
+ )
+
+ //
+ var (
+ _, X = f()
+ )
+
diff --git a/src/go/doc/testdata/g.go b/src/go/doc/testdata/g.go
new file mode 100644
index 0000000..ceeb417
--- /dev/null
+++ b/src/go/doc/testdata/g.go
@@ -0,0 +1,25 @@
+// Copyright 2018 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.
+
+// The package g is a go/doc test for mixed exported/unexported values.
+package g
+
+const (
+ A, b = iota, iota
+ c, D
+ E, f
+ G, H
+)
+
+var (
+ c1, C2, c3 = 1, 2, 3
+ C4, c5, C6 = 4, 5, 6
+ c7, C8, c9 = 7, 8, 9
+ xx, yy, zz = 0, 0, 0 // all unexported and hidden
+)
+
+var (
+ x, X = f()
+ y, z = f()
+)
diff --git a/src/go/doc/testdata/generics.0.golden b/src/go/doc/testdata/generics.0.golden
new file mode 100644
index 0000000..91c874c
--- /dev/null
+++ b/src/go/doc/testdata/generics.0.golden
@@ -0,0 +1,76 @@
+// Package generics contains the new syntax supporting generic ...
+PACKAGE generics
+
+IMPORTPATH
+ testdata/generics
+
+FILENAMES
+ testdata/generics.go
+
+FUNCTIONS
+ // AnotherFunc has an implicit constraint interface. Neither type ...
+ func AnotherFunc[T ~struct{ f int }](_ struct{ f int })
+
+ // Func has an instantiated constraint.
+ func Func[T Constraint[string, Type[int]]]()
+
+ // Single is not a factory function.
+ func Single[T any]() *T
+
+ // Slice is not a factory function.
+ func Slice[T any]() []T
+
+
+TYPES
+ // AFuncType demonstrates filtering of parameters and type ...
+ type AFuncType[T ~struct{ f int }] func(_ struct {
+ // contains filtered or unexported fields
+ })
+
+ // Constraint is a constraint interface with two type parameters.
+ type Constraint[P, Q interface{ string | ~int | Type[int] }] interface {
+ ~int | ~byte | Type[string]
+ M() P
+ }
+
+ // NewEmbeddings demonstrates how we filter the new embedded ...
+ type NewEmbeddings interface {
+ string // should not be filtered
+
+ struct {
+ // contains filtered or unexported fields
+ }
+ ~struct {
+ // contains filtered or unexported fields
+ }
+ *struct {
+ // contains filtered or unexported fields
+ }
+ struct {
+ // contains filtered or unexported fields
+ } | ~struct {
+ // contains filtered or unexported fields
+ }
+ // contains filtered or unexported methods
+ }
+
+ // Parameterized types should be shown.
+ type Type[P any] struct {
+ Field P
+ }
+
+ // Variables with an instantiated type should be shown.
+ var X Type[int]
+
+ // Constructors for parameterized types should be shown.
+ func Constructor[lowerCase any]() Type[lowerCase]
+
+ // MethodA uses a different name for its receiver type parameter.
+ func (t Type[A]) MethodA(p A)
+
+ // MethodB has a blank receiver type parameter.
+ func (t Type[_]) MethodB()
+
+ // MethodC has a lower-case receiver type parameter.
+ func (t Type[c]) MethodC()
+
diff --git a/src/go/doc/testdata/generics.1.golden b/src/go/doc/testdata/generics.1.golden
new file mode 100644
index 0000000..923a4ce
--- /dev/null
+++ b/src/go/doc/testdata/generics.1.golden
@@ -0,0 +1,66 @@
+// Package generics contains the new syntax supporting generic ...
+PACKAGE generics
+
+IMPORTPATH
+ testdata/generics
+
+FILENAMES
+ testdata/generics.go
+
+FUNCTIONS
+ // AnotherFunc has an implicit constraint interface. Neither type ...
+ func AnotherFunc[T ~struct{ f int }](_ struct{ f int })
+
+ // Func has an instantiated constraint.
+ func Func[T Constraint[string, Type[int]]]()
+
+ // Single is not a factory function.
+ func Single[T any]() *T
+
+ // Slice is not a factory function.
+ func Slice[T any]() []T
+
+
+TYPES
+ // AFuncType demonstrates filtering of parameters and type ...
+ type AFuncType[T ~struct{ f int }] func(_ struct{ f int })
+
+ // Constraint is a constraint interface with two type parameters.
+ type Constraint[P, Q interface{ string | ~int | Type[int] }] interface {
+ ~int | ~byte | Type[string]
+ M() P
+ }
+
+ // NewEmbeddings demonstrates how we filter the new embedded ...
+ type NewEmbeddings interface {
+ string // should not be filtered
+ int16
+ struct{ f int }
+ ~struct{ f int }
+ *struct{ f int }
+ struct{ f int } | ~struct{ f int }
+ }
+
+ // Parameterized types should be shown.
+ type Type[P any] struct {
+ Field P
+ }
+
+ // Variables with an instantiated type should be shown.
+ var X Type[int]
+
+ // Constructors for parameterized types should be shown.
+ func Constructor[lowerCase any]() Type[lowerCase]
+
+ // MethodA uses a different name for its receiver type parameter.
+ func (t Type[A]) MethodA(p A)
+
+ // MethodB has a blank receiver type parameter.
+ func (t Type[_]) MethodB()
+
+ // MethodC has a lower-case receiver type parameter.
+ func (t Type[c]) MethodC()
+
+ // int16 shadows the predeclared type int16.
+ type int16 int
+
diff --git a/src/go/doc/testdata/generics.2.golden b/src/go/doc/testdata/generics.2.golden
new file mode 100644
index 0000000..91c874c
--- /dev/null
+++ b/src/go/doc/testdata/generics.2.golden
@@ -0,0 +1,76 @@
+// Package generics contains the new syntax supporting generic ...
+PACKAGE generics
+
+IMPORTPATH
+ testdata/generics
+
+FILENAMES
+ testdata/generics.go
+
+FUNCTIONS
+ // AnotherFunc has an implicit constraint interface. Neither type ...
+ func AnotherFunc[T ~struct{ f int }](_ struct{ f int })
+
+ // Func has an instantiated constraint.
+ func Func[T Constraint[string, Type[int]]]()
+
+ // Single is not a factory function.
+ func Single[T any]() *T
+
+ // Slice is not a factory function.
+ func Slice[T any]() []T
+
+
+TYPES
+ // AFuncType demonstrates filtering of parameters and type ...
+ type AFuncType[T ~struct{ f int }] func(_ struct {
+ // contains filtered or unexported fields
+ })
+
+ // Constraint is a constraint interface with two type parameters.
+ type Constraint[P, Q interface{ string | ~int | Type[int] }] interface {
+ ~int | ~byte | Type[string]
+ M() P
+ }
+
+ // NewEmbeddings demonstrates how we filter the new embedded ...
+ type NewEmbeddings interface {
+ string // should not be filtered
+
+ struct {
+ // contains filtered or unexported fields
+ }
+ ~struct {
+ // contains filtered or unexported fields
+ }
+ *struct {
+ // contains filtered or unexported fields
+ }
+ struct {
+ // contains filtered or unexported fields
+ } | ~struct {
+ // contains filtered or unexported fields
+ }
+ // contains filtered or unexported methods
+ }
+
+ // Parameterized types should be shown.
+ type Type[P any] struct {
+ Field P
+ }
+
+ // Variables with an instantiated type should be shown.
+ var X Type[int]
+
+ // Constructors for parameterized types should be shown.
+ func Constructor[lowerCase any]() Type[lowerCase]
+
+ // MethodA uses a different name for its receiver type parameter.
+ func (t Type[A]) MethodA(p A)
+
+ // MethodB has a blank receiver type parameter.
+ func (t Type[_]) MethodB()
+
+ // MethodC has a lower-case receiver type parameter.
+ func (t Type[c]) MethodC()
+
diff --git a/src/go/doc/testdata/generics.go b/src/go/doc/testdata/generics.go
new file mode 100644
index 0000000..ba7187e
--- /dev/null
+++ b/src/go/doc/testdata/generics.go
@@ -0,0 +1,74 @@
+// Copyright 2021 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 generics contains the new syntax supporting generic programming in
+// Go.
+package generics
+
+// Variables with an instantiated type should be shown.
+var X Type[int]
+
+// Parameterized types should be shown.
+type Type[P any] struct {
+ Field P
+}
+
+// Constructors for parameterized types should be shown.
+func Constructor[lowerCase any]() Type[lowerCase] {
+ return Type[lowerCase]{}
+}
+
+// MethodA uses a different name for its receiver type parameter.
+func (t Type[A]) MethodA(p A) {}
+
+// MethodB has a blank receiver type parameter.
+func (t Type[_]) MethodB() {}
+
+// MethodC has a lower-case receiver type parameter.
+func (t Type[c]) MethodC() {}
+
+// Constraint is a constraint interface with two type parameters.
+type Constraint[P, Q interface{ string | ~int | Type[int] }] interface {
+ ~int | ~byte | Type[string]
+ M() P
+}
+
+// int16 shadows the predeclared type int16.
+type int16 int
+
+// NewEmbeddings demonstrates how we filter the new embedded elements.
+type NewEmbeddings interface {
+ string // should not be filtered
+ int16
+ struct{ f int }
+ ~struct{ f int }
+ *struct{ f int }
+ struct{ f int } | ~struct{ f int }
+}
+
+// Func has an instantiated constraint.
+func Func[T Constraint[string, Type[int]]]() {}
+
+// AnotherFunc has an implicit constraint interface.
+//
+// Neither type parameters nor regular parameters should be filtered.
+func AnotherFunc[T ~struct{ f int }](_ struct{ f int }) {}
+
+// AFuncType demonstrates filtering of parameters and type parameters. Here we
+// don't filter type parameters (to be consistent with function declarations),
+// but DO filter the RHS.
+type AFuncType[T ~struct{ f int }] func(_ struct{ f int })
+
+// See issue #49477: type parameters should not be interpreted as named types
+// for the purpose of determining whether a function is a factory function.
+
+// Slice is not a factory function.
+func Slice[T any]() []T {
+ return nil
+}
+
+// Single is not a factory function.
+func Single[T any]() *T {
+ return nil
+}
diff --git a/src/go/doc/testdata/issue12839.0.golden b/src/go/doc/testdata/issue12839.0.golden
new file mode 100644
index 0000000..6b59774
--- /dev/null
+++ b/src/go/doc/testdata/issue12839.0.golden
@@ -0,0 +1,51 @@
+// Package issue12839 is a go/doc test to test association of a ...
+PACKAGE issue12839
+
+IMPORTPATH
+ testdata/issue12839
+
+IMPORTS
+ p
+
+FILENAMES
+ testdata/issue12839.go
+
+FUNCTIONS
+ // F1 should not be associated with T1
+ func F1() (*T1, *T2)
+
+ // F10 should not be associated with T1.
+ func F10() (T1, T2, error)
+
+ // F4 should not be associated with a type (same as F1)
+ func F4() (a T1, b T2)
+
+ // F9 should not be associated with T1.
+ func F9() (int, T1, T2)
+
+
+TYPES
+ //
+ type T1 struct{}
+
+ // F2 should be associated with T1
+ func F2() (a, b, c T1)
+
+ // F3 should be associated with T1 because b.T3 is from a ...
+ func F3() (a T1, b p.T3)
+
+ // F5 should be associated with T1.
+ func F5() (T1, error)
+
+ // F6 should be associated with T1.
+ func F6() (*T1, error)
+
+ // F7 should be associated with T1.
+ func F7() (T1, string)
+
+ // F8 should be associated with T1.
+ func F8() (int, T1, string)
+
+ //
+ type T2 struct{}
+
diff --git a/src/go/doc/testdata/issue12839.1.golden b/src/go/doc/testdata/issue12839.1.golden
new file mode 100644
index 0000000..4b9b9f6
--- /dev/null
+++ b/src/go/doc/testdata/issue12839.1.golden
@@ -0,0 +1,54 @@
+// Package issue12839 is a go/doc test to test association of a ...
+PACKAGE issue12839
+
+IMPORTPATH
+ testdata/issue12839
+
+IMPORTS
+ p
+
+FILENAMES
+ testdata/issue12839.go
+
+FUNCTIONS
+ // F1 should not be associated with T1
+ func F1() (*T1, *T2)
+
+ // F10 should not be associated with T1.
+ func F10() (T1, T2, error)
+
+ // F4 should not be associated with a type (same as F1)
+ func F4() (a T1, b T2)
+
+ // F9 should not be associated with T1.
+ func F9() (int, T1, T2)
+
+
+TYPES
+ //
+ type T1 struct{}
+
+ // F2 should be associated with T1
+ func F2() (a, b, c T1)
+
+ // F3 should be associated with T1 because b.T3 is from a ...
+ func F3() (a T1, b p.T3)
+
+ // F5 should be associated with T1.
+ func F5() (T1, error)
+
+ // F6 should be associated with T1.
+ func F6() (*T1, error)
+
+ // F7 should be associated with T1.
+ func F7() (T1, string)
+
+ // F8 should be associated with T1.
+ func F8() (int, T1, string)
+
+ //
+ func (t T1) hello() string
+
+ //
+ type T2 struct{}
+
diff --git a/src/go/doc/testdata/issue12839.2.golden b/src/go/doc/testdata/issue12839.2.golden
new file mode 100644
index 0000000..6b59774
--- /dev/null
+++ b/src/go/doc/testdata/issue12839.2.golden
@@ -0,0 +1,51 @@
+// Package issue12839 is a go/doc test to test association of a ...
+PACKAGE issue12839
+
+IMPORTPATH
+ testdata/issue12839
+
+IMPORTS
+ p
+
+FILENAMES
+ testdata/issue12839.go
+
+FUNCTIONS
+ // F1 should not be associated with T1
+ func F1() (*T1, *T2)
+
+ // F10 should not be associated with T1.
+ func F10() (T1, T2, error)
+
+ // F4 should not be associated with a type (same as F1)
+ func F4() (a T1, b T2)
+
+ // F9 should not be associated with T1.
+ func F9() (int, T1, T2)
+
+
+TYPES
+ //
+ type T1 struct{}
+
+ // F2 should be associated with T1
+ func F2() (a, b, c T1)
+
+ // F3 should be associated with T1 because b.T3 is from a ...
+ func F3() (a T1, b p.T3)
+
+ // F5 should be associated with T1.
+ func F5() (T1, error)
+
+ // F6 should be associated with T1.
+ func F6() (*T1, error)
+
+ // F7 should be associated with T1.
+ func F7() (T1, string)
+
+ // F8 should be associated with T1.
+ func F8() (int, T1, string)
+
+ //
+ type T2 struct{}
+
diff --git a/src/go/doc/testdata/issue12839.go b/src/go/doc/testdata/issue12839.go
new file mode 100644
index 0000000..51c7ac1
--- /dev/null
+++ b/src/go/doc/testdata/issue12839.go
@@ -0,0 +1,69 @@
+// Copyright 2018 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 issue12839 is a go/doc test to test association of a function
+// that returns multiple types.
+// See golang.org/issue/12839.
+// (See also golang.org/issue/27928.)
+package issue12839
+
+import "p"
+
+type T1 struct{}
+
+type T2 struct{}
+
+func (t T1) hello() string {
+ return "hello"
+}
+
+// F1 should not be associated with T1
+func F1() (*T1, *T2) {
+ return &T1{}, &T2{}
+}
+
+// F2 should be associated with T1
+func F2() (a, b, c T1) {
+ return T1{}, T1{}, T1{}
+}
+
+// F3 should be associated with T1 because b.T3 is from a different package
+func F3() (a T1, b p.T3) {
+ return T1{}, p.T3{}
+}
+
+// F4 should not be associated with a type (same as F1)
+func F4() (a T1, b T2) {
+ return T1{}, T2{}
+}
+
+// F5 should be associated with T1.
+func F5() (T1, error) {
+ return T1{}, nil
+}
+
+// F6 should be associated with T1.
+func F6() (*T1, error) {
+ return &T1{}, nil
+}
+
+// F7 should be associated with T1.
+func F7() (T1, string) {
+ return T1{}, nil
+}
+
+// F8 should be associated with T1.
+func F8() (int, T1, string) {
+ return 0, T1{}, nil
+}
+
+// F9 should not be associated with T1.
+func F9() (int, T1, T2) {
+ return 0, T1{}, T2{}
+}
+
+// F10 should not be associated with T1.
+func F10() (T1, T2, error) {
+ return T1{}, T2{}, nil
+}
diff --git a/src/go/doc/testdata/issue13742.0.golden b/src/go/doc/testdata/issue13742.0.golden
new file mode 100644
index 0000000..8dee9aa
--- /dev/null
+++ b/src/go/doc/testdata/issue13742.0.golden
@@ -0,0 +1,25 @@
+//
+PACKAGE issue13742
+
+IMPORTPATH
+ testdata/issue13742
+
+IMPORTS
+ go/ast
+
+FILENAMES
+ testdata/issue13742.go
+
+FUNCTIONS
+ // Both F0 and G0 should appear as functions.
+ func F0(Node)
+
+ // Both F1 and G1 should appear as functions.
+ func F1(ast.Node)
+
+ //
+ func G0() Node
+
+ //
+ func G1() ast.Node
+
diff --git a/src/go/doc/testdata/issue13742.1.golden b/src/go/doc/testdata/issue13742.1.golden
new file mode 100644
index 0000000..8dee9aa
--- /dev/null
+++ b/src/go/doc/testdata/issue13742.1.golden
@@ -0,0 +1,25 @@
+//
+PACKAGE issue13742
+
+IMPORTPATH
+ testdata/issue13742
+
+IMPORTS
+ go/ast
+
+FILENAMES
+ testdata/issue13742.go
+
+FUNCTIONS
+ // Both F0 and G0 should appear as functions.
+ func F0(Node)
+
+ // Both F1 and G1 should appear as functions.
+ func F1(ast.Node)
+
+ //
+ func G0() Node
+
+ //
+ func G1() ast.Node
+
diff --git a/src/go/doc/testdata/issue13742.2.golden b/src/go/doc/testdata/issue13742.2.golden
new file mode 100644
index 0000000..8dee9aa
--- /dev/null
+++ b/src/go/doc/testdata/issue13742.2.golden
@@ -0,0 +1,25 @@
+//
+PACKAGE issue13742
+
+IMPORTPATH
+ testdata/issue13742
+
+IMPORTS
+ go/ast
+
+FILENAMES
+ testdata/issue13742.go
+
+FUNCTIONS
+ // Both F0 and G0 should appear as functions.
+ func F0(Node)
+
+ // Both F1 and G1 should appear as functions.
+ func F1(ast.Node)
+
+ //
+ func G0() Node
+
+ //
+ func G1() ast.Node
+
diff --git a/src/go/doc/testdata/issue13742.go b/src/go/doc/testdata/issue13742.go
new file mode 100644
index 0000000..dbc1941
--- /dev/null
+++ b/src/go/doc/testdata/issue13742.go
@@ -0,0 +1,18 @@
+// Copyright 2016 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 issue13742
+
+import (
+ "go/ast"
+ . "go/ast"
+)
+
+// Both F0 and G0 should appear as functions.
+func F0(Node) {}
+func G0() Node { return nil }
+
+// Both F1 and G1 should appear as functions.
+func F1(ast.Node) {}
+func G1() ast.Node { return nil }
diff --git a/src/go/doc/testdata/issue16153.0.golden b/src/go/doc/testdata/issue16153.0.golden
new file mode 100644
index 0000000..189260b
--- /dev/null
+++ b/src/go/doc/testdata/issue16153.0.golden
@@ -0,0 +1,32 @@
+//
+PACKAGE issue16153
+
+IMPORTPATH
+ testdata/issue16153
+
+FILENAMES
+ testdata/issue16153.go
+
+CONSTANTS
+ //
+ const (
+ X3 int64 = iota
+ Y3 = 1
+ )
+
+ //
+ const (
+ X4 int64 = iota
+ Y4
+ )
+
+ // original test case
+ const (
+ Y1 = 256
+ )
+
+ // variations
+ const (
+ Y2 uint8
+ )
+
diff --git a/src/go/doc/testdata/issue16153.1.golden b/src/go/doc/testdata/issue16153.1.golden
new file mode 100644
index 0000000..803df3e
--- /dev/null
+++ b/src/go/doc/testdata/issue16153.1.golden
@@ -0,0 +1,34 @@
+//
+PACKAGE issue16153
+
+IMPORTPATH
+ testdata/issue16153
+
+FILENAMES
+ testdata/issue16153.go
+
+CONSTANTS
+ // original test case
+ const (
+ x1 uint8 = 255
+ Y1 = 256
+ )
+
+ // variations
+ const (
+ x2 uint8 = 255
+ Y2
+ )
+
+ //
+ const (
+ X3 int64 = iota
+ Y3 = 1
+ )
+
+ //
+ const (
+ X4 int64 = iota
+ Y4
+ )
+
diff --git a/src/go/doc/testdata/issue16153.2.golden b/src/go/doc/testdata/issue16153.2.golden
new file mode 100644
index 0000000..189260b
--- /dev/null
+++ b/src/go/doc/testdata/issue16153.2.golden
@@ -0,0 +1,32 @@
+//
+PACKAGE issue16153
+
+IMPORTPATH
+ testdata/issue16153
+
+FILENAMES
+ testdata/issue16153.go
+
+CONSTANTS
+ //
+ const (
+ X3 int64 = iota
+ Y3 = 1
+ )
+
+ //
+ const (
+ X4 int64 = iota
+ Y4
+ )
+
+ // original test case
+ const (
+ Y1 = 256
+ )
+
+ // variations
+ const (
+ Y2 uint8
+ )
+
diff --git a/src/go/doc/testdata/issue16153.go b/src/go/doc/testdata/issue16153.go
new file mode 100644
index 0000000..528be42
--- /dev/null
+++ b/src/go/doc/testdata/issue16153.go
@@ -0,0 +1,27 @@
+// Copyright 2017 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 issue16153
+
+// original test case
+const (
+ x1 uint8 = 255
+ Y1 = 256
+)
+
+// variations
+const (
+ x2 uint8 = 255
+ Y2
+)
+
+const (
+ X3 int64 = iota
+ Y3 = 1
+)
+
+const (
+ X4 int64 = iota
+ Y4
+)
diff --git a/src/go/doc/testdata/issue17788.0.golden b/src/go/doc/testdata/issue17788.0.golden
new file mode 100644
index 0000000..42c00da
--- /dev/null
+++ b/src/go/doc/testdata/issue17788.0.golden
@@ -0,0 +1,8 @@
+//
+PACKAGE issue17788
+
+IMPORTPATH
+ testdata/issue17788
+
+FILENAMES
+ testdata/issue17788.go
diff --git a/src/go/doc/testdata/issue17788.1.golden b/src/go/doc/testdata/issue17788.1.golden
new file mode 100644
index 0000000..42c00da
--- /dev/null
+++ b/src/go/doc/testdata/issue17788.1.golden
@@ -0,0 +1,8 @@
+//
+PACKAGE issue17788
+
+IMPORTPATH
+ testdata/issue17788
+
+FILENAMES
+ testdata/issue17788.go
diff --git a/src/go/doc/testdata/issue17788.2.golden b/src/go/doc/testdata/issue17788.2.golden
new file mode 100644
index 0000000..42c00da
--- /dev/null
+++ b/src/go/doc/testdata/issue17788.2.golden
@@ -0,0 +1,8 @@
+//
+PACKAGE issue17788
+
+IMPORTPATH
+ testdata/issue17788
+
+FILENAMES
+ testdata/issue17788.go
diff --git a/src/go/doc/testdata/issue17788.go b/src/go/doc/testdata/issue17788.go
new file mode 100644
index 0000000..883ad5f
--- /dev/null
+++ b/src/go/doc/testdata/issue17788.go
@@ -0,0 +1,8 @@
+// Copyright 2016 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 issue17788
+
+func ( /* receiver type */ ) f0() {
+}
diff --git a/src/go/doc/testdata/issue22856.0.golden b/src/go/doc/testdata/issue22856.0.golden
new file mode 100644
index 0000000..a88f43f
--- /dev/null
+++ b/src/go/doc/testdata/issue22856.0.golden
@@ -0,0 +1,45 @@
+//
+PACKAGE issue22856
+
+IMPORTPATH
+ testdata/issue22856
+
+FILENAMES
+ testdata/issue22856.go
+
+FUNCTIONS
+ // NewPointerSliceOfSlice is not a factory function because slices ...
+ func NewPointerSliceOfSlice() [][]*T
+
+ // NewSlice3 is not a factory function because 3 nested slices of ...
+ func NewSlice3() [][][]T
+
+ // NewSliceOfSlice is not a factory function because slices of a ...
+ func NewSliceOfSlice() [][]T
+
+
+TYPES
+ //
+ type T struct{}
+
+ //
+ func New() T
+
+ //
+ func NewArray() [1]T
+
+ //
+ func NewPointer() *T
+
+ //
+ func NewPointerArray() [1]*T
+
+ //
+ func NewPointerOfPointer() **T
+
+ //
+ func NewPointerSlice() []*T
+
+ //
+ func NewSlice() []T
+
diff --git a/src/go/doc/testdata/issue22856.1.golden b/src/go/doc/testdata/issue22856.1.golden
new file mode 100644
index 0000000..a88f43f
--- /dev/null
+++ b/src/go/doc/testdata/issue22856.1.golden
@@ -0,0 +1,45 @@
+//
+PACKAGE issue22856
+
+IMPORTPATH
+ testdata/issue22856
+
+FILENAMES
+ testdata/issue22856.go
+
+FUNCTIONS
+ // NewPointerSliceOfSlice is not a factory function because slices ...
+ func NewPointerSliceOfSlice() [][]*T
+
+ // NewSlice3 is not a factory function because 3 nested slices of ...
+ func NewSlice3() [][][]T
+
+ // NewSliceOfSlice is not a factory function because slices of a ...
+ func NewSliceOfSlice() [][]T
+
+
+TYPES
+ //
+ type T struct{}
+
+ //
+ func New() T
+
+ //
+ func NewArray() [1]T
+
+ //
+ func NewPointer() *T
+
+ //
+ func NewPointerArray() [1]*T
+
+ //
+ func NewPointerOfPointer() **T
+
+ //
+ func NewPointerSlice() []*T
+
+ //
+ func NewSlice() []T
+
diff --git a/src/go/doc/testdata/issue22856.2.golden b/src/go/doc/testdata/issue22856.2.golden
new file mode 100644
index 0000000..a88f43f
--- /dev/null
+++ b/src/go/doc/testdata/issue22856.2.golden
@@ -0,0 +1,45 @@
+//
+PACKAGE issue22856
+
+IMPORTPATH
+ testdata/issue22856
+
+FILENAMES
+ testdata/issue22856.go
+
+FUNCTIONS
+ // NewPointerSliceOfSlice is not a factory function because slices ...
+ func NewPointerSliceOfSlice() [][]*T
+
+ // NewSlice3 is not a factory function because 3 nested slices of ...
+ func NewSlice3() [][][]T
+
+ // NewSliceOfSlice is not a factory function because slices of a ...
+ func NewSliceOfSlice() [][]T
+
+
+TYPES
+ //
+ type T struct{}
+
+ //
+ func New() T
+
+ //
+ func NewArray() [1]T
+
+ //
+ func NewPointer() *T
+
+ //
+ func NewPointerArray() [1]*T
+
+ //
+ func NewPointerOfPointer() **T
+
+ //
+ func NewPointerSlice() []*T
+
+ //
+ func NewSlice() []T
+
diff --git a/src/go/doc/testdata/issue22856.go b/src/go/doc/testdata/issue22856.go
new file mode 100644
index 0000000..f456998
--- /dev/null
+++ b/src/go/doc/testdata/issue22856.go
@@ -0,0 +1,27 @@
+// Copyright 2017 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 issue22856
+
+type T struct{}
+
+func New() T { return T{} }
+func NewPointer() *T { return &T{} }
+func NewPointerSlice() []*T { return []*T{&T{}} }
+func NewSlice() []T { return []T{T{}} }
+func NewPointerOfPointer() **T { x := &T{}; return &x }
+func NewArray() [1]T { return [1]T{T{}} }
+func NewPointerArray() [1]*T { return [1]*T{&T{}} }
+
+// NewSliceOfSlice is not a factory function because slices of a slice of
+// type *T are not factory functions of type T.
+func NewSliceOfSlice() [][]T { return []T{[]T{}} }
+
+// NewPointerSliceOfSlice is not a factory function because slices of a
+// slice of type *T are not factory functions of type T.
+func NewPointerSliceOfSlice() [][]*T { return []*T{[]*T{}} }
+
+// NewSlice3 is not a factory function because 3 nested slices of type T
+// are not factory functions of type T.
+func NewSlice3() [][][]T { return []T{[]T{[]T{}}} }
diff --git a/src/go/doc/testdata/pkgdoc/doc.go b/src/go/doc/testdata/pkgdoc/doc.go
new file mode 100644
index 0000000..3f822c7
--- /dev/null
+++ b/src/go/doc/testdata/pkgdoc/doc.go
@@ -0,0 +1,24 @@
+// 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 pkgdoc
+
+import (
+ crand "crypto/rand"
+ "math/rand"
+)
+
+type T int
+
+type U int
+
+func (T) M() {}
+
+var _ = rand.Int
+var _ = crand.Reader
+
+type G[T any] struct{ x T }
+
+func (g G[T]) M1() {}
+func (g *G[T]) M2() {}
diff --git a/src/go/doc/testdata/predeclared.0.golden b/src/go/doc/testdata/predeclared.0.golden
new file mode 100644
index 0000000..9f37b06
--- /dev/null
+++ b/src/go/doc/testdata/predeclared.0.golden
@@ -0,0 +1,8 @@
+// Package predeclared is a go/doc test for handling of exported ...
+PACKAGE predeclared
+
+IMPORTPATH
+ testdata/predeclared
+
+FILENAMES
+ testdata/predeclared.go
diff --git a/src/go/doc/testdata/predeclared.1.golden b/src/go/doc/testdata/predeclared.1.golden
new file mode 100644
index 0000000..2ff8ee6
--- /dev/null
+++ b/src/go/doc/testdata/predeclared.1.golden
@@ -0,0 +1,22 @@
+// Package predeclared is a go/doc test for handling of exported ...
+PACKAGE predeclared
+
+IMPORTPATH
+ testdata/predeclared
+
+FILENAMES
+ testdata/predeclared.go
+
+TYPES
+ //
+ type bool int
+
+ // Must not be visible.
+ func (b bool) String() string
+
+ //
+ type error struct{}
+
+ // Must not be visible.
+ func (e error) Error() string
+
diff --git a/src/go/doc/testdata/predeclared.2.golden b/src/go/doc/testdata/predeclared.2.golden
new file mode 100644
index 0000000..9f37b06
--- /dev/null
+++ b/src/go/doc/testdata/predeclared.2.golden
@@ -0,0 +1,8 @@
+// Package predeclared is a go/doc test for handling of exported ...
+PACKAGE predeclared
+
+IMPORTPATH
+ testdata/predeclared
+
+FILENAMES
+ testdata/predeclared.go
diff --git a/src/go/doc/testdata/predeclared.go b/src/go/doc/testdata/predeclared.go
new file mode 100644
index 0000000..c6dd806
--- /dev/null
+++ b/src/go/doc/testdata/predeclared.go
@@ -0,0 +1,22 @@
+// Copyright 2016 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 predeclared is a go/doc test for handling of
+// exported methods on locally-defined predeclared types.
+// See issue 9860.
+package predeclared
+
+type error struct{}
+
+// Must not be visible.
+func (e error) Error() string {
+ return ""
+}
+
+type bool int
+
+// Must not be visible.
+func (b bool) String() string {
+ return ""
+}
diff --git a/src/go/doc/testdata/template.txt b/src/go/doc/testdata/template.txt
new file mode 100644
index 0000000..1b07382
--- /dev/null
+++ b/src/go/doc/testdata/template.txt
@@ -0,0 +1,68 @@
+{{synopsis .Doc}}
+PACKAGE {{.Name}}
+
+IMPORTPATH
+ {{.ImportPath}}
+
+{{with .Imports}}IMPORTS
+{{range .}} {{.}}
+{{end}}
+{{end}}{{/*
+
+*/}}FILENAMES
+{{range .Filenames}} {{.}}
+{{end}}{{/*
+
+*/}}{{with .Consts}}
+CONSTANTS
+{{range .}} {{synopsis .Doc}}
+ {{node .Decl $.FSet}}
+
+{{end}}{{end}}{{/*
+
+*/}}{{with .Vars}}
+VARIABLES
+{{range .}} {{synopsis .Doc}}
+ {{node .Decl $.FSet}}
+
+{{end}}{{end}}{{/*
+
+*/}}{{with .Funcs}}
+FUNCTIONS
+{{range .}} {{synopsis .Doc}}
+ {{node .Decl $.FSet}}
+
+{{end}}{{end}}{{/*
+
+*/}}{{with .Types}}
+TYPES
+{{range .}} {{synopsis .Doc}}
+ {{node .Decl $.FSet}}
+
+{{range .Consts}} {{synopsis .Doc}}
+ {{node .Decl $.FSet}}
+
+{{end}}{{/*
+
+*/}}{{range .Vars}} {{synopsis .Doc}}
+ {{node .Decl $.FSet}}
+
+{{end}}{{/*
+
+*/}}{{range .Funcs}} {{synopsis .Doc}}
+ {{node .Decl $.FSet}}
+
+{{end}}{{/*
+
+*/}}{{range .Methods}} {{synopsis .Doc}}
+ {{node .Decl $.FSet}}
+
+{{end}}{{end}}{{end}}{{/*
+
+*/}}{{with .Bugs}}
+BUGS .Bugs is now deprecated, please use .Notes instead
+{{range .}}{{indent "\t" .}}
+{{end}}{{end}}{{with .Notes}}{{range $marker, $content := .}}
+{{$marker}}S
+{{range $content}}{{$marker}}({{.UID}}){{indent "\t" .Body}}
+{{end}}{{end}}{{end}} \ No newline at end of file
diff --git a/src/go/doc/testdata/testing.0.golden b/src/go/doc/testdata/testing.0.golden
new file mode 100644
index 0000000..61dac8b
--- /dev/null
+++ b/src/go/doc/testdata/testing.0.golden
@@ -0,0 +1,156 @@
+// Package testing provides support for automated testing of Go ...
+PACKAGE testing
+
+IMPORTPATH
+ testdata/testing
+
+IMPORTS
+ bytes
+ flag
+ fmt
+ io
+ os
+ runtime
+ runtime/pprof
+ strconv
+ strings
+ time
+
+FILENAMES
+ testdata/benchmark.go
+ testdata/example.go
+ testdata/testing.go
+
+FUNCTIONS
+ // An internal function but exported because it is cross-package; ...
+ func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample)
+
+ // An internal function but exported because it is cross-package; ...
+ func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark)
+
+ //
+ func RunExamples(examples []InternalExample) (ok bool)
+
+ //
+ func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool)
+
+ // Short reports whether the -test.short flag is set.
+ func Short() bool
+
+
+TYPES
+ // B is a type passed to Benchmark functions to manage benchmark ...
+ type B struct {
+ N int
+ // contains filtered or unexported fields
+ }
+
+ // Error is equivalent to Log() followed by Fail().
+ func (c *B) Error(args ...any)
+
+ // Errorf is equivalent to Logf() followed by Fail().
+ func (c *B) Errorf(format string, args ...any)
+
+ // Fail marks the function as having failed but continues ...
+ func (c *B) Fail()
+
+ // FailNow marks the function as having failed and stops its ...
+ func (c *B) FailNow()
+
+ // Failed reports whether the function has failed.
+ func (c *B) Failed() bool
+
+ // Fatal is equivalent to Log() followed by FailNow().
+ func (c *B) Fatal(args ...any)
+
+ // Fatalf is equivalent to Logf() followed by FailNow().
+ func (c *B) Fatalf(format string, args ...any)
+
+ // Log formats its arguments using default formatting, analogous ...
+ func (c *B) Log(args ...any)
+
+ // Logf formats its arguments according to the format, analogous ...
+ func (c *B) Logf(format string, args ...any)
+
+ // ResetTimer sets the elapsed benchmark time to zero. It does not ...
+ func (b *B) ResetTimer()
+
+ // SetBytes records the number of bytes processed in a single ...
+ func (b *B) SetBytes(n int64)
+
+ // StartTimer starts timing a test. This function is called ...
+ func (b *B) StartTimer()
+
+ // StopTimer stops timing a test. This can be used to pause the ...
+ func (b *B) StopTimer()
+
+ // The results of a benchmark run.
+ type BenchmarkResult struct {
+ N int // The number of iterations.
+ T time.Duration // The total time taken.
+ Bytes int64 // Bytes processed in one iteration.
+ }
+
+ // Benchmark benchmarks a single function. Useful for creating ...
+ func Benchmark(f func(b *B)) BenchmarkResult
+
+ //
+ func (r BenchmarkResult) NsPerOp() int64
+
+ //
+ func (r BenchmarkResult) String() string
+
+ // An internal type but exported because it is cross-package; part ...
+ type InternalBenchmark struct {
+ Name string
+ F func(b *B)
+ }
+
+ //
+ type InternalExample struct {
+ Name string
+ F func()
+ Output string
+ }
+
+ // An internal type but exported because it is cross-package; part ...
+ type InternalTest struct {
+ Name string
+ F func(*T)
+ }
+
+ // T is a type passed to Test functions to manage test state and ...
+ type T struct {
+ // contains filtered or unexported fields
+ }
+
+ // Error is equivalent to Log() followed by Fail().
+ func (c *T) Error(args ...any)
+
+ // Errorf is equivalent to Logf() followed by Fail().
+ func (c *T) Errorf(format string, args ...any)
+
+ // Fail marks the function as having failed but continues ...
+ func (c *T) Fail()
+
+ // FailNow marks the function as having failed and stops its ...
+ func (c *T) FailNow()
+
+ // Failed reports whether the function has failed.
+ func (c *T) Failed() bool
+
+ // Fatal is equivalent to Log() followed by FailNow().
+ func (c *T) Fatal(args ...any)
+
+ // Fatalf is equivalent to Logf() followed by FailNow().
+ func (c *T) Fatalf(format string, args ...any)
+
+ // Log formats its arguments using default formatting, analogous ...
+ func (c *T) Log(args ...any)
+
+ // Logf formats its arguments according to the format, analogous ...
+ func (c *T) Logf(format string, args ...any)
+
+ // Parallel signals that this test is to be run in parallel with ...
+ func (t *T) Parallel()
+
diff --git a/src/go/doc/testdata/testing.1.golden b/src/go/doc/testdata/testing.1.golden
new file mode 100644
index 0000000..1655af1
--- /dev/null
+++ b/src/go/doc/testdata/testing.1.golden
@@ -0,0 +1,298 @@
+// Package testing provides support for automated testing of Go ...
+PACKAGE testing
+
+IMPORTPATH
+ testdata/testing
+
+IMPORTS
+ bytes
+ flag
+ fmt
+ io
+ os
+ runtime
+ runtime/pprof
+ strconv
+ strings
+ time
+
+FILENAMES
+ testdata/benchmark.go
+ testdata/example.go
+ testdata/testing.go
+
+VARIABLES
+ //
+ var (
+ // The short flag requests that tests run more quickly, but its functionality
+ // is provided by test writers themselves. The testing package is just its
+ // home. The all.bash installation script sets it to make installation more
+ // efficient, but by default the flag is off so a plain "go test" will do a
+ // full test of the package.
+ short = flag.Bool("test.short", false, "run smaller test suite to save time")
+
+ // Report as tests are run; default is silent for success.
+ chatty = flag.Bool("test.v", false, "verbose: print additional output")
+ match = flag.String("test.run", "", "regular expression to select tests to run")
+ memProfile = flag.String("test.memprofile", "", "write a memory profile to the named file after execution")
+ memProfileRate = flag.Int("test.memprofilerate", 0, "if >=0, sets runtime.MemProfileRate")
+ cpuProfile = flag.String("test.cpuprofile", "", "write a cpu profile to the named file during execution")
+ timeout = flag.Duration("test.timeout", 0, "if positive, sets an aggregate time limit for all tests")
+ cpuListStr = flag.String("test.cpu", "", "comma-separated list of number of CPUs to use for each test")
+ parallel = flag.Int("test.parallel", runtime.GOMAXPROCS(0), "maximum test parallelism")
+
+ cpuList []int
+ )
+
+ //
+ var benchTime = flag.Duration("test.benchtime", 1*time.Second, "approximate run time for each benchmark")
+
+ //
+ var matchBenchmarks = flag.String("test.bench", "", "regular expression to select benchmarks to run")
+
+ //
+ var timer *time.Timer
+
+
+FUNCTIONS
+ // An internal function but exported because it is cross-package; ...
+ func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample)
+
+ // An internal function but exported because it is cross-package; ...
+ func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark)
+
+ //
+ func RunExamples(examples []InternalExample) (ok bool)
+
+ //
+ func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool)
+
+ // Short reports whether the -test.short flag is set.
+ func Short() bool
+
+ // after runs after all testing.
+ func after()
+
+ // alarm is called if the timeout expires.
+ func alarm()
+
+ // before runs before all testing.
+ func before()
+
+ // decorate inserts the final newline if needed and indentation ...
+ func decorate(s string, addFileLine bool) string
+
+ //
+ func max(x, y int) int
+
+ //
+ func min(x, y int) int
+
+ //
+ func parseCpuList()
+
+ // roundDown10 rounds a number down to the nearest power of 10.
+ func roundDown10(n int) int
+
+ // roundUp rounds x up to a number of the form [1eX, 2eX, 5eX].
+ func roundUp(n int) int
+
+ // startAlarm starts an alarm if requested.
+ func startAlarm()
+
+ // stopAlarm turns off the alarm.
+ func stopAlarm()
+
+ //
+ func tRunner(t *T, test *InternalTest)
+
+
+TYPES
+ // B is a type passed to Benchmark functions to manage benchmark ...
+ type B struct {
+ common
+ N int
+ benchmark InternalBenchmark
+ bytes int64
+ timerOn bool
+ result BenchmarkResult
+ }
+
+ // Error is equivalent to Log() followed by Fail().
+ func (c *B) Error(args ...any)
+
+ // Errorf is equivalent to Logf() followed by Fail().
+ func (c *B) Errorf(format string, args ...any)
+
+ // Fail marks the function as having failed but continues ...
+ func (c *B) Fail()
+
+ // FailNow marks the function as having failed and stops its ...
+ func (c *B) FailNow()
+
+ // Failed reports whether the function has failed.
+ func (c *B) Failed() bool
+
+ // Fatal is equivalent to Log() followed by FailNow().
+ func (c *B) Fatal(args ...any)
+
+ // Fatalf is equivalent to Logf() followed by FailNow().
+ func (c *B) Fatalf(format string, args ...any)
+
+ // Log formats its arguments using default formatting, analogous ...
+ func (c *B) Log(args ...any)
+
+ // Logf formats its arguments according to the format, analogous ...
+ func (c *B) Logf(format string, args ...any)
+
+ // ResetTimer sets the elapsed benchmark time to zero. It does not ...
+ func (b *B) ResetTimer()
+
+ // SetBytes records the number of bytes processed in a single ...
+ func (b *B) SetBytes(n int64)
+
+ // StartTimer starts timing a test. This function is called ...
+ func (b *B) StartTimer()
+
+ // StopTimer stops timing a test. This can be used to pause the ...
+ func (b *B) StopTimer()
+
+ // launch launches the benchmark function. It gradually increases ...
+ func (b *B) launch()
+
+ // log generates the output. It's always at the same stack depth.
+ func (c *B) log(s string)
+
+ //
+ func (b *B) nsPerOp() int64
+
+ // run times the benchmark function in a separate goroutine.
+ func (b *B) run() BenchmarkResult
+
+ // runN runs a single benchmark for the specified number of ...
+ func (b *B) runN(n int)
+
+ // trimOutput shortens the output from a benchmark, which can be ...
+ func (b *B) trimOutput()
+
+ // The results of a benchmark run.
+ type BenchmarkResult struct {
+ N int // The number of iterations.
+ T time.Duration // The total time taken.
+ Bytes int64 // Bytes processed in one iteration.
+ }
+
+ // Benchmark benchmarks a single function. Useful for creating ...
+ func Benchmark(f func(b *B)) BenchmarkResult
+
+ //
+ func (r BenchmarkResult) NsPerOp() int64
+
+ //
+ func (r BenchmarkResult) String() string
+
+ //
+ func (r BenchmarkResult) mbPerSec() float64
+
+ // An internal type but exported because it is cross-package; part ...
+ type InternalBenchmark struct {
+ Name string
+ F func(b *B)
+ }
+
+ //
+ type InternalExample struct {
+ Name string
+ F func()
+ Output string
+ }
+
+ // An internal type but exported because it is cross-package; part ...
+ type InternalTest struct {
+ Name string
+ F func(*T)
+ }
+
+ // T is a type passed to Test functions to manage test state and ...
+ type T struct {
+ common
+ name string // Name of test.
+ startParallel chan bool // Parallel tests will wait on this.
+ }
+
+ // Error is equivalent to Log() followed by Fail().
+ func (c *T) Error(args ...any)
+
+ // Errorf is equivalent to Logf() followed by Fail().
+ func (c *T) Errorf(format string, args ...any)
+
+ // Fail marks the function as having failed but continues ...
+ func (c *T) Fail()
+
+ // FailNow marks the function as having failed and stops its ...
+ func (c *T) FailNow()
+
+ // Failed reports whether the function has failed.
+ func (c *T) Failed() bool
+
+ // Fatal is equivalent to Log() followed by FailNow().
+ func (c *T) Fatal(args ...any)
+
+ // Fatalf is equivalent to Logf() followed by FailNow().
+ func (c *T) Fatalf(format string, args ...any)
+
+ // Log formats its arguments using default formatting, analogous ...
+ func (c *T) Log(args ...any)
+
+ // Logf formats its arguments according to the format, analogous ...
+ func (c *T) Logf(format string, args ...any)
+
+ // Parallel signals that this test is to be run in parallel with ...
+ func (t *T) Parallel()
+
+ // log generates the output. It's always at the same stack depth.
+ func (c *T) log(s string)
+
+ //
+ func (t *T) report()
+
+ // common holds the elements common between T and B and captures ...
+ type common struct {
+ output []byte // Output generated by test or benchmark.
+ failed bool // Test or benchmark has failed.
+ start time.Time // Time test or benchmark started
+ duration time.Duration
+ self any // To be sent on signal channel when done.
+ signal chan any // Output for serial tests.
+ }
+
+ // Error is equivalent to Log() followed by Fail().
+ func (c *common) Error(args ...any)
+
+ // Errorf is equivalent to Logf() followed by Fail().
+ func (c *common) Errorf(format string, args ...any)
+
+ // Fail marks the function as having failed but continues ...
+ func (c *common) Fail()
+
+ // FailNow marks the function as having failed and stops its ...
+ func (c *common) FailNow()
+
+ // Failed reports whether the function has failed.
+ func (c *common) Failed() bool
+
+ // Fatal is equivalent to Log() followed by FailNow().
+ func (c *common) Fatal(args ...any)
+
+ // Fatalf is equivalent to Logf() followed by FailNow().
+ func (c *common) Fatalf(format string, args ...any)
+
+ // Log formats its arguments using default formatting, analogous ...
+ func (c *common) Log(args ...any)
+
+ // Logf formats its arguments according to the format, analogous ...
+ func (c *common) Logf(format string, args ...any)
+
+ // log generates the output. It's always at the same stack depth.
+ func (c *common) log(s string)
+
diff --git a/src/go/doc/testdata/testing.2.golden b/src/go/doc/testdata/testing.2.golden
new file mode 100644
index 0000000..61dac8b
--- /dev/null
+++ b/src/go/doc/testdata/testing.2.golden
@@ -0,0 +1,156 @@
+// Package testing provides support for automated testing of Go ...
+PACKAGE testing
+
+IMPORTPATH
+ testdata/testing
+
+IMPORTS
+ bytes
+ flag
+ fmt
+ io
+ os
+ runtime
+ runtime/pprof
+ strconv
+ strings
+ time
+
+FILENAMES
+ testdata/benchmark.go
+ testdata/example.go
+ testdata/testing.go
+
+FUNCTIONS
+ // An internal function but exported because it is cross-package; ...
+ func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample)
+
+ // An internal function but exported because it is cross-package; ...
+ func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark)
+
+ //
+ func RunExamples(examples []InternalExample) (ok bool)
+
+ //
+ func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool)
+
+ // Short reports whether the -test.short flag is set.
+ func Short() bool
+
+
+TYPES
+ // B is a type passed to Benchmark functions to manage benchmark ...
+ type B struct {
+ N int
+ // contains filtered or unexported fields
+ }
+
+ // Error is equivalent to Log() followed by Fail().
+ func (c *B) Error(args ...any)
+
+ // Errorf is equivalent to Logf() followed by Fail().
+ func (c *B) Errorf(format string, args ...any)
+
+ // Fail marks the function as having failed but continues ...
+ func (c *B) Fail()
+
+ // FailNow marks the function as having failed and stops its ...
+ func (c *B) FailNow()
+
+ // Failed reports whether the function has failed.
+ func (c *B) Failed() bool
+
+ // Fatal is equivalent to Log() followed by FailNow().
+ func (c *B) Fatal(args ...any)
+
+ // Fatalf is equivalent to Logf() followed by FailNow().
+ func (c *B) Fatalf(format string, args ...any)
+
+ // Log formats its arguments using default formatting, analogous ...
+ func (c *B) Log(args ...any)
+
+ // Logf formats its arguments according to the format, analogous ...
+ func (c *B) Logf(format string, args ...any)
+
+ // ResetTimer sets the elapsed benchmark time to zero. It does not ...
+ func (b *B) ResetTimer()
+
+ // SetBytes records the number of bytes processed in a single ...
+ func (b *B) SetBytes(n int64)
+
+ // StartTimer starts timing a test. This function is called ...
+ func (b *B) StartTimer()
+
+ // StopTimer stops timing a test. This can be used to pause the ...
+ func (b *B) StopTimer()
+
+ // The results of a benchmark run.
+ type BenchmarkResult struct {
+ N int // The number of iterations.
+ T time.Duration // The total time taken.
+ Bytes int64 // Bytes processed in one iteration.
+ }
+
+ // Benchmark benchmarks a single function. Useful for creating ...
+ func Benchmark(f func(b *B)) BenchmarkResult
+
+ //
+ func (r BenchmarkResult) NsPerOp() int64
+
+ //
+ func (r BenchmarkResult) String() string
+
+ // An internal type but exported because it is cross-package; part ...
+ type InternalBenchmark struct {
+ Name string
+ F func(b *B)
+ }
+
+ //
+ type InternalExample struct {
+ Name string
+ F func()
+ Output string
+ }
+
+ // An internal type but exported because it is cross-package; part ...
+ type InternalTest struct {
+ Name string
+ F func(*T)
+ }
+
+ // T is a type passed to Test functions to manage test state and ...
+ type T struct {
+ // contains filtered or unexported fields
+ }
+
+ // Error is equivalent to Log() followed by Fail().
+ func (c *T) Error(args ...any)
+
+ // Errorf is equivalent to Logf() followed by Fail().
+ func (c *T) Errorf(format string, args ...any)
+
+ // Fail marks the function as having failed but continues ...
+ func (c *T) Fail()
+
+ // FailNow marks the function as having failed and stops its ...
+ func (c *T) FailNow()
+
+ // Failed reports whether the function has failed.
+ func (c *T) Failed() bool
+
+ // Fatal is equivalent to Log() followed by FailNow().
+ func (c *T) Fatal(args ...any)
+
+ // Fatalf is equivalent to Logf() followed by FailNow().
+ func (c *T) Fatalf(format string, args ...any)
+
+ // Log formats its arguments using default formatting, analogous ...
+ func (c *T) Log(args ...any)
+
+ // Logf formats its arguments according to the format, analogous ...
+ func (c *T) Logf(format string, args ...any)
+
+ // Parallel signals that this test is to be run in parallel with ...
+ func (t *T) Parallel()
+
diff --git a/src/go/doc/testdata/testing.go b/src/go/doc/testdata/testing.go
new file mode 100644
index 0000000..d3076c9
--- /dev/null
+++ b/src/go/doc/testdata/testing.go
@@ -0,0 +1,404 @@
+// Copyright 2009 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 testing provides support for automated testing of Go packages.
+// It is intended to be used in concert with the “go test” utility, which automates
+// execution of any function of the form
+// func TestXxx(*testing.T)
+// where Xxx can be any alphanumeric string (but the first letter must not be in
+// [a-z]) and serves to identify the test routine.
+// These TestXxx routines should be declared within the package they are testing.
+//
+// Functions of the form
+// func BenchmarkXxx(*testing.B)
+// are considered benchmarks, and are executed by go test when the -test.bench
+// flag is provided.
+//
+// A sample benchmark function looks like this:
+// func BenchmarkHello(b *testing.B) {
+// for i := 0; i < b.N; i++ {
+// fmt.Sprintf("hello")
+// }
+// }
+// The benchmark package will vary b.N until the benchmark function lasts
+// long enough to be timed reliably. The output
+// testing.BenchmarkHello 10000000 282 ns/op
+// means that the loop ran 10000000 times at a speed of 282 ns per loop.
+//
+// If a benchmark needs some expensive setup before running, the timer
+// may be stopped:
+// func BenchmarkBigLen(b *testing.B) {
+// b.StopTimer()
+// big := NewBig()
+// b.StartTimer()
+// for i := 0; i < b.N; i++ {
+// big.Len()
+// }
+// }
+package testing
+
+import (
+ "flag"
+ "fmt"
+ "os"
+ "runtime"
+ "runtime/pprof"
+ "strconv"
+ "strings"
+ "time"
+)
+
+var (
+ // The short flag requests that tests run more quickly, but its functionality
+ // is provided by test writers themselves. The testing package is just its
+ // home. The all.bash installation script sets it to make installation more
+ // efficient, but by default the flag is off so a plain "go test" will do a
+ // full test of the package.
+ short = flag.Bool("test.short", false, "run smaller test suite to save time")
+
+ // Report as tests are run; default is silent for success.
+ chatty = flag.Bool("test.v", false, "verbose: print additional output")
+ match = flag.String("test.run", "", "regular expression to select tests to run")
+ memProfile = flag.String("test.memprofile", "", "write a memory profile to the named file after execution")
+ memProfileRate = flag.Int("test.memprofilerate", 0, "if >=0, sets runtime.MemProfileRate")
+ cpuProfile = flag.String("test.cpuprofile", "", "write a cpu profile to the named file during execution")
+ timeout = flag.Duration("test.timeout", 0, "if positive, sets an aggregate time limit for all tests")
+ cpuListStr = flag.String("test.cpu", "", "comma-separated list of number of CPUs to use for each test")
+ parallel = flag.Int("test.parallel", runtime.GOMAXPROCS(0), "maximum test parallelism")
+
+ cpuList []int
+)
+
+// common holds the elements common between T and B and
+// captures common methods such as Errorf.
+type common struct {
+ output []byte // Output generated by test or benchmark.
+ failed bool // Test or benchmark has failed.
+ start time.Time // Time test or benchmark started
+ duration time.Duration
+ self any // To be sent on signal channel when done.
+ signal chan any // Output for serial tests.
+}
+
+// Short reports whether the -test.short flag is set.
+func Short() bool {
+ return *short
+}
+
+// decorate inserts the final newline if needed and indentation tabs for formatting.
+// If addFileLine is true, it also prefixes the string with the file and line of the call site.
+func decorate(s string, addFileLine bool) string {
+ if addFileLine {
+ _, file, line, ok := runtime.Caller(3) // decorate + log + public function.
+ if ok {
+ // Truncate file name at last file name separator.
+ if index := strings.LastIndex(file, "/"); index >= 0 {
+ file = file[index+1:]
+ } else if index = strings.LastIndex(file, "\\"); index >= 0 {
+ file = file[index+1:]
+ }
+ } else {
+ file = "???"
+ line = 1
+ }
+ s = fmt.Sprintf("%s:%d: %s", file, line, s)
+ }
+ s = "\t" + s // Every line is indented at least one tab.
+ n := len(s)
+ if n > 0 && s[n-1] != '\n' {
+ s += "\n"
+ n++
+ }
+ for i := 0; i < n-1; i++ { // -1 to avoid final newline
+ if s[i] == '\n' {
+ // Second and subsequent lines are indented an extra tab.
+ return s[0:i+1] + "\t" + decorate(s[i+1:n], false)
+ }
+ }
+ return s
+}
+
+// T is a type passed to Test functions to manage test state and support formatted test logs.
+// Logs are accumulated during execution and dumped to standard error when done.
+type T struct {
+ common
+ name string // Name of test.
+ startParallel chan bool // Parallel tests will wait on this.
+}
+
+// Fail marks the function as having failed but continues execution.
+func (c *common) Fail() { c.failed = true }
+
+// Failed reports whether the function has failed.
+func (c *common) Failed() bool { return c.failed }
+
+// FailNow marks the function as having failed and stops its execution.
+// Execution will continue at the next Test.
+func (c *common) FailNow() {
+ c.Fail()
+
+ // Calling runtime.Goexit will exit the goroutine, which
+ // will run the deferred functions in this goroutine,
+ // which will eventually run the deferred lines in tRunner,
+ // which will signal to the test loop that this test is done.
+ //
+ // A previous version of this code said:
+ //
+ // c.duration = ...
+ // c.signal <- c.self
+ // runtime.Goexit()
+ //
+ // This previous version duplicated code (those lines are in
+ // tRunner no matter what), but worse the goroutine teardown
+ // implicit in runtime.Goexit was not guaranteed to complete
+ // before the test exited. If a test deferred an important cleanup
+ // function (like removing temporary files), there was no guarantee
+ // it would run on a test failure. Because we send on c.signal during
+ // a top-of-stack deferred function now, we know that the send
+ // only happens after any other stacked defers have completed.
+ runtime.Goexit()
+}
+
+// log generates the output. It's always at the same stack depth.
+func (c *common) log(s string) {
+ c.output = append(c.output, decorate(s, true)...)
+}
+
+// Log formats its arguments using default formatting, analogous to Println(),
+// and records the text in the error log.
+func (c *common) Log(args ...any) { c.log(fmt.Sprintln(args...)) }
+
+// Logf formats its arguments according to the format, analogous to Printf(),
+// and records the text in the error log.
+func (c *common) Logf(format string, args ...any) { c.log(fmt.Sprintf(format, args...)) }
+
+// Error is equivalent to Log() followed by Fail().
+func (c *common) Error(args ...any) {
+ c.log(fmt.Sprintln(args...))
+ c.Fail()
+}
+
+// Errorf is equivalent to Logf() followed by Fail().
+func (c *common) Errorf(format string, args ...any) {
+ c.log(fmt.Sprintf(format, args...))
+ c.Fail()
+}
+
+// Fatal is equivalent to Log() followed by FailNow().
+func (c *common) Fatal(args ...any) {
+ c.log(fmt.Sprintln(args...))
+ c.FailNow()
+}
+
+// Fatalf is equivalent to Logf() followed by FailNow().
+func (c *common) Fatalf(format string, args ...any) {
+ c.log(fmt.Sprintf(format, args...))
+ c.FailNow()
+}
+
+// Parallel signals that this test is to be run in parallel with (and only with)
+// other parallel tests in this CPU group.
+func (t *T) Parallel() {
+ t.signal <- (*T)(nil) // Release main testing loop
+ <-t.startParallel // Wait for serial tests to finish
+}
+
+// An internal type but exported because it is cross-package; part of the implementation
+// of go test.
+type InternalTest struct {
+ Name string
+ F func(*T)
+}
+
+func tRunner(t *T, test *InternalTest) {
+ t.start = time.Now()
+
+ // When this goroutine is done, either because test.F(t)
+ // returned normally or because a test failure triggered
+ // a call to runtime.Goexit, record the duration and send
+ // a signal saying that the test is done.
+ defer func() {
+ t.duration = time.Since(t.start)
+ t.signal <- t
+ }()
+
+ test.F(t)
+}
+
+// An internal function but exported because it is cross-package; part of the implementation
+// of go test.
+func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample) {
+ flag.Parse()
+ parseCpuList()
+
+ before()
+ startAlarm()
+ testOk := RunTests(matchString, tests)
+ exampleOk := RunExamples(examples)
+ if !testOk || !exampleOk {
+ fmt.Println("FAIL")
+ os.Exit(1)
+ }
+ fmt.Println("PASS")
+ stopAlarm()
+ RunBenchmarks(matchString, benchmarks)
+ after()
+}
+
+func (t *T) report() {
+ tstr := fmt.Sprintf("(%.2f seconds)", t.duration.Seconds())
+ format := "--- %s: %s %s\n%s"
+ if t.failed {
+ fmt.Printf(format, "FAIL", t.name, tstr, t.output)
+ } else if *chatty {
+ fmt.Printf(format, "PASS", t.name, tstr, t.output)
+ }
+}
+
+func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool) {
+ ok = true
+ if len(tests) == 0 {
+ fmt.Fprintln(os.Stderr, "testing: warning: no tests to run")
+ return
+ }
+ for _, procs := range cpuList {
+ runtime.GOMAXPROCS(procs)
+ // We build a new channel tree for each run of the loop.
+ // collector merges in one channel all the upstream signals from parallel tests.
+ // If all tests pump to the same channel, a bug can occur where a test
+ // kicks off a goroutine that Fails, yet the test still delivers a completion signal,
+ // which skews the counting.
+ var collector = make(chan any)
+
+ numParallel := 0
+ startParallel := make(chan bool)
+
+ for i := 0; i < len(tests); i++ {
+ matched, err := matchString(*match, tests[i].Name)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.run: %s\n", err)
+ os.Exit(1)
+ }
+ if !matched {
+ continue
+ }
+ testName := tests[i].Name
+ if procs != 1 {
+ testName = fmt.Sprintf("%s-%d", tests[i].Name, procs)
+ }
+ t := &T{
+ common: common{
+ signal: make(chan any),
+ },
+ name: testName,
+ startParallel: startParallel,
+ }
+ t.self = t
+ if *chatty {
+ fmt.Printf("=== RUN %s\n", t.name)
+ }
+ go tRunner(t, &tests[i])
+ out := (<-t.signal).(*T)
+ if out == nil { // Parallel run.
+ go func() {
+ collector <- <-t.signal
+ }()
+ numParallel++
+ continue
+ }
+ t.report()
+ ok = ok && !out.failed
+ }
+
+ running := 0
+ for numParallel+running > 0 {
+ if running < *parallel && numParallel > 0 {
+ startParallel <- true
+ running++
+ numParallel--
+ continue
+ }
+ t := (<-collector).(*T)
+ t.report()
+ ok = ok && !t.failed
+ running--
+ }
+ }
+ return
+}
+
+// before runs before all testing.
+func before() {
+ if *memProfileRate > 0 {
+ runtime.MemProfileRate = *memProfileRate
+ }
+ if *cpuProfile != "" {
+ f, err := os.Create(*cpuProfile)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "testing: %s", err)
+ return
+ }
+ if err := pprof.StartCPUProfile(f); err != nil {
+ fmt.Fprintf(os.Stderr, "testing: can't start cpu profile: %s", err)
+ f.Close()
+ return
+ }
+ // Could save f so after can call f.Close; not worth the effort.
+ }
+
+}
+
+// after runs after all testing.
+func after() {
+ if *cpuProfile != "" {
+ pprof.StopCPUProfile() // flushes profile to disk
+ }
+ if *memProfile != "" {
+ f, err := os.Create(*memProfile)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "testing: %s", err)
+ return
+ }
+ if err = pprof.WriteHeapProfile(f); err != nil {
+ fmt.Fprintf(os.Stderr, "testing: can't write %s: %s", *memProfile, err)
+ }
+ f.Close()
+ }
+}
+
+var timer *time.Timer
+
+// startAlarm starts an alarm if requested.
+func startAlarm() {
+ if *timeout > 0 {
+ timer = time.AfterFunc(*timeout, alarm)
+ }
+}
+
+// stopAlarm turns off the alarm.
+func stopAlarm() {
+ if *timeout > 0 {
+ timer.Stop()
+ }
+}
+
+// alarm is called if the timeout expires.
+func alarm() {
+ panic("test timed out")
+}
+
+func parseCpuList() {
+ if len(*cpuListStr) == 0 {
+ cpuList = append(cpuList, runtime.GOMAXPROCS(-1))
+ } else {
+ for _, val := range strings.Split(*cpuListStr, ",") {
+ cpu, err := strconv.Atoi(val)
+ if err != nil || cpu <= 0 {
+ fmt.Fprintf(os.Stderr, "testing: invalid value %q for -test.cpu", val)
+ os.Exit(1)
+ }
+ cpuList = append(cpuList, cpu)
+ }
+ }
+}