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  }