mirror of https://github.com/golang/go.git
119 lines
3.2 KiB
Go
119 lines
3.2 KiB
Go
// Copyright 2020 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 source
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"golang.org/x/tools/internal/event"
|
|
"golang.org/x/tools/internal/imports"
|
|
errors "golang.org/x/xerrors"
|
|
)
|
|
|
|
// 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) {
|
|
pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage)
|
|
if err != nil {
|
|
return nil, errors.Errorf("GetParsedFile: %w", err)
|
|
}
|
|
alreadyImported := map[string]struct{}{}
|
|
for _, imp := range pgf.File.Imports {
|
|
alreadyImported[imp.Path.Value] = struct{}{}
|
|
}
|
|
pkgs, err := snapshot.CachedImportPaths(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var (
|
|
seen = make(map[string]struct{})
|
|
paths []string
|
|
)
|
|
for path, knownPkg := range pkgs {
|
|
gofiles := knownPkg.CompiledGoFiles()
|
|
if len(gofiles) == 0 || gofiles[0].File.Name == nil {
|
|
continue
|
|
}
|
|
pkgName := gofiles[0].File.Name.Name
|
|
// package main cannot be imported
|
|
if pkgName == "main" {
|
|
continue
|
|
}
|
|
// test packages cannot be imported
|
|
if knownPkg.ForTest() != "" {
|
|
continue
|
|
}
|
|
// no need to import what the file already imports
|
|
if _, ok := alreadyImported[path]; ok {
|
|
continue
|
|
}
|
|
// snapshot.KnownPackages could have multiple versions of a pkg
|
|
if _, ok := seen[path]; ok {
|
|
continue
|
|
}
|
|
seen[path] = struct{}{}
|
|
// make sure internal packages are importable by the file
|
|
if !IsValidImport(pkg.PkgPath(), path) {
|
|
continue
|
|
}
|
|
// naive check on cyclical imports
|
|
if isDirectlyCyclical(pkg, knownPkg) {
|
|
continue
|
|
}
|
|
paths = append(paths, path)
|
|
seen[path] = struct{}{}
|
|
}
|
|
err = snapshot.RunProcessEnvFunc(ctx, func(o *imports.Options) error {
|
|
var mu sync.Mutex
|
|
ctx, cancel := context.WithTimeout(ctx, time.Millisecond*80)
|
|
defer cancel()
|
|
return imports.GetAllCandidates(ctx, func(ifix imports.ImportFix) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if _, ok := seen[ifix.StmtInfo.ImportPath]; ok {
|
|
return
|
|
}
|
|
paths = append(paths, ifix.StmtInfo.ImportPath)
|
|
}, "", pgf.URI.Filename(), pkg.GetTypes().Name(), o.Env)
|
|
})
|
|
if err != nil {
|
|
// if an error occurred, we stil have a decent list we can
|
|
// show to the user through snapshot.CachedImportPaths
|
|
event.Error(ctx, "imports.GetAllCandidates", err)
|
|
}
|
|
sort.Slice(paths, func(i, j int) bool {
|
|
importI, importJ := paths[i], paths[j]
|
|
iHasDot := strings.Contains(importI, ".")
|
|
jHasDot := strings.Contains(importJ, ".")
|
|
if iHasDot && !jHasDot {
|
|
return false
|
|
}
|
|
if jHasDot && !iHasDot {
|
|
return true
|
|
}
|
|
return importI < importJ
|
|
})
|
|
return paths, nil
|
|
}
|
|
|
|
// isDirectlyCyclical checks if imported directly imports pkg.
|
|
// It does not (yet) offer a full cyclical check because showing a user
|
|
// a list of importable packages already generates a very large list
|
|
// and having a few false positives in there could be worth the
|
|
// performance snappiness.
|
|
func isDirectlyCyclical(pkg, imported Package) bool {
|
|
for _, imp := range imported.Imports() {
|
|
if imp.PkgPath() == pkg.PkgPath() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|