internal/frontend: dedup the list of nested modules and packages

A package can also be a nested module. This change makes sure that
no packages can be listed in the list of nested modules for a given page.

Fixes golang/go#42346

Change-Id: Icf36f3f34516e821a407e41b492db34922df8e34
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/276592
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Julie Qiu <julie@golang.org>
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/frontend/unit_main.go b/internal/frontend/unit_main.go
index 536b224..14aa4e4 100644
--- a/internal/frontend/unit_main.go
+++ b/internal/frontend/unit_main.go
@@ -130,11 +130,11 @@
 	if err != nil {
 		return nil, err
 	}
-	nestedModules, err := getNestedModules(ctx, ds, um)
+	subdirectories := getSubdirectories(um, unit.Subdirectories)
 	if err != nil {
 		return nil, err
 	}
-	subdirectories := getSubdirectories(um, unit.Subdirectories)
+	nestedModules, err := getNestedModules(ctx, ds, um, subdirectories)
 	if err != nil {
 		return nil, err
 	}
@@ -267,11 +267,17 @@
 	return readme, nil
 }
 
-func getNestedModules(ctx context.Context, ds internal.DataSource, um *internal.UnitMeta) ([]*NestedModule, error) {
+func getNestedModules(ctx context.Context, ds internal.DataSource, um *internal.UnitMeta, sds []*Subdirectory) ([]*NestedModule, error) {
 	nestedModules, err := ds.GetNestedModules(ctx, um.ModulePath)
 	if err != nil {
 		return nil, err
 	}
+	// Build a map of existing suffixes in subdirectories to filter out nested modules
+	// which have the same suffix.
+	excludedSuffixes := make(map[string]bool)
+	for _, dir := range sds {
+		excludedSuffixes[dir.Suffix] = true
+	}
 	var mods []*NestedModule
 	for _, m := range nestedModules {
 		if m.SeriesPath() == internal.SeriesPathForModule(um.ModulePath) {
@@ -280,9 +286,13 @@
 		if !strings.HasPrefix(m.ModulePath, um.Path+"/") {
 			continue
 		}
+		suffix := internal.Suffix(m.SeriesPath(), um.Path)
+		if excludedSuffixes[suffix] {
+			continue
+		}
 		mods = append(mods, &NestedModule{
 			URL:    constructUnitURL(m.ModulePath, m.ModulePath, internal.LatestVersion),
-			Suffix: internal.Suffix(m.SeriesPath(), um.Path),
+			Suffix: suffix,
 		})
 	}
 	return mods, nil
diff --git a/internal/frontend/unit_main_test.go b/internal/frontend/unit_main_test.go
index 5932e56..c70f750 100644
--- a/internal/frontend/unit_main_test.go
+++ b/internal/frontend/unit_main_test.go
@@ -27,6 +27,7 @@
 		sample.Module("cloud.google.com/go/storage/v11", "v11.0.0", sample.Suffix),
 		sample.Module("cloud.google.com/go/storage/v9", "v9.0.0", sample.Suffix),
 		sample.Module("cloud.google.com/go/storage/module", "v1.10.0", sample.Suffix),
+		sample.Module("cloud.google.com/go/storage/v9/module", "v9.0.0", sample.Suffix),
 		sample.Module("cloud.google.com/go/v2", "v2.0.0", "storage", "spanner", "pubsub"),
 	} {
 		if err := testDB.InsertModule(ctx, m); err != nil {
@@ -35,8 +36,9 @@
 	}
 
 	for _, test := range []struct {
-		modulePath string
-		want       []*NestedModule
+		modulePath     string
+		subdirectories []*Subdirectory
+		want           []*NestedModule
 	}{
 		{
 			modulePath: "cloud.google.com/go",
@@ -57,6 +59,10 @@
 					Suffix: "storage/module",
 					URL:    "/cloud.google.com/go/storage/module",
 				},
+				{
+					Suffix: "storage/v9/module",
+					URL:    "/cloud.google.com/go/storage/v9/module",
+				},
 			},
 		},
 		{
@@ -69,6 +75,32 @@
 					Suffix: "module",
 					URL:    "/cloud.google.com/go/storage/module",
 				},
+				{
+					Suffix: "v9/module",
+					URL:    "/cloud.google.com/go/storage/v9/module",
+				},
+			},
+		},
+		{
+			modulePath: "cloud.google.com/go/storage",
+			subdirectories: []*Subdirectory{
+				{
+					Suffix: "module",
+					URL:    "/cloud.google.com/go/storage/module",
+				},
+				{
+					Suffix: "v9/module",
+					URL:    "/cloud.google.com/go/storage/v9/module",
+				},
+			},
+		},
+		{
+			modulePath: "cloud.google.com/go/storage/v9",
+			subdirectories: []*Subdirectory{
+				{
+					Suffix: "module",
+					URL:    "/cloud.google.com/go/storage/v9/module",
+				},
 			},
 		},
 	} {
@@ -76,7 +108,7 @@
 			got, err := getNestedModules(ctx, testDB, &internal.UnitMeta{
 				Path:       test.modulePath,
 				ModulePath: test.modulePath,
-			})
+			}, test.subdirectories)
 			if err != nil {
 				t.Fatal(err)
 			}