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 }