cuelang.org/go@v0.10.1/internal/golangorgx/gopls/cache/load.go (about)

     1  // Copyright 2019 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 cache
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"path/filepath"
    13  	"sort"
    14  	"strings"
    15  	"sync/atomic"
    16  	"time"
    17  
    18  	"cuelang.org/go/internal/golangorgx/gopls/cache/metadata"
    19  	"cuelang.org/go/internal/golangorgx/gopls/file"
    20  	"cuelang.org/go/internal/golangorgx/gopls/protocol"
    21  	"cuelang.org/go/internal/golangorgx/gopls/util/bug"
    22  	"cuelang.org/go/internal/golangorgx/gopls/util/immutable"
    23  	"cuelang.org/go/internal/golangorgx/gopls/util/pathutil"
    24  	"cuelang.org/go/internal/golangorgx/gopls/util/slices"
    25  	"cuelang.org/go/internal/golangorgx/tools/event"
    26  	"cuelang.org/go/internal/golangorgx/tools/event/tag"
    27  	"cuelang.org/go/internal/golangorgx/tools/gocommand"
    28  	"cuelang.org/go/internal/golangorgx/tools/packagesinternal"
    29  	"golang.org/x/tools/go/packages"
    30  )
    31  
    32  var loadID uint64 // atomic identifier for loads
    33  
    34  // errNoPackages indicates that a load query matched no packages.
    35  var errNoPackages = errors.New("no packages returned")
    36  
    37  // load calls packages.Load for the given scopes, updating package metadata,
    38  // import graph, and mapped files with the result.
    39  //
    40  // The resulting error may wrap the moduleErrorMap error type, representing
    41  // errors associated with specific modules.
    42  //
    43  // If scopes contains a file scope there must be exactly one scope.
    44  func (s *Snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadScope) (err error) {
    45  	id := atomic.AddUint64(&loadID, 1)
    46  	eventName := fmt.Sprintf("go/packages.Load #%d", id) // unique name for logging
    47  
    48  	var query []string
    49  	var containsDir bool // for logging
    50  	var standalone bool  // whether this is a load of a standalone file
    51  
    52  	// Keep track of module query -> module path so that we can later correlate query
    53  	// errors with errors.
    54  	moduleQueries := make(map[string]string)
    55  	for _, scope := range scopes {
    56  		switch scope := scope.(type) {
    57  		case packageLoadScope:
    58  			// The only time we pass package paths is when we're doing a
    59  			// partial workspace load. In those cases, the paths came back from
    60  			// go list and should already be GOPATH-vendorized when appropriate.
    61  			query = append(query, string(scope))
    62  
    63  		case fileLoadScope:
    64  			// Given multiple scopes, the resulting load might contain inaccurate
    65  			// information. For example go/packages returns at most one command-line
    66  			// arguments package, and does not handle a combination of standalone
    67  			// files and packages.
    68  			uri := protocol.DocumentURI(scope)
    69  			if len(scopes) > 1 {
    70  				panic(fmt.Sprintf("internal error: load called with multiple scopes when a file scope is present (file: %s)", uri))
    71  			}
    72  			fh := s.FindFile(uri)
    73  			if fh == nil || s.FileKind(fh) != file.Go {
    74  				// Don't try to load a file that doesn't exist, or isn't a go file.
    75  				continue
    76  			}
    77  			contents, err := fh.Content()
    78  			if err != nil {
    79  				continue
    80  			}
    81  			if isStandaloneFile(contents, s.Options().StandaloneTags) {
    82  				standalone = true
    83  				query = append(query, uri.Path())
    84  			} else {
    85  				query = append(query, fmt.Sprintf("file=%s", uri.Path()))
    86  			}
    87  
    88  		case moduleLoadScope:
    89  			modQuery := fmt.Sprintf("%s%c...", scope.dir, filepath.Separator)
    90  			query = append(query, modQuery)
    91  			moduleQueries[modQuery] = scope.modulePath
    92  
    93  		case viewLoadScope:
    94  			// If we are outside of GOPATH, a module, or some other known
    95  			// build system, don't load subdirectories.
    96  			if s.view.typ == AdHocView {
    97  				query = append(query, "./")
    98  			} else {
    99  				query = append(query, "./...")
   100  			}
   101  
   102  		default:
   103  			panic(fmt.Sprintf("unknown scope type %T", scope))
   104  		}
   105  		switch scope.(type) {
   106  		case viewLoadScope, moduleLoadScope:
   107  			containsDir = true
   108  		}
   109  	}
   110  	if len(query) == 0 {
   111  		return nil
   112  	}
   113  	sort.Strings(query) // for determinism
   114  
   115  	ctx, done := event.Start(ctx, "cache.snapshot.load", tag.Query.Of(query))
   116  	defer done()
   117  
   118  	flags := LoadWorkspace
   119  	if allowNetwork {
   120  		flags |= AllowNetwork
   121  	}
   122  	_, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{
   123  		WorkingDir: s.view.root.Path(),
   124  	})
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	// Set a last resort deadline on packages.Load since it calls the go
   130  	// command, which may hang indefinitely if it has a bug. golang/go#42132
   131  	// and golang/go#42255 have more context.
   132  	ctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
   133  	defer cancel()
   134  
   135  	cfg := s.config(ctx, inv)
   136  	pkgs, err := packages.Load(cfg, query...)
   137  	cleanup()
   138  
   139  	// If the context was canceled, return early. Otherwise, we might be
   140  	// type-checking an incomplete result. Check the context directly,
   141  	// because go/packages adds extra information to the error.
   142  	if ctx.Err() != nil {
   143  		return ctx.Err()
   144  	}
   145  
   146  	// This log message is sought for by TestReloadOnlyOnce.
   147  	labels := append(s.Labels(), tag.Query.Of(query), tag.PackageCount.Of(len(pkgs)))
   148  	if err != nil {
   149  		event.Error(ctx, eventName, err, labels...)
   150  	} else {
   151  		event.Log(ctx, eventName, labels...)
   152  	}
   153  
   154  	if standalone {
   155  		// Handle standalone package result.
   156  		//
   157  		// In general, this should just be a single "command-line-arguments"
   158  		// package containing the requested file. However, if the file is a test
   159  		// file, go/packages may return test variants of the command-line-arguments
   160  		// package. We don't support this; theoretically we could, but it seems
   161  		// unnecessarily complicated.
   162  		//
   163  		// Prior to golang/go#64233 we just assumed that we'd get exactly one
   164  		// package here. The categorization of bug reports below may be a bit
   165  		// verbose, but anticipates that perhaps we don't fully understand
   166  		// possible failure modes.
   167  		errorf := bug.Errorf
   168  		if s.view.typ == GoPackagesDriverView {
   169  			errorf = fmt.Errorf // all bets are off
   170  		}
   171  
   172  		var standalonePkg *packages.Package
   173  		for _, pkg := range pkgs {
   174  			if pkg.ID == "command-line-arguments" {
   175  				if standalonePkg != nil {
   176  					return errorf("internal error: go/packages returned multiple standalone packages")
   177  				}
   178  				standalonePkg = pkg
   179  			} else if packagesinternal.GetForTest(pkg) == "" && !strings.HasSuffix(pkg.ID, ".test") {
   180  				return errorf("internal error: go/packages returned unexpected package %q for standalone file", pkg.ID)
   181  			}
   182  		}
   183  		if standalonePkg == nil {
   184  			return errorf("internal error: go/packages failed to return non-test standalone package")
   185  		}
   186  		if len(standalonePkg.CompiledGoFiles) > 0 {
   187  			pkgs = []*packages.Package{standalonePkg}
   188  		} else {
   189  			pkgs = nil
   190  		}
   191  	}
   192  
   193  	if len(pkgs) == 0 {
   194  		if err == nil {
   195  			err = errNoPackages
   196  		}
   197  		return fmt.Errorf("packages.Load error: %w", err)
   198  	}
   199  
   200  	moduleErrs := make(map[string][]packages.Error) // module path -> errors
   201  	filterFunc := s.view.filterFunc()
   202  	newMetadata := make(map[PackageID]*metadata.Package)
   203  	for _, pkg := range pkgs {
   204  		// The Go command returns synthetic list results for module queries that
   205  		// encountered module errors.
   206  		//
   207  		// For example, given a module path a.mod, we'll query for "a.mod/..." and
   208  		// the go command will return a package named "a.mod/..." holding this
   209  		// error. Save it for later interpretation.
   210  		//
   211  		// See golang/go#50862 for more details.
   212  		if mod := moduleQueries[pkg.PkgPath]; mod != "" { // a synthetic result for the unloadable module
   213  			if len(pkg.Errors) > 0 {
   214  				moduleErrs[mod] = pkg.Errors
   215  			}
   216  			continue
   217  		}
   218  
   219  		if !containsDir || s.Options().VerboseOutput {
   220  			event.Log(ctx, eventName, append(
   221  				s.Labels(),
   222  				tag.Package.Of(pkg.ID),
   223  				tag.Files.Of(pkg.CompiledGoFiles))...)
   224  		}
   225  
   226  		// Ignore packages with no sources, since we will never be able to
   227  		// correctly invalidate that metadata.
   228  		if len(pkg.GoFiles) == 0 && len(pkg.CompiledGoFiles) == 0 {
   229  			continue
   230  		}
   231  		// Special case for the builtin package, as it has no dependencies.
   232  		if pkg.PkgPath == "builtin" {
   233  			if len(pkg.GoFiles) != 1 {
   234  				return fmt.Errorf("only expected 1 file for builtin, got %v", len(pkg.GoFiles))
   235  			}
   236  			s.setBuiltin(pkg.GoFiles[0])
   237  			continue
   238  		}
   239  		// Skip test main packages.
   240  		if isTestMain(pkg, s.view.folder.Env.GOCACHE) {
   241  			continue
   242  		}
   243  		// Skip filtered packages. They may be added anyway if they're
   244  		// dependencies of non-filtered packages.
   245  		//
   246  		// TODO(rfindley): why exclude metadata arbitrarily here? It should be safe
   247  		// to capture all metadata.
   248  		// TODO(rfindley): what about compiled go files?
   249  		if allFilesExcluded(pkg.GoFiles, filterFunc) {
   250  			continue
   251  		}
   252  		buildMetadata(newMetadata, pkg, cfg.Dir, standalone)
   253  	}
   254  
   255  	s.mu.Lock()
   256  
   257  	// Assert the invariant s.packages.Get(id).m == s.meta.metadata[id].
   258  	s.packages.Range(func(id PackageID, ph *packageHandle) {
   259  		if s.meta.Packages[id] != ph.mp {
   260  			panic("inconsistent metadata")
   261  		}
   262  	})
   263  
   264  	// Compute the minimal metadata updates (for Clone)
   265  	// required to preserve the above invariant.
   266  	var files []protocol.DocumentURI // files to preload
   267  	seenFiles := make(map[protocol.DocumentURI]bool)
   268  	updates := make(map[PackageID]*metadata.Package)
   269  	for _, mp := range newMetadata {
   270  		if existing := s.meta.Packages[mp.ID]; existing == nil {
   271  			// Record any new files we should pre-load.
   272  			for _, uri := range mp.CompiledGoFiles {
   273  				if !seenFiles[uri] {
   274  					seenFiles[uri] = true
   275  					files = append(files, uri)
   276  				}
   277  			}
   278  			updates[mp.ID] = mp
   279  			s.shouldLoad.Delete(mp.ID)
   280  		}
   281  	}
   282  
   283  	event.Log(ctx, fmt.Sprintf("%s: updating metadata for %d packages", eventName, len(updates)))
   284  
   285  	meta := s.meta.Update(updates)
   286  	workspacePackages := computeWorkspacePackagesLocked(s, meta)
   287  	s.meta = meta
   288  	s.workspacePackages = workspacePackages
   289  	s.resetActivePackagesLocked()
   290  
   291  	s.mu.Unlock()
   292  
   293  	// Opt: preLoad files in parallel.
   294  	//
   295  	// Requesting files in batch optimizes the underlying filesystem reads.
   296  	// However, this is also currently necessary for correctness: populating all
   297  	// files in the snapshot is necessary for certain operations that rely on the
   298  	// completeness of the file map, e.g. computing the set of directories to
   299  	// watch.
   300  	//
   301  	// TODO(rfindley, golang/go#57558): determine the set of directories based on
   302  	// loaded packages, so that reading files here is not necessary for
   303  	// correctness.
   304  	s.preloadFiles(ctx, files)
   305  
   306  	if len(moduleErrs) > 0 {
   307  		return &moduleErrorMap{moduleErrs}
   308  	}
   309  
   310  	return nil
   311  }
   312  
   313  type moduleErrorMap struct {
   314  	errs map[string][]packages.Error // module path -> errors
   315  }
   316  
   317  func (m *moduleErrorMap) Error() string {
   318  	var paths []string // sort for stability
   319  	for path, errs := range m.errs {
   320  		if len(errs) > 0 { // should always be true, but be cautious
   321  			paths = append(paths, path)
   322  		}
   323  	}
   324  	sort.Strings(paths)
   325  
   326  	var buf bytes.Buffer
   327  	fmt.Fprintf(&buf, "%d modules have errors:\n", len(paths))
   328  	for _, path := range paths {
   329  		fmt.Fprintf(&buf, "\t%s:%s\n", path, m.errs[path][0].Msg)
   330  	}
   331  
   332  	return buf.String()
   333  }
   334  
   335  // buildMetadata populates the updates map with metadata updates to
   336  // apply, based on the given pkg. It recurs through pkg.Imports to ensure that
   337  // metadata exists for all dependencies.
   338  func buildMetadata(updates map[PackageID]*metadata.Package, pkg *packages.Package, loadDir string, standalone bool) {
   339  	// Allow for multiple ad-hoc packages in the workspace (see #47584).
   340  	pkgPath := PackagePath(pkg.PkgPath)
   341  	id := PackageID(pkg.ID)
   342  
   343  	if metadata.IsCommandLineArguments(id) {
   344  		var f string // file to use as disambiguating suffix
   345  		if len(pkg.CompiledGoFiles) > 0 {
   346  			f = pkg.CompiledGoFiles[0]
   347  
   348  			// If there are multiple files,
   349  			// we can't use only the first.
   350  			// (Can this happen? #64557)
   351  			if len(pkg.CompiledGoFiles) > 1 {
   352  				bug.Reportf("unexpected files in command-line-arguments package: %v", pkg.CompiledGoFiles)
   353  				return
   354  			}
   355  		} else if len(pkg.IgnoredFiles) > 0 {
   356  			// A file=empty.go query results in IgnoredFiles=[empty.go].
   357  			f = pkg.IgnoredFiles[0]
   358  		} else {
   359  			bug.Reportf("command-line-arguments package has neither CompiledGoFiles nor IgnoredFiles: %#v", "") //*pkg.Metadata)
   360  			return
   361  		}
   362  		id = PackageID(pkg.ID + f)
   363  		pkgPath = PackagePath(pkg.PkgPath + f)
   364  	}
   365  
   366  	// Duplicate?
   367  	if _, ok := updates[id]; ok {
   368  		// A package was encountered twice due to shared
   369  		// subgraphs (common) or cycles (rare). Although "go
   370  		// list" usually breaks cycles, we don't rely on it.
   371  		// breakImportCycles in metadataGraph.Clone takes care
   372  		// of it later.
   373  		return
   374  	}
   375  
   376  	if pkg.TypesSizes == nil {
   377  		panic(id + ".TypeSizes is nil")
   378  	}
   379  
   380  	// Recreate the metadata rather than reusing it to avoid locking.
   381  	mp := &metadata.Package{
   382  		ID:         id,
   383  		PkgPath:    pkgPath,
   384  		Name:       PackageName(pkg.Name),
   385  		ForTest:    PackagePath(packagesinternal.GetForTest(pkg)),
   386  		TypesSizes: pkg.TypesSizes,
   387  		LoadDir:    loadDir,
   388  		Module:     pkg.Module,
   389  		Errors:     pkg.Errors,
   390  		DepsErrors: packagesinternal.GetDepsErrors(pkg),
   391  		Standalone: standalone,
   392  	}
   393  
   394  	updates[id] = mp
   395  
   396  	for _, filename := range pkg.CompiledGoFiles {
   397  		uri := protocol.URIFromPath(filename)
   398  		mp.CompiledGoFiles = append(mp.CompiledGoFiles, uri)
   399  	}
   400  	for _, filename := range pkg.GoFiles {
   401  		uri := protocol.URIFromPath(filename)
   402  		mp.GoFiles = append(mp.GoFiles, uri)
   403  	}
   404  	for _, filename := range pkg.IgnoredFiles {
   405  		uri := protocol.URIFromPath(filename)
   406  		mp.IgnoredFiles = append(mp.IgnoredFiles, uri)
   407  	}
   408  
   409  	depsByImpPath := make(map[ImportPath]PackageID)
   410  	depsByPkgPath := make(map[PackagePath]PackageID)
   411  	for importPath, imported := range pkg.Imports {
   412  		importPath := ImportPath(importPath)
   413  
   414  		// It is not an invariant that importPath == imported.PkgPath.
   415  		// For example, package "net" imports "golang.org/x/net/dns/dnsmessage"
   416  		// which refers to the package whose ID and PkgPath are both
   417  		// "vendor/golang.org/x/net/dns/dnsmessage". Notice the ImportMap,
   418  		// which maps ImportPaths to PackagePaths:
   419  		//
   420  		// $ go list -json net vendor/golang.org/x/net/dns/dnsmessage
   421  		// {
   422  		// 	"ImportPath": "net",
   423  		// 	"Name": "net",
   424  		// 	"Imports": [
   425  		// 		"C",
   426  		// 		"vendor/golang.org/x/net/dns/dnsmessage",
   427  		// 		"vendor/golang.org/x/net/route",
   428  		// 		...
   429  		// 	],
   430  		// 	"ImportMap": {
   431  		// 		"golang.org/x/net/dns/dnsmessage": "vendor/golang.org/x/net/dns/dnsmessage",
   432  		// 		"golang.org/x/net/route": "vendor/golang.org/x/net/route"
   433  		// 	},
   434  		//      ...
   435  		// }
   436  		// {
   437  		// 	"ImportPath": "vendor/golang.org/x/net/dns/dnsmessage",
   438  		// 	"Name": "dnsmessage",
   439  		//      ...
   440  		// }
   441  		//
   442  		// (Beware that, for historical reasons, go list uses
   443  		// the JSON field "ImportPath" for the package's
   444  		// path--effectively the linker symbol prefix.)
   445  		//
   446  		// The example above is slightly special to go list
   447  		// because it's in the std module.  Otherwise,
   448  		// vendored modules are simply modules whose directory
   449  		// is vendor/ instead of GOMODCACHE, and the
   450  		// import path equals the package path.
   451  		//
   452  		// But in GOPATH (non-module) mode, it's possible for
   453  		// package vendoring to cause a non-identity ImportMap,
   454  		// as in this example:
   455  		//
   456  		// $ cd $HOME/src
   457  		// $ find . -type f
   458  		// ./b/b.go
   459  		// ./vendor/example.com/a/a.go
   460  		// $ cat ./b/b.go
   461  		// package b
   462  		// import _ "example.com/a"
   463  		// $ cat ./vendor/example.com/a/a.go
   464  		// package a
   465  		// $ GOPATH=$HOME GO111MODULE=off go list -json ./b | grep -A2 ImportMap
   466  		//     "ImportMap": {
   467  		//         "example.com/a": "vendor/example.com/a"
   468  		//     },
   469  
   470  		// Don't remember any imports with significant errors.
   471  		//
   472  		// The len=0 condition is a heuristic check for imports of
   473  		// non-existent packages (for which go/packages will create
   474  		// an edge to a synthesized node). The heuristic is unsound
   475  		// because some valid packages have zero files, for example,
   476  		// a directory containing only the file p_test.go defines an
   477  		// empty package p.
   478  		// TODO(adonovan): clarify this. Perhaps go/packages should
   479  		// report which nodes were synthesized.
   480  		if importPath != "unsafe" && len(imported.CompiledGoFiles) == 0 {
   481  			depsByImpPath[importPath] = "" // missing
   482  			continue
   483  		}
   484  
   485  		// Don't record self-import edges.
   486  		// (This simplifies metadataGraph's cycle check.)
   487  		if PackageID(imported.ID) == id {
   488  			if len(pkg.Errors) == 0 {
   489  				bug.Reportf("self-import without error in package %s", id)
   490  			}
   491  			continue
   492  		}
   493  
   494  		buildMetadata(updates, imported, loadDir, false) // only top level packages can be standalone
   495  
   496  		// Don't record edges to packages with no name, as they cause trouble for
   497  		// the importer (golang/go#60952).
   498  		//
   499  		// However, we do want to insert these packages into the update map
   500  		// (buildMetadata above), so that we get type-checking diagnostics for the
   501  		// invalid packages.
   502  		if imported.Name == "" {
   503  			depsByImpPath[importPath] = "" // missing
   504  			continue
   505  		}
   506  
   507  		depsByImpPath[importPath] = PackageID(imported.ID)
   508  		depsByPkgPath[PackagePath(imported.PkgPath)] = PackageID(imported.ID)
   509  	}
   510  	mp.DepsByImpPath = depsByImpPath
   511  	mp.DepsByPkgPath = depsByPkgPath
   512  
   513  	// m.Diagnostics is set later in the loading pass, using
   514  	// computeLoadDiagnostics.
   515  }
   516  
   517  // computeLoadDiagnostics computes and sets m.Diagnostics for the given metadata m.
   518  //
   519  // It should only be called during package handle construction in buildPackageHandle.
   520  func computeLoadDiagnostics(ctx context.Context, snapshot *Snapshot, mp *metadata.Package) []*Diagnostic {
   521  	var diags []*Diagnostic
   522  	for _, packagesErr := range mp.Errors {
   523  		// Filter out parse errors from go list. We'll get them when we
   524  		// actually parse, and buggy overlay support may generate spurious
   525  		// errors. (See TestNewModule_Issue38207.)
   526  		if strings.Contains(packagesErr.Msg, "expected '") {
   527  			continue
   528  		}
   529  		pkgDiags, err := goPackagesErrorDiagnostics(ctx, packagesErr, mp, snapshot)
   530  		if err != nil {
   531  			// There are certain cases where the go command returns invalid
   532  			// positions, so we cannot panic or even bug.Reportf here.
   533  			event.Error(ctx, "unable to compute positions for list errors", err, tag.Package.Of(string(mp.ID)))
   534  			continue
   535  		}
   536  		diags = append(diags, pkgDiags...)
   537  	}
   538  
   539  	// TODO(rfindley): this is buggy: an insignificant change to a modfile
   540  	// (or an unsaved modfile) could affect the position of deps errors,
   541  	// without invalidating the package.
   542  	depsDiags, err := depsErrors(ctx, snapshot, mp)
   543  	if err != nil {
   544  		if ctx.Err() == nil {
   545  			// TODO(rfindley): consider making this a bug.Reportf. depsErrors should
   546  			// not normally fail.
   547  			event.Error(ctx, "unable to compute deps errors", err, tag.Package.Of(string(mp.ID)))
   548  		}
   549  	} else {
   550  		diags = append(diags, depsDiags...)
   551  	}
   552  	return diags
   553  }
   554  
   555  // isWorkspacePackageLocked reports whether p is a workspace package for the
   556  // snapshot s.
   557  //
   558  // Workspace packages are packages that we consider the user to be actively
   559  // working on. As such, they are re-diagnosed on every keystroke, and searched
   560  // for various workspace-wide queries such as references or workspace symbols.
   561  //
   562  // See the commentary inline for a description of the workspace package
   563  // heuristics.
   564  //
   565  // s.mu must be held while calling this function.
   566  func isWorkspacePackageLocked(s *Snapshot, meta *metadata.Graph, pkg *metadata.Package) bool {
   567  	if metadata.IsCommandLineArguments(pkg.ID) {
   568  		// Ad-hoc command-line-arguments packages aren't workspace packages.
   569  		// With zero-config gopls (golang/go#57979) they should be very rare, as
   570  		// they should only arise when the user opens a file outside the workspace
   571  		// which isn't present in the import graph of a workspace package.
   572  		//
   573  		// Considering them as workspace packages tends to be racy, as they don't
   574  		// deterministically belong to any view.
   575  		if !pkg.Standalone {
   576  			return false
   577  		}
   578  
   579  		// If all the files contained in pkg have a real package, we don't need to
   580  		// keep pkg as a workspace package.
   581  		if allFilesHaveRealPackages(meta, pkg) {
   582  			return false
   583  		}
   584  
   585  		// For now, allow open standalone packages (i.e. go:build ignore) to be
   586  		// workspace packages, but this means they could belong to multiple views.
   587  		return containsOpenFileLocked(s, pkg)
   588  	}
   589  
   590  	// Apply filtering logic.
   591  	//
   592  	// Workspace packages must contain at least one non-filtered file.
   593  	filterFunc := s.view.filterFunc()
   594  	uris := make(map[protocol.DocumentURI]unit) // filtered package URIs
   595  	for _, uri := range slices.Concat(pkg.CompiledGoFiles, pkg.GoFiles) {
   596  		if !strings.Contains(string(uri), "/vendor/") && !filterFunc(uri) {
   597  			uris[uri] = struct{}{}
   598  		}
   599  	}
   600  	if len(uris) == 0 {
   601  		return false // no non-filtered files
   602  	}
   603  
   604  	// For non-module views (of type GOPATH or AdHoc), or if
   605  	// expandWorkspaceToModule is unset, workspace packages must be contained in
   606  	// the workspace folder.
   607  	//
   608  	// For module views (of type GoMod or GoWork), packages must in any case be
   609  	// in a workspace module (enforced below).
   610  	if !s.view.moduleMode() || !s.Options().ExpandWorkspaceToModule {
   611  		folder := s.view.folder.Dir.Path()
   612  		inFolder := false
   613  		for uri := range uris {
   614  			if pathutil.InDir(folder, uri.Path()) {
   615  				inFolder = true
   616  				break
   617  			}
   618  		}
   619  		if !inFolder {
   620  			return false
   621  		}
   622  	}
   623  
   624  	// In module mode, a workspace package must be contained in a workspace
   625  	// module.
   626  	if s.view.moduleMode() {
   627  		if pkg.Module == nil {
   628  			return false
   629  		}
   630  		modURI := protocol.URIFromPath(pkg.Module.GoMod)
   631  		_, ok := s.view.workspaceModFiles[modURI]
   632  		return ok
   633  	}
   634  
   635  	return true // an ad-hoc package or GOPATH package
   636  }
   637  
   638  // containsOpenFileLocked reports whether any file referenced by m is open in
   639  // the snapshot s.
   640  //
   641  // s.mu must be held while calling this function.
   642  func containsOpenFileLocked(s *Snapshot, mp *metadata.Package) bool {
   643  	uris := map[protocol.DocumentURI]struct{}{}
   644  	for _, uri := range mp.CompiledGoFiles {
   645  		uris[uri] = struct{}{}
   646  	}
   647  	for _, uri := range mp.GoFiles {
   648  		uris[uri] = struct{}{}
   649  	}
   650  
   651  	for uri := range uris {
   652  		fh, _ := s.files.get(uri)
   653  		if _, open := fh.(*overlay); open {
   654  			return true
   655  		}
   656  	}
   657  	return false
   658  }
   659  
   660  // computeWorkspacePackagesLocked computes workspace packages in the
   661  // snapshot s for the given metadata graph. The result does not
   662  // contain intermediate test variants.
   663  //
   664  // s.mu must be held while calling this function.
   665  func computeWorkspacePackagesLocked(s *Snapshot, meta *metadata.Graph) immutable.Map[PackageID, PackagePath] {
   666  	workspacePackages := make(map[PackageID]PackagePath)
   667  	for _, mp := range meta.Packages {
   668  		if !isWorkspacePackageLocked(s, meta, mp) {
   669  			continue
   670  		}
   671  
   672  		switch {
   673  		case mp.ForTest == "":
   674  			// A normal package.
   675  			workspacePackages[mp.ID] = mp.PkgPath
   676  		case mp.ForTest == mp.PkgPath, mp.ForTest+"_test" == mp.PkgPath:
   677  			// The test variant of some workspace package or its x_test.
   678  			// To load it, we need to load the non-test variant with -test.
   679  			//
   680  			// Notably, this excludes intermediate test variants from workspace
   681  			// packages.
   682  			assert(!mp.IsIntermediateTestVariant(), "unexpected ITV")
   683  			workspacePackages[mp.ID] = mp.ForTest
   684  		}
   685  	}
   686  	return immutable.MapOf(workspacePackages)
   687  }
   688  
   689  // allFilesHaveRealPackages reports whether all files referenced by m are
   690  // contained in a "real" package (not command-line-arguments).
   691  //
   692  // If m is valid but all "real" packages containing any file are invalid, this
   693  // function returns false.
   694  //
   695  // If m is not a command-line-arguments package, this is trivially true.
   696  func allFilesHaveRealPackages(g *metadata.Graph, mp *metadata.Package) bool {
   697  	n := len(mp.CompiledGoFiles)
   698  checkURIs:
   699  	for _, uri := range append(mp.CompiledGoFiles[0:n:n], mp.GoFiles...) {
   700  		for _, id := range g.IDs[uri] {
   701  			if !metadata.IsCommandLineArguments(id) {
   702  				continue checkURIs
   703  			}
   704  		}
   705  		return false
   706  	}
   707  	return true
   708  }
   709  
   710  func isTestMain(pkg *packages.Package, gocache string) bool {
   711  	// Test mains must have an import path that ends with ".test".
   712  	if !strings.HasSuffix(pkg.PkgPath, ".test") {
   713  		return false
   714  	}
   715  	// Test main packages are always named "main".
   716  	if pkg.Name != "main" {
   717  		return false
   718  	}
   719  	// Test mains always have exactly one GoFile that is in the build cache.
   720  	if len(pkg.GoFiles) > 1 {
   721  		return false
   722  	}
   723  	if !pathutil.InDir(gocache, pkg.GoFiles[0]) {
   724  		return false
   725  	}
   726  	return true
   727  }