gopls/internal/lsp/cache: use typed strings (PackagePath et al) throughout

This changes uses the string types to enforce better hygiene
of conversions. Fishy conversions were flagged with TODO comments.
Perhaps some of these contribute to bugs in our support for vendoring.

Also, the function formerly known as ImportPath is now UnquoteImportPath.

Change-Id: Ia6bf8749908d881fb0a62cb6c98f7dd00563a69e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/449497
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Alan Donovan <adonovan@google.com>
Run-TryBot: Alan Donovan <adonovan@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Alan Donovan 2022-11-10 13:02:38 -05:00 committed by Gopher Robot
parent 004d1181ca
commit 3c3713e6a5
27 changed files with 193 additions and 191 deletions

View File

@ -24,7 +24,7 @@ import (
"golang.org/x/tools/internal/memoize"
)
func (s *snapshot) Analyze(ctx context.Context, id string, analyzers []*source.Analyzer) ([]*source.Diagnostic, error) {
func (s *snapshot) Analyze(ctx context.Context, id PackageID, analyzers []*source.Analyzer) ([]*source.Diagnostic, error) {
// TODO(adonovan): merge these two loops. There's no need to
// construct all the root action handles before beginning
// analysis. Operations should be concurrent (though that first
@ -35,7 +35,7 @@ func (s *snapshot) Analyze(ctx context.Context, id string, analyzers []*source.A
if !a.IsEnabled(s.view) {
continue
}
ah, err := s.actionHandle(ctx, PackageID(id), a.Analyzer)
ah, err := s.actionHandle(ctx, id, a.Analyzer)
if err != nil {
return nil, err
}
@ -416,7 +416,7 @@ func actionImpl(ctx context.Context, snapshot *snapshot, deps []*actionHandle, a
for _, diag := range rawDiagnostics {
srcDiags, err := analysisDiagnosticDiagnostics(snapshot, pkg, analyzer, &diag)
if err != nil {
event.Error(ctx, "unable to compute analysis error position", err, tag.Category.Of(diag.Category), tag.Package.Of(pkg.ID()))
event.Error(ctx, "unable to compute analysis error position", err, tag.Category.Of(diag.Category), tag.Package.Of(string(pkg.ID())))
continue
}
diagnostics = append(diagnostics, srcDiags...)
@ -477,7 +477,7 @@ func (s *snapshot) DiagnosePackage(ctx context.Context, spkg source.Package) (ma
errorAnalyzerDiag, err = s.Analyze(ctx, pkg.ID(), analyzers)
if err != nil {
// Keep going: analysis failures should not block diagnostics.
event.Error(ctx, "type error analysis failed", err, tag.Package.Of(pkg.ID()))
event.Error(ctx, "type error analysis failed", err, tag.Package.Of(string(pkg.ID())))
}
}
diags := map[span.URI][]*source.Diagnostic{}

View File

@ -378,7 +378,7 @@ func typeCheckImpl(ctx context.Context, snapshot *snapshot, goFiles, compiledGoF
for _, e := range m.Errors {
diags, err := goPackagesErrorDiagnostics(snapshot, pkg, e)
if err != nil {
event.Error(ctx, "unable to compute positions for list errors", err, tag.Package.Of(pkg.ID()))
event.Error(ctx, "unable to compute positions for list errors", err, tag.Package.Of(string(pkg.ID())))
continue
}
pkg.diagnostics = append(pkg.diagnostics, diags...)
@ -400,7 +400,7 @@ func typeCheckImpl(ctx context.Context, snapshot *snapshot, goFiles, compiledGoF
for _, e := range pkg.parseErrors {
diags, err := parseErrorDiagnostics(snapshot, pkg, e)
if err != nil {
event.Error(ctx, "unable to compute positions for parse errors", err, tag.Package.Of(pkg.ID()))
event.Error(ctx, "unable to compute positions for parse errors", err, tag.Package.Of(string(pkg.ID())))
continue
}
for _, diag := range diags {
@ -418,7 +418,7 @@ func typeCheckImpl(ctx context.Context, snapshot *snapshot, goFiles, compiledGoF
for _, e := range expandErrors(unexpanded, snapshot.View().Options().RelatedInformationSupported) {
diags, err := typeErrorDiagnostics(snapshot, pkg, e)
if err != nil {
event.Error(ctx, "unable to compute positions for type errors", err, tag.Package.Of(pkg.ID()))
event.Error(ctx, "unable to compute positions for type errors", err, tag.Package.Of(string(pkg.ID())))
continue
}
pkg.typeErrors = append(pkg.typeErrors, e.primary)
@ -535,7 +535,7 @@ func doTypeCheck(ctx context.Context, snapshot *snapshot, goFiles, compiledGoFil
if !ok {
return nil, snapshot.missingPkgError(path)
}
if !source.IsValidImport(string(m.PkgPath), string(dep.m.PkgPath)) {
if !source.IsValidImport(m.PkgPath, dep.m.PkgPath) {
return nil, fmt.Errorf("invalid use of internal package %s", path)
}
depPkg, err := dep.await(ctx, snapshot)

View File

@ -93,8 +93,8 @@ func (g *metadataGraph) build() {
}
// 2. command-line-args packages appear later.
cli := source.IsCommandLineArguments(string(ids[i]))
clj := source.IsCommandLineArguments(string(ids[j]))
cli := source.IsCommandLineArguments(ids[i])
clj := source.IsCommandLineArguments(ids[j])
if cli != clj {
return clj
}
@ -121,7 +121,7 @@ func (g *metadataGraph) build() {
}
// 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)) {
if i > 0 && source.IsCommandLineArguments(id) {
g.ids[uri] = ids[:i]
break
}

View File

@ -50,7 +50,9 @@ func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadSc
for _, scope := range scopes {
switch scope := scope.(type) {
case packageLoadScope:
if source.IsCommandLineArguments(string(scope)) {
// TODO(adonovan): is this cast sound?? A
// packageLoadScope is really a PackagePath I think.
if source.IsCommandLineArguments(PackageID(scope)) {
panic("attempted to load command-line-arguments")
}
// The only time we pass package paths is when we're doing a
@ -480,10 +482,10 @@ func buildMetadata(ctx context.Context, pkg *packages.Package, cfg *packages.Con
// Allow for multiple ad-hoc packages in the workspace (see #47584).
pkgPath := PackagePath(pkg.PkgPath)
id := PackageID(pkg.ID)
if source.IsCommandLineArguments(pkg.ID) {
if source.IsCommandLineArguments(id) {
suffix := ":" + strings.Join(query, ",")
id = PackageID(string(id) + suffix)
pkgPath = PackagePath(string(pkgPath) + suffix)
id = PackageID(pkg.ID + suffix)
pkgPath = PackagePath(pkg.PkgPath + suffix)
}
if _, ok := updates[id]; ok {
@ -712,7 +714,7 @@ func computeWorkspacePackagesLocked(s *snapshot, meta *metadataGraph) map[Packag
continue
}
if source.IsCommandLineArguments(string(m.ID)) {
if source.IsCommandLineArguments(m.ID) {
// If all the files contained in m have a real package, we don't need to
// keep m as a workspace package.
if allFilesHaveRealPackages(meta, m) {
@ -754,7 +756,7 @@ func allFilesHaveRealPackages(g *metadataGraph, m *KnownMetadata) bool {
checkURIs:
for _, uri := range append(m.CompiledGoFiles[0:n:n], m.GoFiles...) {
for _, id := range g.ids[uri] {
if !source.IsCommandLineArguments(string(id)) && (g.metadata[id].Valid || !m.Valid) {
if !source.IsCommandLineArguments(id) && (g.metadata[id].Valid || !m.Valid) {
continue checkURIs
}
}

View File

@ -8,19 +8,16 @@ import (
"go/types"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/gopls/internal/lsp/source"
"golang.org/x/tools/gopls/internal/span"
"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")
PackageID = source.PackageID
PackagePath = source.PackagePath
PackageName = source.PackageName
ImportPath = source.ImportPath
)
// Metadata holds package Metadata extracted from a call to packages.Load.
@ -43,19 +40,13 @@ type Metadata struct {
}
// PackageID implements the source.Metadata interface.
func (m *Metadata) PackageID() string {
return string(m.ID)
}
func (m *Metadata) PackageID() PackageID { return m.ID }
// Name implements the source.Metadata interface.
func (m *Metadata) PackageName() string {
return string(m.Name)
}
func (m *Metadata) PackageName() PackageName { return m.Name }
// PkgPath implements the source.Metadata interface.
func (m *Metadata) PackagePath() string {
return string(m.PkgPath)
}
func (m *Metadata) PackagePath() PackagePath { return m.PkgPath }
// IsIntermediateTestVariant reports whether the given package is an
// intermediate test variant, e.g. "net/http [net/url.test]".

View File

@ -36,7 +36,7 @@ type pkg struct {
analyses memoize.Store // maps analyzer.Name to Promise[actionResult]
}
func (p *pkg) String() string { return p.ID() }
func (p *pkg) String() string { return string(p.ID()) }
// A loadScope defines a package loading scope for use with go/packages.
type loadScope interface {
@ -56,17 +56,9 @@ func (packageLoadScope) aScope() {}
func (moduleLoadScope) aScope() {}
func (viewLoadScope) aScope() {}
func (p *pkg) ID() string {
return string(p.m.ID)
}
func (p *pkg) Name() string {
return string(p.m.Name)
}
func (p *pkg) PkgPath() string {
return string(p.m.PkgPath)
}
func (p *pkg) ID() PackageID { return p.m.ID }
func (p *pkg) Name() PackageName { return p.m.Name }
func (p *pkg) PkgPath() PackagePath { return p.m.PkgPath }
func (p *pkg) ParseMode() source.ParseMode {
return p.mode
@ -118,8 +110,8 @@ func (p *pkg) ForTest() string {
// given its PackagePath. (If you have an ImportPath, e.g. a string
// from an import declaration, use ResolveImportPath instead.
// They may differ in case of vendoring.)
func (p *pkg) DirectDep(pkgPath string) (source.Package, error) {
if id, ok := p.m.DepsByPkgPath[PackagePath(pkgPath)]; ok {
func (p *pkg) DirectDep(pkgPath PackagePath) (source.Package, error) {
if id, ok := p.m.DepsByPkgPath[pkgPath]; ok {
if imp := p.deps[id]; imp != nil {
return imp, nil
}
@ -129,8 +121,8 @@ func (p *pkg) DirectDep(pkgPath string) (source.Package, error) {
// ResolveImportPath returns the directly imported dependency of this package,
// given its ImportPath. See also DirectDep.
func (p *pkg) ResolveImportPath(importPath string) (source.Package, error) {
if id, ok := p.m.DepsByImpPath[ImportPath(importPath)]; ok && id != "" {
func (p *pkg) ResolveImportPath(importPath ImportPath) (source.Package, error) {
if id, ok := p.m.DepsByImpPath[importPath]; ok && id != "" {
if imp := p.deps[id]; imp != nil {
return imp, nil
}
@ -138,7 +130,7 @@ func (p *pkg) ResolveImportPath(importPath string) (source.Package, error) {
return nil, fmt.Errorf("package does not import %s", importPath)
}
func (p *pkg) MissingDependencies() []string {
func (p *pkg) MissingDependencies() []ImportPath {
// We don't invalidate metadata for import deletions,
// so check the package imports via the *types.Package.
//
@ -162,13 +154,14 @@ func (p *pkg) MissingDependencies() []string {
// should be fast) then we can simply return the blank entries
// in DepsByImpPath. (They are PackageIDs not PackagePaths,
// but the caller only cares whether the set is empty!)
var missing []string
var missing []ImportPath
for _, pkg := range p.types.Imports() {
if id, ok := p.m.DepsByImpPath[ImportPath(pkg.Path())]; ok && id == "" {
missing = append(missing, pkg.Path())
importPath := ImportPath(pkg.Path())
if id, ok := p.m.DepsByImpPath[importPath]; ok && id == "" {
missing = append(missing, importPath)
}
}
sort.Strings(missing)
sort.Slice(missing, func(i, j int) bool { return missing[i] < missing[j] })
return missing
}

View File

@ -811,17 +811,17 @@ func (s *snapshot) useInvalidMetadata() bool {
return s.view.goversion >= 13 && s.view.Options().ExperimentalUseInvalidMetadata
}
func (s *snapshot) GetReverseDependencies(ctx context.Context, id string) ([]source.Package, error) {
func (s *snapshot) GetReverseDependencies(ctx context.Context, id PackageID) ([]source.Package, error) {
if err := s.awaitLoaded(ctx); err != nil {
return nil, err
}
s.mu.Lock()
meta := s.meta
s.mu.Unlock()
ids := meta.reverseTransitiveClosure(s.useInvalidMetadata(), PackageID(id))
ids := meta.reverseTransitiveClosure(s.useInvalidMetadata(), id)
// Make sure to delete the original package ID from the map.
delete(ids, PackageID(id))
delete(ids, id)
var pkgs []source.Package
for id := range ids {
@ -1212,12 +1212,11 @@ func (s *snapshot) AllValidMetadata(ctx context.Context) ([]source.Metadata, err
return meta, nil
}
func (s *snapshot) WorkspacePackageByID(ctx context.Context, id string) (source.Package, error) {
packageID := PackageID(id)
return s.checkedPackage(ctx, packageID, s.workspaceParseMode(packageID))
func (s *snapshot) WorkspacePackageByID(ctx context.Context, id PackageID) (source.Package, error) {
return s.checkedPackage(ctx, id, s.workspaceParseMode(id))
}
func (s *snapshot) CachedImportPaths(ctx context.Context) (map[string]source.Package, error) {
func (s *snapshot) CachedImportPaths(ctx context.Context) (map[PackagePath]source.Package, error) {
// Don't reload workspace package metadata.
// This function is meant to only return currently cached information.
s.AwaitInitialized(ctx)
@ -1225,7 +1224,7 @@ func (s *snapshot) CachedImportPaths(ctx context.Context) (map[string]source.Pac
s.mu.Lock()
defer s.mu.Unlock()
results := map[string]source.Package{}
results := map[PackagePath]source.Package{}
s.packages.Range(func(_, v interface{}) {
cachedPkg, err := v.(*packageHandle).cached()
if err != nil {
@ -1983,7 +1982,7 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
// For metadata that has been newly invalidated, capture package paths
// requiring reloading in the shouldLoad map.
if invalidateMetadata && !source.IsCommandLineArguments(string(v.ID)) {
if invalidateMetadata && !source.IsCommandLineArguments(v.ID) {
if result.shouldLoad == nil {
result.shouldLoad = make(map[PackageID][]PackagePath)
}

View File

@ -735,8 +735,10 @@ func (c *commandHandler) ListKnownPackages(ctx context.Context, args command.URI
progress: "Listing packages",
forURI: args.URI,
}, func(ctx context.Context, deps commandDeps) error {
var err error
result.Packages, err = source.KnownPackages(ctx, deps.snapshot, deps.fh)
pkgs, err := source.KnownPackages(ctx, deps.snapshot, deps.fh)
for _, pkg := range pkgs {
result.Packages = append(result.Packages, string(pkg))
}
return err
})
return result, err
@ -765,14 +767,14 @@ func (c *commandHandler) ListImports(ctx context.Context, args command.URIArg) (
name = imp.Name.Name
}
result.Imports = append(result.Imports, command.FileImport{
Path: source.ImportPath(imp),
Path: string(source.UnquoteImportPath(imp)),
Name: name,
})
}
}
for _, imp := range pkg.Imports() {
result.PackageImports = append(result.PackageImports, command.PackageImport{
Path: imp.PkgPath(), // This might be the vendored path under GOPATH vendoring, in which case it's a bug.
Path: string(imp.PkgPath()), // This might be the vendored path under GOPATH vendoring, in which case it's a bug.
})
}
sort.Slice(result.PackageImports, func(i, j int) bool {

View File

@ -346,7 +346,7 @@ func (s *Server) diagnose(ctx context.Context, snapshot source.Snapshot, forceAn
}
func (s *Server) diagnosePkg(ctx context.Context, snapshot source.Snapshot, pkg source.Package, alwaysAnalyze bool) {
ctx, done := event.Start(ctx, "Server.diagnosePkg", tag.Snapshot.Of(snapshot.ID()), tag.Package.Of(pkg.ID()))
ctx, done := event.Start(ctx, "Server.diagnosePkg", tag.Snapshot.Of(snapshot.ID()), tag.Package.Of(string(pkg.ID())))
defer done()
enableDiagnostics := false
includeAnalysis := alwaysAnalyze // only run analyses for packages with open files
@ -361,7 +361,7 @@ func (s *Server) diagnosePkg(ctx context.Context, snapshot source.Snapshot, pkg
pkgDiagnostics, err := snapshot.DiagnosePackage(ctx, pkg)
if err != nil {
event.Error(ctx, "warning: diagnosing package", err, tag.Snapshot.Of(snapshot.ID()), tag.Package.Of(pkg.ID()))
event.Error(ctx, "warning: diagnosing package", err, tag.Snapshot.Of(snapshot.ID()), tag.Package.Of(string(pkg.ID())))
return
}
for _, cgf := range pkg.CompiledGoFiles() {
@ -374,7 +374,7 @@ func (s *Server) diagnosePkg(ctx context.Context, snapshot source.Snapshot, pkg
if includeAnalysis && !pkg.HasListOrParseErrors() {
reports, err := source.Analyze(ctx, snapshot, pkg, false)
if err != nil {
event.Error(ctx, "warning: analyzing package", err, tag.Snapshot.Of(snapshot.ID()), tag.Package.Of(pkg.ID()))
event.Error(ctx, "warning: analyzing package", err, tag.Snapshot.Of(snapshot.ID()), tag.Package.Of(string(pkg.ID())))
return
}
for _, cgf := range pkg.CompiledGoFiles() {
@ -390,7 +390,7 @@ func (s *Server) diagnosePkg(ctx context.Context, snapshot source.Snapshot, pkg
if enableGCDetails {
gcReports, err := source.GCOptimizationDetails(ctx, snapshot, pkg)
if err != nil {
event.Error(ctx, "warning: gc details", err, tag.Snapshot.Of(snapshot.ID()), tag.Package.Of(pkg.ID()))
event.Error(ctx, "warning: gc details", err, tag.Snapshot.Of(snapshot.ID()), tag.Package.Of(string(pkg.ID())))
}
s.gcOptimizationDetailsMu.Lock()
_, enableGCDetails := s.gcOptimizationDetails[pkg.ID()]

View File

@ -12,7 +12,6 @@ import (
"go/token"
"net/url"
"regexp"
"strconv"
"strings"
"sync"
@ -133,21 +132,21 @@ func goLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle
// https://pkg.go.dev.
if view.Options().ImportShortcut.ShowLinks() {
for _, imp := range imports {
target, err := strconv.Unquote(imp.Path.Value)
if err != nil {
target := source.UnquoteImportPath(imp)
if target == "" {
continue
}
// See golang/go#36998: don't link to modules matching GOPRIVATE.
if view.IsGoPrivatePath(target) {
if view.IsGoPrivatePath(string(target)) {
continue
}
if mod, version, ok := moduleAtVersion(target, pkg); ok && strings.ToLower(view.Options().LinkTarget) == "pkg.go.dev" {
target = strings.Replace(target, mod, mod+"@"+version, 1)
target = source.ImportPath(strings.Replace(string(target), mod, mod+"@"+version, 1))
}
// Account for the quotation marks in the positions.
start := imp.Path.Pos() + 1
end := imp.Path.End() - 1
targetURL := source.BuildLink(view.Options().LinkTarget, target, "")
targetURL := source.BuildLink(view.Options().LinkTarget, string(target), "")
l, err := toProtocolLink(pgf.Tok, pgf.Mapper, targetURL, start, end)
if err != nil {
return nil, err
@ -174,7 +173,7 @@ func goLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle
return links, nil
}
func moduleAtVersion(targetImportPath string, pkg source.Package) (string, string, bool) {
func moduleAtVersion(targetImportPath source.ImportPath, pkg source.Package) (string, string, bool) {
impPkg, err := pkg.ResolveImportPath(targetImportPath)
if err != nil {
return "", "", false

View File

@ -15,7 +15,6 @@ import (
"log"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
@ -905,8 +904,8 @@ func (e *encoded) importSpec(d *ast.ImportSpec) {
}
return // don't mark anything for . or _
}
importPath, err := strconv.Unquote(d.Path.Value)
if err != nil {
importPath := source.UnquoteImportPath(d)
if importPath == "" {
return
}
// Import strings are implementation defined. Try to match with parse information.
@ -916,7 +915,7 @@ func (e *encoded) importSpec(d *ast.ImportSpec) {
return
}
// Check whether the original literal contains the package's declared name.
j := strings.LastIndex(d.Path.Value, imported.Name())
j := strings.LastIndex(d.Path.Value, string(imported.Name()))
if j == -1 {
// name doesn't show up, for whatever reason, so nothing to report
return

View File

@ -27,7 +27,7 @@ func NewServer(session *cache.Session, client protocol.ClientCloser) *Server {
session.SetProgressTracker(tracker)
return &Server{
diagnostics: map[span.URI]*fileReports{},
gcOptimizationDetails: make(map[string]struct{}),
gcOptimizationDetails: make(map[source.PackageID]struct{}),
watchedGlobPatterns: make(map[string]struct{}),
changedFiles: make(map[span.URI]struct{}),
session: session,
@ -97,7 +97,7 @@ type Server struct {
// optimization details to be included in the diagnostics. The key is the
// ID of the package.
gcOptimizationDetailsMu sync.Mutex
gcOptimizationDetails map[string]struct{}
gcOptimizationDetails map[source.PackageID]struct{}
// diagnosticsSema limits the concurrency of diagnostics runs, which can be
// expensive.

View File

@ -1089,7 +1089,7 @@ func (c *completer) selector(ctx context.Context, sel *ast.SelectorExpr) error {
if pkgName, ok := c.pkg.GetTypesInfo().Uses[id].(*types.PkgName); ok {
var pkg source.Package
for _, imp := range c.pkg.Imports() {
if imp.PkgPath() == pkgName.Imported().Path() {
if imp.PkgPath() == source.PackagePath(pkgName.Imported().Path()) {
pkg = imp
}
}
@ -1140,7 +1140,7 @@ func (c *completer) unimportedMembers(ctx context.Context, id *ast.Ident) error
if pkg.GetTypes().Name() != id.Name {
continue
}
paths = append(paths, path)
paths = append(paths, string(path))
}
var relevances map[string]float64
@ -1158,7 +1158,7 @@ func (c *completer) unimportedMembers(ctx context.Context, id *ast.Ident) error
})
for _, path := range paths {
pkg := known[path]
pkg := known[source.PackagePath(path)]
if pkg.GetTypes().Name() != id.Name {
continue
}
@ -1182,7 +1182,8 @@ func (c *completer) unimportedMembers(ctx context.Context, id *ast.Ident) error
add := func(pkgExport imports.PackageExport) {
mu.Lock()
defer mu.Unlock()
if _, ok := known[pkgExport.Fix.StmtInfo.ImportPath]; ok {
// TODO(adonovan): what if the actual package has a vendor/ prefix?
if _, ok := known[source.PackagePath(pkgExport.Fix.StmtInfo.ImportPath)]; ok {
return // We got this one above.
}
@ -1379,7 +1380,8 @@ func (c *completer) lexical(ctx context.Context) error {
// Make sure the package name isn't already in use by another
// object, and that this file doesn't import the package yet.
if _, ok := seen[pkg.Name()]; !ok && pkg != c.pkg.GetTypes() && !alreadyImports(c.file, pkg.Path()) {
// TODO(adonovan): what if pkg.Path has vendor/ prefix?
if _, ok := seen[pkg.Name()]; !ok && pkg != c.pkg.GetTypes() && !alreadyImports(c.file, source.ImportPath(pkg.Path())) {
seen[pkg.Name()] = struct{}{}
obj := types.NewPkgName(0, nil, pkg.Name(), pkg)
imp := &importInfo{
@ -1481,12 +1483,12 @@ func (c *completer) unimportedPackages(ctx context.Context, seen map[string]stru
if err != nil {
return err
}
var paths []string
var paths []string // actually PackagePaths
for path, pkg := range known {
if !strings.HasPrefix(pkg.GetTypes().Name(), prefix) {
continue
}
paths = append(paths, path)
paths = append(paths, string(path))
}
var relevances map[string]float64
@ -1511,7 +1513,7 @@ func (c *completer) unimportedPackages(ctx context.Context, seen map[string]stru
})
for _, path := range paths {
pkg := known[path]
pkg := known[source.PackagePath(path)]
if _, ok := seen[pkg.GetTypes().Name()]; ok {
continue
}
@ -1526,7 +1528,7 @@ func (c *completer) unimportedPackages(ctx context.Context, seen map[string]stru
}
c.deepState.enqueue(candidate{
// Pass an empty *types.Package to disable deep completions.
obj: types.NewPkgName(0, nil, pkg.GetTypes().Name(), types.NewPackage(path, pkg.Name())),
obj: types.NewPkgName(0, nil, pkg.GetTypes().Name(), types.NewPackage(path, string(pkg.Name()))),
score: unimportedScore(relevances[path]),
imp: imp,
})
@ -1573,9 +1575,9 @@ func (c *completer) unimportedPackages(ctx context.Context, seen map[string]stru
}
// alreadyImports reports whether f has an import with the specified path.
func alreadyImports(f *ast.File, path string) bool {
func alreadyImports(f *ast.File, path source.ImportPath) bool {
for _, s := range f.Imports {
if source.ImportPath(s) == path {
if source.UnquoteImportPath(s) == path {
return true
}
}

View File

@ -240,7 +240,7 @@ func packageSuggestions(ctx context.Context, snapshot source.Snapshot, fileURI s
}
pkgName := convertDirNameToPkgName(dirName)
seenPkgs := make(map[string]struct{})
seenPkgs := make(map[source.PackageName]struct{})
// The `go` command by default only allows one package per directory but we
// support multiple package suggestions since gopls is build system agnostic.
@ -267,30 +267,30 @@ func packageSuggestions(ctx context.Context, snapshot source.Snapshot, fileURI s
// Add a found package used in current directory as a high relevance
// suggestion and the test package for it as a medium relevance
// suggestion.
if score := float64(matcher.Score(pkg.Name())); score > 0 {
packages = append(packages, toCandidate(pkg.Name(), score*highScore))
if score := float64(matcher.Score(string(pkg.Name()))); score > 0 {
packages = append(packages, toCandidate(string(pkg.Name()), score*highScore))
}
seenPkgs[pkg.Name()] = struct{}{}
testPkgName := pkg.Name() + "_test"
if _, ok := seenPkgs[testPkgName]; ok || strings.HasSuffix(pkg.Name(), "_test") {
if _, ok := seenPkgs[testPkgName]; ok || strings.HasSuffix(string(pkg.Name()), "_test") {
continue
}
if score := float64(matcher.Score(testPkgName)); score > 0 {
packages = append(packages, toCandidate(testPkgName, score*stdScore))
if score := float64(matcher.Score(string(testPkgName))); score > 0 {
packages = append(packages, toCandidate(string(testPkgName), score*stdScore))
}
seenPkgs[testPkgName] = struct{}{}
}
// Add current directory name as a low relevance suggestion.
if _, ok := seenPkgs[pkgName]; !ok {
if score := float64(matcher.Score(pkgName)); score > 0 {
packages = append(packages, toCandidate(pkgName, score*lowScore))
if score := float64(matcher.Score(string(pkgName))); score > 0 {
packages = append(packages, toCandidate(string(pkgName), score*lowScore))
}
testPkgName := pkgName + "_test"
if score := float64(matcher.Score(testPkgName)); score > 0 {
packages = append(packages, toCandidate(testPkgName, score*lowScore))
if score := float64(matcher.Score(string(testPkgName))); score > 0 {
packages = append(packages, toCandidate(string(testPkgName), score*lowScore))
}
}
@ -330,7 +330,7 @@ func isValidDirName(dirName string) bool {
// convertDirNameToPkgName converts a valid directory name to a valid package name.
// It leaves only letters and digits. All letters are mapped to lower case.
func convertDirNameToPkgName(dirName string) string {
func convertDirNameToPkgName(dirName string) source.PackageName {
var buf bytes.Buffer
for _, ch := range dirName {
switch {
@ -341,7 +341,7 @@ func convertDirNameToPkgName(dirName string) string {
buf.WriteRune(ch)
}
}
return buf.String()
return source.PackageName(buf.String())
}
// isLetter and isDigit allow only ASCII characters because

View File

@ -4,7 +4,11 @@
package completion
import "testing"
import (
"testing"
"golang.org/x/tools/gopls/internal/lsp/source"
)
func TestIsValidDirName(t *testing.T) {
tests := []struct {
@ -51,7 +55,7 @@ func TestIsValidDirName(t *testing.T) {
func TestConvertDirNameToPkgName(t *testing.T) {
tests := []struct {
dirName string
pkgName string
pkgName source.PackageName
}{
{dirName: "a", pkgName: "a"},
{dirName: "abcdef", pkgName: "abcdef"},

View File

@ -16,11 +16,11 @@ import (
"sync"
"text/template"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/imports"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/lsp/snippet"
"golang.org/x/tools/gopls/internal/lsp/source"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/imports"
)
// Postfix snippets are artificial methods that allow the user to
@ -442,7 +442,8 @@ func (c *completer) importIfNeeded(pkgPath string, scope *types.Scope) (string,
// Check if file already imports pkgPath.
for _, s := range c.file.Imports {
if source.ImportPath(s) == pkgPath {
// TODO(adonovan): what if pkgPath has a vendor/ prefix?
if source.UnquoteImportPath(s) == source.ImportPath(pkgPath) {
if s.Name == nil {
return defaultName, nil, nil
}

View File

@ -448,7 +448,7 @@ func moduleAtVersion(path string, i *IdentifierInfo) (string, string, bool) {
if strings.ToLower(i.Snapshot.View().Options().LinkTarget) != "pkg.go.dev" {
return "", "", false
}
impPkg, err := i.pkg.DirectDep(path)
impPkg, err := i.pkg.DirectDep(PackagePath(path))
if err != nil {
return "", "", false
}
@ -539,7 +539,7 @@ func FindHoverContext(ctx context.Context, s Snapshot, pkg Package, obj types.Ob
if err != nil {
return nil, err
}
imp, err := pkg.ResolveImportPath(importPath)
imp, err := pkg.ResolveImportPath(ImportPath(importPath))
if err != nil {
return nil, err
}

View File

@ -456,7 +456,7 @@ func importSpec(snapshot Snapshot, pkg Package, file *ast.File, pos token.Pos) (
if err != nil {
return nil, fmt.Errorf("import path not quoted: %s (%v)", imp.Path.Value, err)
}
imported, err := pkg.ResolveImportPath(importPath)
imported, err := pkg.ResolveImportPath(ImportPath(importPath))
if err != nil {
return nil, err
}

View File

@ -323,7 +323,7 @@ func qualifiedObjsAtLocation(ctx context.Context, s Snapshot, key positionKey, s
// Look up the implicit *types.PkgName.
obj := searchpkg.GetTypesInfo().Implicits[leaf]
if obj == nil {
return nil, fmt.Errorf("%w for import %q", errNoObjectFound, ImportPath(leaf))
return nil, fmt.Errorf("%w for import %s", errNoObjectFound, UnquoteImportPath(leaf))
}
objs = append(objs, obj)
}

View File

@ -19,14 +19,15 @@ import (
// KnownPackages returns a list of all known packages
// in the package graph that could potentially be imported
// by the given file.
func KnownPackages(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle) ([]string, error) {
func KnownPackages(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle) ([]PackagePath, error) {
pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
if err != nil {
return nil, fmt.Errorf("GetParsedFile: %w", err)
}
alreadyImported := map[string]struct{}{}
alreadyImported := map[PackagePath]struct{}{}
for _, imp := range pgf.File.Imports {
alreadyImported[imp.Path.Value] = struct{}{}
// TODO(adonovan): the correct PackagePath might need a "vendor/" prefix.
alreadyImported[PackagePath(imp.Path.Value)] = struct{}{}
}
// TODO(adonovan): this whole algorithm could be more
// simply expressed in terms of Metadata, not Packages.
@ -35,8 +36,8 @@ func KnownPackages(ctx context.Context, snapshot Snapshot, fh VersionedFileHandl
return nil, err
}
var (
seen = make(map[string]struct{})
paths []string
seen = make(map[PackagePath]struct{})
paths []PackagePath
)
for path, knownPkg := range pkgs {
gofiles := knownPkg.CompiledGoFiles()
@ -79,10 +80,12 @@ func KnownPackages(ctx context.Context, snapshot Snapshot, fh VersionedFileHandl
return imports.GetAllCandidates(ctx, func(ifix imports.ImportFix) {
mu.Lock()
defer mu.Unlock()
if _, ok := seen[ifix.StmtInfo.ImportPath]; ok {
// TODO(adonovan): what if the actual package path has a vendor/ prefix?
path := PackagePath(ifix.StmtInfo.ImportPath)
if _, ok := seen[path]; ok {
return
}
paths = append(paths, ifix.StmtInfo.ImportPath)
paths = append(paths, path)
}, "", pgf.URI.Filename(), pkg.GetTypes().Name(), o.Env)
})
if err != nil {
@ -92,8 +95,8 @@ func KnownPackages(ctx context.Context, snapshot Snapshot, fh VersionedFileHandl
}
sort.Slice(paths, func(i, j int) bool {
importI, importJ := paths[i], paths[j]
iHasDot := strings.Contains(importI, ".")
jHasDot := strings.Contains(importJ, ".")
iHasDot := strings.Contains(string(importI), ".")
jHasDot := strings.Contains(string(importJ), ".")
if iHasDot && !jHasDot {
return false
}

View File

@ -78,7 +78,7 @@ func References(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Posit
for _, dep := range rdeps {
for _, f := range dep.CompiledGoFiles() {
for _, imp := range f.File.Imports {
if path, err := strconv.Unquote(imp.Path.Value); err == nil && path == renamingPkg.PkgPath() {
if path, err := strconv.Unquote(imp.Path.Value); err == nil && path == string(renamingPkg.PkgPath()) {
refs = append(refs, &ReferenceInfo{
Name: packageName,
MappedRange: NewMappedRange(f.Tok, f.Mapper, imp.Pos(), imp.End()),

View File

@ -93,7 +93,7 @@ func PrepareRename(ctx context.Context, snapshot Snapshot, f FileHandle, pp prot
return nil, err, err
}
if strings.HasSuffix(meta.PackageName(), "_test") {
if strings.HasSuffix(string(meta.PackageName()), "_test") {
err := errors.New("can't rename x_test packages")
return nil, err, err
}
@ -103,7 +103,7 @@ func PrepareRename(ctx context.Context, snapshot Snapshot, f FileHandle, pp prot
return nil, err, err
}
if meta.ModuleInfo().Path == meta.PackagePath() {
if meta.ModuleInfo().Path == string(meta.PackagePath()) {
err := fmt.Errorf("can't rename package: package path %q is the same as module path %q", meta.PackagePath(), meta.ModuleInfo().Path)
return nil, err, err
}
@ -113,7 +113,7 @@ func PrepareRename(ctx context.Context, snapshot Snapshot, f FileHandle, pp prot
err = fmt.Errorf("error building package to rename: %v", err)
return nil, err, err
}
result, err := computePrepareRenameResp(snapshot, pkg, pgf.File.Name, pkg.Name())
result, err := computePrepareRenameResp(snapshot, pkg, pgf.File.Name, string(pkg.Name()))
if err != nil {
return nil, nil, err
}
@ -202,11 +202,11 @@ func Rename(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position,
// Fix this.
meta := fileMeta[0]
oldPath := meta.PackagePath()
var modulePath string
var modulePath PackagePath
if mi := meta.ModuleInfo(); mi == nil {
return nil, true, fmt.Errorf("cannot rename package: missing module information for package %q", meta.PackagePath())
} else {
modulePath = mi.Path
modulePath = PackagePath(mi.Path)
}
if strings.HasSuffix(newName, "_test") {
@ -218,7 +218,7 @@ func Rename(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position,
return nil, true, err
}
renamingEdits, err := renamePackage(ctx, s, modulePath, oldPath, newName, metadata)
renamingEdits, err := renamePackage(ctx, s, modulePath, oldPath, PackageName(newName), metadata)
if err != nil {
return nil, true, err
}
@ -245,12 +245,12 @@ func Rename(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position,
// It updates package clauses and import paths for the renamed package as well
// as any other packages affected by the directory renaming among packages
// described by allMetadata.
func renamePackage(ctx context.Context, s Snapshot, modulePath, oldPath, newName string, allMetadata []Metadata) (map[span.URI][]protocol.TextEdit, error) {
func renamePackage(ctx context.Context, s Snapshot, modulePath, oldPath PackagePath, newName PackageName, allMetadata []Metadata) (map[span.URI][]protocol.TextEdit, error) {
if modulePath == oldPath {
return nil, fmt.Errorf("cannot rename package: module path %q is the same as the package path, so renaming the package directory would have no effect", modulePath)
}
newPathPrefix := path.Join(path.Dir(oldPath), newName)
newPathPrefix := path.Join(path.Dir(string(oldPath)), string(newName))
edits := make(map[span.URI][]protocol.TextEdit)
seen := make(seenPackageRename) // track per-file import renaming we've already processed
@ -272,7 +272,7 @@ func renamePackage(ctx context.Context, s Snapshot, modulePath, oldPath, newName
// Subtle: check this condition before checking for valid module info
// below, because we should not fail this operation if unrelated packages
// lack module info.
if !strings.HasPrefix(m.PackagePath()+"/", oldPath+"/") {
if !strings.HasPrefix(string(m.PackagePath())+"/", string(oldPath)+"/") {
continue // not affected by the package renaming
}
@ -280,16 +280,16 @@ func renamePackage(ctx context.Context, s Snapshot, modulePath, oldPath, newName
return nil, fmt.Errorf("cannot rename package: missing module information for package %q", m.PackagePath())
}
if modulePath != m.ModuleInfo().Path {
if modulePath != PackagePath(m.ModuleInfo().Path) {
continue // don't edit imports if nested package and renaming package have different module paths
}
// Renaming a package consists of changing its import path and package name.
suffix := strings.TrimPrefix(m.PackagePath(), oldPath)
suffix := strings.TrimPrefix(string(m.PackagePath()), string(oldPath))
newPath := newPathPrefix + suffix
pkgName := m.PackageName()
if m.PackagePath() == oldPath {
if m.PackagePath() == PackagePath(oldPath) {
pkgName = newName
if err := renamePackageClause(ctx, m, s, newName, seen, edits); err != nil {
@ -297,7 +297,8 @@ func renamePackage(ctx context.Context, s Snapshot, modulePath, oldPath, newName
}
}
if err := renameImports(ctx, s, m, newPath, pkgName, seen, edits); err != nil {
imp := ImportPath(newPath) // TODO(adonovan): what if newPath has vendor/ prefix?
if err := renameImports(ctx, s, m, imp, pkgName, seen, edits); err != nil {
return nil, err
}
}
@ -314,14 +315,14 @@ func renamePackage(ctx context.Context, s Snapshot, modulePath, oldPath, newName
// However, in all cases the resulting edits will be the same.
type seenPackageRename map[seenPackageKey]bool
type seenPackageKey struct {
uri span.URI
importPath string
uri span.URI
path PackagePath
}
// add reports whether uri and importPath have been seen, and records them as
// seen if not.
func (s seenPackageRename) add(uri span.URI, importPath string) bool {
key := seenPackageKey{uri, importPath}
func (s seenPackageRename) add(uri span.URI, path PackagePath) bool {
key := seenPackageKey{uri, path}
seen := s[key]
if !seen {
s[key] = true
@ -336,7 +337,7 @@ func (s seenPackageRename) add(uri span.URI, importPath string) bool {
// package clause has already been updated, to prevent duplicate edits.
//
// Edits are written into the edits map.
func renamePackageClause(ctx context.Context, m Metadata, s Snapshot, newName string, seen seenPackageRename, edits map[span.URI][]protocol.TextEdit) error {
func renamePackageClause(ctx context.Context, m Metadata, s Snapshot, newName PackageName, seen seenPackageRename, edits map[span.URI][]protocol.TextEdit) error {
pkg, err := s.WorkspacePackageByID(ctx, m.PackageID())
if err != nil {
return err
@ -358,7 +359,7 @@ func renamePackageClause(ctx context.Context, m Metadata, s Snapshot, newName st
}
edits[f.URI] = append(edits[f.URI], protocol.TextEdit{
Range: rng,
NewText: newName,
NewText: string(newName),
})
}
@ -370,7 +371,7 @@ func renamePackageClause(ctx context.Context, m Metadata, s Snapshot, newName st
// newPath and name newName.
//
// Edits are written into the edits map.
func renameImports(ctx context.Context, s Snapshot, m Metadata, newPath, newName string, seen seenPackageRename, edits map[span.URI][]protocol.TextEdit) error {
func renameImports(ctx context.Context, s Snapshot, m Metadata, newPath ImportPath, newName PackageName, seen seenPackageRename, edits map[span.URI][]protocol.TextEdit) error {
// TODO(rfindley): we should get reverse dependencies as metadata first,
// rather then building the package immediately. We don't need reverse
// dependencies if they are intermediate test variants.
@ -399,7 +400,8 @@ func renameImports(ctx context.Context, s Snapshot, m Metadata, newPath, newName
}
for _, imp := range f.File.Imports {
if impPath, _ := strconv.Unquote(imp.Path.Value); impPath != m.PackagePath() {
// TODO(adonovan): what if RHS has "vendor/" prefix?
if UnquoteImportPath(imp) != ImportPath(m.PackagePath()) {
continue // not the import we're looking for
}
@ -409,10 +411,9 @@ func renameImports(ctx context.Context, s Snapshot, m Metadata, newPath, newName
if err != nil {
return err
}
newText := strconv.Quote(newPath)
edits[f.URI] = append(edits[f.URI], protocol.TextEdit{
Range: rng,
NewText: newText,
NewText: strconv.Quote(string(newPath)),
})
// If the package name of an import has not changed or if its import
@ -430,7 +431,7 @@ func renameImports(ctx context.Context, s Snapshot, m Metadata, newPath, newName
fileScope := dep.GetTypesInfo().Scopes[f.File]
var changes map[span.URI][]protocol.TextEdit
localName := newName
localName := string(newName)
try := 0
// Keep trying with fresh names until one succeeds.
@ -446,7 +447,7 @@ func renameImports(ctx context.Context, s Snapshot, m Metadata, newPath, newName
// If the chosen local package name matches the package's new name, delete the
// change that would have inserted an explicit local name, which is always
// the lexically first change.
if localName == newName {
if localName == string(newName) {
v := changes[f.URI]
sort.Slice(v, func(i, j int) bool {
return protocol.CompareRange(v[i].Range, v[j].Range) < 0

View File

@ -12,7 +12,6 @@ import (
"go/token"
"go/types"
"reflect"
"strconv"
"strings"
"unicode"
@ -834,8 +833,8 @@ func pathEnclosingInterval(fset *token.FileSet, pkg Package, start, end token.Po
if imp == nil {
continue
}
importPath, err := strconv.Unquote(imp.Path.Value)
if err != nil {
importPath := UnquoteImportPath(imp)
if importPath == "" {
continue
}
imported, err := pkg.ResolveImportPath(importPath)

View File

@ -194,7 +194,7 @@ func deducePkgFromTypes(ctx context.Context, snapshot Snapshot, ifaceObj types.O
return nil, err
}
for _, p := range pkgs {
if p.PkgPath() == ifaceObj.Pkg().Path() {
if p.PkgPath() == PackagePath(ifaceObj.Pkg().Path()) {
return p, nil
}
}
@ -254,11 +254,11 @@ func missingMethods(ctx context.Context, snapshot Snapshot, concMS *types.Method
for i := 0; i < iface.NumEmbeddeds(); i++ {
eiface := iface.Embedded(i).Obj()
depPkg := ifacePkg
if eiface.Pkg().Path() != ifacePkg.PkgPath() {
if path := PackagePath(eiface.Pkg().Path()); path != ifacePkg.PkgPath() {
// TODO(adonovan): I'm not sure what this is trying to do, but it
// looks wrong the in case of type aliases.
var err error
depPkg, err = ifacePkg.DirectDep(eiface.Pkg().Path())
depPkg, err = ifacePkg.DirectDep(path)
if err != nil {
return nil, err
}

View File

@ -314,7 +314,7 @@ func CompareDiagnostic(a, b *Diagnostic) int {
// findFileInDeps finds uri in pkg or its dependencies.
func findFileInDeps(pkg Package, uri span.URI) (*ParsedGoFile, Package, error) {
queue := []Package{pkg}
seen := make(map[string]bool)
seen := make(map[PackageID]bool)
for len(queue) > 0 {
pkg := queue[0]
@ -333,14 +333,14 @@ func findFileInDeps(pkg Package, uri span.URI) (*ParsedGoFile, Package, error) {
return nil, nil, fmt.Errorf("no file for %s in package %s", uri, pkg.ID())
}
// ImportPath returns the unquoted import path of s,
// UnquoteImportPath returns the unquoted import path of s,
// or "" if the path is not properly quoted.
func ImportPath(s *ast.ImportSpec) string {
t, err := strconv.Unquote(s.Path.Value)
func UnquoteImportPath(s *ast.ImportSpec) ImportPath {
path, err := strconv.Unquote(s.Path.Value)
if err != nil {
return ""
}
return t
return ImportPath(path)
}
// NodeContains returns true if a node encloses a given position pos.
@ -532,14 +532,14 @@ func InDirLex(dir, path string) bool {
// IsValidImport returns whether importPkgPath is importable
// by pkgPath
func IsValidImport(pkgPath, importPkgPath string) bool {
func IsValidImport(pkgPath, importPkgPath PackagePath) bool {
i := strings.LastIndex(string(importPkgPath), "/internal/")
if i == -1 {
return true
}
// TODO(rfindley): this looks wrong: IsCommandLineArguments is meant to
// operate on package IDs, not package paths.
if IsCommandLineArguments(string(pkgPath)) {
if IsCommandLineArguments(PackageID(pkgPath)) {
return true
}
// TODO(rfindley): this is wrong. mod.testx/p should not be able to
@ -551,10 +551,8 @@ func IsValidImport(pkgPath, importPkgPath string) bool {
// "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.
//
// TODO(rfindley): this should accept a PackageID.
func IsCommandLineArguments(s string) bool {
return strings.Contains(s, "command-line-arguments")
func IsCommandLineArguments(id PackageID) bool {
return strings.Contains(string(id), "command-line-arguments")
}
// RecvIdent returns the type identifier of a method receiver.

View File

@ -84,7 +84,7 @@ type Snapshot interface {
DiagnosePackage(ctx context.Context, pkg Package) (map[span.URI][]*Diagnostic, error)
// Analyze runs the analyses for the given package at this snapshot.
Analyze(ctx context.Context, pkgID string, analyzers []*Analyzer) ([]*Diagnostic, error)
Analyze(ctx context.Context, pkgID PackageID, analyzers []*Analyzer) ([]*Diagnostic, error)
// RunGoCommandPiped runs the given `go` command, writing its output
// to stdout and stderr. Verb, Args, and WorkingDir must be specified.
@ -149,12 +149,12 @@ type Snapshot interface {
// GetActiveReverseDeps returns the active files belonging to the reverse
// dependencies of this file's package, checked in TypecheckWorkspace mode.
GetReverseDependencies(ctx context.Context, id string) ([]Package, error)
GetReverseDependencies(ctx context.Context, id PackageID) ([]Package, error)
// CachedImportPaths returns all the imported packages loaded in this
// snapshot, indexed by their package path (not import path, despite the name)
// and checked in TypecheckWorkspace mode.
CachedImportPaths(ctx context.Context) (map[string]Package, error)
CachedImportPaths(ctx context.Context) (map[PackagePath]Package, error)
// KnownPackages returns all the packages loaded in this snapshot, checked
// in TypecheckWorkspace mode.
@ -171,7 +171,7 @@ type Snapshot interface {
// WorkspacePackageByID returns the workspace package with id, type checked
// in 'workspace' mode.
WorkspacePackageByID(ctx context.Context, id string) (Package, error)
WorkspacePackageByID(ctx context.Context, id PackageID) (Package, error)
// Symbols returns all symbols in the snapshot.
Symbols(ctx context.Context) map[span.URI][]Symbol
@ -338,17 +338,15 @@ type TidiedModule struct {
}
// Metadata represents package metadata retrieved from go/packages.
//
// TODO(rfindley): move the strongly typed strings from the cache package here.
type Metadata interface {
// PackageID is the unique package id.
PackageID() string
PackageID() PackageID
// PackageName is the package name.
PackageName() string
PackageName() PackageName
// PackagePath is the package path.
PackagePath() string
PackagePath() PackagePath
// ModuleInfo returns the go/packages module information for the given package.
ModuleInfo() *packages.Module
@ -593,12 +591,23 @@ func (a Analyzer) IsEnabled(view View) bool {
return a.Enabled
}
// 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 a Go package that has been type-checked. It maintains
// only the relevant fields of a *go/packages.Package.
type Package interface {
ID() string // logically a cache.PackageID
Name() string // logically a cache.PackageName
PkgPath() string // logically a cache.PackagePath
ID() PackageID
Name() PackageName
PkgPath() PackagePath
CompiledGoFiles() []*ParsedGoFile
File(uri span.URI) (*ParsedGoFile, error)
GetSyntax() []*ast.File
@ -606,9 +615,9 @@ type Package interface {
GetTypesInfo() *types.Info
GetTypesSizes() types.Sizes
ForTest() string
DirectDep(packagePath string) (Package, error) // logically a cache.PackagePath
ResolveImportPath(importPath string) (Package, error) // logically a cache.ImportPath
MissingDependencies() []string // unordered; logically cache.ImportPaths
DirectDep(path PackagePath) (Package, error)
ResolveImportPath(path ImportPath) (Package, error)
MissingDependencies() []ImportPath // unordered
Imports() []Package
Version() *module.Version
HasListOrParseErrors() bool

View File

@ -92,7 +92,7 @@ type symbolizer func(space []string, name string, pkg Metadata, m matcherFunc) (
func fullyQualifiedSymbolMatch(space []string, name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
if _, score := dynamicSymbolMatch(space, name, pkg, matcher); score > 0 {
return append(space, pkg.PackagePath(), ".", name), score
return append(space, string(pkg.PackagePath()), ".", name), score
}
return nil, 0
}
@ -106,12 +106,12 @@ func dynamicSymbolMatch(space []string, name string, pkg Metadata, matcher match
var score float64
endsInPkgName := strings.HasSuffix(pkg.PackagePath(), pkg.PackageName())
endsInPkgName := strings.HasSuffix(string(pkg.PackagePath()), string(pkg.PackageName()))
// If the package path does not end in the package name, we need to check the
// package-qualified symbol as an extra pass first.
if !endsInPkgName {
pkgQualified := append(space, pkg.PackageName(), ".", name)
pkgQualified := append(space, string(pkg.PackageName()), ".", name)
idx, score := matcher(pkgQualified)
nameStart := len(pkg.PackageName()) + 1
if score > 0 {
@ -126,7 +126,7 @@ func dynamicSymbolMatch(space []string, name string, pkg Metadata, matcher match
}
// Now try matching the fully qualified symbol.
fullyQualified := append(space, pkg.PackagePath(), ".", name)
fullyQualified := append(space, string(pkg.PackagePath()), ".", name)
idx, score := matcher(fullyQualified)
// As above, check if we matched just the unqualified symbol name.
@ -141,7 +141,7 @@ func dynamicSymbolMatch(space []string, name string, pkg Metadata, matcher match
if endsInPkgName && idx >= 0 {
pkgStart := len(pkg.PackagePath()) - len(pkg.PackageName())
if idx >= pkgStart {
return append(space, pkg.PackageName(), ".", name), score
return append(space, string(pkg.PackageName()), ".", name), score
}
}
@ -151,7 +151,7 @@ func dynamicSymbolMatch(space []string, name string, pkg Metadata, matcher match
}
func packageSymbolMatch(space []string, name string, pkg Metadata, matcher matcherFunc) ([]string, float64) {
qualified := append(space, pkg.PackageName(), ".", name)
qualified := append(space, string(pkg.PackageName()), ".", name)
if _, s := matcher(qualified); s > 0 {
return qualified, s
}
@ -526,7 +526,7 @@ func matchFile(store *symbolStore, symbolizer symbolizer, matcher matcherFunc, r
kind: sym.Kind,
uri: i.uri,
rng: sym.Range,
container: i.md.PackagePath(),
container: string(i.md.PackagePath()),
}
store.store(si)
}