Avoid duplicate packages for replaced Go modules (#5069)

* Prevent duplicate Go packages after source replacement resolution

Source analysis already applies module replacements through go/packages. Avoid synthesizing the same replacement again from go.mod while retaining fallback synthesis for modules that source analysis did not resolve.

Constraint: Preserve unimported and local-path replacement cataloging.

Rejected: Deduplicate only during final assembly | That retains redundant license lookup and ambiguous metadata ownership.

Confidence: high

Scope-risk: narrow

Directive: Keep go.mod fallback packages limited to modules absent from source analysis.

Tested: Focused replacement regression, related Go module parser tests, go vet, gofmt, and diff checks.

Not-tested: Docker-backed full cataloger fixtures; local root storage was exhausted by image generation.
Signed-off-by: ychampion <ychampion@users.noreply.github.com>

* Keep replacement fixtures with the Go module test data

Constraint: The maintainer reserves internal/gotestdata for fixtures that need special Go tooling discovery.
Rejected: Leave this fixture in gotestdata | The regression opens its module explicitly and does not need the special location.
Confidence: high
Scope-risk: narrow
Directive: Use internal/gotestdata only when a fixture must avoid Go testdata discovery rules.
Tested: replacement regression repeated 10 times; Go module parser table; go vet for the Go cataloger; gofmt; diff checks.
Not-tested: Full cataloger package; three unrelated parser fixtures fail identically on exact prior head in this environment.
Signed-off-by: ychampion <ychampion@users.noreply.github.com>

---------

Signed-off-by: ychampion <ychampion@users.noreply.github.com>
Co-authored-by: ychampion <ychampion@users.noreply.github.com>
This commit is contained in:
Yashas Gunderia
2026-07-20 12:30:56 -04:00
committed by GitHub
co-authored by ychampion
parent 22ffd5209d
commit 289137ff3c
5 changed files with 81 additions and 2 deletions
+5 -2
View File
@@ -72,7 +72,7 @@ func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resol
// only use go.mod packages NOT found in source analysis
goModPackages := c.createGoModPackages(ctx, resolver, modFile, sourceModules, reader, digests)
c.applyReplaceDirectives(ctx, resolver, modFile, goModPackages, reader, digests)
c.applyReplaceDirectives(ctx, resolver, modFile, sourceModules, goModPackages, reader, digests)
c.applyExcludeDirectives(modFile, goModPackages)
pkgs = c.assembleResults(catalogedModules, goModPackages)
@@ -365,8 +365,11 @@ func (c *goModCataloger) createGoModPackages(ctx context.Context, resolver file.
}
// applyReplaceDirectives processes replace directives from go.mod
func (c *goModCataloger) applyReplaceDirectives(ctx context.Context, resolver file.Resolver, modFile *modfile.File, goModPackages map[string]pkg.Package, reader file.LocationReadCloser, digests map[string]string) {
func (c *goModCataloger) applyReplaceDirectives(ctx context.Context, resolver file.Resolver, modFile *modfile.File, sourceModules map[string]*packages.Module, goModPackages map[string]pkg.Package, reader file.LocationReadCloser, digests map[string]string) {
for _, m := range modFile.Replace {
if sourceModules != nil && sourceModules[m.Old.Path] != nil {
continue
}
lics := c.licenseResolver.getLicenses(ctx, resolver, m.New.Path, m.New.Version)
var finalPath string
if !strings.HasPrefix(m.New.Path, ".") && !strings.HasPrefix(m.New.Path, "/") {