@@ -59,6 +59,63 @@ func init() {
5959 }
6060}
6161
62+ // isExactTestPackage checks if a package ID represents an exact test match.
63+ // Returns true for IDs like "github.com/foo/bar [github.com/foo/bar.test]"
64+ // Returns false for IDs like "github.com/foo/bar [github.com/foo/bar/nested.test]"
65+ func isExactTestPackage (pkg * packages.Package ) bool {
66+ // Test packages have IDs in the format: "pkgpath [pkgpath.test]"
67+ // or for nested test dependencies: "pkgpath [pkgpath/nested.test]"
68+ expectedTestID := pkg .PkgPath + " [" + pkg .PkgPath + ".test]"
69+ return pkg .ID == expectedTestID
70+ }
71+
72+ // isBetterPackage determines if pkg is a better choice than current for extraction.
73+ // Preferences:
74+ // 1. Exact test package (e.g., "pkg [pkg.test]") over nested test dependencies
75+ // 2. More Syntax nodes (more files to extract)
76+ // 3. Longer ID string as tiebreaker
77+ func isBetterPackage (pkg , current * packages.Package ) bool {
78+ pkgIsExact := isExactTestPackage (pkg )
79+ currentIsExact := isExactTestPackage (current )
80+
81+ // Prefer exact test packages
82+ if pkgIsExact != currentIsExact {
83+ return pkgIsExact
84+ }
85+
86+ // Prefer packages with more syntax nodes (more files)
87+ pkgSyntaxCount := len (pkg .Syntax )
88+ currentSyntaxCount := len (current .Syntax )
89+ if pkgSyntaxCount != currentSyntaxCount {
90+ return pkgSyntaxCount > currentSyntaxCount
91+ }
92+
93+ // Fall back to string length
94+ return len (pkg .ID ) > len (current .ID )
95+ }
96+
97+ // selectBestPackages builds a map from package paths to their best package variants.
98+ // In the context of a `go test -c` compilation, we see the same package more than
99+ // once, with IDs like "abc.com/pkgname [abc.com/pkgname.test]" to distinguish the version
100+ // that contains and is used by test code.
101+ // We prefer the version with the most complete test coverage, which is typically:
102+ // 1. The exact test package (e.g., "pkg [pkg.test]") over nested test dependencies
103+ // 2. The package with the most Syntax nodes (most files to extract)
104+ // 3. The longest ID string as a tiebreaker
105+ func selectBestPackages (pkgs []* packages.Package ) map [string ]* packages.Package {
106+ bestPackageIds := make (map [string ]* packages.Package )
107+ packages .Visit (pkgs , nil , func (pkg * packages.Package ) {
108+ if bestSoFar , present := bestPackageIds [pkg .PkgPath ]; present {
109+ if isBetterPackage (pkg , bestSoFar ) {
110+ bestPackageIds [pkg .PkgPath ] = pkg
111+ }
112+ } else {
113+ bestPackageIds [pkg .PkgPath ] = pkg
114+ }
115+ })
116+ return bestPackageIds
117+ }
118+
62119// ExtractWithFlags extracts the packages specified by the given patterns and build flags
63120func ExtractWithFlags (buildFlags []string , patterns []string , extractTests bool , sourceRoot string ) error {
64121 startTime := time .Now ()
@@ -153,22 +210,8 @@ func ExtractWithFlags(buildFlags []string, patterns []string, extractTests bool,
153210
154211 pkgsNotFound := make ([]string , 0 , len (pkgs ))
155212
156- // Build a map from package paths to their longest IDs--
157- // in the context of a `go test -c` compilation, we will see the same package more than
158- // once, with IDs like "abc.com/pkgname [abc.com/pkgname.test]" to distinguish the version
159- // that contains and is used by test code.
160- // For our purposes it is simplest to just ignore the non-test version, since the test
161- // version seems to be a superset of it.
162- longestPackageIds := make (map [string ]string )
163- packages .Visit (pkgs , nil , func (pkg * packages.Package ) {
164- if longestIDSoFar , present := longestPackageIds [pkg .PkgPath ]; present {
165- if len (pkg .ID ) > len (longestIDSoFar ) {
166- longestPackageIds [pkg .PkgPath ] = pkg .ID
167- }
168- } else {
169- longestPackageIds [pkg .PkgPath ] = pkg .ID
170- }
171- })
213+ // Build a map from package paths to their best IDs
214+ bestPackageIds := selectBestPackages (pkgs )
172215
173216 // Do a post-order traversal and extract the package scope of each package
174217 packages .Visit (pkgs , nil , func (pkg * packages.Package ) {
@@ -257,15 +300,15 @@ func ExtractWithFlags(buildFlags []string, patterns []string, extractTests bool,
257300 // extract AST information for all packages
258301 packages .Visit (pkgs , nil , func (pkg * packages.Package ) {
259302
260- // If this is a variant of a package that also occurs with a longer ID, skip it;
303+ // If this is a variant of a package that also occurs with a better ID, skip it;
261304 // otherwise we would extract the same file more than once including extracting the
262305 // body of methods twice, causing database inconsistencies.
263306 //
264- // We prefer the version with the longest ID because that is (so far as I know) always
265- // the version that defines more entities -- the only case I'm aware of being a test
266- // variant of a package, which includes test-only functions in addition to the complete
267- // contents of the main variant.
268- if pkg .ID != longestPackageIds [pkg .PkgPath ] {
307+ // We prefer the version with the most complete test coverage, prioritizing:
308+ // 1. Exact test packages (e.g., "pkg [pkg.test]") over nested test dependencies
309+ // 2. Packages with more Syntax nodes (more files to extract)
310+ // 3. Longer ID strings as a tiebreaker
311+ if pkg .ID != bestPackageIds [pkg .PkgPath ]. ID {
269312 return
270313 }
271314
0 commit comments