github.com/jd-ly/tools@v0.5.7/internal/lsp/source/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 source
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"fmt"
    11  	"go/ast"
    12  	"go/token"
    13  	"go/types"
    14  	"io"
    15  	"strings"
    16  
    17  	"golang.org/x/mod/modfile"
    18  	"golang.org/x/mod/module"
    19  	"github.com/jd-ly/tools/go/analysis"
    20  	"github.com/jd-ly/tools/internal/gocommand"
    21  	"github.com/jd-ly/tools/internal/imports"
    22  	"github.com/jd-ly/tools/internal/lsp/protocol"
    23  	"github.com/jd-ly/tools/internal/span"
    24  	errors "golang.org/x/xerrors"
    25  )
    26  
    27  // Snapshot represents the current state for the given view.
    28  type Snapshot interface {
    29  	ID() uint64
    30  
    31  	// View returns the View associated with this snapshot.
    32  	View() View
    33  
    34  	// BackgroundContext returns a context used for all background processing
    35  	// on behalf of this snapshot.
    36  	BackgroundContext() context.Context
    37  
    38  	// Fileset returns the Fileset used to parse all the Go files in this snapshot.
    39  	FileSet() *token.FileSet
    40  
    41  	// ValidBuildConfiguration returns true if there is some error in the
    42  	// user's workspace. In particular, if they are both outside of a module
    43  	// and their GOPATH.
    44  	ValidBuildConfiguration() bool
    45  
    46  	// WriteEnv writes the view-specific environment to the io.Writer.
    47  	WriteEnv(ctx context.Context, w io.Writer) error
    48  
    49  	// FindFile returns the FileHandle for the given URI, if it is already
    50  	// in the given snapshot.
    51  	FindFile(uri span.URI) VersionedFileHandle
    52  
    53  	// GetVersionedFile returns the VersionedFileHandle for a given URI,
    54  	// initializing it if it is not already part of the snapshot.
    55  	GetVersionedFile(ctx context.Context, uri span.URI) (VersionedFileHandle, error)
    56  
    57  	// GetFile returns the FileHandle for a given URI, initializing it if it is
    58  	// not already part of the snapshot.
    59  	GetFile(ctx context.Context, uri span.URI) (FileHandle, error)
    60  
    61  	// AwaitInitialized waits until the snapshot's view is initialized.
    62  	AwaitInitialized(ctx context.Context)
    63  
    64  	// IsOpen returns whether the editor currently has a file open.
    65  	IsOpen(uri span.URI) bool
    66  
    67  	// IgnoredFile reports if a file would be ignored by a `go list` of the whole
    68  	// workspace.
    69  	IgnoredFile(uri span.URI) bool
    70  
    71  	// ParseGo returns the parsed AST for the file.
    72  	// If the file is not available, returns nil and an error.
    73  	ParseGo(ctx context.Context, fh FileHandle, mode ParseMode) (*ParsedGoFile, error)
    74  
    75  	// PosToField is a cache of *ast.Fields by token.Pos. This allows us
    76  	// to quickly find corresponding *ast.Field node given a *types.Var.
    77  	// We must refer to the AST to render type aliases properly when
    78  	// formatting signatures and other types.
    79  	PosToField(ctx context.Context, pgf *ParsedGoFile) (map[token.Pos]*ast.Field, error)
    80  
    81  	// PosToDecl maps certain objects' positions to their surrounding
    82  	// ast.Decl. This mapping is used when building the documentation
    83  	// string for the objects.
    84  	PosToDecl(ctx context.Context, pgf *ParsedGoFile) (map[token.Pos]ast.Decl, error)
    85  
    86  	// Analyze runs the analyses for the given package at this snapshot.
    87  	Analyze(ctx context.Context, pkgID string, analyzers ...*analysis.Analyzer) ([]*Error, error)
    88  
    89  	// RunGoCommandPiped runs the given `go` command, writing its output
    90  	// to stdout and stderr. Verb, Args, and WorkingDir must be specified.
    91  	RunGoCommandPiped(ctx context.Context, mode InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error
    92  
    93  	// RunGoCommandDirect runs the given `go` command. Verb, Args, and
    94  	// WorkingDir must be specified.
    95  	RunGoCommandDirect(ctx context.Context, mode InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error)
    96  
    97  	// RunProcessEnvFunc runs fn with the process env for this snapshot's view.
    98  	// Note: the process env contains cached module and filesystem state.
    99  	RunProcessEnvFunc(ctx context.Context, fn func(*imports.Options) error) error
   100  
   101  	// ModFiles are the go.mod files enclosed in the snapshot's view and known
   102  	// to the snapshot.
   103  	ModFiles() []span.URI
   104  
   105  	// ParseMod is used to parse go.mod files.
   106  	ParseMod(ctx context.Context, fh FileHandle) (*ParsedModule, error)
   107  
   108  	// ModWhy returns the results of `go mod why` for the module specified by
   109  	// the given go.mod file.
   110  	ModWhy(ctx context.Context, fh FileHandle) (map[string]string, error)
   111  
   112  	// ModUpgrade returns the possible updates for the module specified by the
   113  	// given go.mod file.
   114  	ModUpgrade(ctx context.Context, fh FileHandle) (map[string]string, error)
   115  
   116  	// ModTidy returns the results of `go mod tidy` for the module specified by
   117  	// the given go.mod file.
   118  	ModTidy(ctx context.Context, pm *ParsedModule) (*TidiedModule, error)
   119  
   120  	// GoModForFile returns the URI of the go.mod file for the given URI.
   121  	GoModForFile(ctx context.Context, uri span.URI) span.URI
   122  
   123  	// BuiltinPackage returns information about the special builtin package.
   124  	BuiltinPackage(ctx context.Context) (*BuiltinPackage, error)
   125  
   126  	// PackagesForFile returns the packages that this file belongs to, checked
   127  	// in mode.
   128  	PackagesForFile(ctx context.Context, uri span.URI, mode TypecheckMode) ([]Package, error)
   129  
   130  	// PackageForFile returns a single package that this file belongs to,
   131  	// checked in mode and filtered by the package policy.
   132  	PackageForFile(ctx context.Context, uri span.URI, mode TypecheckMode, selectPackage PackageFilter) (Package, error)
   133  
   134  	// GetActiveReverseDeps returns the active files belonging to the reverse
   135  	// dependencies of this file's package, checked in TypecheckWorkspace mode.
   136  	GetReverseDependencies(ctx context.Context, id string) ([]Package, error)
   137  
   138  	// CachedImportPaths returns all the imported packages loaded in this
   139  	// snapshot, indexed by their import path and checked in TypecheckWorkspace
   140  	// mode.
   141  	CachedImportPaths(ctx context.Context) (map[string]Package, error)
   142  
   143  	// KnownPackages returns all the packages loaded in this snapshot, checked
   144  	// in TypecheckWorkspace mode.
   145  	KnownPackages(ctx context.Context) ([]Package, error)
   146  
   147  	// WorkspacePackages returns the snapshot's top-level packages.
   148  	WorkspacePackages(ctx context.Context) ([]Package, error)
   149  }
   150  
   151  // PackageFilter sets how a package is filtered out from a set of packages
   152  // containing a given file.
   153  type PackageFilter int
   154  
   155  const (
   156  	// NarrowestPackage picks the "narrowest" package for a given file.
   157  	// By "narrowest" package, we mean the package with the fewest number of
   158  	// files that includes the given file. This solves the problem of test
   159  	// variants, as the test will have more files than the non-test package.
   160  	NarrowestPackage PackageFilter = iota
   161  
   162  	// WidestPackage returns the Package containing the most files.
   163  	// This is useful for something like diagnostics, where we'd prefer to
   164  	// offer diagnostics for as many files as possible.
   165  	WidestPackage
   166  )
   167  
   168  // InvocationFlags represents the settings of a particular go command invocation.
   169  // It is a mode, plus a set of flag bits.
   170  type InvocationFlags int
   171  
   172  const (
   173  	// Normal is appropriate for commands that might be run by a user and don't
   174  	// deliberately modify go.mod files, e.g. `go test`.
   175  	Normal InvocationFlags = iota
   176  	// UpdateUserModFile is for commands that intend to update the user's real
   177  	// go.mod file, e.g. `go mod tidy` in response to a user's request to tidy.
   178  	UpdateUserModFile
   179  	// WriteTemporaryModFile is for commands that need information from a
   180  	// modified version of the user's go.mod file, e.g. `go mod tidy` used to
   181  	// generate diagnostics.
   182  	WriteTemporaryModFile
   183  	// LoadWorkspace is for packages.Load, and other operations that should
   184  	// consider the whole workspace at once.
   185  	LoadWorkspace
   186  
   187  	// AllowNetwork is a flag bit that indicates the invocation should be
   188  	// allowed to access the network.
   189  	AllowNetwork = 1 << 10
   190  )
   191  
   192  func (m InvocationFlags) Mode() InvocationFlags {
   193  	return m & (AllowNetwork - 1)
   194  }
   195  
   196  func (m InvocationFlags) AllowNetwork() bool {
   197  	return m&AllowNetwork != 0
   198  }
   199  
   200  // View represents a single workspace.
   201  // This is the level at which we maintain configuration like working directory
   202  // and build tags.
   203  type View interface {
   204  	// Name returns the name this view was constructed with.
   205  	Name() string
   206  
   207  	// Folder returns the folder with which this view was created.
   208  	Folder() span.URI
   209  
   210  	// Shutdown closes this view, and detaches it from its session.
   211  	Shutdown(ctx context.Context)
   212  
   213  	// Options returns a copy of the Options for this view.
   214  	Options() *Options
   215  
   216  	// SetOptions sets the options of this view to new values.
   217  	// Calling this may cause the view to be invalidated and a replacement view
   218  	// added to the session. If so the new view will be returned, otherwise the
   219  	// original one will be.
   220  	SetOptions(context.Context, *Options) (View, error)
   221  
   222  	// Snapshot returns the current snapshot for the view.
   223  	Snapshot(ctx context.Context) (Snapshot, func())
   224  
   225  	// Rebuild rebuilds the current view, replacing the original view in its session.
   226  	Rebuild(ctx context.Context) (Snapshot, func(), error)
   227  
   228  	// IsGoPrivatePath reports whether target is a private import path, as identified
   229  	// by the GOPRIVATE environment variable.
   230  	IsGoPrivatePath(path string) bool
   231  }
   232  
   233  // A FileSource maps uris to FileHandles. This abstraction exists both for
   234  // testability, and so that algorithms can be run equally on session and
   235  // snapshot files.
   236  type FileSource interface {
   237  	// GetFile returns the FileHandle for a given URI.
   238  	GetFile(ctx context.Context, uri span.URI) (FileHandle, error)
   239  }
   240  
   241  type BuiltinPackage struct {
   242  	Package    *ast.Package
   243  	ParsedFile *ParsedGoFile
   244  }
   245  
   246  // A ParsedGoFile contains the results of parsing a Go file.
   247  type ParsedGoFile struct {
   248  	URI  span.URI
   249  	Mode ParseMode
   250  	File *ast.File
   251  	Tok  *token.File
   252  	// Source code used to build the AST. It may be different from the
   253  	// actual content of the file if we have fixed the AST.
   254  	Src      []byte
   255  	Mapper   *protocol.ColumnMapper
   256  	ParseErr error
   257  }
   258  
   259  // A ParsedModule contains the results of parsing a go.mod file.
   260  type ParsedModule struct {
   261  	URI         span.URI
   262  	File        *modfile.File
   263  	Mapper      *protocol.ColumnMapper
   264  	ParseErrors []*Error
   265  }
   266  
   267  // A TidiedModule contains the results of running `go mod tidy` on a module.
   268  type TidiedModule struct {
   269  	// Diagnostics representing changes made by `go mod tidy`.
   270  	Errors []*Error
   271  	// The bytes of the go.mod file after it was tidied.
   272  	TidiedContent []byte
   273  }
   274  
   275  // Session represents a single connection from a client.
   276  // This is the level at which things like open files are maintained on behalf
   277  // of the client.
   278  // A session may have many active views at any given time.
   279  type Session interface {
   280  	// NewView creates a new View, returning it and its first snapshot.
   281  	NewView(ctx context.Context, name string, folder, tempWorkspaceDir span.URI, options *Options) (View, Snapshot, func(), error)
   282  
   283  	// Cache returns the cache that created this session, for debugging only.
   284  	Cache() interface{}
   285  
   286  	// View returns a view with a matching name, if the session has one.
   287  	View(name string) View
   288  
   289  	// ViewOf returns a view corresponding to the given URI.
   290  	ViewOf(uri span.URI) (View, error)
   291  
   292  	// Views returns the set of active views built by this session.
   293  	Views() []View
   294  
   295  	// Shutdown the session and all views it has created.
   296  	Shutdown(ctx context.Context)
   297  
   298  	// GetFile returns a handle for the specified file.
   299  	GetFile(ctx context.Context, uri span.URI) (FileHandle, error)
   300  
   301  	// DidModifyFile reports a file modification to the session. It returns the
   302  	// resulting snapshots, a guaranteed one per view.
   303  	DidModifyFiles(ctx context.Context, changes []FileModification) (map[span.URI]View, map[View]Snapshot, []func(), error)
   304  
   305  	// ExpandModificationsToDirectories returns the set of changes with the
   306  	// directory changes removed and expanded to include all of the files in
   307  	// the directory.
   308  	ExpandModificationsToDirectories(ctx context.Context, changes []FileModification) []FileModification
   309  
   310  	// Overlays returns a slice of file overlays for the session.
   311  	Overlays() []Overlay
   312  
   313  	// Options returns a copy of the SessionOptions for this session.
   314  	Options() *Options
   315  
   316  	// SetOptions sets the options of this session to new values.
   317  	SetOptions(*Options)
   318  
   319  	// FileWatchingGlobPatterns returns glob patterns to watch every directory
   320  	// known by the view. For views within a module, this is the module root,
   321  	// any directory in the module root, and any replace targets.
   322  	FileWatchingGlobPatterns(ctx context.Context) map[string]struct{}
   323  }
   324  
   325  // Overlay is the type for a file held in memory on a session.
   326  type Overlay interface {
   327  	VersionedFileHandle
   328  }
   329  
   330  // FileModification represents a modification to a file.
   331  type FileModification struct {
   332  	URI    span.URI
   333  	Action FileAction
   334  
   335  	// OnDisk is true if a watched file is changed on disk.
   336  	// If true, Version will be -1 and Text will be nil.
   337  	OnDisk bool
   338  
   339  	// Version will be -1 and Text will be nil when they are not supplied,
   340  	// specifically on textDocument/didClose and for on-disk changes.
   341  	Version float64
   342  	Text    []byte
   343  
   344  	// LanguageID is only sent from the language client on textDocument/didOpen.
   345  	LanguageID string
   346  }
   347  
   348  type FileAction int
   349  
   350  const (
   351  	UnknownFileAction = FileAction(iota)
   352  	Open
   353  	Change
   354  	Close
   355  	Save
   356  	Create
   357  	Delete
   358  	InvalidateMetadata
   359  )
   360  
   361  func (a FileAction) String() string {
   362  	switch a {
   363  	case Open:
   364  		return "Open"
   365  	case Change:
   366  		return "Change"
   367  	case Close:
   368  		return "Close"
   369  	case Save:
   370  		return "Save"
   371  	case Create:
   372  		return "Create"
   373  	case Delete:
   374  		return "Delete"
   375  	case InvalidateMetadata:
   376  		return "InvalidateMetadata"
   377  	default:
   378  		return "Unknown"
   379  	}
   380  }
   381  
   382  var ErrTmpModfileUnsupported = errors.New("-modfile is unsupported for this Go version")
   383  var ErrNoModOnDisk = errors.New("go.mod file is not on disk")
   384  
   385  func IsNonFatalGoModError(err error) bool {
   386  	return err == ErrTmpModfileUnsupported || err == ErrNoModOnDisk
   387  }
   388  
   389  // ParseMode controls the content of the AST produced when parsing a source file.
   390  type ParseMode int
   391  
   392  const (
   393  	// ParseHeader specifies that the main package declaration and imports are needed.
   394  	// This is the mode used when attempting to examine the package graph structure.
   395  	ParseHeader ParseMode = iota
   396  
   397  	// ParseExported specifies that the public symbols are needed, but things like
   398  	// private symbols and function bodies are not.
   399  	// This mode is used for things where a package is being consumed only as a
   400  	// dependency.
   401  	ParseExported
   402  
   403  	// ParseFull specifies the full AST is needed.
   404  	// This is used for files of direct interest where the entire contents must
   405  	// be considered.
   406  	ParseFull
   407  )
   408  
   409  // TypecheckMode controls what kind of parsing should be done (see ParseMode)
   410  // while type checking a package.
   411  type TypecheckMode int
   412  
   413  const (
   414  	// Invalid default value.
   415  	TypecheckUnknown TypecheckMode = iota
   416  	// TypecheckFull means to use ParseFull.
   417  	TypecheckFull
   418  	// TypecheckWorkspace means to use ParseFull for workspace packages, and
   419  	// ParseExported for others.
   420  	TypecheckWorkspace
   421  	// TypecheckAll means ParseFull for workspace packages, and both Full and
   422  	// Exported for others. Only valid for some functions.
   423  	TypecheckAll
   424  )
   425  
   426  type VersionedFileHandle interface {
   427  	FileHandle
   428  	Version() float64
   429  	Session() string
   430  
   431  	// LSPIdentity returns the version identity of a file.
   432  	VersionedFileIdentity() VersionedFileIdentity
   433  }
   434  
   435  type VersionedFileIdentity struct {
   436  	URI span.URI
   437  
   438  	// SessionID is the ID of the LSP session.
   439  	SessionID string
   440  
   441  	// Version is the version of the file, as specified by the client. It should
   442  	// only be set in combination with SessionID.
   443  	Version float64
   444  }
   445  
   446  // FileHandle represents a handle to a specific version of a single file.
   447  type FileHandle interface {
   448  	URI() span.URI
   449  	Kind() FileKind
   450  
   451  	// FileIdentity returns a FileIdentity for the file, even if there was an
   452  	// error reading it.
   453  	FileIdentity() FileIdentity
   454  	// Read reads the contents of a file.
   455  	// If the file is not available, returns a nil slice and an error.
   456  	Read() ([]byte, error)
   457  	// Saved reports whether the file has the same content on disk.
   458  	Saved() bool
   459  }
   460  
   461  // FileIdentity uniquely identifies a file at a version from a FileSystem.
   462  type FileIdentity struct {
   463  	URI span.URI
   464  
   465  	// Identifier represents a unique identifier for the file's content.
   466  	Hash string
   467  
   468  	// Kind is the file's kind.
   469  	Kind FileKind
   470  }
   471  
   472  func (id FileIdentity) String() string {
   473  	return fmt.Sprintf("%s%s%s", id.URI, id.Hash, id.Kind)
   474  }
   475  
   476  // FileKind describes the kind of the file in question.
   477  // It can be one of Go, mod, or sum.
   478  type FileKind int
   479  
   480  const (
   481  	// UnknownKind is a file type we don't know about.
   482  	UnknownKind = FileKind(iota)
   483  
   484  	// Go is a normal go source file.
   485  	Go
   486  	// Mod is a go.mod file.
   487  	Mod
   488  	// Sum is a go.sum file.
   489  	Sum
   490  )
   491  
   492  // Analyzer represents a go/analysis analyzer with some boolean properties
   493  // that let the user know how to use the analyzer.
   494  type Analyzer struct {
   495  	Analyzer *analysis.Analyzer
   496  
   497  	// Enabled reports whether the analyzer is enabled. This value can be
   498  	// configured per-analysis in user settings. For staticcheck analyzers,
   499  	// the value of the Staticcheck setting overrides this field.
   500  	Enabled bool
   501  
   502  	// Command is the name of the command used to invoke the suggested fixes
   503  	// for the analyzer. It is non-nil if we expect this analyzer to provide
   504  	// its fix separately from its diagnostics. That is, we should apply the
   505  	// analyzer's suggested fixes through a Command, not a TextEdit.
   506  	Command *Command
   507  
   508  	// If this is true, then we can apply the suggested fixes
   509  	// as part of a source.FixAll codeaction.
   510  	HighConfidence bool
   511  
   512  	// FixesError is only set for type-error analyzers.
   513  	// It reports true if the message provided indicates an error that could be
   514  	// fixed by the analyzer.
   515  	FixesError func(msg string) bool
   516  }
   517  
   518  func (a Analyzer) IsEnabled(view View) bool {
   519  	// Staticcheck analyzers can only be enabled when staticcheck is on.
   520  	if _, ok := view.Options().StaticcheckAnalyzers[a.Analyzer.Name]; ok {
   521  		if !view.Options().Staticcheck {
   522  			return false
   523  		}
   524  	}
   525  	if enabled, ok := view.Options().Analyses[a.Analyzer.Name]; ok {
   526  		return enabled
   527  	}
   528  	return a.Enabled
   529  }
   530  
   531  // Package represents a Go package that has been type-checked. It maintains
   532  // only the relevant fields of a *go/packages.Package.
   533  type Package interface {
   534  	ID() string
   535  	Name() string
   536  	PkgPath() string
   537  	CompiledGoFiles() []*ParsedGoFile
   538  	File(uri span.URI) (*ParsedGoFile, error)
   539  	GetSyntax() []*ast.File
   540  	GetErrors() []*Error
   541  	GetTypes() *types.Package
   542  	GetTypesInfo() *types.Info
   543  	GetTypesSizes() types.Sizes
   544  	IsIllTyped() bool
   545  	ForTest() string
   546  	GetImport(pkgPath string) (Package, error)
   547  	MissingDependencies() []string
   548  	Imports() []Package
   549  	Version() *module.Version
   550  }
   551  
   552  type CriticalError struct {
   553  	MainError error
   554  	ErrorList
   555  }
   556  
   557  func (err *CriticalError) Error() string {
   558  	if err.MainError == nil {
   559  		return ""
   560  	}
   561  	return err.MainError.Error()
   562  }
   563  
   564  type ErrorList []*Error
   565  
   566  func (err ErrorList) Error() string {
   567  	var list []string
   568  	for _, e := range err {
   569  		list = append(list, e.Error())
   570  	}
   571  	return strings.Join(list, "\n\t")
   572  }
   573  
   574  // An Error corresponds to an LSP Diagnostic.
   575  // https://microsoft.github.io/language-server-protocol/specification#diagnostic
   576  type Error struct {
   577  	URI      span.URI
   578  	Range    protocol.Range
   579  	Kind     ErrorKind
   580  	Message  string
   581  	Category string // only used by analysis errors so far
   582  	Related  []RelatedInformation
   583  
   584  	// SuggestedFixes is used to generate quick fixes for a CodeAction request.
   585  	// It isn't part of the Diagnostic type.
   586  	SuggestedFixes []SuggestedFix
   587  }
   588  
   589  // GoModTidy is the source for a diagnostic computed by running `go mod tidy`.
   590  const GoModTidy = "go mod tidy"
   591  
   592  type ErrorKind int
   593  
   594  const (
   595  	UnknownError = ErrorKind(iota)
   596  	ListError
   597  	ParseError
   598  	TypeError
   599  	ModTidyError
   600  	Analysis
   601  )
   602  
   603  func (e *Error) Error() string {
   604  	if e.URI == "" {
   605  		return e.Message
   606  	}
   607  	return fmt.Sprintf("%s:%s: %s", e.URI, e.Range, e.Message)
   608  }
   609  
   610  var (
   611  	PackagesLoadError = errors.New("packages.Load error")
   612  )
   613  
   614  // WorkspaceModuleVersion is the nonexistent pseudoversion suffix used in the
   615  // construction of the workspace module. It is exported so that we can make
   616  // sure not to show this version to end users in error messages, to avoid
   617  // confusion.
   618  // The major version is not included, as that depends on the module path.
   619  const workspaceModuleVersion = ".0.0-goplsworkspace"
   620  
   621  func IsWorkspaceModuleVersion(version string) bool {
   622  	return strings.HasSuffix(version, workspaceModuleVersion)
   623  }
   624  
   625  func WorkspaceModuleVersion(majorVersion string) string {
   626  	return majorVersion + workspaceModuleVersion
   627  }