github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/source/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 source 6 7 import ( 8 "context" 9 "sort" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/jhump/golang-x-tools/internal/event" 15 "github.com/jhump/golang-x-tools/internal/imports" 16 errors "golang.org/x/xerrors" 17 ) 18 19 // KnownPackages returns a list of all known packages 20 // in the package graph that could potentially be imported 21 // by the given file. 22 func KnownPackages(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle) ([]string, error) { 23 pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage) 24 if err != nil { 25 return nil, errors.Errorf("GetParsedFile: %w", err) 26 } 27 alreadyImported := map[string]struct{}{} 28 for _, imp := range pgf.File.Imports { 29 alreadyImported[imp.Path.Value] = struct{}{} 30 } 31 pkgs, err := snapshot.CachedImportPaths(ctx) 32 if err != nil { 33 return nil, err 34 } 35 var ( 36 seen = make(map[string]struct{}) 37 paths []string 38 ) 39 for path, knownPkg := range pkgs { 40 gofiles := knownPkg.CompiledGoFiles() 41 if len(gofiles) == 0 || gofiles[0].File.Name == nil { 42 continue 43 } 44 pkgName := gofiles[0].File.Name.Name 45 // package main cannot be imported 46 if pkgName == "main" { 47 continue 48 } 49 // test packages cannot be imported 50 if knownPkg.ForTest() != "" { 51 continue 52 } 53 // no need to import what the file already imports 54 if _, ok := alreadyImported[path]; ok { 55 continue 56 } 57 // snapshot.KnownPackages could have multiple versions of a pkg 58 if _, ok := seen[path]; ok { 59 continue 60 } 61 seen[path] = struct{}{} 62 // make sure internal packages are importable by the file 63 if !IsValidImport(pkg.PkgPath(), path) { 64 continue 65 } 66 // naive check on cyclical imports 67 if isDirectlyCyclical(pkg, knownPkg) { 68 continue 69 } 70 paths = append(paths, path) 71 seen[path] = struct{}{} 72 } 73 err = snapshot.RunProcessEnvFunc(ctx, func(o *imports.Options) error { 74 var mu sync.Mutex 75 ctx, cancel := context.WithTimeout(ctx, time.Millisecond*80) 76 defer cancel() 77 return imports.GetAllCandidates(ctx, func(ifix imports.ImportFix) { 78 mu.Lock() 79 defer mu.Unlock() 80 if _, ok := seen[ifix.StmtInfo.ImportPath]; ok { 81 return 82 } 83 paths = append(paths, ifix.StmtInfo.ImportPath) 84 }, "", pgf.URI.Filename(), pkg.GetTypes().Name(), o.Env) 85 }) 86 if err != nil { 87 // if an error occurred, we stil have a decent list we can 88 // show to the user through snapshot.CachedImportPaths 89 event.Error(ctx, "imports.GetAllCandidates", err) 90 } 91 sort.Slice(paths, func(i, j int) bool { 92 importI, importJ := paths[i], paths[j] 93 iHasDot := strings.Contains(importI, ".") 94 jHasDot := strings.Contains(importJ, ".") 95 if iHasDot && !jHasDot { 96 return false 97 } 98 if jHasDot && !iHasDot { 99 return true 100 } 101 return importI < importJ 102 }) 103 return paths, nil 104 } 105 106 // isDirectlyCyclical checks if imported directly imports pkg. 107 // It does not (yet) offer a full cyclical check because showing a user 108 // a list of importable packages already generates a very large list 109 // and having a few false positives in there could be worth the 110 // performance snappiness. 111 func isDirectlyCyclical(pkg, imported Package) bool { 112 for _, imp := range imported.Imports() { 113 if imp.PkgPath() == pkg.PkgPath() { 114 return true 115 } 116 } 117 return false 118 }