internal/godoc: fix some bugs in example rendering

First, don't try to render an example if we can't convert the AST to a
string. (This was causing a panic.) Instead, render a message saying
that we failed to produce an example. In practice, this is only going
to happen for playable exammples, because we synthesize the AST for
those and that is a tricky proposition. We will log an error if that
happens, so occasional error sweeps of the logs will find these, but
absent those we will have to rely on user reports.

Second, fix the bug that caused the panic in the first place, which
was that the code for trimming unused global declarations could
convert a valid const group like
```
const (
	a = iota
	b
)
```
to merely
```
const b
```
which isn't valid Go. The fix is to keep an entire iota const group if
any of it is used.

Change-Id: I33637aa1e88d9c8731bb2ccc58e6991b84eba6b6
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/286914
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/internal/godoc/dochtml/internal/render/linkify.go b/internal/godoc/dochtml/internal/render/linkify.go
index c77a785..807773c 100644
--- a/internal/godoc/dochtml/internal/render/linkify.go
+++ b/internal/godoc/dochtml/internal/render/linkify.go
@@ -175,7 +175,9 @@
 			Node:     ex.Code,
 			Comments: ex.Comments,
 		}
-		format.Node(&buf, r.fset, n)
+		if err := format.Node(&buf, r.fset, n); err != nil {
+			return "", err
+		}
 	}
 
 	return buf.String(), nil
@@ -185,6 +187,7 @@
 	codeStr, err := r.codeString(ex)
 	if err != nil {
 		log.Errorf(r.ctx, "Error converting *doc.Example into string: %v", err)
+		return template.MustParseAndExecuteToHTML(`<pre class="Documentation-exampleCode">Error rendering example code.</pre>`)
 	}
 	return codeHTML(codeStr, r.exampleTmpl)
 }
diff --git a/internal/godoc/dochtml/internal/render/render.go b/internal/godoc/dochtml/internal/render/render.go
index 21c58f5..1bb9975 100644
--- a/internal/godoc/dochtml/internal/render/render.go
+++ b/internal/godoc/dochtml/internal/render/render.go
@@ -119,6 +119,7 @@
 		disablePermalinks: disablePermalinks,
 		docTmpl:           docDataTmpl,
 		exampleTmpl:       exampleTmpl,
+		ctx:               ctx,
 	}
 }
 
diff --git a/internal/godoc/internal/doc/example.go b/internal/godoc/internal/doc/example.go
index 9a09f41..e02ba83 100644
--- a/internal/godoc/internal/doc/example.go
+++ b/internal/godoc/internal/doc/example.go
@@ -372,6 +372,11 @@
 	// 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 furthermore, removing any constant from the group could
+	// change the values of subsequent ones.
+	// See testdata/examples/iota.go for a minimal example.
 	ds := depDecls[:0]
 	for _, d := range depDecls {
 		switch d := d.(type) {
@@ -406,19 +411,37 @@
 				}
 			}
 			if len(specs) > 0 {
-				nd := *d // copy the GenDecl
-				nd.Specs = specs
-				if len(specs) == 1 {
-					// Remove grouping parens if there is only one spec.
-					nd.Lparen = 0
+				// Constant with iota? Keep it all.
+				if d.Tok == token.CONST && hasIota(d.Specs[0]) {
+					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)
 				}
-				ds = append(ds, &nd)
 			}
 		}
 	}
 	return ds, unresolved
 }
 
+func hasIota(s ast.Spec) bool {
+	has := false
+	ast.Inspect(s, func(n ast.Node) bool {
+		if id, ok := n.(*ast.Ident); ok && id.Name == "iota" {
+			has = true
+			return false
+		}
+		return true
+	})
+	return has
+}
+
 // synthesizeImportDecl creates the imports for the example. We want the imports
 // divided into two groups, one for the standard library and one for all others.
 // To get ast.SortImports (called by the formatter) to do that, we must assign
@@ -451,7 +474,7 @@
 			specs = &others
 		}
 		s := &ast.ImportSpec{
-			Path:   &ast.BasicLit{Value: strconv.Quote(p), ValuePos: pos},
+			Path:   &ast.BasicLit{Value: strconv.Quote(p), Kind: token.STRING, ValuePos: pos},
 			EndPos: pos,
 		}
 		if path.Base(p) != n {
diff --git a/internal/godoc/internal/doc/example_test.go b/internal/godoc/internal/doc/example_test.go
index baccfcd..2e83832 100644
--- a/internal/godoc/internal/doc/example_test.go
+++ b/internal/godoc/internal/doc/example_test.go
@@ -30,7 +30,7 @@
 		t.Fatal(err)
 	}
 	for _, filename := range filenames {
-		t.Run(filepath.Base(filename), func(t *testing.T) {
+		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 {
@@ -81,6 +81,7 @@
 }
 
 func formatFile(t *testing.T, fset *token.FileSet, n *ast.File) string {
+	t.Helper()
 	if n == nil {
 		return "<nil>"
 	}
diff --git a/internal/godoc/internal/doc/testdata/examples/iota.go b/internal/godoc/internal/doc/testdata/examples/iota.go
new file mode 100644
index 0000000..c878b77
--- /dev/null
+++ b/internal/godoc/internal/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/internal/godoc/internal/doc/testdata/examples/iota.golden b/internal/godoc/internal/doc/testdata/examples/iota.golden
new file mode 100644
index 0000000..d1c3da9
--- /dev/null
+++ b/internal/godoc/internal/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() {
+}