golang.org/x/tools/gopls@v0.15.3/internal/golang/known_packages.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package golang 6 7 import ( 8 "context" 9 "go/parser" 10 "go/token" 11 "sort" 12 "strings" 13 "sync" 14 "time" 15 16 "golang.org/x/tools/gopls/internal/cache" 17 "golang.org/x/tools/gopls/internal/cache/metadata" 18 "golang.org/x/tools/gopls/internal/file" 19 "golang.org/x/tools/internal/event" 20 "golang.org/x/tools/internal/imports" 21 ) 22 23 // KnownPackagePaths returns a new list of package paths of all known 24 // packages in the package graph that could potentially be imported by 25 // the given file. The list is ordered lexicographically, except that 26 // all dot-free paths (standard packages) appear before dotful ones. 27 // 28 // It is part of the gopls.list_known_packages command. 29 func KnownPackagePaths(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle) ([]PackagePath, error) { 30 // This algorithm is expressed in terms of Metadata, not Packages, 31 // so it doesn't cause or wait for type checking. 32 33 current, err := NarrowestMetadataForFile(ctx, snapshot, fh.URI()) 34 if err != nil { 35 return nil, err // e.g. context cancelled 36 } 37 38 // Parse the file's imports so we can compute which 39 // PackagePaths are imported by this specific file. 40 src, err := fh.Content() 41 if err != nil { 42 return nil, err 43 } 44 file, err := parser.ParseFile(token.NewFileSet(), fh.URI().Path(), src, parser.ImportsOnly) 45 if err != nil { 46 return nil, err 47 } 48 imported := make(map[PackagePath]bool) 49 for _, imp := range file.Imports { 50 if id := current.DepsByImpPath[metadata.UnquoteImportPath(imp)]; id != "" { 51 if mp := snapshot.Metadata(id); mp != nil { 52 imported[mp.PkgPath] = true 53 } 54 } 55 } 56 57 // Now find candidates among all known packages. 58 knownPkgs, err := snapshot.AllMetadata(ctx) 59 if err != nil { 60 return nil, err 61 } 62 seen := make(map[PackagePath]bool) 63 for _, knownPkg := range knownPkgs { 64 // package main cannot be imported 65 if knownPkg.Name == "main" { 66 continue 67 } 68 // test packages cannot be imported 69 if knownPkg.ForTest != "" { 70 continue 71 } 72 // No need to import what the file already imports. 73 // This check is based on PackagePath, not PackageID, 74 // so that all test variants are filtered out too. 75 if imported[knownPkg.PkgPath] { 76 continue 77 } 78 // make sure internal packages are importable by the file 79 if !metadata.IsValidImport(current.PkgPath, knownPkg.PkgPath) { 80 continue 81 } 82 // naive check on cyclical imports 83 if isDirectlyCyclical(current, knownPkg) { 84 continue 85 } 86 // AllMetadata may have multiple variants of a pkg. 87 seen[knownPkg.PkgPath] = true 88 } 89 90 // Augment the set by invoking the goimports algorithm. 91 if err := snapshot.RunProcessEnvFunc(ctx, func(ctx context.Context, o *imports.Options) error { 92 ctx, cancel := context.WithTimeout(ctx, time.Millisecond*80) 93 defer cancel() 94 var seenMu sync.Mutex 95 wrapped := func(ifix imports.ImportFix) { 96 seenMu.Lock() 97 defer seenMu.Unlock() 98 // TODO(adonovan): what if the actual package path has a vendor/ prefix? 99 seen[PackagePath(ifix.StmtInfo.ImportPath)] = true 100 } 101 return imports.GetAllCandidates(ctx, wrapped, "", fh.URI().Path(), string(current.Name), o.Env) 102 }); err != nil { 103 // If goimports failed, proceed with just the candidates from the metadata. 104 event.Error(ctx, "imports.GetAllCandidates", err) 105 } 106 107 // Sort lexicographically, but with std before non-std packages. 108 paths := make([]PackagePath, 0, len(seen)) 109 for path := range seen { 110 paths = append(paths, path) 111 } 112 sort.Slice(paths, func(i, j int) bool { 113 importI, importJ := paths[i], paths[j] 114 iHasDot := strings.Contains(string(importI), ".") 115 jHasDot := strings.Contains(string(importJ), ".") 116 if iHasDot != jHasDot { 117 return jHasDot // dot-free paths (standard packages) compare less 118 } 119 return importI < importJ 120 }) 121 122 return paths, nil 123 } 124 125 // isDirectlyCyclical checks if imported directly imports pkg. 126 // It does not (yet) offer a full cyclical check because showing a user 127 // a list of importable packages already generates a very large list 128 // and having a few false positives in there could be worth the 129 // performance snappiness. 130 // 131 // TODO(adonovan): ensure that metadata graph is always cyclic! 132 // Many algorithms will get confused or even stuck in the 133 // presence of cycles. Then replace this function by 'false'. 134 func isDirectlyCyclical(pkg, imported *metadata.Package) bool { 135 _, ok := imported.DepsByPkgPath[pkg.PkgPath] 136 return ok 137 }