1// Copyright 2019 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 5package source 6 7import ( 8 "context" 9 "fmt" 10 "path/filepath" 11 "regexp" 12 "strings" 13 "sync" 14 "time" 15 16 "golang.org/x/tools/go/analysis" 17 "golang.org/x/tools/go/analysis/passes/asmdecl" 18 "golang.org/x/tools/go/analysis/passes/assign" 19 "golang.org/x/tools/go/analysis/passes/atomic" 20 "golang.org/x/tools/go/analysis/passes/atomicalign" 21 "golang.org/x/tools/go/analysis/passes/bools" 22 "golang.org/x/tools/go/analysis/passes/buildtag" 23 "golang.org/x/tools/go/analysis/passes/cgocall" 24 "golang.org/x/tools/go/analysis/passes/composite" 25 "golang.org/x/tools/go/analysis/passes/copylock" 26 "golang.org/x/tools/go/analysis/passes/deepequalerrors" 27 "golang.org/x/tools/go/analysis/passes/errorsas" 28 "golang.org/x/tools/go/analysis/passes/fieldalignment" 29 "golang.org/x/tools/go/analysis/passes/httpresponse" 30 "golang.org/x/tools/go/analysis/passes/ifaceassert" 31 "golang.org/x/tools/go/analysis/passes/loopclosure" 32 "golang.org/x/tools/go/analysis/passes/lostcancel" 33 "golang.org/x/tools/go/analysis/passes/nilfunc" 34 "golang.org/x/tools/go/analysis/passes/printf" 35 "golang.org/x/tools/go/analysis/passes/shadow" 36 "golang.org/x/tools/go/analysis/passes/shift" 37 "golang.org/x/tools/go/analysis/passes/sortslice" 38 "golang.org/x/tools/go/analysis/passes/stdmethods" 39 "golang.org/x/tools/go/analysis/passes/stringintconv" 40 "golang.org/x/tools/go/analysis/passes/structtag" 41 "golang.org/x/tools/go/analysis/passes/testinggoroutine" 42 "golang.org/x/tools/go/analysis/passes/tests" 43 "golang.org/x/tools/go/analysis/passes/unmarshal" 44 "golang.org/x/tools/go/analysis/passes/unreachable" 45 "golang.org/x/tools/go/analysis/passes/unsafeptr" 46 "golang.org/x/tools/go/analysis/passes/unusedresult" 47 "golang.org/x/tools/internal/lsp/analysis/fillreturns" 48 "golang.org/x/tools/internal/lsp/analysis/fillstruct" 49 "golang.org/x/tools/internal/lsp/analysis/nonewvars" 50 "golang.org/x/tools/internal/lsp/analysis/noresultvalues" 51 "golang.org/x/tools/internal/lsp/analysis/simplifycompositelit" 52 "golang.org/x/tools/internal/lsp/analysis/simplifyrange" 53 "golang.org/x/tools/internal/lsp/analysis/simplifyslice" 54 "golang.org/x/tools/internal/lsp/analysis/undeclaredname" 55 "golang.org/x/tools/internal/lsp/analysis/unusedparams" 56 "golang.org/x/tools/internal/lsp/diff" 57 "golang.org/x/tools/internal/lsp/diff/myers" 58 "golang.org/x/tools/internal/lsp/protocol" 59 errors "golang.org/x/xerrors" 60) 61 62var ( 63 optionsOnce sync.Once 64 defaultOptions *Options 65) 66 67// DefaultOptions is the options that are used for Gopls execution independent 68// of any externally provided configuration (LSP initialization, command 69// invokation, etc.). 70func DefaultOptions() *Options { 71 optionsOnce.Do(func() { 72 var commands []string 73 for _, c := range Commands { 74 commands = append(commands, c.ID()) 75 } 76 defaultOptions = &Options{ 77 ClientOptions: ClientOptions{ 78 InsertTextFormat: protocol.PlainTextTextFormat, 79 PreferredContentFormat: protocol.Markdown, 80 ConfigurationSupported: true, 81 DynamicConfigurationSupported: true, 82 DynamicWatchedFilesSupported: true, 83 LineFoldingOnly: false, 84 HierarchicalDocumentSymbolSupport: true, 85 }, 86 ServerOptions: ServerOptions{ 87 SupportedCodeActions: map[FileKind]map[protocol.CodeActionKind]bool{ 88 Go: { 89 protocol.SourceFixAll: true, 90 protocol.SourceOrganizeImports: true, 91 protocol.QuickFix: true, 92 protocol.RefactorRewrite: true, 93 protocol.RefactorExtract: true, 94 }, 95 Mod: { 96 protocol.SourceOrganizeImports: true, 97 }, 98 Sum: {}, 99 }, 100 SupportedCommands: commands, 101 }, 102 UserOptions: UserOptions{ 103 BuildOptions: BuildOptions{ 104 ExpandWorkspaceToModule: true, 105 ExperimentalPackageCacheKey: true, 106 }, 107 UIOptions: UIOptions{ 108 DiagnosticOptions: DiagnosticOptions{ 109 ExperimentalDiagnosticsDelay: 250 * time.Millisecond, 110 Annotations: map[Annotation]bool{ 111 Bounds: true, 112 Escape: true, 113 Inline: true, 114 Nil: true, 115 }, 116 }, 117 DocumentationOptions: DocumentationOptions{ 118 HoverKind: FullDocumentation, 119 LinkTarget: "pkg.go.dev", 120 LinksInHover: true, 121 }, 122 NavigationOptions: NavigationOptions{ 123 ImportShortcut: Both, 124 SymbolMatcher: SymbolFuzzy, 125 SymbolStyle: DynamicSymbols, 126 }, 127 CompletionOptions: CompletionOptions{ 128 Matcher: Fuzzy, 129 CompletionBudget: 100 * time.Millisecond, 130 }, 131 Codelenses: map[string]bool{ 132 CommandGenerate.Name: true, 133 CommandRegenerateCgo.Name: true, 134 CommandTidy.Name: true, 135 CommandToggleDetails.Name: false, 136 CommandUpgradeDependency.Name: true, 137 CommandVendor.Name: true, 138 }, 139 }, 140 }, 141 InternalOptions: InternalOptions{ 142 LiteralCompletions: true, 143 TempModfile: true, 144 CompleteUnimported: true, 145 CompletionDocumentation: true, 146 DeepCompletion: true, 147 }, 148 Hooks: Hooks{ 149 ComputeEdits: myers.ComputeEdits, 150 URLRegexp: urlRegexp(), 151 DefaultAnalyzers: defaultAnalyzers(), 152 TypeErrorAnalyzers: typeErrorAnalyzers(), 153 ConvenienceAnalyzers: convenienceAnalyzers(), 154 StaticcheckAnalyzers: map[string]Analyzer{}, 155 GoDiff: true, 156 }, 157 } 158 }) 159 return defaultOptions 160} 161 162// Options holds various configuration that affects Gopls execution, organized 163// by the nature or origin of the settings. 164type Options struct { 165 ClientOptions 166 ServerOptions 167 UserOptions 168 InternalOptions 169 Hooks 170} 171 172// ClientOptions holds LSP-specific configuration that is provided by the 173// client. 174type ClientOptions struct { 175 InsertTextFormat protocol.InsertTextFormat 176 ConfigurationSupported bool 177 DynamicConfigurationSupported bool 178 DynamicWatchedFilesSupported bool 179 PreferredContentFormat protocol.MarkupKind 180 LineFoldingOnly bool 181 HierarchicalDocumentSymbolSupport bool 182 SemanticTypes []string 183 SemanticMods []string 184} 185 186// ServerOptions holds LSP-specific configuration that is provided by the 187// server. 188type ServerOptions struct { 189 SupportedCodeActions map[FileKind]map[protocol.CodeActionKind]bool 190 SupportedCommands []string 191} 192 193type BuildOptions struct { 194 // BuildFlags is the set of flags passed on to the build system when invoked. 195 // It is applied to queries like `go list`, which is used when discovering files. 196 // The most common use is to set `-tags`. 197 BuildFlags []string 198 199 // Env adds environment variables to external commands run by `gopls`, most notably `go list`. 200 Env map[string]string 201 202 // DirectoryFilters can be used to exclude unwanted directories from the 203 // workspace. By default, all directories are included. Filters are an 204 // operator, `+` to include and `-` to exclude, followed by a path prefix 205 // relative to the workspace folder. They are evaluated in order, and 206 // the last filter that applies to a path controls whether it is included. 207 // The path prefix can be empty, so an initial `-` excludes everything. 208 // 209 // Examples: 210 // Exclude node_modules: `-node_modules` 211 // Include only project_a: `-` (exclude everything), `+project_a` 212 // Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules` 213 DirectoryFilters []string 214 215 // ExpandWorkspaceToModule instructs `gopls` to adjust the scope of the 216 // workspace to find the best available module root. `gopls` first looks for 217 // a go.mod file in any parent directory of the workspace folder, expanding 218 // the scope to that directory if it exists. If no viable parent directory is 219 // found, gopls will check if there is exactly one child directory containing 220 // a go.mod file, narrowing the scope to that directory if it exists. 221 ExpandWorkspaceToModule bool `status:"experimental"` 222 223 // ExperimentalWorkspaceModule opts a user into the experimental support 224 // for multi-module workspaces. 225 ExperimentalWorkspaceModule bool `status:"experimental"` 226 227 // ExperimentalPackageCacheKey controls whether to use a coarser cache key 228 // for package type information to increase cache hits. This setting removes 229 // the user's environment, build flags, and working directory from the cache 230 // key, which should be a safe change as all relevant inputs into the type 231 // checking pass are already hashed into the key. This is temporarily guarded 232 // by an experiment because caching behavior is subtle and difficult to 233 // comprehensively test. 234 ExperimentalPackageCacheKey bool `status:"experimental"` 235 236 // AllowModfileModifications disables -mod=readonly, allowing imports from 237 // out-of-scope modules. This option will eventually be removed. 238 AllowModfileModifications bool `status:"experimental"` 239 240 // AllowImplicitNetworkAccess disables GOPROXY=off, allowing implicit module 241 // downloads rather than requiring user action. This option will eventually 242 // be removed. 243 AllowImplicitNetworkAccess bool `status:"experimental"` 244} 245 246type UIOptions struct { 247 DocumentationOptions 248 CompletionOptions 249 NavigationOptions 250 DiagnosticOptions 251 252 // Codelenses overrides the enabled/disabled state of code lenses. See the 253 // "Code Lenses" section of the 254 // [Settings page](https://github.com/golang/tools/blob/master/gopls/doc/settings.md) 255 // for the list of supported lenses. 256 // 257 // Example Usage: 258 // 259 // ```json5 260 // "gopls": { 261 // ... 262 // "codelens": { 263 // "generate": false, // Don't show the `go generate` lens. 264 // "gc_details": true // Show a code lens toggling the display of gc's choices. 265 // } 266 // ... 267 // } 268 // ``` 269 Codelenses map[string]bool 270 271 // SemanticTokens controls whether the LSP server will send 272 // semantic tokens to the client. 273 SemanticTokens bool `status:"experimental"` 274} 275 276type CompletionOptions struct { 277 // Placeholders enables placeholders for function parameters or struct 278 // fields in completion responses. 279 UsePlaceholders bool 280 281 // CompletionBudget is the soft latency goal for completion requests. Most 282 // requests finish in a couple milliseconds, but in some cases deep 283 // completions can take much longer. As we use up our budget we 284 // dynamically reduce the search scope to ensure we return timely 285 // results. Zero means unlimited. 286 CompletionBudget time.Duration `status:"debug"` 287 288 // Matcher sets the algorithm that is used when calculating completion 289 // candidates. 290 Matcher Matcher `status:"advanced"` 291} 292 293type DocumentationOptions struct { 294 // HoverKind controls the information that appears in the hover text. 295 // SingleLine and Structured are intended for use only by authors of editor plugins. 296 HoverKind HoverKind 297 298 // LinkTarget controls where documentation links go. 299 // It might be one of: 300 // 301 // * `"godoc.org"` 302 // * `"pkg.go.dev"` 303 // 304 // If company chooses to use its own `godoc.org`, its address can be used as well. 305 LinkTarget string 306 307 // LinksInHover toggles the presence of links to documentation in hover. 308 LinksInHover bool 309} 310 311type FormattingOptions struct { 312 // Local is the equivalent of the `goimports -local` flag, which puts 313 // imports beginning with this string after third-party packages. It should 314 // be the prefix of the import path whose imports should be grouped 315 // separately. 316 Local string 317 318 // Gofumpt indicates if we should run gofumpt formatting. 319 Gofumpt bool 320} 321 322type DiagnosticOptions struct { 323 // Analyses specify analyses that the user would like to enable or disable. 324 // A map of the names of analysis passes that should be enabled/disabled. 325 // A full list of analyzers that gopls uses can be found 326 // [here](https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md). 327 // 328 // Example Usage: 329 // 330 // ```json5 331 // ... 332 // "analyses": { 333 // "unreachable": false, // Disable the unreachable analyzer. 334 // "unusedparams": true // Enable the unusedparams analyzer. 335 // } 336 // ... 337 // ``` 338 Analyses map[string]bool 339 340 // Staticcheck enables additional analyses from staticcheck.io. 341 Staticcheck bool `status:"experimental"` 342 343 // Annotations specifies the various kinds of optimization diagnostics 344 // that should be reported by the gc_details command. 345 Annotations map[Annotation]bool `status:"experimental"` 346 347 // ExperimentalDiagnosticsDelay controls the amount of time that gopls waits 348 // after the most recent file modification before computing deep diagnostics. 349 // Simple diagnostics (parsing and type-checking) are always run immediately 350 // on recently modified packages. 351 // 352 // This option must be set to a valid duration string, for example `"250ms"`. 353 ExperimentalDiagnosticsDelay time.Duration `status:"experimental"` 354} 355 356type NavigationOptions struct { 357 // ImportShortcut specifies whether import statements should link to 358 // documentation or go to definitions. 359 ImportShortcut ImportShortcut 360 361 // SymbolMatcher sets the algorithm that is used when finding workspace symbols. 362 SymbolMatcher SymbolMatcher `status:"advanced"` 363 364 // SymbolStyle controls how symbols are qualified in symbol responses. 365 // 366 // Example Usage: 367 // 368 // ```json5 369 // "gopls": { 370 // ... 371 // "symbolStyle": "dynamic", 372 // ... 373 // } 374 // ``` 375 SymbolStyle SymbolStyle `status:"advanced"` 376} 377 378// UserOptions holds custom Gopls configuration (not part of the LSP) that is 379// modified by the client. 380type UserOptions struct { 381 BuildOptions 382 UIOptions 383 FormattingOptions 384 385 // VerboseOutput enables additional debug logging. 386 VerboseOutput bool `status:"debug"` 387} 388 389// EnvSlice returns Env as a slice of k=v strings. 390func (u *UserOptions) EnvSlice() []string { 391 var result []string 392 for k, v := range u.Env { 393 result = append(result, fmt.Sprintf("%v=%v", k, v)) 394 } 395 return result 396} 397 398// SetEnvSlice sets Env from a slice of k=v strings. 399func (u *UserOptions) SetEnvSlice(env []string) { 400 u.Env = map[string]string{} 401 for _, kv := range env { 402 split := strings.SplitN(kv, "=", 2) 403 if len(split) != 2 { 404 continue 405 } 406 u.Env[split[0]] = split[1] 407 } 408} 409 410// Hooks contains configuration that is provided to the Gopls command by the 411// main package. 412type Hooks struct { 413 GoDiff bool 414 ComputeEdits diff.ComputeEdits 415 URLRegexp *regexp.Regexp 416 GofumptFormat func(ctx context.Context, src []byte) ([]byte, error) 417 DefaultAnalyzers map[string]Analyzer 418 TypeErrorAnalyzers map[string]Analyzer 419 ConvenienceAnalyzers map[string]Analyzer 420 StaticcheckAnalyzers map[string]Analyzer 421} 422 423// InternalOptions contains settings that are not intended for use by the 424// average user. These may be settings used by tests or outdated settings that 425// will soon be deprecated. Some of these settings may not even be configurable 426// by the user. 427type InternalOptions struct { 428 // LiteralCompletions controls whether literal candidates such as 429 // "&someStruct{}" are offered. Tests disable this flag to simplify 430 // their expected values. 431 LiteralCompletions bool 432 433 // VerboseWorkDoneProgress controls whether the LSP server should send 434 // progress reports for all work done outside the scope of an RPC. 435 // Used by the regression tests. 436 VerboseWorkDoneProgress bool 437 438 // The following options were previously available to users, but they 439 // really shouldn't be configured by anyone other than "power users". 440 441 // CompletionDocumentation enables documentation with completion results. 442 CompletionDocumentation bool 443 444 // CompleteUnimported enables completion for packages that you do not 445 // currently import. 446 CompleteUnimported bool 447 448 // DeepCompletion enables the ability to return completions from deep 449 // inside relevant entities, rather than just the locally accessible ones. 450 // 451 // Consider this example: 452 // 453 // ```go 454 // package main 455 // 456 // import "fmt" 457 // 458 // type wrapString struct { 459 // str string 460 // } 461 // 462 // func main() { 463 // x := wrapString{"hello world"} 464 // fmt.Printf(<>) 465 // } 466 // ``` 467 // 468 // At the location of the `<>` in this program, deep completion would suggest the result `x.str`. 469 DeepCompletion bool 470 471 // TempModfile controls the use of the -modfile flag in Go 1.14. 472 TempModfile bool 473} 474 475type ImportShortcut string 476 477const ( 478 Both ImportShortcut = "Both" 479 Link ImportShortcut = "Link" 480 Definition ImportShortcut = "Definition" 481) 482 483func (s ImportShortcut) ShowLinks() bool { 484 return s == Both || s == Link 485} 486 487func (s ImportShortcut) ShowDefinition() bool { 488 return s == Both || s == Definition 489} 490 491type Matcher string 492 493const ( 494 Fuzzy Matcher = "Fuzzy" 495 CaseInsensitive Matcher = "CaseInsensitive" 496 CaseSensitive Matcher = "CaseSensitive" 497) 498 499type SymbolMatcher string 500 501const ( 502 SymbolFuzzy SymbolMatcher = "Fuzzy" 503 SymbolCaseInsensitive SymbolMatcher = "CaseInsensitive" 504 SymbolCaseSensitive SymbolMatcher = "CaseSensitive" 505) 506 507type SymbolStyle string 508 509const ( 510 // PackageQualifiedSymbols is package qualified symbols i.e. 511 // "pkg.Foo.Field". 512 PackageQualifiedSymbols SymbolStyle = "Package" 513 // FullyQualifiedSymbols is fully qualified symbols, i.e. 514 // "path/to/pkg.Foo.Field". 515 FullyQualifiedSymbols SymbolStyle = "Full" 516 // DynamicSymbols uses whichever qualifier results in the highest scoring 517 // match for the given symbol query. Here a "qualifier" is any "/" or "." 518 // delimited suffix of the fully qualified symbol. i.e. "to/pkg.Foo.Field" or 519 // just "Foo.Field". 520 DynamicSymbols SymbolStyle = "Dynamic" 521) 522 523type HoverKind string 524 525const ( 526 SingleLine HoverKind = "SingleLine" 527 NoDocumentation HoverKind = "NoDocumentation" 528 SynopsisDocumentation HoverKind = "SynopsisDocumentation" 529 FullDocumentation HoverKind = "FullDocumentation" 530 531 // Structured is an experimental setting that returns a structured hover format. 532 // This format separates the signature from the documentation, so that the client 533 // can do more manipulation of these fields. 534 // 535 // This should only be used by clients that support this behavior. 536 Structured HoverKind = "Structured" 537) 538 539type OptionResults []OptionResult 540 541type OptionResult struct { 542 Name string 543 Value interface{} 544 Error error 545 546 State OptionState 547 Replacement string 548} 549 550type OptionState int 551 552const ( 553 OptionHandled = OptionState(iota) 554 OptionDeprecated 555 OptionUnexpected 556) 557 558type LinkTarget string 559 560func SetOptions(options *Options, opts interface{}) OptionResults { 561 var results OptionResults 562 switch opts := opts.(type) { 563 case nil: 564 case map[string]interface{}: 565 // If the user's settings contains "allExperiments", set that first, 566 // and then let them override individual settings independently. 567 var enableExperiments bool 568 for name, value := range opts { 569 if b, ok := value.(bool); name == "allExperiments" && ok && b { 570 enableExperiments = true 571 options.enableAllExperiments() 572 } 573 } 574 seen := map[string]struct{}{} 575 for name, value := range opts { 576 results = append(results, options.set(name, value, seen)) 577 } 578 // Finally, enable any experimental features that are specified in 579 // maps, which allows users to individually toggle them on or off. 580 if enableExperiments { 581 options.enableAllExperimentMaps() 582 } 583 default: 584 results = append(results, OptionResult{ 585 Value: opts, 586 Error: errors.Errorf("Invalid options type %T", opts), 587 }) 588 } 589 return results 590} 591 592func (o *Options) ForClientCapabilities(caps protocol.ClientCapabilities) { 593 // Check if the client supports snippets in completion items. 594 if c := caps.TextDocument.Completion; c.CompletionItem.SnippetSupport { 595 o.InsertTextFormat = protocol.SnippetTextFormat 596 } 597 // Check if the client supports configuration messages. 598 o.ConfigurationSupported = caps.Workspace.Configuration 599 o.DynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration 600 o.DynamicWatchedFilesSupported = caps.Workspace.DidChangeWatchedFiles.DynamicRegistration 601 602 // Check which types of content format are supported by this client. 603 if hover := caps.TextDocument.Hover; len(hover.ContentFormat) > 0 { 604 o.PreferredContentFormat = hover.ContentFormat[0] 605 } 606 // Check if the client supports only line folding. 607 fr := caps.TextDocument.FoldingRange 608 o.LineFoldingOnly = fr.LineFoldingOnly 609 // Check if the client supports hierarchical document symbols. 610 o.HierarchicalDocumentSymbolSupport = caps.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport 611 // Check if the client supports semantic tokens 612 o.SemanticTypes = caps.TextDocument.SemanticTokens.TokenTypes 613 o.SemanticMods = caps.TextDocument.SemanticTokens.TokenModifiers 614 // we don't need Requests, as we support full functionality 615 // we don't need Formats, as there is only one, for now 616} 617 618func (o *Options) Clone() *Options { 619 result := &Options{ 620 ClientOptions: o.ClientOptions, 621 InternalOptions: o.InternalOptions, 622 Hooks: Hooks{ 623 GoDiff: o.Hooks.GoDiff, 624 ComputeEdits: o.Hooks.ComputeEdits, 625 GofumptFormat: o.GofumptFormat, 626 URLRegexp: o.URLRegexp, 627 }, 628 ServerOptions: o.ServerOptions, 629 UserOptions: o.UserOptions, 630 } 631 // Fully clone any slice or map fields. Only Hooks, ExperimentalOptions, 632 // and UserOptions can be modified. 633 copyStringMap := func(src map[string]bool) map[string]bool { 634 dst := make(map[string]bool) 635 for k, v := range src { 636 dst[k] = v 637 } 638 return dst 639 } 640 result.Analyses = copyStringMap(o.Analyses) 641 result.Codelenses = copyStringMap(o.Codelenses) 642 643 copySlice := func(src []string) []string { 644 dst := make([]string, len(src)) 645 copy(dst, src) 646 return dst 647 } 648 result.SetEnvSlice(o.EnvSlice()) 649 result.BuildFlags = copySlice(o.BuildFlags) 650 result.DirectoryFilters = copySlice(o.DirectoryFilters) 651 652 copyAnalyzerMap := func(src map[string]Analyzer) map[string]Analyzer { 653 dst := make(map[string]Analyzer) 654 for k, v := range src { 655 dst[k] = v 656 } 657 return dst 658 } 659 result.DefaultAnalyzers = copyAnalyzerMap(o.DefaultAnalyzers) 660 result.TypeErrorAnalyzers = copyAnalyzerMap(o.TypeErrorAnalyzers) 661 result.ConvenienceAnalyzers = copyAnalyzerMap(o.ConvenienceAnalyzers) 662 result.StaticcheckAnalyzers = copyAnalyzerMap(o.StaticcheckAnalyzers) 663 return result 664} 665 666func (o *Options) AddStaticcheckAnalyzer(a *analysis.Analyzer) { 667 o.StaticcheckAnalyzers[a.Name] = Analyzer{Analyzer: a, Enabled: true} 668} 669 670// enableAllExperiments turns on all of the experimental "off-by-default" 671// features offered by gopls. Any experimental features specified in maps 672// should be enabled in enableAllExperimentMaps. 673func (o *Options) enableAllExperiments() { 674 // There are currently no experimental features in development. 675} 676 677func (o *Options) enableAllExperimentMaps() { 678 if _, ok := o.Codelenses[CommandToggleDetails.Name]; !ok { 679 o.Codelenses[CommandToggleDetails.Name] = true 680 } 681 if _, ok := o.Analyses[unusedparams.Analyzer.Name]; !ok { 682 o.Analyses[unusedparams.Analyzer.Name] = true 683 } 684} 685 686func (o *Options) set(name string, value interface{}, seen map[string]struct{}) OptionResult { 687 // Flatten the name in case we get options with a hierarchy. 688 split := strings.Split(name, ".") 689 name = split[len(split)-1] 690 691 result := OptionResult{Name: name, Value: value} 692 if _, ok := seen[name]; ok { 693 result.errorf("duplicate configuration for %s", name) 694 } 695 seen[name] = struct{}{} 696 697 switch name { 698 case "env": 699 menv, ok := value.(map[string]interface{}) 700 if !ok { 701 result.errorf("invalid type %T, expect map", value) 702 break 703 } 704 if o.Env == nil { 705 o.Env = make(map[string]string) 706 } 707 for k, v := range menv { 708 o.Env[k] = fmt.Sprint(v) 709 } 710 711 case "buildFlags": 712 iflags, ok := value.([]interface{}) 713 if !ok { 714 result.errorf("invalid type %T, expect list", value) 715 break 716 } 717 flags := make([]string, 0, len(iflags)) 718 for _, flag := range iflags { 719 flags = append(flags, fmt.Sprintf("%s", flag)) 720 } 721 o.BuildFlags = flags 722 case "directoryFilters": 723 ifilters, ok := value.([]interface{}) 724 if !ok { 725 result.errorf("invalid type %T, expect list", value) 726 break 727 } 728 var filters []string 729 for _, ifilter := range ifilters { 730 filter := fmt.Sprint(ifilter) 731 if filter[0] != '+' && filter[0] != '-' { 732 result.errorf("invalid filter %q, must start with + or -", filter) 733 return result 734 } 735 filters = append(filters, filepath.FromSlash(filter)) 736 } 737 o.DirectoryFilters = filters 738 case "completionDocumentation": 739 result.setBool(&o.CompletionDocumentation) 740 case "usePlaceholders": 741 result.setBool(&o.UsePlaceholders) 742 case "deepCompletion": 743 result.setBool(&o.DeepCompletion) 744 case "completeUnimported": 745 result.setBool(&o.CompleteUnimported) 746 case "completionBudget": 747 result.setDuration(&o.CompletionBudget) 748 case "matcher": 749 if s, ok := result.asOneOf( 750 string(Fuzzy), 751 string(CaseSensitive), 752 string(CaseInsensitive), 753 ); ok { 754 o.Matcher = Matcher(s) 755 } 756 757 case "symbolMatcher": 758 if s, ok := result.asOneOf( 759 string(SymbolFuzzy), 760 string(SymbolCaseInsensitive), 761 string(SymbolCaseSensitive), 762 ); ok { 763 o.SymbolMatcher = SymbolMatcher(s) 764 } 765 766 case "symbolStyle": 767 if s, ok := result.asOneOf( 768 string(FullyQualifiedSymbols), 769 string(PackageQualifiedSymbols), 770 string(DynamicSymbols), 771 ); ok { 772 o.SymbolStyle = SymbolStyle(s) 773 } 774 775 case "hoverKind": 776 if s, ok := result.asOneOf( 777 string(NoDocumentation), 778 string(SingleLine), 779 string(SynopsisDocumentation), 780 string(FullDocumentation), 781 string(Structured), 782 ); ok { 783 o.HoverKind = HoverKind(s) 784 } 785 786 case "linkTarget": 787 result.setString(&o.LinkTarget) 788 789 case "linksInHover": 790 result.setBool(&o.LinksInHover) 791 792 case "importShortcut": 793 if s, ok := result.asOneOf(string(Both), string(Link), string(Definition)); ok { 794 o.ImportShortcut = ImportShortcut(s) 795 } 796 797 case "analyses": 798 result.setBoolMap(&o.Analyses) 799 800 case "annotations": 801 result.setAnnotationMap(&o.Annotations) 802 803 case "codelenses", "codelens": 804 var lensOverrides map[string]bool 805 result.setBoolMap(&lensOverrides) 806 if result.Error == nil { 807 if o.Codelenses == nil { 808 o.Codelenses = make(map[string]bool) 809 } 810 for lens, enabled := range lensOverrides { 811 o.Codelenses[lens] = enabled 812 } 813 } 814 815 // codelens is deprecated, but still works for now. 816 // TODO(rstambler): Remove this for the gopls/v0.7.0 release. 817 if name == "codelens" { 818 result.State = OptionDeprecated 819 result.Replacement = "codelenses" 820 } 821 822 case "staticcheck": 823 result.setBool(&o.Staticcheck) 824 825 case "local": 826 result.setString(&o.Local) 827 828 case "verboseOutput": 829 result.setBool(&o.VerboseOutput) 830 831 case "verboseWorkDoneProgress": 832 result.setBool(&o.VerboseWorkDoneProgress) 833 834 case "tempModfile": 835 result.setBool(&o.TempModfile) 836 837 case "gofumpt": 838 result.setBool(&o.Gofumpt) 839 840 case "semanticTokens": 841 result.setBool(&o.SemanticTokens) 842 843 case "expandWorkspaceToModule": 844 result.setBool(&o.ExpandWorkspaceToModule) 845 846 case "experimentalWorkspaceModule": 847 result.setBool(&o.ExperimentalWorkspaceModule) 848 849 case "experimentalDiagnosticsDelay": 850 result.setDuration(&o.ExperimentalDiagnosticsDelay) 851 852 case "experimentalPackageCacheKey": 853 result.setBool(&o.ExperimentalPackageCacheKey) 854 855 case "allowModfileModifications": 856 result.setBool(&o.AllowModfileModifications) 857 858 case "allowImplicitNetworkAccess": 859 result.setBool(&o.AllowImplicitNetworkAccess) 860 861 case "allExperiments": 862 // This setting should be handled before all of the other options are 863 // processed, so do nothing here. 864 865 // Replaced settings. 866 case "experimentalDisabledAnalyses": 867 result.State = OptionDeprecated 868 result.Replacement = "analyses" 869 870 case "disableDeepCompletion": 871 result.State = OptionDeprecated 872 result.Replacement = "deepCompletion" 873 874 case "disableFuzzyMatching": 875 result.State = OptionDeprecated 876 result.Replacement = "fuzzyMatching" 877 878 case "wantCompletionDocumentation": 879 result.State = OptionDeprecated 880 result.Replacement = "completionDocumentation" 881 882 case "wantUnimportedCompletions": 883 result.State = OptionDeprecated 884 result.Replacement = "completeUnimported" 885 886 case "fuzzyMatching": 887 result.State = OptionDeprecated 888 result.Replacement = "matcher" 889 890 case "caseSensitiveCompletion": 891 result.State = OptionDeprecated 892 result.Replacement = "matcher" 893 894 // Deprecated settings. 895 case "wantSuggestedFixes": 896 result.State = OptionDeprecated 897 898 case "noIncrementalSync": 899 result.State = OptionDeprecated 900 901 case "watchFileChanges": 902 result.State = OptionDeprecated 903 904 case "go-diff": 905 result.State = OptionDeprecated 906 907 default: 908 result.State = OptionUnexpected 909 } 910 return result 911} 912 913func (r *OptionResult) errorf(msg string, values ...interface{}) { 914 prefix := fmt.Sprintf("parsing setting %q: ", r.Name) 915 r.Error = errors.Errorf(prefix+msg, values...) 916} 917 918func (r *OptionResult) asBool() (bool, bool) { 919 b, ok := r.Value.(bool) 920 if !ok { 921 r.errorf("invalid type %T, expect bool", r.Value) 922 return false, false 923 } 924 return b, true 925} 926 927func (r *OptionResult) setBool(b *bool) { 928 if v, ok := r.asBool(); ok { 929 *b = v 930 } 931} 932 933func (r *OptionResult) setDuration(d *time.Duration) { 934 if v, ok := r.asString(); ok { 935 parsed, err := time.ParseDuration(v) 936 if err != nil { 937 r.errorf("failed to parse duration %q: %v", v, err) 938 return 939 } 940 *d = parsed 941 } 942} 943 944func (r *OptionResult) setBoolMap(bm *map[string]bool) { 945 m := r.asBoolMap() 946 *bm = m 947} 948 949func (r *OptionResult) setAnnotationMap(bm *map[Annotation]bool) { 950 all := r.asBoolMap() 951 if all == nil { 952 return 953 } 954 // Default to everything enabled by default. 955 m := make(map[Annotation]bool) 956 for k, enabled := range all { 957 a, err := asOneOf( 958 k, 959 string(Nil), 960 string(Escape), 961 string(Inline), 962 string(Bounds), 963 ) 964 if err != nil { 965 // In case of an error, process any legacy values. 966 switch k { 967 case "noEscape": 968 m[Escape] = false 969 r.errorf(`"noEscape" is deprecated, set "Escape: false" instead`) 970 case "noNilcheck": 971 m[Nil] = false 972 r.errorf(`"noNilcheck" is deprecated, set "Nil: false" instead`) 973 case "noInline": 974 m[Inline] = false 975 r.errorf(`"noInline" is deprecated, set "Inline: false" instead`) 976 case "noBounds": 977 m[Bounds] = false 978 r.errorf(`"noBounds" is deprecated, set "Bounds: false" instead`) 979 default: 980 r.errorf(err.Error()) 981 } 982 continue 983 } 984 m[Annotation(a)] = enabled 985 } 986 *bm = m 987} 988 989func (r *OptionResult) asBoolMap() map[string]bool { 990 all, ok := r.Value.(map[string]interface{}) 991 if !ok { 992 r.errorf("invalid type %T for map[string]bool option", r.Value) 993 return nil 994 } 995 m := make(map[string]bool) 996 for a, enabled := range all { 997 if enabled, ok := enabled.(bool); ok { 998 m[a] = enabled 999 } else { 1000 r.errorf("invalid type %T for map key %q", enabled, a) 1001 return m 1002 } 1003 } 1004 return m 1005} 1006 1007func (r *OptionResult) asString() (string, bool) { 1008 b, ok := r.Value.(string) 1009 if !ok { 1010 r.errorf("invalid type %T, expect string", r.Value) 1011 return "", false 1012 } 1013 return b, true 1014} 1015 1016func (r *OptionResult) asOneOf(options ...string) (string, bool) { 1017 s, ok := r.asString() 1018 if !ok { 1019 return "", false 1020 } 1021 s, err := asOneOf(s, options...) 1022 if err != nil { 1023 r.errorf(err.Error()) 1024 } 1025 return s, err == nil 1026} 1027 1028func asOneOf(str string, options ...string) (string, error) { 1029 lower := strings.ToLower(str) 1030 for _, opt := range options { 1031 if strings.ToLower(opt) == lower { 1032 return opt, nil 1033 } 1034 } 1035 return "", fmt.Errorf("invalid option %q for enum", str) 1036} 1037 1038func (r *OptionResult) setString(s *string) { 1039 if v, ok := r.asString(); ok { 1040 *s = v 1041 } 1042} 1043 1044// EnabledAnalyzers returns all of the analyzers enabled for the given 1045// snapshot. 1046func EnabledAnalyzers(snapshot Snapshot) (analyzers []Analyzer) { 1047 for _, a := range snapshot.View().Options().DefaultAnalyzers { 1048 if a.IsEnabled(snapshot.View()) { 1049 analyzers = append(analyzers, a) 1050 } 1051 } 1052 for _, a := range snapshot.View().Options().TypeErrorAnalyzers { 1053 if a.IsEnabled(snapshot.View()) { 1054 analyzers = append(analyzers, a) 1055 } 1056 } 1057 for _, a := range snapshot.View().Options().ConvenienceAnalyzers { 1058 if a.IsEnabled(snapshot.View()) { 1059 analyzers = append(analyzers, a) 1060 } 1061 } 1062 for _, a := range snapshot.View().Options().StaticcheckAnalyzers { 1063 if a.IsEnabled(snapshot.View()) { 1064 analyzers = append(analyzers, a) 1065 } 1066 } 1067 return analyzers 1068} 1069 1070func typeErrorAnalyzers() map[string]Analyzer { 1071 return map[string]Analyzer{ 1072 fillreturns.Analyzer.Name: { 1073 Analyzer: fillreturns.Analyzer, 1074 FixesError: fillreturns.FixesError, 1075 HighConfidence: true, 1076 Enabled: true, 1077 }, 1078 nonewvars.Analyzer.Name: { 1079 Analyzer: nonewvars.Analyzer, 1080 FixesError: nonewvars.FixesError, 1081 Enabled: true, 1082 }, 1083 noresultvalues.Analyzer.Name: { 1084 Analyzer: noresultvalues.Analyzer, 1085 FixesError: noresultvalues.FixesError, 1086 Enabled: true, 1087 }, 1088 undeclaredname.Analyzer.Name: { 1089 Analyzer: undeclaredname.Analyzer, 1090 FixesError: undeclaredname.FixesError, 1091 Command: CommandUndeclaredName, 1092 Enabled: true, 1093 }, 1094 } 1095} 1096 1097func convenienceAnalyzers() map[string]Analyzer { 1098 return map[string]Analyzer{ 1099 fillstruct.Analyzer.Name: { 1100 Analyzer: fillstruct.Analyzer, 1101 Command: CommandFillStruct, 1102 Enabled: true, 1103 }, 1104 } 1105} 1106 1107func defaultAnalyzers() map[string]Analyzer { 1108 return map[string]Analyzer{ 1109 // The traditional vet suite: 1110 asmdecl.Analyzer.Name: {Analyzer: asmdecl.Analyzer, Enabled: true}, 1111 assign.Analyzer.Name: {Analyzer: assign.Analyzer, Enabled: true}, 1112 atomic.Analyzer.Name: {Analyzer: atomic.Analyzer, Enabled: true}, 1113 bools.Analyzer.Name: {Analyzer: bools.Analyzer, Enabled: true}, 1114 buildtag.Analyzer.Name: {Analyzer: buildtag.Analyzer, Enabled: true}, 1115 cgocall.Analyzer.Name: {Analyzer: cgocall.Analyzer, Enabled: true}, 1116 composite.Analyzer.Name: {Analyzer: composite.Analyzer, Enabled: true}, 1117 copylock.Analyzer.Name: {Analyzer: copylock.Analyzer, Enabled: true}, 1118 errorsas.Analyzer.Name: {Analyzer: errorsas.Analyzer, Enabled: true}, 1119 httpresponse.Analyzer.Name: {Analyzer: httpresponse.Analyzer, Enabled: true}, 1120 ifaceassert.Analyzer.Name: {Analyzer: ifaceassert.Analyzer, Enabled: true}, 1121 loopclosure.Analyzer.Name: {Analyzer: loopclosure.Analyzer, Enabled: true}, 1122 lostcancel.Analyzer.Name: {Analyzer: lostcancel.Analyzer, Enabled: true}, 1123 nilfunc.Analyzer.Name: {Analyzer: nilfunc.Analyzer, Enabled: true}, 1124 printf.Analyzer.Name: {Analyzer: printf.Analyzer, Enabled: true}, 1125 shift.Analyzer.Name: {Analyzer: shift.Analyzer, Enabled: true}, 1126 stdmethods.Analyzer.Name: {Analyzer: stdmethods.Analyzer, Enabled: true}, 1127 stringintconv.Analyzer.Name: {Analyzer: stringintconv.Analyzer, Enabled: true}, 1128 structtag.Analyzer.Name: {Analyzer: structtag.Analyzer, Enabled: true}, 1129 tests.Analyzer.Name: {Analyzer: tests.Analyzer, Enabled: true}, 1130 unmarshal.Analyzer.Name: {Analyzer: unmarshal.Analyzer, Enabled: true}, 1131 unreachable.Analyzer.Name: {Analyzer: unreachable.Analyzer, Enabled: true}, 1132 unsafeptr.Analyzer.Name: {Analyzer: unsafeptr.Analyzer, Enabled: true}, 1133 unusedresult.Analyzer.Name: {Analyzer: unusedresult.Analyzer, Enabled: true}, 1134 1135 // Non-vet analyzers: 1136 atomicalign.Analyzer.Name: {Analyzer: atomicalign.Analyzer, Enabled: true}, 1137 deepequalerrors.Analyzer.Name: {Analyzer: deepequalerrors.Analyzer, Enabled: true}, 1138 fieldalignment.Analyzer.Name: {Analyzer: fieldalignment.Analyzer, Enabled: false}, 1139 shadow.Analyzer.Name: {Analyzer: shadow.Analyzer, Enabled: false}, 1140 sortslice.Analyzer.Name: {Analyzer: sortslice.Analyzer, Enabled: true}, 1141 testinggoroutine.Analyzer.Name: {Analyzer: testinggoroutine.Analyzer, Enabled: true}, 1142 unusedparams.Analyzer.Name: {Analyzer: unusedparams.Analyzer, Enabled: false}, 1143 1144 // gofmt -s suite: 1145 simplifycompositelit.Analyzer.Name: {Analyzer: simplifycompositelit.Analyzer, Enabled: true, HighConfidence: true}, 1146 simplifyrange.Analyzer.Name: {Analyzer: simplifyrange.Analyzer, Enabled: true, HighConfidence: true}, 1147 simplifyslice.Analyzer.Name: {Analyzer: simplifyslice.Analyzer, Enabled: true, HighConfidence: true}, 1148 } 1149} 1150 1151func urlRegexp() *regexp.Regexp { 1152 // Ensure links are matched as full words, not anywhere. 1153 re := regexp.MustCompile(`\b(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?\b`) 1154 re.Longest() 1155 return re 1156} 1157 1158type APIJSON struct { 1159 Options map[string][]*OptionJSON 1160 Commands []*CommandJSON 1161 Lenses []*LensJSON 1162 Analyzers []*AnalyzerJSON 1163} 1164 1165type OptionJSON struct { 1166 Name string 1167 Type string 1168 Doc string 1169 EnumKeys EnumKeys 1170 EnumValues []EnumValue 1171 Default string 1172 Status string 1173 Hierarchy string 1174} 1175 1176type EnumKeys struct { 1177 ValueType string 1178 Keys []EnumKey 1179} 1180 1181type EnumKey struct { 1182 Name string 1183 Doc string 1184 Default string 1185} 1186 1187type EnumValue struct { 1188 Value string 1189 Doc string 1190} 1191 1192type CommandJSON struct { 1193 Command string 1194 Title string 1195 Doc string 1196} 1197 1198type LensJSON struct { 1199 Lens string 1200 Title string 1201 Doc string 1202} 1203 1204type AnalyzerJSON struct { 1205 Name string 1206 Doc string 1207 Default bool 1208} 1209