internal: add fields to unit meta struct

Adds HasGoMod and VersionType fields to the UnitMeta struct
and ModFileURL and IsStableVersion to UnitMain to support the
details section in the right sidebar.

For golang/go#43129

Change-Id: Id605d27d6a421d7c5ff2afc39f4532b11e49ace6
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/277832
Reviewed-by: Julie Qiu <julie@golang.org>
Trust: Julie Qiu <julie@golang.org>
Trust: Jamal Carvalho <jamal@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
TryBot-Result: kokoro <noreply+kokoro@google.com>
diff --git a/internal/frontend/unit_main.go b/internal/frontend/unit_main.go
index 4ad94b3..536b224 100644
--- a/internal/frontend/unit_main.go
+++ b/internal/frontend/unit_main.go
@@ -15,6 +15,7 @@
 
 	"github.com/google/safehtml"
 	"github.com/google/safehtml/template"
+	"golang.org/x/mod/semver"
 	"golang.org/x/pkgsite/internal"
 	"golang.org/x/pkgsite/internal/derrors"
 	"golang.org/x/pkgsite/internal/experiment"
@@ -23,6 +24,7 @@
 	"golang.org/x/pkgsite/internal/log"
 	"golang.org/x/pkgsite/internal/middleware"
 	"golang.org/x/pkgsite/internal/postgres"
+	"golang.org/x/pkgsite/internal/version"
 )
 
 // MainDetails contains data needed to render the unit template.
@@ -89,6 +91,15 @@
 
 	// ExpandReadme is holds the expandable readme state.
 	ExpandReadme bool
+
+	// ModFileURL is an URL to the mod file.
+	ModFileURL string
+
+	// IsTaggedVersion is true if the version is not a psuedorelease.
+	IsTaggedVersion bool
+
+	// IsStableVersion is true if the major version is v1 or greater.
+	IsStableVersion bool
 }
 
 // File is a source file for a package.
@@ -185,6 +196,12 @@
 		}
 	}
 
+	versionType, err := version.ParseType(um.Version)
+	if err != nil {
+		return nil, err
+	}
+	isTaggedVersion := versionType != version.TypePseudo
+
 	return &MainDetails{
 		ExpandReadme:      expandReadme,
 		NestedModules:     nestedModules,
@@ -206,6 +223,9 @@
 		NumImports:        unit.NumImports,
 		ImportedByCount:   importedByCount,
 		IsPackage:         unit.IsPackage(),
+		ModFileURL:        um.SourceInfo.ModuleURL() + "/go.mod",
+		IsTaggedVersion:   isTaggedVersion,
+		IsStableVersion:   semver.Major(um.Version) != "v0",
 	}, nil
 }
 
diff --git a/internal/postgres/path_test.go b/internal/postgres/path_test.go
index 8dcb7b5..8d4e121 100644
--- a/internal/postgres/path_test.go
+++ b/internal/postgres/path_test.go
@@ -188,6 +188,7 @@
 			}
 			opts := []cmp.Option{
 				cmpopts.IgnoreFields(licenses.Metadata{}, "Coverage"),
+				cmpopts.IgnoreFields(internal.UnitMeta{}, "HasGoMod"),
 				cmp.AllowUnexported(source.Info{}, safehtml.HTML{}),
 			}
 			if diff := cmp.Diff(test.want, got, opts...); diff != "" {
@@ -380,6 +381,7 @@
 				}
 				opts := []cmp.Option{
 					cmpopts.IgnoreFields(licenses.Metadata{}, "Coverage"),
+					cmpopts.IgnoreFields(internal.UnitMeta{}, "HasGoMod"),
 					cmp.AllowUnexported(source.Info{}, safehtml.HTML{}),
 				}
 				if diff := cmp.Diff(test.want, got, opts...); diff != "" {
diff --git a/internal/postgres/unit.go b/internal/postgres/unit.go
index 8a2fec9..18e64c9 100644
--- a/internal/postgres/unit.go
+++ b/internal/postgres/unit.go
@@ -37,6 +37,7 @@
 		"m.version",
 		"m.commit_time",
 		"m.source_info",
+		"m.has_go_mod",
 		"u.name",
 		"u.redistributable",
 		"u.license_types",
@@ -67,6 +68,7 @@
 		&um.Version,
 		&um.CommitTime,
 		jsonbScanner{&um.SourceInfo},
+		&um.HasGoMod,
 		&um.Name,
 		&um.IsRedistributable,
 		pq.Array(&licenseTypes),
diff --git a/internal/unit.go b/internal/unit.go
index e766aef..82a116f 100644
--- a/internal/unit.go
+++ b/internal/unit.go
@@ -26,6 +26,7 @@
 	ModulePath string
 	CommitTime time.Time
 	SourceInfo *source.Info
+	HasGoMod   bool
 }
 
 // IsPackage reports whether the path represents a package path.
diff --git a/internal/worker/fetch_test.go b/internal/worker/fetch_test.go
index a20d3ce..622f85e 100644
--- a/internal/worker/fetch_test.go
+++ b/internal/worker/fetch_test.go
@@ -145,6 +145,7 @@
 	myModuleV100 := &internal.Unit{
 		UnitMeta: internal.UnitMeta{
 			ModulePath:        "github.com/my/module",
+			HasGoMod:          true,
 			Version:           sample.VersionString,
 			CommitTime:        testProxyCommitTime,
 			SourceInfo:        source.NewGitHubInfo("https://github.com/my/module", "", sample.VersionString),
@@ -198,6 +199,7 @@
 				UnitMeta: internal.UnitMeta{
 					ModulePath:        "nonredistributable.mod/module",
 					Version:           "v1.0.0",
+					HasGoMod:          true,
 					CommitTime:        testProxyCommitTime,
 					SourceInfo:        nil,
 					IsRedistributable: true,
@@ -224,6 +226,7 @@
 				UnitMeta: internal.UnitMeta{
 					ModulePath:        "nonredistributable.mod/module",
 					Version:           sample.VersionString,
+					HasGoMod:          true,
 					CommitTime:        testProxyCommitTime,
 					SourceInfo:        nil,
 					IsRedistributable: false,
@@ -244,6 +247,7 @@
 				UnitMeta: internal.UnitMeta{
 					ModulePath:        "std",
 					Version:           "v1.12.5",
+					HasGoMod:          true,
 					CommitTime:        stdlib.TestCommitTime,
 					SourceInfo:        source.NewGitHubInfo(goRepositoryURLPrefix+"/go", "src", "go1.12.5"),
 					IsRedistributable: true,
@@ -272,6 +276,7 @@
 				UnitMeta: internal.UnitMeta{
 					ModulePath:        "std",
 					Version:           "v1.12.5",
+					HasGoMod:          true,
 					CommitTime:        stdlib.TestCommitTime,
 					SourceInfo:        source.NewGitHubInfo(goRepositoryURLPrefix+"/go", "src", "go1.12.5"),
 					IsRedistributable: true,
@@ -299,6 +304,7 @@
 				UnitMeta: internal.UnitMeta{
 					ModulePath:        "std",
 					Version:           "v1.12.5",
+					HasGoMod:          true,
 					CommitTime:        stdlib.TestCommitTime,
 					SourceInfo:        source.NewGitHubInfo(goRepositoryURLPrefix+"/go", "src", "go1.12.5"),
 					IsRedistributable: true,
@@ -340,6 +346,7 @@
 				UnitMeta: internal.UnitMeta{
 					ModulePath:        buildConstraintsMod.ModulePath,
 					Version:           buildConstraintsMod.Version,
+					HasGoMod:          false,
 					CommitTime:        testProxyCommitTime,
 					IsRedistributable: true,
 					Path:              buildConstraintsMod.ModulePath + "/cpu",
diff --git a/internal/worker/refetch_test.go b/internal/worker/refetch_test.go
index 2135bd5..a845b48 100644
--- a/internal/worker/refetch_test.go
+++ b/internal/worker/refetch_test.go
@@ -119,7 +119,8 @@
 	}
 	if diff := cmp.Diff(want.UnitMeta, *got,
 		cmp.AllowUnexported(source.Info{}),
-		cmpopts.IgnoreFields(licenses.Metadata{}, "Coverage")); diff != "" {
+		cmpopts.IgnoreFields(licenses.Metadata{}, "Coverage"),
+		cmpopts.IgnoreFields(internal.UnitMeta{}, "HasGoMod"),); diff != "" {
 		t.Fatalf("testDB.GetUnitMeta(ctx, %q, %q) mismatch (-want +got):\n%s", want.ModulePath, want.Version, diff)
 	}
 
@@ -130,7 +131,8 @@
 	if diff := cmp.Diff(want, gotPkg,
 		cmp.AllowUnexported(source.Info{}),
 		cmpopts.IgnoreFields(internal.Unit{}, "Documentation"),
-		cmpopts.IgnoreFields(licenses.Metadata{}, "Coverage")); diff != "" {
+		cmpopts.IgnoreFields(licenses.Metadata{}, "Coverage"),
+		cmpopts.IgnoreFields(internal.UnitMeta{}, "HasGoMod")); diff != "" {
 		t.Errorf("mismatch on readme (-want +got):\n%s", diff)
 	}
 	if got, want := gotPkg.Documentation, want.Documentation; got == nil || want == nil {