1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
|
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The metadata package defines types and functions for working with package
// metadata, which describes Go packages and their relationships.
//
// Package metadata is loaded by gopls using go/packages, and the [Package]
// type is itself a projection and translation of data from
// go/packages.Package.
//
// Packages are assembled into an immutable [Graph]
package metadata
import (
"go/ast"
"go/types"
"sort"
"strconv"
"strings"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/gopls/internal/protocol"
"golang.org/x/tools/internal/packagesinternal"
)
// Declare explicit types for package paths, names, and IDs to ensure that we
// never use an ID where a path belongs, and vice versa. If we confused these,
// it would result in confusing errors because package IDs often look like
// package paths.
type (
PackageID string // go list's unique identifier for a package (e.g. "vendor/example.com/foo [vendor/example.com/bar.test]")
PackagePath string // name used to prefix linker symbols (e.g. "vendor/example.com/foo")
PackageName string // identifier in 'package' declaration (e.g. "foo")
ImportPath string // path that appears in an import declaration (e.g. "example.com/foo")
)
// Package represents package metadata retrieved from go/packages.
// The DepsBy{Imp,Pkg}Path maps do not contain self-import edges.
//
// An ad-hoc package (without go.mod or GOPATH) has its ID, PkgPath,
// and LoadDir equal to the absolute path of its directory.
type Package struct {
ID PackageID
PkgPath PackagePath
Name PackageName
// these three fields are as defined by go/packages.Package
GoFiles []protocol.DocumentURI
CompiledGoFiles []protocol.DocumentURI
IgnoredFiles []protocol.DocumentURI
ForTest PackagePath // q in a "p [q.test]" package, else ""
TypesSizes types.Sizes
Errors []packages.Error // must be set for packages in import cycles
DepsByImpPath map[ImportPath]PackageID // may contain dups; empty ID => missing
DepsByPkgPath map[PackagePath]PackageID // values are unique and non-empty
Module *packages.Module
DepsErrors []*packagesinternal.PackageError
LoadDir string // directory from which go/packages was run
Standalone bool // package synthesized for a standalone file (e.g. ignore-tagged)
}
func (mp *Package) String() string { return string(mp.ID) }
// IsIntermediateTestVariant reports whether the given package is an
// intermediate test variant (ITV), e.g. "net/http [net/url.test]".
//
// An ITV has identical syntax to the regular variant, but different
// import metadata (DepsBy{Imp,Pkg}Path).
//
// Such test variants arise when an x_test package (in this case net/url_test)
// imports a package (in this case net/http) that itself imports the
// non-x_test package (in this case net/url).
//
// This is done so that the forward transitive closure of net/url_test has
// only one package for the "net/url" import.
// The ITV exists to hold the test variant import:
//
// net/url_test [net/url.test]
//
// | "net/http" -> net/http [net/url.test]
// | "net/url" -> net/url [net/url.test]
// | ...
//
// net/http [net/url.test]
//
// | "net/url" -> net/url [net/url.test]
// | ...
//
// This restriction propagates throughout the import graph of net/http: for
// every package imported by net/http that imports net/url, there must be an
// intermediate test variant that instead imports "net/url [net/url.test]".
//
// As one can see from the example of net/url and net/http, intermediate test
// variants can result in many additional packages that are essentially (but
// not quite) identical. For this reason, we filter these variants wherever
// possible.
//
// # Why we mostly ignore intermediate test variants
//
// In projects with complicated tests, there may be a very large
// number of ITVs--asymptotically more than the number of ordinary
// variants. Since they have identical syntax, it is fine in most
// cases to ignore them since the results of analyzing the ordinary
// variant suffice. However, this is not entirely sound.
//
// Consider this package:
//
// // p/p.go -- in all variants of p
// package p
// type T struct { io.Closer }
//
// // p/p_test.go -- in test variant of p
// package p
// func (T) Close() error { ... }
//
// The ordinary variant "p" defines T with a Close method promoted
// from io.Closer. But its test variant "p [p.test]" defines a type T
// with a Close method from p_test.go.
//
// Now consider a package q that imports p, perhaps indirectly. Within
// it, T.Close will resolve to the first Close method:
//
// // q/q.go -- in all variants of q
// package q
// import "p"
// var _ = new(p.T).Close
//
// Let's assume p also contains this file defining an external test (xtest):
//
// // p/p_x_test.go -- external test of p
// package p_test
// import ( "q"; "testing" )
// func Test(t *testing.T) { ... }
//
// Note that q imports p, but p's xtest imports q. Now, in "q
// [p.test]", the intermediate test variant of q built for p's
// external test, T.Close resolves not to the io.Closer.Close
// interface method, but to the concrete method of T.Close
// declared in p_test.go.
//
// If we now request all references to the T.Close declaration in
// p_test.go, the result should include the reference from q's ITV.
// (It's not just methods that can be affected; fields can too, though
// it requires bizarre code to achieve.)
//
// As a matter of policy, gopls mostly ignores this subtlety,
// because to account for it would require that we type-check every
// intermediate test variant of p, of which there could be many.
// Good code doesn't rely on such trickery.
//
// Most callers of MetadataForFile call RemoveIntermediateTestVariants
// to discard them before requesting type checking, or the products of
// type-checking such as the cross-reference index or method set index.
//
// MetadataForFile doesn't do this filtering itself because in some
// cases we need to make a reverse dependency query on the metadata
// graph, and it's important to include the rdeps of ITVs in that
// query. But the filtering of ITVs should be applied after that step,
// before type checking.
//
// In general, we should never type check an ITV.
func (mp *Package) IsIntermediateTestVariant() bool {
return mp.ForTest != "" && mp.ForTest != mp.PkgPath && mp.ForTest+"_test" != mp.PkgPath
}
// A Source maps package IDs to metadata for the packages.
//
// TODO(rfindley): replace this with a concrete metadata graph, once it is
// exposed from the snapshot.
type Source interface {
// Metadata returns the [Package] for the given package ID, or nil if it does
// not exist.
// TODO(rfindley): consider returning (*Metadata, bool)
// TODO(rfindley): consider renaming this method.
Metadata(PackageID) *Package
}
// TODO(rfindley): move the utility functions below to a util.go file.
// IsCommandLineArguments reports whether a given value denotes
// "command-line-arguments" package, which is a package with an unknown ID
// created by the go command. It can have a test variant, which is why callers
// should not check that a value equals "command-line-arguments" directly.
func IsCommandLineArguments(id PackageID) bool {
return strings.Contains(string(id), "command-line-arguments")
}
// SortPostOrder sorts the IDs so that if x depends on y, then y appears before x.
func SortPostOrder(meta Source, ids []PackageID) {
postorder := make(map[PackageID]int)
order := 0
var visit func(PackageID)
visit = func(id PackageID) {
if _, ok := postorder[id]; !ok {
postorder[id] = -1 // break recursion
if mp := meta.Metadata(id); mp != nil {
for _, depID := range mp.DepsByPkgPath {
visit(depID)
}
}
order++
postorder[id] = order
}
}
for _, id := range ids {
visit(id)
}
sort.Slice(ids, func(i, j int) bool {
return postorder[ids[i]] < postorder[ids[j]]
})
}
// UnquoteImportPath returns the unquoted import path of s,
// or "" if the path is not properly quoted.
func UnquoteImportPath(spec *ast.ImportSpec) ImportPath {
path, err := strconv.Unquote(spec.Path.Value)
if err != nil {
return ""
}
return ImportPath(path)
}
// RemoveIntermediateTestVariants removes intermediate test variants, modifying
// the array. We use a pointer to a slice make it impossible to forget to use
// the result.
func RemoveIntermediateTestVariants(pmetas *[]*Package) {
metas := *pmetas
res := metas[:0]
for _, mp := range metas {
if !mp.IsIntermediateTestVariant() {
res = append(res, mp)
}
}
*pmetas = res
}
// IsValidImport returns whether from may import to.
func IsValidImport(from, to PackagePath, goList bool) bool {
// If the metadata came from a build system other than go list
// (e.g. bazel) it is beyond our means to compute visibility.
if !goList {
return true
}
i := strings.LastIndex(string(to), "/internal/")
if i == -1 {
return true
}
// TODO(rfindley): this looks wrong: IsCommandLineArguments is meant to
// operate on package IDs, not package paths.
if IsCommandLineArguments(PackageID(from)) {
return true
}
// TODO(rfindley): this is wrong. mod.testx/p should not be able to
// import mod.test/internal: https://go.dev/play/p/-Ca6P-E4V4q
return strings.HasPrefix(string(from), string(to[:i]))
}
|