internal/frontend: avoid decoding source twice

We were decoding the source for both doc rendering and getting the
list of source files.  Decode it just once.

Change-Id: Ib1eaa71d06ea1afd53e1b17e6e460bf75f1910b6
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/262579
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
diff --git a/internal/frontend/doc.go b/internal/frontend/doc.go
index c81eef5..c80a6cc 100644
--- a/internal/frontend/doc.go
+++ b/internal/frontend/doc.go
@@ -30,12 +30,18 @@
 
 // fetchDocumentationDetails returns a DocumentationDetails.
 func fetchDocumentationDetails(ctx context.Context, ds internal.DataSource, um *internal.UnitMeta) (_ *DocumentationDetails, err error) {
+	derrors.Wrap(&err, "fetchDocumentationDetails(%q, %q, %q)", um.Path, um.ModulePath, um.Version)
+
 	u, err := ds.GetUnit(ctx, um, internal.WithDocumentation)
 	if err != nil {
 		return nil, err
 	}
 	if experiment.IsActive(ctx, internal.ExperimentFrontendRenderDoc) && len(u.Documentation.Source) > 0 {
-		dd, err := renderDoc(ctx, u)
+		docPkg, err := godoc.DecodePackage(u.Documentation.Source)
+		if err != nil {
+			return nil, err
+		}
+		dd, err := renderDoc(ctx, u, docPkg)
 		if err != nil {
 			log.Errorf(ctx, "render doc failed: %v", err)
 			// Fall through to use stored doc.
@@ -50,15 +56,11 @@
 	}, nil
 }
 
-func renderDoc(ctx context.Context, u *internal.Unit) (_ *DocumentationDetails, err error) {
+func renderDoc(ctx context.Context, u *internal.Unit, docPkg *godoc.Package) (_ *DocumentationDetails, err error) {
 	defer derrors.Wrap(&err, "renderDoc")
 	defer middleware.ElapsedStat(ctx, "renderDoc")()
 
 	start := time.Now()
-	docPkg, err := godoc.DecodePackage(u.Documentation.Source)
-	if err != nil {
-		return nil, err
-	}
 	modInfo := &godoc.ModuleInfo{
 		ModulePath:      u.ModulePath,
 		ResolvedVersion: u.Version,
@@ -83,11 +85,7 @@
 }
 
 // sourceFiles returns the .go files for a package.
-func sourceFiles(u *internal.Unit) ([]*File, error) {
-	docPkg, err := godoc.DecodePackage(u.Documentation.Source)
-	if err != nil {
-		return nil, err
-	}
+func sourceFiles(u *internal.Unit, docPkg *godoc.Package) []*File {
 	var files []*File
 	for _, f := range docPkg.Files {
 		if strings.HasSuffix(f.Name, "_test.go") {
@@ -98,7 +96,7 @@
 			URL:  u.SourceInfo.FileURL(path.Join(internal.Suffix(u.Path, u.ModulePath), f.Name)),
 		})
 	}
-	return files, nil
+	return files
 }
 
 // fileSource returns the original filepath in the module zip where the given
diff --git a/internal/frontend/unit_main.go b/internal/frontend/unit_main.go
index 76c8050..b7c6382 100644
--- a/internal/frontend/unit_main.go
+++ b/internal/frontend/unit_main.go
@@ -122,7 +122,11 @@
 		files                              []*File
 	)
 	if unit.Documentation != nil {
-		docHTML := getHTML(ctx, unit)
+		docPkg, err := godoc.DecodePackage(unit.Documentation.Source)
+		if err != nil {
+			return nil, err
+		}
+		docHTML := getHTML(ctx, unit, docPkg)
 		// TODO: Deprecate godoc.Parse. The sidenav and body can
 		// either be rendered using separate functions, or all this content can
 		// be passed to the template via the UnitPage struct.
@@ -146,7 +150,7 @@
 		end()
 
 		end = middleware.ElapsedStat(ctx, "sourceFiles")
-		files, err = sourceFiles(unit)
+		files = sourceFiles(unit, docPkg)
 		end()
 		if err != nil {
 			return nil, err
@@ -234,9 +238,9 @@
 	return sdirs
 }
 
-func getHTML(ctx context.Context, u *internal.Unit) safehtml.HTML {
+func getHTML(ctx context.Context, u *internal.Unit, docPkg *godoc.Package) safehtml.HTML {
 	if experiment.IsActive(ctx, internal.ExperimentFrontendRenderDoc) && len(u.Documentation.Source) > 0 {
-		dd, err := renderDoc(ctx, u)
+		dd, err := renderDoc(ctx, u, docPkg)
 		if err != nil {
 			log.Errorf(ctx, "render doc failed: %v", err)
 			// Fall through to use stored doc.