mirror of https://github.com/golang/go.git
161 lines
4.6 KiB
Go
161 lines
4.6 KiB
Go
// Copyright 2022 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.
|
|
|
|
package cache
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"golang.org/x/tools/internal/lsp/source"
|
|
"golang.org/x/tools/internal/span"
|
|
)
|
|
|
|
// A metadataGraph holds information about a transtively closed import graph of
|
|
// Go packages, as obtained from go/packages.
|
|
//
|
|
// Currently a new metadata graph is created for each snapshot.
|
|
// TODO(rfindley): make this type immutable, so that it may be shared across
|
|
// snapshots.
|
|
type metadataGraph struct {
|
|
// metadata maps package IDs to their associated metadata.
|
|
metadata map[PackageID]*KnownMetadata
|
|
|
|
// importedBy maps package IDs to the list of packages that import them.
|
|
importedBy map[PackageID][]PackageID
|
|
|
|
// ids maps file URIs to package IDs. A single file may belong to multiple
|
|
// packages due to tests packages.
|
|
ids map[span.URI][]PackageID
|
|
}
|
|
|
|
// Clone creates a new metadataGraph, applying the given updates to the
|
|
// receiver.
|
|
func (g *metadataGraph) Clone(updates map[PackageID]*KnownMetadata) *metadataGraph {
|
|
if len(updates) == 0 {
|
|
// Optimization: since the graph is immutable, we can return the receiver.
|
|
return g
|
|
}
|
|
result := &metadataGraph{metadata: make(map[PackageID]*KnownMetadata, len(g.metadata))}
|
|
// Copy metadata.
|
|
for id, m := range g.metadata {
|
|
result.metadata[id] = m
|
|
}
|
|
for id, m := range updates {
|
|
if m == nil {
|
|
delete(result.metadata, id)
|
|
} else {
|
|
result.metadata[id] = m
|
|
}
|
|
}
|
|
result.build()
|
|
return result
|
|
}
|
|
|
|
// build constructs g.importedBy and g.uris from g.metadata.
|
|
func (g *metadataGraph) build() {
|
|
// Build the import graph.
|
|
g.importedBy = make(map[PackageID][]PackageID)
|
|
for id, m := range g.metadata {
|
|
for _, importID := range m.Deps {
|
|
g.importedBy[importID] = append(g.importedBy[importID], id)
|
|
}
|
|
}
|
|
|
|
// Collect file associations.
|
|
g.ids = make(map[span.URI][]PackageID)
|
|
for id, m := range g.metadata {
|
|
uris := map[span.URI]struct{}{}
|
|
for _, uri := range m.CompiledGoFiles {
|
|
uris[uri] = struct{}{}
|
|
}
|
|
for _, uri := range m.GoFiles {
|
|
uris[uri] = struct{}{}
|
|
}
|
|
for uri := range uris {
|
|
g.ids[uri] = append(g.ids[uri], id)
|
|
}
|
|
}
|
|
|
|
// Sort and filter file associations.
|
|
//
|
|
// We choose the first non-empty set of package associations out of the
|
|
// following. For simplicity, call a non-command-line-arguments package a
|
|
// "real" package.
|
|
//
|
|
// 1: valid real packages
|
|
// 2: a valid command-line-arguments package
|
|
// 3: invalid real packages
|
|
// 4: an invalid command-line-arguments package
|
|
for uri, ids := range g.ids {
|
|
sort.Slice(ids, func(i, j int) bool {
|
|
// Sort valid packages first.
|
|
validi := g.metadata[ids[i]].Valid
|
|
validj := g.metadata[ids[j]].Valid
|
|
if validi != validj {
|
|
return validi
|
|
}
|
|
|
|
cli := source.IsCommandLineArguments(string(ids[i]))
|
|
clj := source.IsCommandLineArguments(string(ids[j]))
|
|
if cli && !clj {
|
|
return false
|
|
}
|
|
if !cli && clj {
|
|
return true
|
|
}
|
|
return ids[i] < ids[j]
|
|
})
|
|
|
|
// Choose the best IDs for each URI, according to the following rules:
|
|
// - If there are any valid real packages, choose them.
|
|
// - Else, choose the first valid command-line-argument package, if it exists.
|
|
// - Else, keep using all the invalid metadata.
|
|
//
|
|
// TODO(rfindley): it might be better to track all IDs here, and exclude
|
|
// them later in PackagesForFile, but this is the existing behavior.
|
|
hasValidMetadata := false
|
|
for i, id := range ids {
|
|
m := g.metadata[id]
|
|
if m.Valid {
|
|
hasValidMetadata = true
|
|
} else if hasValidMetadata {
|
|
g.ids[uri] = ids[:i]
|
|
break
|
|
}
|
|
// If we've seen *anything* prior to command-line arguments package, take
|
|
// it. Note that ids[0] may itself be command-line-arguments.
|
|
if i > 0 && source.IsCommandLineArguments(string(id)) {
|
|
g.ids[uri] = ids[:i]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// reverseTransitiveClosure calculates the set of packages that transitively
|
|
// reach an id in ids via their Deps. The result also includes given ids.
|
|
//
|
|
// If includeInvalid is false, the algorithm ignores packages with invalid
|
|
// metadata (including those in the given list of ids).
|
|
func (g *metadataGraph) reverseTransitiveClosure(includeInvalid bool, ids ...PackageID) map[PackageID]struct{} {
|
|
seen := make(map[PackageID]struct{})
|
|
var visitAll func([]PackageID)
|
|
visitAll = func(ids []PackageID) {
|
|
for _, id := range ids {
|
|
if _, ok := seen[id]; ok {
|
|
continue
|
|
}
|
|
m := g.metadata[id]
|
|
// Only use invalid metadata if we support it.
|
|
if m == nil || !(m.Valid || includeInvalid) {
|
|
continue
|
|
}
|
|
seen[id] = struct{}{}
|
|
visitAll(g.importedBy[id])
|
|
}
|
|
}
|
|
visitAll(ids)
|
|
return seen
|
|
}
|