cuelang.org/go@v0.13.0/internal/golangorgx/gopls/cache/view.go (about)

     1  // Copyright 2018 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 is the core of gopls: it is concerned with state
     6  // management, dependency analysis, and invalidation; and it holds the
     7  // machinery of type checking and modular static analysis. Its
     8  // principal types are [Session], [Folder], [View], [Snapshot],
     9  // [Cache], and [Package].
    10  package cache
    11  
    12  import (
    13  	"bytes"
    14  	"context"
    15  	"errors"
    16  	"fmt"
    17  	"path"
    18  	"path/filepath"
    19  	"regexp"
    20  	"slices"
    21  	"sort"
    22  	"strings"
    23  	"sync"
    24  
    25  	"cuelang.org/go/internal/golangorgx/gopls/cache/metadata"
    26  	"cuelang.org/go/internal/golangorgx/gopls/file"
    27  	"cuelang.org/go/internal/golangorgx/gopls/protocol"
    28  	"cuelang.org/go/internal/golangorgx/gopls/settings"
    29  	"cuelang.org/go/internal/golangorgx/gopls/util/maps"
    30  	"cuelang.org/go/internal/golangorgx/tools/event"
    31  	"cuelang.org/go/internal/golangorgx/tools/xcontext"
    32  	"cuelang.org/go/mod/modfile"
    33  )
    34  
    35  // A Folder represents an LSP workspace folder, together with its per-folder
    36  // options and environment variables that affect build configuration.
    37  //
    38  // Folders (Name and Dir) are specified by the 'initialize' and subsequent
    39  // 'didChangeWorkspaceFolders' requests; their options come from
    40  // didChangeConfiguration.
    41  //
    42  // Folders must not be mutated, as they may be shared across multiple views.
    43  type Folder struct {
    44  	Dir     protocol.DocumentURI
    45  	Name    string // decorative name for UI; not necessarily unique
    46  	Options *settings.Options
    47  }
    48  
    49  // GoEnv holds the environment variables and data from the Go command that is
    50  // required for operating on a workspace folder.
    51  type GoEnv struct {
    52  	// Go environment variables. These correspond directly with the Go env var of
    53  	// the same name.
    54  	GOOS        string
    55  	GOARCH      string
    56  	GOCACHE     string
    57  	GOMODCACHE  string
    58  	GOPATH      string
    59  	GOPRIVATE   string
    60  	GOFLAGS     string
    61  	GO111MODULE string
    62  
    63  	// Go version output.
    64  	GoVersion       int    // The X in Go 1.X
    65  	GoVersionOutput string // complete go version output
    66  
    67  	// OS environment variables (notably not go env).
    68  	GOWORK           string
    69  	GOPACKAGESDRIVER string
    70  }
    71  
    72  // View represents a single build for a workspace.
    73  //
    74  // A View is a logical build (the viewDefinition) along with a state of that
    75  // build (the Snapshot).
    76  type View struct {
    77  	id string // a unique string to identify this View in (e.g.) serialized Commands
    78  
    79  	*viewDefinition // build configuration
    80  
    81  	// baseCtx is the context handed to NewView. This is the parent of all
    82  	// background contexts created for this view.
    83  	baseCtx context.Context
    84  
    85  	importsState *importsState
    86  
    87  	// parseCache holds an LRU cache of recently parsed files.
    88  	parseCache *parseCache
    89  
    90  	// fs is the file source used to populate this view.
    91  	fs *overlayFS
    92  
    93  	// cancelInitialWorkspaceLoad can be used to terminate the view's first
    94  	// attempt at initialization.
    95  	cancelInitialWorkspaceLoad context.CancelFunc
    96  
    97  	snapshotMu sync.Mutex
    98  	snapshot   *Snapshot // latest snapshot; nil after shutdown has been called
    99  
   100  	// initialWorkspaceLoad is closed when the first workspace initialization has
   101  	// completed. If we failed to load, we only retry if the go.mod file changes,
   102  	// to avoid too many go/packages calls.
   103  	initialWorkspaceLoad chan struct{}
   104  
   105  	// initializationSema is used limit concurrent initialization of snapshots in
   106  	// the view. We use a channel instead of a mutex to avoid blocking when a
   107  	// context is canceled.
   108  	//
   109  	// This field (along with snapshot.initialized) guards against duplicate
   110  	// initialization of snapshots. Do not change it without adjusting snapshot
   111  	// accordingly.
   112  	initializationSema chan struct{}
   113  
   114  	// Document filters are constructed once, in View.filterFunc.
   115  	filterFuncOnce sync.Once
   116  	_filterFunc    func(protocol.DocumentURI) bool // only accessed by View.filterFunc
   117  }
   118  
   119  // definition implements the viewDefiner interface.
   120  func (v *View) definition() *viewDefinition { return v.viewDefinition }
   121  
   122  // A viewDefinition is a logical build, i.e. configuration (Folder) along with
   123  // a build directory and possibly an environment overlay (e.g. GOWORK=off or
   124  // GOOS, GOARCH=...) to affect the build.
   125  //
   126  // This type is immutable, and compared to see if the View needs to be
   127  // reconstructed.
   128  //
   129  // Note: whenever modifying this type, also modify the equivalence relation
   130  // implemented by viewDefinitionsEqual.
   131  //
   132  // TODO(golang/go#57979): viewDefinition should be sufficient for running
   133  // go/packages. Enforce this in the API.
   134  type viewDefinition struct {
   135  	folder *Folder // pointer comparison is OK, as any new Folder creates a new def
   136  
   137  	typ ViewType
   138  
   139  	// root represents the directory root of the CUE module that contains
   140  	// the WorkspaceFolder folder
   141  	root   protocol.DocumentURI
   142  	cuemod protocol.DocumentURI // the nearest cue.mod/module.cue file, or ""
   143  
   144  	// workspaceModFiles holds the set of cue.mod/module.cue files
   145  	// active in this snapshot.
   146  	//
   147  	// For a go.work workspace, this is the set of workspace modfiles. For a
   148  	// go.mod workspace, this contains the go.mod file defining the workspace
   149  	// root, as well as any locally replaced modules (if
   150  	// "includeReplaceInWorkspace" is set).
   151  	workspaceModFiles    map[protocol.DocumentURI]struct{}
   152  	workspaceModFilesErr error // error encountered computing workspaceModFiles
   153  
   154  	// envOverlay holds additional environment to apply to this viewDefinition.
   155  	envOverlay map[string]string
   156  }
   157  
   158  // definition implements the viewDefiner interface.
   159  func (d *viewDefinition) definition() *viewDefinition { return d }
   160  
   161  // Type returns the ViewType type, which determines how go/packages are loaded
   162  // for this View.
   163  func (d *viewDefinition) Type() ViewType { return d.typ }
   164  
   165  // Root returns the view root, which determines where packages are loaded from.
   166  func (d *viewDefinition) Root() protocol.DocumentURI { return d.root }
   167  
   168  // EnvOverlay returns a new sorted slice of environment variables (in the form
   169  // "k=v") for this view definition's env overlay.
   170  func (d *viewDefinition) EnvOverlay() []string {
   171  	var env []string
   172  	for k, v := range d.envOverlay {
   173  		env = append(env, fmt.Sprintf("%s=%s", k, v))
   174  	}
   175  	sort.Strings(env)
   176  	return env
   177  }
   178  
   179  // ModFiles are the cue.mod/module.cue files enclosed in the
   180  // snapshot's view and known to the snapshot.
   181  func (d viewDefinition) ModFiles() []protocol.DocumentURI {
   182  	var uris []protocol.DocumentURI
   183  	for modURI := range d.workspaceModFiles {
   184  		uris = append(uris, modURI)
   185  	}
   186  	return uris
   187  }
   188  
   189  // viewDefinitionsEqual reports whether x and y are equivalent.
   190  func viewDefinitionsEqual(x, y *viewDefinition) bool {
   191  	if (x.workspaceModFilesErr == nil) != (y.workspaceModFilesErr == nil) {
   192  		return false
   193  	}
   194  	if x.workspaceModFilesErr != nil {
   195  		if x.workspaceModFilesErr.Error() != y.workspaceModFilesErr.Error() {
   196  			return false
   197  		}
   198  	} else if !maps.SameKeys(x.workspaceModFiles, y.workspaceModFiles) {
   199  		return false
   200  	}
   201  	if len(x.envOverlay) != len(y.envOverlay) {
   202  		return false
   203  	}
   204  	for i, xv := range x.envOverlay {
   205  		if xv != y.envOverlay[i] {
   206  			return false
   207  		}
   208  	}
   209  	return x.folder == y.folder &&
   210  		x.typ == y.typ &&
   211  		x.root == y.root
   212  }
   213  
   214  // A ViewType describes how we load package information for a view.
   215  //
   216  // This is used for constructing the go/packages.Load query, and for
   217  // interpreting missing packages, imports, or errors.
   218  //
   219  // See the documentation for individual ViewType values for details.
   220  type ViewType int
   221  
   222  const (
   223  	// An AdHocView is a collection of files in a given directory, not in GOPATH
   224  	// or a module.
   225  	//
   226  	// Load: . from the workspace folder.
   227  	AdHocView ViewType = iota
   228  
   229  	CUEModView
   230  )
   231  
   232  func (t ViewType) String() string {
   233  	switch t {
   234  	case AdHocView:
   235  		return "AdHocView"
   236  	case CUEModView:
   237  		return "CUEModView"
   238  	default:
   239  		return "Unknown"
   240  	}
   241  }
   242  
   243  // moduleMode reports whether the view uses Go modules.
   244  func (w viewDefinition) moduleMode() bool {
   245  	switch w.typ {
   246  	case CUEModView:
   247  		return true
   248  	default:
   249  		return false
   250  	}
   251  }
   252  
   253  func (v *View) ID() string { return v.id }
   254  
   255  // Folder returns the folder at the base of this view.
   256  func (v *View) Folder() *Folder {
   257  	return v.folder
   258  }
   259  
   260  // UpdateFolders updates the set of views for the new folders.
   261  //
   262  // Calling this causes each view to be reinitialized.
   263  func (s *Session) UpdateFolders(ctx context.Context, newFolders []*Folder) error {
   264  	s.viewMu.Lock()
   265  	defer s.viewMu.Unlock()
   266  
   267  	overlays := s.Overlays()
   268  	var openFiles []protocol.DocumentURI
   269  	for _, o := range overlays {
   270  		openFiles = append(openFiles, o.URI())
   271  	}
   272  
   273  	defs, err := selectViewDefs(ctx, s, newFolders, openFiles)
   274  	if err != nil {
   275  		return err
   276  	}
   277  	var newViews []*View
   278  	for _, def := range defs {
   279  		v, _, release := s.createView(ctx, def)
   280  		release()
   281  		newViews = append(newViews, v)
   282  	}
   283  	for _, v := range s.views {
   284  		v.shutdown()
   285  	}
   286  	s.views = newViews
   287  	return nil
   288  }
   289  
   290  // viewEnv returns a string describing the environment of a newly created view.
   291  //
   292  // It must not be called concurrently with any other view methods.
   293  // TODO(rfindley): rethink this function, or inline sole call.
   294  func viewEnv(v *View) string {
   295  	var buf bytes.Buffer
   296  	fmt.Fprintf(&buf, `go info for %v
   297  (view type %v)
   298  (root dir %s)
   299  (build flags: %v)
   300  (env overlay: %v)
   301  `,
   302  		v.folder.Dir.Path(),
   303  		v.typ,
   304  		v.root.Path(),
   305  		v.folder.Options.BuildFlags,
   306  		v.envOverlay,
   307  	)
   308  
   309  	return buf.String()
   310  }
   311  
   312  // separated out from its sole use in locateTemplateFiles for testability
   313  func fileHasExtension(path string, suffixes []string) bool {
   314  	ext := filepath.Ext(path)
   315  	if ext != "" && ext[0] == '.' {
   316  		ext = ext[1:]
   317  	}
   318  	for _, s := range suffixes {
   319  		if s != "" && ext == s {
   320  			return true
   321  		}
   322  	}
   323  	return false
   324  }
   325  
   326  // filterFunc returns a func that reports whether uri is filtered by the currently configured
   327  // directoryFilters.
   328  func (v *View) filterFunc() func(protocol.DocumentURI) bool {
   329  	v.filterFuncOnce.Do(func() {
   330  		v._filterFunc = func(uri protocol.DocumentURI) bool {
   331  			return false
   332  		}
   333  	})
   334  	return v._filterFunc
   335  }
   336  
   337  // shutdown releases resources associated with the view.
   338  func (v *View) shutdown() {
   339  	// Cancel the initial workspace load if it is still running.
   340  	v.cancelInitialWorkspaceLoad()
   341  
   342  	v.snapshotMu.Lock()
   343  	if v.snapshot != nil {
   344  		v.snapshot.cancel()
   345  		v.snapshot.decref()
   346  		v.snapshot = nil
   347  	}
   348  	v.snapshotMu.Unlock()
   349  }
   350  
   351  // Snapshot returns the current snapshot for the view, and a
   352  // release function that must be called when the Snapshot is
   353  // no longer needed.
   354  //
   355  // The resulting error is non-nil if and only if the view is shut down, in
   356  // which case the resulting release function will also be nil.
   357  func (v *View) Snapshot() (*Snapshot, func(), error) {
   358  	v.snapshotMu.Lock()
   359  	defer v.snapshotMu.Unlock()
   360  	if v.snapshot == nil {
   361  		return nil, nil, errors.New("view is shutdown")
   362  	}
   363  	return v.snapshot, v.snapshot.Acquire(), nil
   364  }
   365  
   366  // initialize loads the metadata (and currently, file contents, due to
   367  // golang/go#57558) for the main package query of the View, which depends on
   368  // the view type (see ViewType). If s.initialized is already true, initialize
   369  // is a no op.
   370  //
   371  // The first attempt--which populates the first snapshot for a new view--must
   372  // be allowed to run to completion without being cancelled.
   373  //
   374  // Subsequent attempts are triggered by conditions where gopls can't enumerate
   375  // specific packages that require reloading, such as a change to a go.mod file.
   376  // These attempts may be cancelled, and then retried by a later call.
   377  //
   378  // Postcondition: if ctx was not cancelled, s.initialized is true, s.initialErr
   379  // holds the error resulting from initialization, if any, and s.metadata holds
   380  // the resulting metadata graph.
   381  func (s *Snapshot) initialize(ctx context.Context, firstAttempt bool) {
   382  	// Acquire initializationSema, which is
   383  	// (in effect) a mutex with a timeout.
   384  	select {
   385  	case <-ctx.Done():
   386  		return
   387  	case s.view.initializationSema <- struct{}{}:
   388  	}
   389  
   390  	defer func() {
   391  		<-s.view.initializationSema
   392  	}()
   393  
   394  	s.mu.Lock()
   395  	initialized := s.initialized
   396  	s.mu.Unlock()
   397  
   398  	if initialized {
   399  		return
   400  	}
   401  
   402  	defer func() {
   403  		if firstAttempt {
   404  			close(s.view.initialWorkspaceLoad)
   405  		}
   406  	}()
   407  
   408  	var scopes []loadScope           // scopes to load
   409  	var modDiagnostics []*Diagnostic // diagnostics for broken cue.mod/module.cue files
   410  	addError := func(uri protocol.DocumentURI, err error) {
   411  		modDiagnostics = append(modDiagnostics, &Diagnostic{
   412  			URI:      uri,
   413  			Severity: protocol.SeverityError,
   414  			Source:   ListError,
   415  			Message:  err.Error(),
   416  		})
   417  	}
   418  
   419  	if len(s.view.workspaceModFiles) > 0 {
   420  		for modURI := range s.view.workspaceModFiles {
   421  			fh, err := s.ReadFile(ctx, modURI)
   422  			if err != nil {
   423  				if ctx.Err() != nil {
   424  					return
   425  				}
   426  				addError(modURI, err)
   427  				continue
   428  			}
   429  			modContent, err := fh.Content()
   430  			if err != nil {
   431  				if ctx.Err() != nil {
   432  					return
   433  				}
   434  				addError(modURI, err)
   435  				continue
   436  			}
   437  
   438  			parsed, err := modfile.ParseNonStrict(modContent, "module.cue")
   439  			if err != nil {
   440  				if ctx.Err() != nil {
   441  					return
   442  				}
   443  				addError(modURI, err)
   444  				continue
   445  			}
   446  			rootDir := filepath.Dir(filepath.Dir(modURI.Path()))
   447  			scopes = append(scopes, moduleLoadScope{dir: rootDir, modulePath: parsed.ModulePath()})
   448  		}
   449  	} else {
   450  		scopes = append(scopes, viewLoadScope{})
   451  	}
   452  
   453  	loadErr := s.load(ctx, true, scopes...)
   454  
   455  	// A failure is retryable if it may have been due to context
   456  	// cancellation, and this is not the initial workspace load
   457  	// (firstAttempt==true).
   458  	//
   459  	// The Initial Workspace Load (IWL) runs on a detached context with
   460  	// a long (~10m) timeout, so if the context was canceled we
   461  	// consider loading to have failed permanently.
   462  	if loadErr != nil && ctx.Err() != nil && !firstAttempt {
   463  		return
   464  	}
   465  
   466  	var initialErr *InitializationError
   467  	switch {
   468  	case loadErr != nil:
   469  		event.Error(ctx, fmt.Sprintf("initial workspace load: %v", loadErr), loadErr)
   470  		initialErr = &InitializationError{
   471  			MainError: loadErr,
   472  		}
   473  	case s.view.workspaceModFilesErr != nil:
   474  		initialErr = &InitializationError{
   475  			MainError: s.view.workspaceModFilesErr,
   476  		}
   477  	case len(modDiagnostics) > 0:
   478  		initialErr = &InitializationError{
   479  			MainError: fmt.Errorf(modDiagnostics[0].Message),
   480  		}
   481  	}
   482  
   483  	s.mu.Lock()
   484  	defer s.mu.Unlock()
   485  
   486  	s.initialized = true
   487  	s.initialErr = initialErr
   488  }
   489  
   490  // A StateChange describes external state changes that may affect a snapshot.
   491  //
   492  // By far the most common of these is a change to file state, but a query of
   493  // module upgrade information or vulnerabilities also affects gopls' behavior.
   494  type StateChange struct {
   495  	Modifications  []file.Modification // if set, the raw modifications originating this change
   496  	Files          map[protocol.DocumentURI]file.Handle
   497  	ModuleUpgrades map[protocol.DocumentURI]map[string]string
   498  	GCDetails      map[metadata.PackageID]bool // package -> whether or not we want details
   499  }
   500  
   501  // InvalidateView processes the provided state change, invalidating any derived
   502  // results that depend on the changed state.
   503  //
   504  // The resulting snapshot is non-nil, representing the outcome of the state
   505  // change. The second result is a function that must be called to release the
   506  // snapshot when the snapshot is no longer needed.
   507  //
   508  // An error is returned if the given view is no longer active in the session.
   509  func (s *Session) InvalidateView(ctx context.Context, view *View, changed StateChange) (*Snapshot, func(), error) {
   510  	s.viewMu.Lock()
   511  	defer s.viewMu.Unlock()
   512  
   513  	if !slices.Contains(s.views, view) {
   514  		return nil, nil, fmt.Errorf("view is no longer active")
   515  	}
   516  	snapshot, release, _ := s.invalidateViewLocked(ctx, view, changed)
   517  	return snapshot, release, nil
   518  }
   519  
   520  // invalidateViewLocked invalidates the content of the given view.
   521  // (See [Session.InvalidateView]).
   522  //
   523  // The resulting bool reports whether the View needs to be re-diagnosed.
   524  // (See [Snapshot.clone]).
   525  //
   526  // s.viewMu must be held while calling this method.
   527  func (s *Session) invalidateViewLocked(ctx context.Context, v *View, changed StateChange) (*Snapshot, func(), bool) {
   528  	// Detach the context so that content invalidation cannot be canceled.
   529  	ctx = xcontext.Detach(ctx)
   530  
   531  	// This should be the only time we hold the view's snapshot lock for any period of time.
   532  	v.snapshotMu.Lock()
   533  	defer v.snapshotMu.Unlock()
   534  
   535  	prevSnapshot := v.snapshot
   536  
   537  	if prevSnapshot == nil {
   538  		panic("invalidateContent called after shutdown")
   539  	}
   540  
   541  	// Cancel all still-running previous requests, since they would be
   542  	// operating on stale data.
   543  	prevSnapshot.cancel()
   544  
   545  	// Do not clone a snapshot until its view has finished initializing.
   546  	//
   547  	// TODO(rfindley): shouldn't we do this before canceling?
   548  	prevSnapshot.AwaitInitialized(ctx)
   549  
   550  	var needsDiagnosis bool
   551  	s.snapshotWG.Add(1)
   552  	v.snapshot, needsDiagnosis = prevSnapshot.clone(ctx, v.baseCtx, changed, s.snapshotWG.Done)
   553  
   554  	// Remove the initial reference created when prevSnapshot was created.
   555  	prevSnapshot.decref()
   556  
   557  	// Return a second lease to the caller.
   558  	return v.snapshot, v.snapshot.Acquire(), needsDiagnosis
   559  }
   560  
   561  // defineView computes the view definition for the provided workspace folder
   562  // and URI.
   563  //
   564  // If forFile is non-empty, this view should be the best view including forFile.
   565  // Otherwise, it is the default view for the folder. Per below TODO(myitcv), we
   566  // need to better understand when this can happen, and what the preceding sentence
   567  // actually means.
   568  //
   569  // defineView only returns an error in the event of context cancellation.
   570  //
   571  // gopls note: keep this function in sync with bestView.
   572  func defineView(ctx context.Context, fs file.Source, folder *Folder, forFile file.Handle) (*viewDefinition, error) {
   573  	if err := checkPathValid(folder.Dir.Path()); err != nil {
   574  		return nil, fmt.Errorf("invalid workspace folder path: %w; check that the spelling of the configured workspace folder path agrees with the spelling reported by the operating system", err)
   575  	}
   576  	dir := folder.Dir.Path()
   577  
   578  	if forFile != nil {
   579  		// TODO(myitcv): fix the implementation here. forFile != nil when we are trying
   580  		// to compute the set of views given the set of open files/known folders. This is
   581  		// part of the zero config approach in gopls, and we don't have anything like that
   582  		// yet for 'cue lsp'.
   583  		return nil, fmt.Errorf("defineView with forFile != nil; not yet supported")
   584  	}
   585  
   586  	def := new(viewDefinition)
   587  	def.folder = folder
   588  
   589  	var err error
   590  	dirURI := protocol.URIFromPath(dir)
   591  	moduleCue, err := findRootPattern(ctx, dirURI, filepath.FromSlash("cue.mod/module.cue"), fs)
   592  	if err != nil {
   593  		return nil, err
   594  	}
   595  	if moduleCue == "" {
   596  		// We found no module, and currently we only support workspaces with modules.
   597  		return nil, fmt.Errorf("WorkspaceFolder %s does not correspond to a CUE module", folder.Dir.Path())
   598  	}
   599  	def.cuemod = moduleCue
   600  
   601  	def.typ = CUEModView
   602  	def.root = def.cuemod.Dir().Dir()
   603  	if def.root != dirURI {
   604  		return nil, fmt.Errorf("WorkspaceFolder %s does not correspond to a CUE module", folder.Dir.Path())
   605  	}
   606  	def.workspaceModFiles = map[protocol.DocumentURI]struct{}{def.cuemod: {}}
   607  
   608  	return def, nil
   609  }
   610  
   611  // findRootPattern looks for files with the given basename in dir or any parent
   612  // directory of dir, using the provided FileSource. It returns the first match,
   613  // starting from dir and search parents.
   614  //
   615  // The resulting string is either the file path of a matching file with the
   616  // given basename, or "" if none was found.
   617  //
   618  // findRootPattern only returns an error in the case of context cancellation.
   619  func findRootPattern(ctx context.Context, dirURI protocol.DocumentURI, basename string, fs file.Source) (protocol.DocumentURI, error) {
   620  	dir := dirURI.Path()
   621  	for dir != "" {
   622  		target := filepath.Join(dir, basename)
   623  		uri := protocol.URIFromPath(target)
   624  		fh, err := fs.ReadFile(ctx, uri)
   625  		if err != nil {
   626  			return "", err // context cancelled
   627  		}
   628  		if fileExists(fh) {
   629  			return uri, nil
   630  		}
   631  		// Trailing separators must be trimmed, otherwise filepath.Split is a noop.
   632  		next, _ := filepath.Split(strings.TrimRight(dir, string(filepath.Separator)))
   633  		if next == dir {
   634  			break
   635  		}
   636  		dir = next
   637  	}
   638  	return "", nil
   639  }
   640  
   641  // checkPathValid performs an OS-specific path validity check. The
   642  // implementation varies for filesystems that are case-insensitive
   643  // (e.g. macOS, Windows), and for those that disallow certain file
   644  // names (e.g. path segments ending with a period on Windows, or
   645  // reserved names such as "com"; see
   646  // https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file).
   647  var checkPathValid = defaultCheckPathValid
   648  
   649  // CheckPathValid checks whether a directory is suitable as a workspace folder.
   650  func CheckPathValid(dir string) error { return checkPathValid(dir) }
   651  
   652  func defaultCheckPathValid(path string) error {
   653  	return nil
   654  }
   655  
   656  // Copied from
   657  // https://cs.opensource.google/go/go/+/master:src/cmd/go/internal/str/path.go;l=58;drc=2910c5b4a01a573ebc97744890a07c1a3122c67a
   658  func globsMatchPath(globs, target string) bool {
   659  	for globs != "" {
   660  		// Extract next non-empty glob in comma-separated list.
   661  		var glob string
   662  		if i := strings.Index(globs, ","); i >= 0 {
   663  			glob, globs = globs[:i], globs[i+1:]
   664  		} else {
   665  			glob, globs = globs, ""
   666  		}
   667  		if glob == "" {
   668  			continue
   669  		}
   670  
   671  		// A glob with N+1 path elements (N slashes) needs to be matched
   672  		// against the first N+1 path elements of target,
   673  		// which end just before the N+1'th slash.
   674  		n := strings.Count(glob, "/")
   675  		prefix := target
   676  		// Walk target, counting slashes, truncating at the N+1'th slash.
   677  		for i := 0; i < len(target); i++ {
   678  			if target[i] == '/' {
   679  				if n == 0 {
   680  					prefix = target[:i]
   681  					break
   682  				}
   683  				n--
   684  			}
   685  		}
   686  		if n > 0 {
   687  			// Not enough prefix elements.
   688  			continue
   689  		}
   690  		matched, _ := path.Match(glob, prefix)
   691  		if matched {
   692  			return true
   693  		}
   694  	}
   695  	return false
   696  }
   697  
   698  var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`)
   699  
   700  // TODO(rfindley): clean up the redundancy of allFilesExcluded,
   701  // pathExcludedByFilterFunc, pathExcludedByFilter, view.filterFunc...
   702  func allFilesExcluded(files []string, filterFunc func(protocol.DocumentURI) bool) bool {
   703  	for _, f := range files {
   704  		uri := protocol.URIFromPath(f)
   705  		if !filterFunc(uri) {
   706  			return false
   707  		}
   708  	}
   709  	return true
   710  }
   711  
   712  func relPathExcludedByFilter(path string, filterer *Filterer) bool {
   713  	path = strings.TrimPrefix(filepath.ToSlash(path), "/")
   714  	return filterer.Disallow(path)
   715  }