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