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